@vibecheckai/cli 3.2.1 โ†’ 3.2.2

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.
@@ -1,8 +1,13 @@
1
1
  /**
2
- * CLI Output Utilities - World-Class Consistency
3
- *
4
- * Provides standardized JSON output, run ID generation,
5
- * and artifact management for all CLI commands.
2
+ * CLI Output Utilities - Enterprise Grade
3
+ * * Standardizes run lifecycle, artifact management, telemetry,
4
+ * * and JSON output for the Vibecheck suite.
5
+ * * Features:
6
+ * - ๐Ÿ›ก๏ธ Atomic file writes (no corruption on crash)
7
+ * - ๐Ÿ”„ Automatic "latest" symlinking for easy debugging
8
+ * - ๐Ÿ•ต๏ธ Deep CI/Git context extraction
9
+ * - ๐Ÿ›‘ Beautiful Crash Reporting (matches UI style)
10
+ * - ๐Ÿงน Automatic Log Rotation
6
11
  */
7
12
 
8
13
  "use strict";
@@ -10,50 +15,151 @@
10
15
  const fs = require("fs");
11
16
  const path = require("path");
12
17
  const crypto = require("crypto");
18
+ const os = require("os");
19
+ const childProcess = require("child_process");
20
+ const chalk = require("chalk"); // Added for premium error reporting
21
+
22
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
23
+ // CONFIGURATION
24
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
25
+
26
+ const CONFIG = {
27
+ DIR_NAME: ".vibecheck",
28
+ MAX_RETAINED_RUNS: 50, // Keep last 50 runs, delete older
29
+ WIDTH: 76,
30
+ };
13
31
 
14
32
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
15
33
  // RUN ID GENERATION
16
34
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
17
35
 
18
36
  /**
19
- * Generate a unique run ID for tracking
37
+ * Generate a unique, time-sortable run ID
38
+ * Format: YYYY-MM-DD-HHmm-UUID
20
39
  */
21
40
  function generateRunId() {
22
- return crypto.randomUUID();
41
+ const date = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 16);
42
+ const uuid = crypto.randomUUID().slice(0, 8);
43
+ return `${date}-${uuid}`;
23
44
  }
24
45
 
25
46
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
26
- // OUTPUT DIRECTORY MANAGEMENT
47
+ // CONTEXT GATHERING (Telemetry)
48
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
49
+
50
+ function getSystemContext() {
51
+ return {
52
+ platform: os.platform(),
53
+ arch: os.arch(),
54
+ node: process.version,
55
+ cpus: os.cpus().length,
56
+ memory: Math.round(os.totalmem() / 1024 / 1024 / 1024) + 'GB',
57
+ cwd: process.cwd(),
58
+ };
59
+ }
60
+
61
+ function getGitContext() {
62
+ try {
63
+ const commit = childProcess.execSync('git rev-parse --short HEAD', { stdio: 'pipe' }).toString().trim();
64
+ const branch = childProcess.execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
65
+ const isDirty = childProcess.execSync('git status --porcelain', { stdio: 'pipe' }).toString().trim().length > 0;
66
+ return { commit, branch, isDirty };
67
+ } catch (e) {
68
+ return null; // Not a git repo
69
+ }
70
+ }
71
+
72
+ function getCiContext() {
73
+ if (process.env.GITHUB_ACTIONS) return { provider: 'GitHub Actions', runId: process.env.GITHUB_RUN_ID };
74
+ if (process.env.GITLAB_CI) return { provider: 'GitLab CI', jobId: process.env.CI_JOB_ID };
75
+ if (process.env.JENKINS_URL) return { provider: 'Jenkins', buildUrl: process.env.BUILD_URL };
76
+ if (process.env.CI) return { provider: 'Generic CI' };
77
+ return { provider: null };
78
+ }
79
+
80
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
81
+ // FILESYSTEM OPS (Atomic & Safe)
27
82
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
28
83
 
