@vibecheckai/cli 3.0.10 → 3.1.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.
@@ -22,6 +22,14 @@ const path = require("path");
22
22
  const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
23
23
  const { shipCore } = require("./runShip");
24
24
  const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
25
+ const {
26
+ generateRunId,
27
+ createJsonOutput,
28
+ writeJsonOutput,
29
+ exitCodeToVerdict,
30
+ verdictToExitCode,
31
+ saveArtifact
32
+ } = require("./lib/cli-output");
25
33
 
26
34
  let runReality;
27
35
  try {
@@ -723,7 +731,11 @@ function stamp() {
723
731
  // MAIN PROVE FUNCTION
724
732
  // ═══════════════════════════════════════════════════════════════════════════════
725
733
 
726
- async function runProve(argsOrOpts = {}) {
734
+ async function runProve(argsOrOpts = {}, context = {}) {
735
+ // Extract runId from context or generate new one
736
+ const runId = context.runId || generateRunId();
737
+ const startTime = context.startTime || new Date().toISOString();
738
+
727
739
  // Handle array args from CLI
728
740
  if (Array.isArray(argsOrOpts)) {
729
741
  if (argsOrOpts.includes("--help") || argsOrOpts.includes("-h")) {
@@ -747,6 +759,9 @@ async function runProve(argsOrOpts = {}) {
747
759
  skipFix: argsOrOpts.includes("--skip-fix"),
748
760
  headed: argsOrOpts.includes("--headed"),
749
761
  danger: argsOrOpts.includes("--danger"),
762
+ json: argsOrOpts.includes("--json"),
763
+ output: getArg(["--output", "-o"]),
764
+ ci: argsOrOpts.includes("--ci"),
750
765
  };
751
766
  }
752
767
 
@@ -765,33 +780,38 @@ async function runProve(argsOrOpts = {}) {
765
780
  danger = false,
766
781
  maxPages = 18,
767
782
  maxDepth = 2,
768
- timeoutMs = 15000
783
+ timeoutMs = 15000,
784
+ json = false,
785
+ output = null,
786
+ ci = false
769
787
  } = argsOrOpts;
770
788
 
771
789
  const root = repoRoot || process.cwd();
772
790
  const projectName = path.basename(root);
773
- const startTime = Date.now();
774
791
 
775
- // Print banner
776
- printBanner();
777
-
778
- console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
779
- console.log(` ${c.dim}Path:${c.reset} ${root}`);
780
- if (url) {
781
- console.log(` ${c.dim}URL:${c.reset} ${colors.accent}${url}${c.reset}`);
782
- }
783
- console.log(` ${c.dim}Fix Rounds:${c.reset} ${maxFixRounds} max`);
784
-
785
- // Show pipeline overview
792
+ // Initialize pipeline status
786
793
  const pipelineStatus = ['pending', 'pending', 'pending', 'pending', 'pending'];
787
- printPipelineOverview(pipelineStatus);
788
-
789
794
  const outDir = path.join(root, ".vibecheck", "prove", stamp());
790
795
  ensureDir(outDir);
791
-
796
+
792
797
  const timeline = [];
793
798
  let finalVerdict = "UNKNOWN";
794
799
 
800
+ // Print banner (unless JSON or CI mode)
801
+ if (!json && !ci) {
802
+ printBanner();
803
+
804
+ console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
805
+ console.log(` ${c.dim}Path:${c.reset} ${root}`);
806
+ if (url) {
807
+ console.log(` ${c.dim}URL:${c.reset} ${colors.accent}${url}${c.reset}`);
808
+ }
809
+ console.log(` ${c.dim}Fix Rounds:${c.reset} ${maxFixRounds} max`);
810
+
811
+ // Show pipeline overview
812
+ printPipelineOverview(pipelineStatus);
813
+ }
814
+
795
815
  // ═══════════════════════════════════════════════════════════════════════════
796
816
  // STEP 1: CTX - Refresh truthpack
797
817
  // ═══════════════════════════════════════════════════════════════════════════
@@ -1138,12 +1158,12 @@ async function runProve(argsOrOpts = {}) {
1138
1158
  // ═══════════════════════════════════════════════════════════════════════════
1139
1159
  // SUMMARY
1140
1160
  // ═══════════════════════════════════════════════════════════════════════════
1141
- const duration = Date.now() - startTime;
1161
+ const duration = Date.now() - executionStart;
1142
1162
  const durationStr = formatDuration(duration);
1143
1163
 
1144
1164
  const report = {
1145
1165
  meta: {
1146
- startedAt: new Date(startTime).toISOString(),
1166
+ startedAt: new Date(executionStart).toISOString(),
1147
1167
  finishedAt: new Date().toISOString(),
1148
1168
  durationMs: duration,
1149
1169
  url: url || null,
@@ -1161,34 +1181,73 @@ async function runProve(argsOrOpts = {}) {
1161
1181
  ensureDir(path.dirname(latestPath));
1162
1182
  fs.writeFileSync(latestPath, JSON.stringify(report, null, 2), "utf8");
1163
1183
 
1164
- // Final verdict display
1165
- printFinalVerdict(finalVerdict, durationStr, fixRound, shipResult.report?.findings?.length || 0);
1166
-
1167
- // Timeline summary
1168
- printTimelineSummary(timeline);
1184
+ // Save artifacts
1185
+ saveArtifact(runId, "prove-report", report);
1186
+ saveArtifact(runId, "timeline", timeline);
1169
1187
 
1170
- // Report links
1171
- printSection('REPORTS', ICONS.doc);
1172
- console.log();
1173
- console.log(` ${colors.accent}${outDir}/prove_report.json${c.reset}`);
1174
- console.log(` ${c.dim}${path.join(root, '.vibecheck', 'prove', 'last_prove.json')}${c.reset}`);
1175
- console.log();
1188
+ // JSON output mode
1189
+ if (json) {
1190
+ const output = createJsonOutput({
1191
+ runId,
1192
+ command: "prove",
1193
+ startTime: executionStart,
1194
+ exitCode: verdictToExitCode(finalVerdict),
1195
+ verdict: finalVerdict,
1196
+ result: {
1197
+ meta: report.meta,
1198
+ timeline,
1199
+ finalVerdict,
1200
+ fixRounds: fixRound,
1201
+ duration: duration
1202
+ },
1203
+ tier: "pro",
1204
+ version: require("../../package.json").version,
1205
+ artifacts: [
1206
+ {
1207
+ type: "report",
1208
+ path: path.join(outDir, "prove_report.json"),
1209
+ description: "Prove report with timeline"
1210
+ }
1211
+ ]
1212
+ });
1213
+
1214
+ writeJsonOutput(output, output);
1215
+ }
1176
1216
 
1177
- // Next steps if not proved
1178
- if (finalVerdict !== 'SHIP') {
1179
- printSection('NEXT STEPS', ICONS.lightning);
1217
+ // Final verdict display (unless JSON or CI mode)
1218
+ if (!json && !ci) {
1219
+ printFinalVerdict(finalVerdict, durationStr, fixRound, shipResult.report?.findings?.length || 0);
1220
+
1221
+ // Timeline summary
1222
+ printTimelineSummary(timeline);
1223
+
1224
+ // Report links
1225
+ printSection('REPORTS', ICONS.doc);
1180
1226
  console.log();
1181
- if (skipFix) {
1182
- console.log(` ${colors.accent}vibecheck prove --url ${url || '<url>'}${c.reset} ${c.dim}Enable auto-fix${c.reset}`);
1183
- } else if (fixRound >= maxFixRounds) {
1184
- console.log(` ${colors.accent}vibecheck prove --max-fix-rounds ${maxFixRounds + 2}${c.reset} ${c.dim}Try more fix rounds${c.reset}`);
1185
- }
1186
- console.log(` ${colors.accent}vibecheck ship --fix${c.reset} ${c.dim}Manual fix mode${c.reset}`);
1227
+ console.log(` ${colors.accent}${outDir}/prove_report.json${c.reset}`);
1228
+ console.log(` ${c.dim}${path.join(root, '.vibecheck', 'prove', 'last_prove.json')}${c.reset}`);
1187
1229
  console.log();
1230
+
1231
+ // Next steps if not proved
1232
+ if (finalVerdict !== 'SHIP') {
1233
+ printSection('NEXT STEPS', ICONS.lightning);
1234
+ console.log();
1235
+ if (skipFix) {
1236
+ console.log(` ${colors.accent}vibecheck prove --url ${url || '<url>'}${c.reset} ${c.dim}Enable auto-fix${c.reset}`);
1237
+ } else if (fixRound >= maxFixRounds) {
1238
+ console.log(` ${colors.accent}vibecheck prove --max-fix-rounds ${maxFixRounds + 2}${c.reset} ${c.dim}Try more fix rounds${c.reset}`);
1239
+ }
1240
+ console.log(` ${colors.accent}vibecheck ship --fix${c.reset} ${c.dim}Manual fix mode${c.reset}`);
1241
+ console.log();
1242
+ }
1243
+ } else if (ci) {
1244
+ // CI mode - minimal output
1245
+ console.log(`VERDICT=${finalVerdict}`);
1246
+ console.log(`DURATION=${duration}ms`);
1247
+ console.log(`FIX_ROUNDS=${fixRound}`);
1188
1248
  }
1189
1249
 
1190
- process.exitCode = finalVerdict === "SHIP" ? 0 : finalVerdict === "WARN" ? 1 : 2;
1191
- return process.exitCode;
1250
+ return verdictToExitCode(finalVerdict);
1192
1251
  }
1193
1252
 
1194
1253
  module.exports = { runProve };
@@ -13,6 +13,14 @@ const { withErrorHandling } = require("./lib/error-handler");
13
13
  const { ensureOutputDir, detectProjectFeatures } = require("./utils");
14
14
  const { enforceLimit, enforceFeature, trackUsage, getCurrentTier } = require("./lib/entitlements");
15
15
  const { emitShipCheck } = require("./lib/audit-bridge");
16
+ const {
17
+ generateRunId,
18
+ createJsonOutput,
19
+ writeJsonOutput,
20
+ exitCodeToVerdict,
21
+ verdictToExitCode,
22
+ saveArtifact
23
+ } = require("./lib/cli-output");
16
24
 
17
25
  // Route Truth v1 - Fake endpoint detection
18
26
  const { buildTruthpack, writeTruthpack, detectFastifyEntry } = require("./lib/truth");
@@ -27,6 +35,8 @@ const {
27
35
  } = require("./lib/analyzers");
28
36
  const { findingsFromReality } = require("./lib/reality-findings");
29
37
  const { findContractDrift, loadContracts, hasContracts, getDriftSummary } = require("./lib/drift");
38
+ const upsell = require("./lib/upsell");
39
+ const entitlements = require("./lib/entitlements-v2");
30
40
 
31
41
  // ═══════════════════════════════════════════════════════════════════════════════
32
42
  // ADVANCED TERMINAL - ANSI CODES & UTILITIES
@@ -846,9 +856,13 @@ function parseArgs(args) {
846
856
  // MAIN SHIP FUNCTION
847
857
  // ═══════════════════════════════════════════════════════════════════════════════
848
858
 
849
- async function runShip(args) {
859
+ async function runShip(args, context = {}) {
860
+ // Extract runId from context or generate new one
861
+ const runId = context.runId || generateRunId();
862
+ const startTime = context.startTime || new Date().toISOString();
863
+
850
864
  const opts = parseArgs(args);
851
- const startTime = Date.now();
865
+ const executionStart = Date.now();
852
866
 
853
867
  // Show help if requested
854
868
  if (opts.help) {
@@ -990,23 +1004,53 @@ async function runShip(args) {
990
1004
 
991
1005
  // JSON output mode
992
1006
  if (opts.json) {
993
- const output = {
994
- schemaVersion: '1.0.0',
995
- timestamp: new Date().toISOString(),
1007
+ const output = createJsonOutput({
1008
+ runId,
1009
+ command: "ship",
1010
+ startTime,
1011
+ exitCode: verdictToExitCode(verdict),
996
1012
  verdict,
997
- score: results.score,
998
- canShip: results.canShip,
999
- summary: {
1000
- blockers: blockers.length,
1001
- warnings: warnings.length,
1002
- total: allFindings.length,
1013
+ result: {
1014
+ verdict,
1015
+ score: results.score,
1016
+ grade: results.grade,
1017
+ canShip: results.canShip,
1018
+ summary: {
1019
+ blockers: blockers.length,
1020
+ warnings: warnings.length,
1021
+ total: allFindings.length,
1022
+ },
1023
+ findings: allFindings,
1024
+ proofGraph: proofGraph.summary,
1025
+ duration: Date.now() - executionStart,
1003
1026
  },
1004
- findings: allFindings,
1005
- proofGraph: proofGraph.summary,
1006
- duration,
1007
- };
1008
- console.log(JSON.stringify(output, null, 2));
1009
- return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
1027
+ tier: getCurrentTier(),
1028
+ version: require("../../package.json").version,
1029
+ artifacts: [
1030
+ {
1031
+ type: "report",
1032
+ path: path.join(outputDir, "report.json"),
1033
+ description: "Ship report with findings"
1034
+ },
1035
+ {
1036
+ type: "proof",
1037
+ path: path.join(outputDir, "proof-graph.json"),
1038
+ description: "Proof graph analysis"
1039
+ }
1040
+ ]
1041
+ });
1042
+
1043
+ writeJsonOutput(output, opts.output);
1044
+
1045
+ // Save artifacts
1046
+ const reportPath = saveArtifact(runId, "report", {
1047
+ ...output.result,
1048
+ truthpack,
1049
+ integrity: results.integrity
1050
+ });
1051
+ const proofPath = saveArtifact(runId, "proof-graph", proofGraph);
1052
+
1053
+ return verdictToExitCode(verdict);
1010
1054
  }
1011
1055
 
1012
1056
  // CI output mode (minimal)
@@ -1015,7 +1059,17 @@ async function runShip(args) {
1015
1059
  console.log(`SCORE=${results.score}`);
1016
1060
  console.log(`BLOCKERS=${blockers.length}`);
1017
1061
  console.log(`WARNINGS=${warnings.length}`);
1018
- return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
1062
+
1063
+ // Save CI artifacts
1064
+ saveArtifact(runId, "ci-summary", {
1065
+ verdict,
1066
+ score: results.score,
1067
+ blockers: blockers.length,
1068
+ warnings: warnings.length,
1069
+ timestamp: new Date().toISOString()
1070
+ });
1071
+
1072
+ return verdictToExitCode(verdict);
1019
1073
  }
1020
1074
 
1021
1075
  // Fix mode
@@ -1076,6 +1130,20 @@ async function runShip(args) {
1076
1130
  }
1077
1131
  console.log(` ${colors.accent}vibecheck ship --assist${c.reset} ${c.dim}Get AI help for complex issues${c.reset}`);
1078
1132
  console.log();
1133
+
1134
+ // Earned upsell: Badge withheld when verdict != SHIP
1135
+ const currentTier = context?.authInfo?.access?.tier || "free";
1136
+ if (entitlements.tierMeetsMinimum(currentTier, "starter")) {
1137
+ // User has badge access but verdict prevents it
1138
+ console.log(upsell.formatEarnedUpsell({
1139
+ cmd: "ship",
1140
+ verdict,
1141
+ topIssues: blockers.slice(0, 3),
1142
+ withheldArtifact: "badge",
1143
+ currentTier,
1144
+ suggestedCmd: currentTier === "free" ? "vibecheck fix --plan-only" : "vibecheck fix",
1145
+ }));
1146
+ }
1079
1147
  }
1080
1148
 
1081
1149
  // Emit audit event
@@ -1093,7 +1161,18 @@ async function runShip(args) {
1093
1161
  } catch {}
1094
1162
 
1095
1163
  // Exit code: 0=SHIP, 1=WARN, 2=BLOCK
1096
- return verdict === 'SHIP' ? 0 : verdict === 'WARN' ? 1 : 2;
1164
+ const exitCode = verdictToExitCode(verdict);
1165
+
1166
+ // Save final results
1167
+ saveArtifact(runId, "summary", {
1168
+ verdict,
1169
+ score: results.score,
1170
+ canShip: results.canShip,
1171
+ exitCode,
1172
+ timestamp: new Date().toISOString()
1173
+ });
1174
+
1175
+ return exitCode;
1097
1176
 
1098
1177
  } catch (error) {
1099
1178
  if (!opts.json) stopSpinner(`Ship check failed: ${error.message}`, false);
@@ -0,0 +1,272 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * runVerify.js - CLI Verify Command
4
+ * Verifies AI-generated agent output against vibecheck-v1 protocol
5
+ *
6
+ * Usage:
7
+ * vibecheck verify [options]
8
+ * vibecheck verify --input response.json
9
+ * vibecheck verify --stdin
10
+ * vibecheck verify --mode ship --strict
11
+ */
12
+
13
+ "use strict";
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { parseArgs } = require('util');
18
+
19
+ const HELP = `
20
+ vibecheck verify - Verify AI-generated agent output
21
+
22
+ Usage:
23
+ vibecheck verify [options]
24
+
25
+ Options:
26
+ --input, -i <file> Input file containing agent response JSON
27
+ --stdin Read agent response from stdin
28
+ --root, -r <dir> Project root directory (default: cwd)
29
+ --mode, -m <mode> Verification mode: explore|build|ship (default: build)
30
+ --strict Enable strict mode (fail on warnings)
31
+ --json Output results as JSON
32
+ --help, -h Show this help message
33
+
34
+ Examples:
35
+ vibecheck verify --input agent-response.json
36
+ echo '{"format":"vibecheck-v1",...}' | vibecheck verify --stdin
37
+ vibecheck verify --input response.json --mode ship --strict
38
+ `;
39
+
40
+ async function main() {
41
+ const { values, positionals } = parseArgs({
42
+ options: {
43
+ input: { type: 'string', short: 'i' },
44
+ stdin: { type: 'boolean', default: false },
45
+ root: { type: 'string', short: 'r' },
46
+ mode: { type: 'string', short: 'm', default: 'build' },
47
+ strict: { type: 'boolean', default: false },
48
+ json: { type: 'boolean', default: false },
49
+ help: { type: 'boolean', short: 'h' },
50
+ },
51
+ allowPositionals: true,
52
+ strict: false,
53
+ });
54
+
55
+ if (values.help) {
56
+ console.log(HELP);
57
+ process.exit(0);
58
+ }
59
+
60
+ const projectRoot = values.root ? path.resolve(values.root) : process.cwd();
61
+ const mode = values.mode || 'build';
62
+ const strict = values.strict || false;
63
+ const outputJson = values.json || false;
64
+
65
+ // Validate mode
66
+ if (!['explore', 'build', 'ship'].includes(mode)) {
67
+ console.error(`Error: Invalid mode "${mode}". Must be one of: explore, build, ship`);
68
+ process.exit(2);
69
+ }
70
+
71
+ // Get input
72
+ let rawInput = '';
73
+
74
+ if (values.stdin) {
75
+ rawInput = await readStdin();
76
+ } else if (values.input) {
77
+ const inputPath = path.resolve(values.input);
78
+ if (!fs.existsSync(inputPath)) {
79
+ console.error(`Error: Input file not found: ${inputPath}`);
80
+ process.exit(2);
81
+ }
82
+ rawInput = fs.readFileSync(inputPath, 'utf-8');
83
+ } else if (positionals.length > 0) {
84
+ // Allow positional argument as input file
85
+ const inputPath = path.resolve(positionals[0]);
86
+ if (!fs.existsSync(inputPath)) {
87
+ console.error(`Error: Input file not found: ${inputPath}`);
88
+ process.exit(2);
89
+ }
90
+ rawInput = fs.readFileSync(inputPath, 'utf-8');
91
+ } else {
92
+ console.error('Error: No input provided. Use --input <file> or --stdin');
93
+ console.log(HELP);
94
+ process.exit(2);
95
+ }
96
+
97
+ if (!rawInput.trim()) {
98
+ console.error('Error: Empty input');
99
+ process.exit(2);
100
+ }
101
+
102
+ // Load the verification module
103
+ let validateFormat, verifyAgentOutput, formatCheckResults, buildJsonReport;
104
+ try {
105
+ // Try loading from the built TypeScript output
106
+ const verification = require('../../dist/lib/verification/index.js');
107
+ validateFormat = verification.validateFormat;
108
+ verifyAgentOutput = verification.verifyAgentOutput;
109
+ formatCheckResults = verification.formatCheckResults;
110
+ buildJsonReport = verification.buildJsonReport;
111
+ } catch (err) {
112
+ // Fallback: try loading directly (for development)
113
+ try {
114
+ const verification = require('../../src/lib/verification/index.ts');
115
+ validateFormat = verification.validateFormat;
116
+ verifyAgentOutput = verification.verifyAgentOutput;
117
+ formatCheckResults = verification.formatCheckResults;
118
+ buildJsonReport = verification.buildJsonReport;
119
+ } catch (err2) {
120
+ console.error('Error loading verification module:', err.message);
121
+ console.error('Make sure the project is built: pnpm build');
122
+ process.exit(2);
123
+ }
124
+ }
125
+
126
+ // Step 1: Validate format
127
+ if (!outputJson) {
128
+ console.log('🔍 Validating agent output format...');
129
+ }
130
+
131
+ const formatResult = validateFormat(rawInput);
132
+
133
+ if (!formatResult.valid) {
134
+ if (outputJson) {
135
+ console.log(JSON.stringify({
136
+ status: 'fail',
137
+ phase: 'format-validation',
138
+ error: formatResult.error,
139
+ retryPrompt: formatResult.retryPrompt,
140
+ }, null, 2));
141
+ } else {
142
+ console.error('❌ Format validation failed:', formatResult.error);
143
+ console.error('\n📝 Retry prompt:');
144
+ console.error(formatResult.retryPrompt);
145
+ }
146
+ process.exit(2);
147
+ }
148
+
149
+ // Check if it's an error response
150
+ if ('error' in formatResult && formatResult.error) {
151
+ if (outputJson) {
152
+ console.log(JSON.stringify({
153
+ status: 'error',
154
+ phase: 'agent-error',
155
+ error: formatResult.error.error,
156
+ }, null, 2));
157
+ } else {
158
+ console.log('⚠️ Agent returned an error response:');
159
+ console.log(formatResult.error.error);
160
+ }
161
+ process.exit(1);
162
+ }
163
+
164
+ const agentOutput = formatResult.output;
165
+
166
+ if (!outputJson) {
167
+ console.log('✅ Format valid');
168
+ console.log(` Files in diff: ${countFilesInDiff(agentOutput.diff)}`);
169
+ console.log(` Commands: ${agentOutput.commands?.length || 0}`);
170
+ console.log(` Tests: ${agentOutput.tests?.length || 0}`);
171
+ console.log('');
172
+ }
173
+
174
+ // Step 2: Run verification pipeline
175
+ if (!outputJson) {
176
+ console.log('🔬 Running verification pipeline...');
177
+ }
178
+
179
+ const context = {
180
+ projectRoot,
181
+ mode,
182
+ runTests: mode === 'ship',
183
+ strictMode: strict,
184
+ maxFilesChanged: mode === 'ship' ? 10 : mode === 'build' ? 20 : 50,
185
+ };
186
+
187
+ let result;
188
+ try {
189
+ result = await verifyAgentOutput(agentOutput, context);
190
+ } catch (err) {
191
+ if (outputJson) {
192
+ console.log(JSON.stringify({
193
+ status: 'fail',
194
+ phase: 'verification',
195
+ error: err.message,
196
+ }, null, 2));
197
+ } else {
198
+ console.error('❌ Verification error:', err.message);
199
+ }
200
+ process.exit(2);
201
+ }
202
+
203
+ // Output results
204
+ if (outputJson) {
205
+ console.log(JSON.stringify(buildJsonReport(result), null, 2));
206
+ } else {
207
+ console.log('');
208
+ console.log(formatCheckResults(result.checks));
209
+ console.log('');
210
+
211
+ if (result.status === 'pass') {
212
+ console.log('✅ VERIFICATION PASSED');
213
+ } else if (result.status === 'warn') {
214
+ console.log('⚠️ VERIFICATION PASSED WITH WARNINGS');
215
+ } else {
216
+ console.log('❌ VERIFICATION FAILED');
217
+ if (result.failureContext) {
218
+ console.log('\n📝 Failure context for retry:');
219
+ console.log(result.failureContext);
220
+ }
221
+ }
222
+ }
223
+
224
+ // Exit codes: 0 = pass, 1 = warn (if strict), 2 = fail
225
+ if (result.status === 'fail') {
226
+ process.exit(2);
227
+ } else if (result.status === 'warn' && strict) {
228
+ process.exit(1);
229
+ } else {
230
+ process.exit(0);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Read all input from stdin
236
+ */
237
+ function readStdin() {
238
+ return new Promise((resolve, reject) => {
239
+ let data = '';
240
+ process.stdin.setEncoding('utf-8');
241
+ process.stdin.on('data', chunk => { data += chunk; });
242
+ process.stdin.on('end', () => resolve(data));
243
+ process.stdin.on('error', reject);
244
+
245
+ // Timeout after 5 seconds if no input
246
+ setTimeout(() => {
247
+ if (data === '') {
248
+ reject(new Error('No input received from stdin'));
249
+ }
250
+ }, 5000);
251
+ });
252
+ }
253
+
254
+ /**
255
+ * Count files in a diff string
256
+ */
257
+ function countFilesInDiff(diff) {
258
+ if (!diff) return 0;
259
+ const matches = diff.match(/^diff --git/gm);
260
+ return matches ? matches.length : 0;
261
+ }
262
+
263
+ // Export for CLI registry
264
+ module.exports = { main };
265
+
266
+ // Run if executed directly
267
+ if (require.main === module) {
268
+ main().catch(err => {
269
+ console.error('Fatal error:', err.message);
270
+ process.exit(2);
271
+ });
272
+ }