@vibecheckai/cli 3.1.6 → 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 (56) hide show
  1. package/README.md +27 -32
  2. package/bin/registry.js +208 -343
  3. package/bin/runners/context/generators/mcp.js +18 -0
  4. package/bin/runners/context/index.js +72 -4
  5. package/bin/runners/context/proof-context.js +293 -1
  6. package/bin/runners/context/security-scanner.js +311 -73
  7. package/bin/runners/lib/analyzers.js +607 -20
  8. package/bin/runners/lib/detectors-v2.js +172 -15
  9. package/bin/runners/lib/entitlements-v2.js +48 -1
  10. package/bin/runners/lib/evidence-pack.js +678 -0
  11. package/bin/runners/lib/html-proof-report.js +913 -0
  12. package/bin/runners/lib/missions/plan.js +231 -41
  13. package/bin/runners/lib/missions/templates.js +125 -0
  14. package/bin/runners/lib/scan-output.js +492 -253
  15. package/bin/runners/lib/ship-output.js +901 -641
  16. package/bin/runners/runCheckpoint.js +44 -3
  17. package/bin/runners/runContext.d.ts +4 -0
  18. package/bin/runners/runContext.js +2 -3
  19. package/bin/runners/runDoctor.js +11 -4
  20. package/bin/runners/runFix.js +51 -341
  21. package/bin/runners/runInit.js +37 -20
  22. package/bin/runners/runPolish.d.ts +4 -0
  23. package/bin/runners/runPolish.js +608 -29
  24. package/bin/runners/runProve.js +210 -25
  25. package/bin/runners/runReality.js +861 -107
  26. package/bin/runners/runScan.js +238 -4
  27. package/bin/runners/runShip.js +19 -3
  28. package/bin/runners/runWatch.js +25 -5
  29. package/bin/vibecheck.js +35 -47
  30. package/mcp-server/consolidated-tools.js +408 -42
  31. package/mcp-server/index.js +152 -15
  32. package/mcp-server/package.json +1 -1
  33. package/mcp-server/proof-tools.js +571 -0
  34. package/mcp-server/tier-auth.js +22 -19
  35. package/mcp-server/tools-v3.js +744 -0
  36. package/mcp-server/truth-firewall-tools.js +190 -4
  37. package/package.json +3 -1
  38. package/bin/runners/runBadge.js +0 -916
  39. package/bin/runners/runContracts.js +0 -105
  40. package/bin/runners/runCtx.js +0 -680
  41. package/bin/runners/runCtxDiff.js +0 -301
  42. package/bin/runners/runCtxGuard.js +0 -176
  43. package/bin/runners/runCtxSync.js +0 -116
  44. package/bin/runners/runExport.js +0 -93
  45. package/bin/runners/runGraph.js +0 -454
  46. package/bin/runners/runInstall.js +0 -273
  47. package/bin/runners/runLabs.js +0 -341
  48. package/bin/runners/runLaunch.js +0 -181
  49. package/bin/runners/runPR.js +0 -255
  50. package/bin/runners/runPermissions.js +0 -310
  51. package/bin/runners/runPreflight.js +0 -580
  52. package/bin/runners/runReplay.js +0 -499
  53. package/bin/runners/runSecurity.js +0 -92
  54. package/bin/runners/runShare.js +0 -212
  55. package/bin/runners/runStatus.js +0 -102
  56. package/bin/runners/runVerify.js +0 -272