29
84
  /**
30
- * Get run directory paths
85
+ * Atomic write to prevent file corruption if process dies mid-write
86
+ */
87
+ function safeWriteJson(filePath, data) {
88
+ const tempPath = `${filePath}.tmp.${crypto.randomUUID().slice(0, 6)}`;
89
+ try {
90
+ fs.writeFileSync(tempPath, JSON.stringify(data, null, 2));
91
+ fs.renameSync(tempPath, filePath); // Atomic operation
92
+ } catch (err) {
93
+ try { fs.unlinkSync(tempPath); } catch (e) {} // Cleanup
94
+ throw err;
95
+ }
96
+ }
97
+
98
+ function updateSymlink(target, linkPath) {
99
+ try {
100
+ if (fs.existsSync(linkPath)) fs.unlinkSync(linkPath);
101
+ // 'junction' type is required for Windows to not require Admin
102
+ fs.symlinkSync(target, linkPath, 'junction');
103
+ } catch (e) {
104
+ // Ignore symlink errors (often permissions)
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Cleanup old runs to save disk space (Log Rotation)
31
110
  */
111
+ function rotateLogs(runsDir) {
112
+ try {
113
+ if (!fs.existsSync(runsDir)) return;
114
+
115
+ const runs = fs.readdirSync(runsDir)
116
+ .map(name => ({ name, path: path.join(runsDir, name) }))
117
+ .filter(item => fs.statSync(item.path).isDirectory() && item.name !== 'latest')
118
+ .sort((a, b) => b.name.localeCompare(a.name)); // Newest first
119
+
120
+ if (runs.length > CONFIG.MAX_RETAINED_RUNS) {
121
+ const toDelete = runs.slice(CONFIG.MAX_RETAINED_RUNS);
122
+ toDelete.forEach(run => fs.rmSync(run.path, { recursive: true, force: true }));
123
+ }
124
+ } catch (e) {
125
+ // Non-blocking cleanup failure
126
+ }
127
+ }
128
+
129
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
130
+ // OUTPUT DIRECTORY MANAGEMENT
131
+ // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
132
+
32
133
  function getRunPaths(runId, projectPath = process.cwd()) {
33
- const runDir = path.join(projectPath, ".vibecheck", "runs", runId);
134
+ const baseDir = path.join(projectPath, CONFIG.DIR_NAME);
135
+ const runDir = path.join(baseDir, "runs", runId);
136
+ const latestLink = path.join(baseDir, "runs", "latest");
34
137
  const artifactsDir = path.join(runDir, "artifacts");
35
138
 
36
139
  // Ensure directories exist
37
- if (!fs.existsSync(runDir)) fs.mkdirSync(runDir, { recursive: true });
38
- if (!fs.existsSync(artifactsDir)) fs.mkdirSync(artifactsDir, { recursive: true });
140
+ fs.mkdirSync(artifactsDir, { recursive: true });
141
+
142
+ // Update 'latest' symlink for easy debugging
143
+ updateSymlink(runDir, latestLink);
39
144
 
145
+ // Trigger cleanup async (don't block the CLI)
146
+ setTimeout(() => rotateLogs(path.join(baseDir, "runs")), 100).unref();
147
+
40
148
  return {
41
149
  runDir,
42
150
  artifactsDir,
43
151
  manifestPath: path.join(runDir, "manifest.json"),
44
152
  commandPath: path.join(runDir, "command.json"),
153
+ crashPath: path.join(runDir, "crash.json"),
45
154
  };
46
155
  }
47
156
 
48
- /**
49
- * Save artifact to run directory
50
- */
51
157
  function saveArtifact(runId, name, data, type = "json") {
52
158
  const { artifactsDir } = getRunPaths(runId);
53
159
  const artifactPath = path.join(artifactsDir, `${name}.${type}`);
54
160
 
55
161
  if (type === "json") {
56
- fs.writeFileSync(artifactPath, JSON.stringify(data, null, 2));
162
+ safeWriteJson(artifactPath, data);
57
163
  } else {
58
164
  fs.writeFileSync(artifactPath, data);
59
165
  }
@@ -65,9 +171,6 @@ function saveArtifact(runId, name, data, type = "json") {
65
171
  // JSON OUTPUT SCHEMA
66
172
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
67
173
 
68
- /**
69
- * Create standardized JSON output
70
- */
71
174
  function createJsonOutput(options) {
72
175
  const {
73
176
  runId,
@@ -82,84 +185,71 @@ function createJsonOutput(options) {
82
185
  artifacts = [],
83
186
  } = options;
84
187
 
85
- const output = {
86
- runId,
87
- command,
88
- timestamp: new Date().toISOString(),
89
- duration: startTime ? Date.now() - new Date(startTime).getTime() : null,
90
- exitCode,
91
- verdict,
92
- result,
188
+ return {
93
189
  meta: {
94
- tier,
95
- version,
96
- path: projectPath,
190
+ schemaVersion: "2.0.0",
191
+ generatedAt: new Date().toISOString(),
192
+ tool: { name: "vibecheck", version, tier },
193
+ system: getSystemContext(),
194
+ git: getGitContext(),
195
+ ci: getCiContext(),
196
+ },
197
+ run: {
198
+ id: runId,
199
+ command,
200
+ durationMs: startTime ? Date.now() - new Date(startTime).getTime() : 0,
201
+ exitCode,
202
+ verdict,
97
203
  },
204
+ // The actual data payload
205
+ data: result,
206
+ // Links to files created
98
207
  artifacts: artifacts.map(art => ({
99
208
  type: art.type,
100
- path: path.relative(projectPath, art.path),
209
+ name: path.basename(art.path),
210
+ path: path.relative(projectPath, art.path), // Relative for portability
101
211
  description: art.description,
102
212
  })),
103
213
  };
104
-
105
- return output;
106
214
  }
107
215
 
108
- /**
109
- * Write JSON output to file or stdout
110
- */
111
216
  function writeJsonOutput(output, outputPath) {
112
217
  const jsonString = JSON.stringify(output, null, 2);
113
218
 
114
219
  if (outputPath) {
115
- fs.writeFileSync(outputPath, jsonString);
220
+ // If output is a file, write to it
221
+ safeWriteJson(outputPath, output);
116
222
  return outputPath;
117
223
  } else {
118
- console.log(jsonString);
224
+ // If stdout, we must ensure we don't break pipe consumers
225
+ process.stdout.write(jsonString + '\n');
119
226
  return null;
120
227
  }
121
228
  }
122
229
 
123
230
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
124
- // VERDCT MAPPING
231
+ // VERDICT MAPPING
125
232
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
126
233
 
127
- /**
128
- * Map exit codes to verdict strings
129
- */
130
234
  function exitCodeToVerdict(exitCode) {
131
- switch (exitCode) {
132
- case 0: return "SHIP";
133
- case 1: return "WARN";
134
- case 2: return "BLOCK";
135
- case 3: return "FEATURE_DENIED";
136
- case 4: return "INVALID_INPUT";
137
- case 5: return "INTERNAL_ERROR";
138
- default: return "UNKNOWN";
139
- }
140
- }
141
-
142
- /**
143
- * Map verdict to exit code
144
- */
145
- function verdictToExitCode(verdict) {
146
- switch (verdict?.toUpperCase()) {
147
- case "SHIP": return 0;
148
- case "WARN": return 1;
149
- case "BLOCK": return 2;
150
- case "FEATURE_DENIED": return 3;
151
- case "INVALID_INPUT": return 4;
152
- case "INTERNAL_ERROR": return 5;
153
- default: return 5;
154
- }
235
+ const map = {
236
+ 0: "SHIP",
237
+ 1: "WARN",
238
+ 2: "BLOCK",
239
+ 3: "FEATURE_DENIED",
240
+ 4: "INVALID_INPUT",
241
+ 5: "INTERNAL_ERROR"
242
+ };
243
+ return map[exitCode] || "UNKNOWN";
155
244
  }
156
245
 
157
246
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
158
- // COMMAND WRAPPER
247
+ // COMMAND WRAPPER (The Safety Net)
159
248
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
160
249
 
161
250
  /**
162
- * Wrap a command runner with standard output handling
251
+ * Wrap a command runner with world-class output handling,
252
+ * telemetry, and crash protection.
163
253
  */
164
254
  async function withStandardOutput(commandFn, options = {}) {
165
255
  const {
@@ -168,104 +258,88 @@ async function withStandardOutput(commandFn, options = {}) {
168
258
  runId = generateRunId(),
169
259
  json = false,
170
260
  output = null,
171
- ci = false,
172
- verbose = false,
173
261
  tier = "free",
174
262
  version = "1.0.0",
175
263
  } = options;
176
264
 
177
- // Initialize paths
178
265
  const paths = getRunPaths(runId);
179
266
  const artifacts = [];
180
267
 
268
+ // Artifact helper injected into the command
269
+ const artifactSaver = (name, data, type = 'json') => {
270
+ try {
271
+ const artifactPath = saveArtifact(runId, name, data, type);
272
+ artifacts.push({ type, path: artifactPath, description: name });
273
+ return artifactPath;
274
+ } catch (e) {
275
+ console.error(chalk.yellow(`โš ๏ธ Warning: Failed to save artifact ${name}: ${e.message}`));
276
+ return null;
277
+ }
278
+ };
279
+
181
280
  try {
182
- // Run the command
281
+ // Execute Command
183
282
  const result = await commandFn({
184
283
  runId,
185
284
  paths,
186
- saveArtifact: (name, data, type) => {
187
- const artifactPath = saveArtifact(runId, name, data, type);
188
- artifacts.push({
189
- type,
190
- path: artifactPath,
191
- description: `${name} artifact`,
192
- });
193
- return artifactPath;
194
- },
285
+ saveArtifact: artifactSaver,
195
286
  });
196
287
 
197
- // Extract exit code and verdict
198
- const exitCode = typeof result === "number" ? result : result.exitCode || 0;
288
+ // Normalize Result
289
+ const exitCode = typeof result === "number" ? result : (result.exitCode ?? 0);
199
290
  const verdict = result.verdict || exitCodeToVerdict(exitCode);
200
-
201
- // Create JSON output if requested
291
+ const resultData = typeof result === "object" ? result : {};
292
+
293
+ const outputObj = createJsonOutput({
294
+ runId, command, startTime, exitCode, verdict,
295
+ result: resultData, tier, version, artifacts
296
+ });
297
+
298
+ // 1. Always save to internal history
299
+ safeWriteJson(paths.commandPath, outputObj);
300
+
301
+ // 2. If JSON requested by user, print/write it
202
302
  if (json) {
203
- const jsonOutput = createJsonOutput({
204
- runId,
205
- command,
206
- startTime,
207
- exitCode,
208
- verdict,
209
- result: typeof result === "object" ? result : {},
210
- tier,
211
- version,
212
- artifacts,
213
- });
214
-
215
- writeJsonOutput(jsonOutput, output);
216
- }
217
-
218
- // Save command output to run directory
219
- const commandOutput = {
220
- exitCode,
221
- verdict,
222
- result: typeof result === "object" ? result : {},
223
- artifacts,
224
- };
225
- fs.writeFileSync(paths.commandPath, JSON.stringify(commandOutput, null, 2));
226
-
227
- // Update stable pointer
228
- const lastPath = path.join(process.cwd(), ".vibecheck", "last", `${command}.json`);
229
- if (!fs.existsSync(path.dirname(lastPath))) {
230
- fs.mkdirSync(path.dirname(lastPath), { recursive: true });
303
+ writeJsonOutput(outputObj, output);
231
304
  }
232
- fs.writeFileSync(lastPath, JSON.stringify(commandOutput, null, 2));
233
-
305
+
234
306
  return exitCode;
307
+
235
308
  } catch (error) {
236
- // Handle errors consistently
309
+ // ๐Ÿ›‘ CRITICAL ERROR HANDLING
237
310
  const exitCode = error.exitCode || 5;
238
311
  const verdict = "INTERNAL_ERROR";
239
312
 
313
+ const errorData = {
314
+ message: error.message,
315
+ stack: error.stack,
316
+ code: error.code || 'UNKNOWN',
317
+ };
318
+
319
+ const outputObj = createJsonOutput({
320
+ runId, command, startTime, exitCode, verdict,
321
+ result: { error: errorData }, tier, version, artifacts
322
+ });
323
+
324
+ // Save crash report to disk
325
+ safeWriteJson(paths.crashPath, outputObj);
326
+ safeWriteJson(paths.commandPath, outputObj);
327
+
240
328
  if (json) {
241
- const jsonOutput = createJsonOutput({
242
- runId,
243
- command,
244
- startTime,
245
- exitCode,
246
- verdict,
247
- result: {
248
- error: error.message,
249
- stack: error.stack,
250
- },
251
- tier,
252
- version,
253
- artifacts,
254
- });
255
-
256
- writeJsonOutput(jsonOutput, output);
329
+ writeJsonOutput(outputObj, output);
330
+ } else {
331
+ // VISUAL CRASH REPORT
332
+ console.error('');
333
+ console.error(chalk.bgRed.white.bold(` ๐Ÿ›‘ SYSTEM ALERT: INTERNAL CRASH `));
334
+ console.error(chalk.red('โ”€'.repeat(CONFIG.WIDTH)));
335
+ console.error(`${chalk.bold('Error:')} ${error.message}`);
336
+ console.error(`${chalk.bold('Run ID:')} ${runId}`);
337
+ console.error(`${chalk.bold('Log:')} ${chalk.cyan(paths.crashPath)}`);
338
+ console.error(chalk.red('โ”€'.repeat(CONFIG.WIDTH)));
339
+ console.error('');
257
340
  }
258
-
259
- // Save error to run directory
260
- const commandOutput = {
261
- exitCode,
262
- verdict,
263
- error: error.message,
264
- artifacts,
265
- };
266
- fs.writeFileSync(paths.commandPath, JSON.stringify(commandOutput, null, 2));
267
-
268
- throw error;
341
+
342
+ throw error; // Re-throw so the process actually exits with error
269
343
  }
270
344
  }
271
345
 
@@ -273,13 +347,10 @@ async function withStandardOutput(commandFn, options = {}) {
273
347
  // STANDARD FLAGS PARSER
274
348
  // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
275
349
 
276
- /**
277
- * Parse standard CLI flags
278
- */
279
350
  function parseStandardFlags(args) {
280
351
  const flags = {
281
352
  json: false,
282
- ci: false,
353
+ ci: !!process.env.CI, // Auto-detect CI
283
354
  path: process.cwd(),
284
355
  output: null,
285
356
  verbose: false,
@@ -287,74 +358,30 @@ function parseStandardFlags(args) {
287
358
  help: false,
288
359
  version: false,
289
360
  debug: false,
290
- quiet: false,
291
361
  };
292
362
 
293
- const parsed = args.filter(arg => {
294
- switch (arg) {
295
- case "--json":
296
- case "-j":
297
- flags.json = true;
298
- return false;
299
- case "--ci":
300
- flags.ci = true;
301
- return false;
302
- case "--verbose":
303
- case "-v":
304
- flags.verbose = true;
305
- return false;
306
- case "--strict":
307
- flags.strict = true;
308
- return false;
309
- case "--help":
310
- case "-h":
311
- flags.help = true;
312
- return false;
313
- case "--version":
314
- case "-V":
315
- flags.version = true;
316
- return false;
317
- case "--debug":
318
- case "-d":
319
- flags.debug = true;
320
- return false;
321
- case "--quiet":
322
- case "-q":
323
- flags.quiet = true;
324
- return false;
325
- default:
326
- if (arg.startsWith("--path=")) {
327
- flags.path = arg.split("=")[1];
328
- return false;
329
- } else if (arg.startsWith("-p")) {
330
- const idx = args.indexOf(arg);
331
- if (idx < args.length - 1) {
332
- flags.path = args[idx + 1];
333
- args.splice(idx, 2);
334
- return false;
335
- }
336
- } else if (arg.startsWith("--output=")) {
337
- flags.output = arg.split("=")[1];
338
- return false;
339
- } else if (arg.startsWith("-o")) {
340
- const idx = args.indexOf(arg);
341
- if (idx < args.length - 1) {
342
- flags.output = args[idx + 1];
343
- args.splice(idx, 2);
344
- return false;
345
- }
346
- }
347
- return true;
348
- }
349
- });
363
+ const parsed = [];
364
+
365
+ for (let i = 0; i < args.length; i++) {
366
+ const arg = args[i];
367
+
368
+ if (arg === '--json' || arg === '-j') flags.json = true;
369
+ else if (arg === '--ci') flags.ci = true;
370
+ else if (arg === '--verbose' || arg === '-v') flags.verbose = true;
371
+ else if (arg === '--strict') flags.strict = true;
372
+ else if (arg === '--help' || arg === '-h') flags.help = true;
373
+ else if (arg === '--version' || arg === '-V') flags.version = true;
374
+ else if (arg === '--debug' || arg === '-d') flags.debug = true;
375
+ else if (arg.startsWith('--path=')) flags.path = arg.split('=')[1];
376
+ else if (arg === '-p' && args[i+1]) { flags.path = args[++i]; }
377
+ else if (arg.startsWith('--output=')) flags.output = arg.split('=')[1];
378
+ else if (arg === '-o' && args[i+1]) { flags.output = args[++i]; }
379
+ else parsed.push(arg);
380
+ }
350
381
 
351
382
  return { flags, parsed };
352
383
  }
353
384
 
354
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
355
- // EXPORTS
356
- // โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
357
-
358
385
  module.exports = {
359
386
  generateRunId,
360
387
  getRunPaths,
@@ -362,7 +389,6 @@ module.exports = {
362
389
  createJsonOutput,
363
390
  writeJsonOutput,
364
391
  exitCodeToVerdict,
365
- verdictToExitCode,
366
392
  withStandardOutput,
367
393
  parseStandardFlags,
368
- };
394
+ };