@@ -1,212 +0,0 @@
1
- /**
2
- * vibecheck share - Generate Shareable Proof Bundles
3
- *
4
- * ═══════════════════════════════════════════════════════════════════════════════
5
- * ENTERPRISE EDITION - World-Class Terminal Experience
6
- * ═══════════════════════════════════════════════════════════════════════════════
7
- */
8
-
9
- const fs = require("fs");
10
- const path = require("path");
11
- const { buildSharePack, findLatestMissionDir } = require("./lib/share-pack");
12
-
13
- // ═══════════════════════════════════════════════════════════════════════════════
14
- // ADVANCED TERMINAL - ANSI CODES & UTILITIES
15
- // ═══════════════════════════════════════════════════════════════════════════════
16
-
17
- const c = {
18
- reset: '\x1b[0m',
19
- bold: '\x1b[1m',
20
- dim: '\x1b[2m',
21
- italic: '\x1b[3m',
22
- red: '\x1b[31m',
23
- green: '\x1b[32m',
24
- yellow: '\x1b[33m',
25
- blue: '\x1b[34m',
26
- magenta: '\x1b[35m',
27
- cyan: '\x1b[36m',
28
- white: '\x1b[37m',
29
- gray: '\x1b[90m',
30
- clearLine: '\x1b[2K',
31
- hideCursor: '\x1b[?25l',
32
- showCursor: '\x1b[?25h',
33
- };
34
-
35
- const rgb = (r, g, b) => `\x1b[38;2;${r};${g};${b}m`;
36
-
37
- const colors = {
38
- gradient1: rgb(100, 255, 200),
39
- gradient2: rgb(80, 235, 180),
40
- gradient3: rgb(60, 215, 160),
41
- shipGreen: rgb(0, 255, 150),
42
- warnAmber: rgb(255, 200, 0),
43
- blockRed: rgb(255, 80, 80),
44
- accent: rgb(100, 255, 200),
45
- muted: rgb(120, 120, 140),
46
- };
47
-
48
- // ═══════════════════════════════════════════════════════════════════════════════
49
- // PREMIUM BANNER
50
- // ═══════════════════════════════════════════════════════════════════════════════
51
-
52
- const SHARE_BANNER = `
53
- ${rgb(100, 255, 200)} ███████╗██╗ ██╗ █████╗ ██████╗ ███████╗${c.reset}
54
- ${rgb(80, 235, 180)} ██╔════╝██║ ██║██╔══██╗██╔══██╗██╔════╝${c.reset}
55
- ${rgb(60, 215, 160)} ███████╗███████║███████║██████╔╝█████╗ ${c.reset}
56
- ${rgb(40, 195, 140)} ╚════██║██╔══██║██╔══██║██╔══██╗██╔══╝ ${c.reset}
57
- ${rgb(20, 175, 120)} ███████║██║ ██║██║ ██║██║ ██║███████╗${c.reset}
58
- ${rgb(0, 155, 100)} ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝${c.reset}
59
- `;
60
-
61
- const BANNER_FULL = `
62
- ${rgb(100, 255, 200)} ██╗ ██╗██╗██████╗ ███████╗ ██████╗██╗ ██╗███████╗ ██████╗██╗ ██╗${c.reset}
63
- ${rgb(90, 245, 190)} ██║ ██║██║██╔══██╗██╔════╝██╔════╝██║ ██║██╔════╝██╔════╝██║ ██╔╝${c.reset}
64
- ${rgb(80, 235, 180)} ██║ ██║██║██████╔╝█████╗ ██║ ███████║█████╗ ██║ █████╔╝ ${c.reset}
65
- ${rgb(60, 215, 160)} ╚██╗ ██╔╝██║██╔══██╗██╔══╝ ██║ ██╔══██║██╔══╝ ██║ ██╔═██╗ ${c.reset}
66
- ${rgb(40, 195, 140)} ╚████╔╝ ██║██████╔╝███████╗╚██████╗██║ ██║███████╗╚██████╗██║ ██╗${c.reset}
67
- ${rgb(20, 175, 120)} ╚═══╝ ╚═╝╚═════╝ ╚══════╝ ╚═════╝╚═╝ ╚═╝╚══════╝ ╚═════╝╚═╝ ╚═╝${c.reset}
68
-
69
- ${c.dim} ┌─────────────────────────────────────────────────────────────────────┐${c.reset}
70
- ${c.dim} │${c.reset} ${rgb(100, 255, 200)}📤${c.reset} ${c.bold}SHARE${c.reset} ${c.dim}•${c.reset} ${rgb(200, 200, 200)}Proof Bundles${c.reset} ${c.dim}•${c.reset} ${rgb(150, 150, 150)}PR Comments${c.reset} ${c.dim}•${c.reset} ${rgb(100, 100, 100)}Collaboration${c.reset} ${c.dim}│${c.reset}
71
- ${c.dim} └─────────────────────────────────────────────────────────────────────┘${c.reset}
72
- `;
73
-
74
- const ICONS = {
75
- share: '📤',
76
- package: '📦',
77
- file: '📄',
78
- check: '✓',
79
- cross: '✗',
80
- warning: '⚠',
81
- arrow: '→',
82
- bullet: '•',
83
- github: '🐙',
84
- sparkle: '✨',
85
- folder: '📁',
86
- };
87
-
88
- function printDivider(char = '─', width = 69, color = c.dim) {
89
- console.log(`${color} ${char.repeat(width)}${c.reset}`);
90
- }
91
-
92
- function printSection(title, icon = '◆') {
93
- console.log();
94
- console.log(` ${colors.accent}${icon}${c.reset} ${c.bold}${title}${c.reset}`);
95
- printDivider();
96
- }
97
-
98
- function printHelp() {
99
- console.log(BANNER_FULL);
100
- console.log(`
101
- ${c.bold}Usage:${c.reset} vibecheck share [options]
102
-
103
- ${c.bold}Proof Bundles${c.reset} — Generate shareable mission proof for collaboration.
104
-
105
- ${c.bold}Options:${c.reset}
106
- ${colors.accent}--mission-dir <path>${c.reset} Specific mission directory to share
107
- ${colors.accent}--output-dir <path>${c.reset} Output directory for share pack
108
- ${colors.accent}--pr-comment${c.reset} Print PR comment to stdout
109
- ${colors.accent}--help, -h${c.reset} Show this help
110
-
111
- ${c.bold}What It Does:${c.reset}
112
- ${colors.shipGreen}1.${c.reset} Finds latest mission pack from .vibecheck/missions/
113
- ${colors.shipGreen}2.${c.reset} Generates share bundle with:
114
- ${ICONS.file} ${colors.accent}share.json${c.reset} ${c.dim}Machine readable${c.reset}
115
- ${ICONS.file} ${colors.accent}share.md${c.reset} ${c.dim}Human readable${c.reset}
116
- ${ICONS.github} ${colors.accent}pr_comment.md${c.reset} ${c.dim}GitHub ready${c.reset}
117
-
118
- ${c.bold}Output Files:${c.reset}
119
- ${c.dim}.vibecheck/missions/<timestamp>/share/${c.reset}
120
- ${colors.accent}share.json${c.reset} ${c.dim}Mission data + findings${c.reset}
121
- ${colors.accent}share.md${c.reset} ${c.dim}Markdown summary${c.reset}
122
- ${colors.accent}pr_comment.md${c.reset} ${c.dim}Ready to paste into GitHub${c.reset}
123
-
124
- ${c.bold}Examples:${c.reset}
125
- ${c.dim}# Share latest mission${c.reset}
126
- vibecheck share
127
-
128
- ${c.dim}# Print PR comment body${c.reset}
129
- vibecheck share --pr-comment
130
-
131
- ${c.dim}# Fix then auto-share${c.reset}
132
- vibecheck fix --autopilot --share
133
- `);
134
- }
135
-
136
- async function runShare(argsOrOpts = {}) {
137
- // Handle array args from CLI
138
- if (Array.isArray(argsOrOpts)) {
139
- if (argsOrOpts.includes("--help") || argsOrOpts.includes("-h")) {
140
- printHelp();
141
- return 0;
142
- }
143
- const getArg = (flags) => {
144
- for (const f of flags) {
145
- const idx = argsOrOpts.indexOf(f);
146
- if (idx !== -1 && idx < argsOrOpts.length - 1) return argsOrOpts[idx + 1];
147
- }
148
- return undefined;
149
- };
150
- argsOrOpts = {
151
- missionDir: getArg(["--mission-dir"]),
152
- outputDir: getArg(["--output-dir"]),
153
- prComment: argsOrOpts.includes("--pr-comment"),
154
- };
155
- }
156
-
157
- const { repoRoot, missionDir, outputDir, prComment } = argsOrOpts;
158
- const root = repoRoot || process.cwd();
159
- const projectName = path.basename(root);
160
-
161
- // Print banner
162
- console.log(BANNER_FULL);
163
- console.log(` ${c.dim}Project:${c.reset} ${c.bold}${projectName}${c.reset}`);
164
- console.log(` ${c.dim}Path:${c.reset} ${root}`);
165
- console.log();
166
-
167
- let resolvedMissionDir;
168
- try {
169
- resolvedMissionDir = missionDir
170
- ? (path.isAbsolute(missionDir) ? missionDir : path.join(root, missionDir))
171
- : findLatestMissionDir(root);
172
- } catch (e) {
173
- console.log(` ${colors.blockRed}${ICONS.cross}${c.reset} ${c.bold}Error:${c.reset} ${e.message}`);
174
- console.log();
175
- console.log(` ${c.dim}Run ${colors.accent}vibecheck fix --autopilot${c.reset}${c.dim} first to generate mission packs.${c.reset}`);
176
- console.log();
177
- return 1;
178
- }
179
-
180
- const res = buildSharePack({
181
- repoRoot: root,
182
- missionDirAbs: resolvedMissionDir,
183
- outputDirAbs: outputDir ? (path.isAbsolute(outputDir) ? outputDir : path.join(root, outputDir)) : null
184
- });
185
-
186
- printSection('SHARE BUNDLE', ICONS.package);
187
- console.log();
188
- console.log(` ${c.dim}Mission pack:${c.reset} ${colors.accent}${path.relative(root, res.missionDir).replace(/\\/g,"/")}${c.reset}`);
189
- console.log(` ${c.dim}Output:${c.reset} ${colors.accent}${path.relative(root, res.outputDir).replace(/\\/g,"/")}${c.reset}`);
190
- console.log();
191
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}share.json${c.reset}`);
192
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.file} ${colors.accent}share.md${c.reset}`);
193
- console.log(` ${colors.shipGreen}${ICONS.check}${c.reset} ${ICONS.github} ${colors.accent}pr_comment.md${c.reset}`);
194
- console.log();
195
-
196
- if (prComment) {
197
- printSection('PR COMMENT', ICONS.github);
198
- console.log();
199
- console.log(` ${c.dim}Paste into GitHub:${c.reset}`);
200
- printDivider('─', 60);
201
- console.log();
202
- console.log(fs.readFileSync(res.files.prComment, "utf8"));
203
- printDivider('─', 60);
204
- }
205
-
206
- console.log(` ${colors.shipGreen}${ICONS.sparkle}${c.reset} Share bundle generated successfully`);
207
- console.log();
208
-
209
- return 0;
210
- }
211
-
212
- module.exports = { runShare };
@@ -1,102 +0,0 @@
1
- /**
2
- * vibecheck status - Show current project status at a glance
3
- *
4
- * Displays:
5
- * - Last ship verdict
6
- * - Last reality run
7
- * - Pending findings count
8
- * - Config status
9
- * - Quick health indicators
10
- */
11
-
12
- "use strict";
13
-
14
- const fs = require("fs");
15
- const path = require("path");
16
- const { formatStatusOutput } = require("./lib/status-output");
17
- const { parseGlobalFlags } = require("./lib/global-flags");
18
-
19
- function readJsonSafe(p) {
20
- try {
21
- if (!fs.existsSync(p)) return null;
22
- return JSON.parse(fs.readFileSync(p, "utf8"));
23
- } catch {
24
- return null;
25
- }
26
- }
27
-
28
- async function runStatus(args = []) {
29
- // Parse global flags
30
- const { flags: globalFlags, cleanArgs } = parseGlobalFlags(args);
31
-
32
- // Support both old API ({ repoRoot, json }) and new API (args array)
33
- let repoRoot, json;
34
- if (typeof args === 'object' && !Array.isArray(args) && args.repoRoot !== undefined) {
35
- // Old API compatibility
36
- repoRoot = args.repoRoot;
37
- json = args.json || false;
38
- } else {
39
- // New API - parse from args
40
- repoRoot = globalFlags.path || process.cwd();
41
- json = globalFlags.json || false;
42
- }
43
-
44
- const root = repoRoot || process.cwd();
45
-
46
- // Gather data
47
- const lastShip = readJsonSafe(path.join(root, ".vibecheck", "last_ship.json"));
48
- const truthpack = readJsonSafe(path.join(root, ".vibecheck", "truth", "truthpack.json"));
49
- const lastReality = readJsonSafe(path.join(root, ".vibecheck", "reality", "last_reality.json"));
50
- const lastProve = readJsonSafe(path.join(root, ".vibecheck", "prove", "last_prove.json"));
51
- const config = readJsonSafe(path.join(root, ".vibecheck", "config.json"));
52
-
53
- // Count findings
54
- const findings = lastShip?.findings || [];
55
- const blocks = findings.filter(f => f.severity === "BLOCK").length;
56
- const warns = findings.filter(f => f.severity === "WARN").length;
57
-
58
- // Reality coverage
59
- const coverage = lastReality?.coverage?.percent;
60
-
61
- // Truthpack stats
62
- const routes = truthpack?.routes?.server?.length || 0;
63
- const envVars = truthpack?.env?.vars?.length || 0;
64
- const gaps = truthpack?.routes?.gaps?.length || 0;
65
-
66
- if (json) {
67
- const status = {
68
- verdict: lastShip?.meta?.verdict || null,
69
- lastShipAt: lastShip?.meta?.generatedAt || null,
70
- lastRealityAt: lastReality?.meta?.startedAt || null,
71
- lastProveAt: lastProve?.meta?.startedAt || null,
72
- findings: { BLOCK: blocks, WARN: warns },
73
- truthpack: { routes, envVars, gaps },
74
- coverage: coverage || null,
75
- config: !!config
76
- };
77
- console.log(JSON.stringify(status, null, 2));
78
- return 0;
79
- }
80
-
81
- const status = {
82
- verdict: lastShip?.meta?.verdict || null,
83
- lastShipAt: lastShip?.meta?.generatedAt || null,
84
- blockers: blocks,
85
- warnings: warns,
86
- routes,
87
- envVars,
88
- gaps,
89
- lastRealityAt: lastReality?.meta?.startedAt || null,
90
- coverage: coverage || null,
91
- hasTruthpack: !!truthpack,
92
- hasReality: !!lastReality,
93
- };
94
-
95
- console.log(formatStatusOutput(status, { root }));
96
-
97
- // Exit 0 when showing status successfully (even if no ship yet)
98
- // Only return non-zero if explicitly checking for BLOCK
99
- return 0;
100
- }
101
-
102
- module.exports = { runStatus };
@@ -1,272 +0,0 @@
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
- }