vibebug 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,1720 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/utils/constants.ts
13
+ var VERSION, APP_NAME, DB_FILENAME, VIBEBUG_DIR, DEFAULT_DASHBOARD_PORT, DEFAULT_RING_BUFFER_SIZE, DEFAULT_TAIL_LINES, DEFAULT_STREAM_QUIET_TIMEOUT_MS, DEFAULT_STREAM_COOLDOWN_MS, MAX_ERROR_BLOCK_LINES, MAX_DIFF_SIZE, DEFAULT_AI_MODEL, AI_PRICING, DEFAULT_ESTIMATED_OUTPUT_TOKENS, CHARS_PER_TOKEN;
14
+ var init_constants = __esm({
15
+ "src/utils/constants.ts"() {
16
+ "use strict";
17
+ VERSION = "0.1.0";
18
+ APP_NAME = "vibebug";
19
+ DB_FILENAME = "vibebug.db";
20
+ VIBEBUG_DIR = ".vibebug";
21
+ DEFAULT_DASHBOARD_PORT = 7600;
22
+ DEFAULT_RING_BUFFER_SIZE = 200 * 1024;
23
+ DEFAULT_TAIL_LINES = 200;
24
+ DEFAULT_STREAM_QUIET_TIMEOUT_MS = 2e3;
25
+ DEFAULT_STREAM_COOLDOWN_MS = 6e4;
26
+ MAX_ERROR_BLOCK_LINES = 50;
27
+ MAX_DIFF_SIZE = 50 * 1024;
28
+ DEFAULT_AI_MODEL = "claude-sonnet";
29
+ AI_PRICING = {
30
+ "claude-sonnet": { input: 3, output: 15 },
31
+ "claude-opus": { input: 15, output: 75 },
32
+ "claude-haiku": { input: 0.25, output: 1.25 },
33
+ "gpt-4o": { input: 2.5, output: 10 }
34
+ };
35
+ DEFAULT_ESTIMATED_OUTPUT_TOKENS = 1e3;
36
+ CHARS_PER_TOKEN = 4;
37
+ }
38
+ });
39
+
40
+ // src/utils/ring-buffer.ts
41
+ var RingBuffer;
42
+ var init_ring_buffer = __esm({
43
+ "src/utils/ring-buffer.ts"() {
44
+ "use strict";
45
+ RingBuffer = class {
46
+ chunks = [];
47
+ totalBytes = 0;
48
+ maxBytes;
49
+ constructor(maxBytes) {
50
+ this.maxBytes = maxBytes;
51
+ }
52
+ push(chunk) {
53
+ this.chunks.push(chunk);
54
+ this.totalBytes += chunk.length;
55
+ while (this.totalBytes > this.maxBytes && this.chunks.length > 1) {
56
+ const dropped = this.chunks.shift();
57
+ this.totalBytes -= dropped.length;
58
+ }
59
+ }
60
+ toString() {
61
+ return Buffer.concat(this.chunks).toString("utf-8");
62
+ }
63
+ get size() {
64
+ return this.totalBytes;
65
+ }
66
+ };
67
+ }
68
+ });
69
+
70
+ // src/core/runner.ts
71
+ import { spawn } from "child_process";
72
+ async function runCommand(argv, onChunk) {
73
+ if (argv.length === 0) {
74
+ return {
75
+ exitCode: 1,
76
+ signal: null,
77
+ stdout: "",
78
+ stderr: "No command provided",
79
+ durationMs: 0,
80
+ command: ""
81
+ };
82
+ }
83
+ const commandStr = argv.join(" ");
84
+ const startTime = performance.now();
85
+ const stdoutBuffer = new RingBuffer(DEFAULT_RING_BUFFER_SIZE);
86
+ const stderrBuffer = new RingBuffer(DEFAULT_RING_BUFFER_SIZE);
87
+ return new Promise((resolve) => {
88
+ const child = spawn(commandStr, [], {
89
+ stdio: ["inherit", "pipe", "pipe"],
90
+ env: {
91
+ ...process.env,
92
+ FORCE_COLOR: "1"
93
+ },
94
+ shell: true
95
+ });
96
+ child.stdout?.on("data", (chunk) => {
97
+ process.stdout.write(chunk);
98
+ stdoutBuffer.push(chunk);
99
+ onChunk?.(chunk.toString("utf-8"));
100
+ });
101
+ child.stderr?.on("data", (chunk) => {
102
+ process.stderr.write(chunk);
103
+ stderrBuffer.push(chunk);
104
+ onChunk?.(chunk.toString("utf-8"));
105
+ });
106
+ const onSigInt = () => child.kill("SIGINT");
107
+ const onSigTerm = () => child.kill("SIGTERM");
108
+ process.on("SIGINT", onSigInt);
109
+ process.on("SIGTERM", onSigTerm);
110
+ child.on("close", (exitCode, signal) => {
111
+ process.removeListener("SIGINT", onSigInt);
112
+ process.removeListener("SIGTERM", onSigTerm);
113
+ const durationMs = Math.round(performance.now() - startTime);
114
+ resolve({
115
+ exitCode: exitCode ?? (signal ? 1 : 0),
116
+ signal: signal ?? null,
117
+ stdout: stdoutBuffer.toString(),
118
+ stderr: stderrBuffer.toString(),
119
+ durationMs,
120
+ command: commandStr
121
+ });
122
+ });
123
+ child.on("error", (err) => {
124
+ process.removeListener("SIGINT", onSigInt);
125
+ process.removeListener("SIGTERM", onSigTerm);
126
+ const durationMs = Math.round(performance.now() - startTime);
127
+ resolve({
128
+ exitCode: 1,
129
+ signal: null,
130
+ stdout: stdoutBuffer.toString(),
131
+ stderr: err.message,
132
+ durationMs,
133
+ command: commandStr
134
+ });
135
+ });
136
+ });
137
+ }
138
+ var init_runner = __esm({
139
+ "src/core/runner.ts"() {
140
+ "use strict";
141
+ init_ring_buffer();
142
+ init_constants();
143
+ }
144
+ });
145
+
146
+ // src/core/signature.ts
147
+ import { createHash } from "crypto";
148
+ function generateSignature(rawLog, command) {
149
+ const normalized = normalizeLog(rawLog);
150
+ const anchors = extractAnchors(normalized);
151
+ const input2 = anchors.length > 0 ? `${anchors.join("\n")}
152
+ ${command}` : `${normalized}
153
+ ${command}`;
154
+ return createHash("sha256").update(input2).digest("hex").slice(0, 16);
155
+ }
156
+ function extractAnchors(normalizedLog) {
157
+ const lines = normalizedLog.split("\n").map((l) => l.trim()).filter(Boolean);
158
+ const anchors = [];
159
+ for (let i = 0; i < lines.length; i++) {
160
+ const line = lines[i];
161
+ if (isErrorHeader(line)) {
162
+ anchors.push(line);
163
+ const frames = collectStackFrames(lines, i + 1, 5);
164
+ anchors.push(...frames);
165
+ }
166
+ }
167
+ return [...new Set(anchors)];
168
+ }
169
+ function isErrorHeader(line) {
170
+ return ERROR_HEADER_PATTERNS.some((re) => re.test(line));
171
+ }
172
+ function collectStackFrames(lines, startIdx, maxFrames) {
173
+ const frames = [];
174
+ let collected = 0;
175
+ for (let i = startIdx; i < lines.length && collected < maxFrames; i++) {
176
+ const line = lines[i];
177
+ if (!line || /^[-=_]{3,}$/.test(line) || /^$/.test(line)) break;
178
+ if (isErrorHeader(line)) break;
179
+ if (STACK_FRAME_PATTERNS.some((re) => re.test(line))) {
180
+ frames.push(line);
181
+ collected++;
182
+ continue;
183
+ }
184
+ if (/^\s+/.test(lines[startIdx - 1] ? line : "") || isDiagnosticLine(line)) {
185
+ frames.push(line);
186
+ collected++;
187
+ continue;
188
+ }
189
+ }
190
+ return frames;
191
+ }
192
+ function isDiagnosticLine(line) {
193
+ return DIAGNOSTIC_PATTERNS.some((re) => re.test(line));
194
+ }
195
+ function normalizeLog(raw) {
196
+ let s = raw;
197
+ s = s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "");
198
+ s = s.replace(/\d{4}-\d{2}-\d{2}[T_]\d{2}[:\-_]\d{2}[:\-_]\d{2}[.\d_Z]*/g, "<TS>");
199
+ s = s.replace(/\d{1,2}:\d{2}:\d{2}\s*(AM|PM)?/gi, "<TS>");
200
+ s = s.replace(/0x[0-9a-fA-F]{6,}/g, "<HEX>");
201
+ s = s.replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, "<UUID>");
202
+ s = s.replace(/\b(dep|chunk|vendor|assets?)-[a-zA-Z0-9_-]{4,}\b/g, "$1-<HASH>");
203
+ s = s.replace(/(?:\/[\w.@-]+)+\/([\w.@-]+)/g, "$1");
204
+ s = s.replace(/([\w.-]+):\d+:\d+/g, "$1:<L>:<C>");
205
+ s = s.replace(/([\w.-]+):(\d+)\b(?!:)/g, "$1:<L>");
206
+ s = s.replace(/\b\d+\.\d+s\b/g, "<DUR>");
207
+ s = s.replace(/\b\d+ (items?|modules?|files?|tests?|errors?|warnings?|problems?)\b/gi, "<N> $1");
208
+ s = s.replace(/v?\d+\.\d+\.\d+/g, "<VER>");
209
+ s = s.replace(/\bplatform (linux|darwin|win32|windows)\b/gi, "platform <PLATFORM>");
210
+ s = s.replace(/[^\S\n]+/g, " ");
211
+ s = s.replace(/\n{3,}/g, "\n\n");
212
+ return s.trim();
213
+ }
214
+ var ERROR_HEADER_PATTERNS, STACK_FRAME_PATTERNS, DIAGNOSTIC_PATTERNS;
215
+ var init_signature = __esm({
216
+ "src/core/signature.ts"() {
217
+ "use strict";
218
+ ERROR_HEADER_PATTERNS = [
219
+ // JS/TS errors
220
+ /^(Error|TypeError|ReferenceError|SyntaxError|RangeError|URIError|EvalError):/,
221
+ /^Uncaught\s/,
222
+ // TypeScript compiler
223
+ /error TS\d+:/,
224
+ // Rust compiler
225
+ /^error\[E\d+\]:/,
226
+ /^error: aborting due to/,
227
+ // Python
228
+ /^(ModuleNotFoundError|ImportError|ConnectionRefusedError|AssertionError|AttributeError|KeyError|ValueError|RuntimeError|FileNotFoundError|PermissionError|OSError):/,
229
+ /^Traceback \(most recent call last\)/,
230
+ // Go
231
+ /^panic:/,
232
+ // npm
233
+ /^npm ERR! code /,
234
+ // Build tools
235
+ /^Build failed/i,
236
+ /^Failed to compile/i,
237
+ /Module not found/,
238
+ /Cannot find module/,
239
+ // Vite/Rollup
240
+ /Rollup failed to resolve import/,
241
+ // ESLint
242
+ /ESLint couldn't find the config/,
243
+ // Generic
244
+ /^FATAL/i,
245
+ /^ERROR\s/,
246
+ /CompileError/,
247
+ /ELIFECYCLE/
248
+ ];
249
+ STACK_FRAME_PATTERNS = [
250
+ // JS: "at Function.name (file:line:col)"
251
+ /^\s*at\s/,
252
+ // Python: "File "path", line N"
253
+ /^\s*File\s+"/,
254
+ // Go: "file.go:42 +0x..."
255
+ /^\s+[\w./]+\.go:\d+/,
256
+ // Rust: "--> src/file.rs:line:col"
257
+ /^\s*-->/,
258
+ // Generic: "path:line:col"
259
+ /^\s*[\w./]+\.\w+:<L>:<C>/
260
+ ];
261
+ DIAGNOSTIC_PATTERNS = [
262
+ // TS: "Property 'X' is missing"
263
+ /Property '.+' is missing/,
264
+ // TS: "Type 'X' is not assignable to type 'Y'"
265
+ /Type '.+' is not assignable/,
266
+ // TS: "Cannot find name 'X'"
267
+ /Cannot find name/,
268
+ // TS: "Cannot find module 'X'"
269
+ /Cannot find module/,
270
+ // Rust: "expected `X`, found `Y`"
271
+ /expected `.+`, found/,
272
+ // Rust: "cannot borrow"
273
+ /cannot borrow/,
274
+ // Python: "E ..."
275
+ /^E\s{2,}/,
276
+ // Go: "cannot use"
277
+ /cannot use/,
278
+ // npm: peer dependency info
279
+ /peer .+ from/,
280
+ // ESLint rule names
281
+ /@typescript-eslint\//,
282
+ // Vite/Rollup: resolve info
283
+ /explicitly add it to/
284
+ ];
285
+ }
286
+ });
287
+
288
+ // src/core/cost-estimator.ts
289
+ function estimateCost(rawLogLength, model) {
290
+ const modelKey = model ?? DEFAULT_AI_MODEL;
291
+ const pricing = AI_PRICING[modelKey] ?? AI_PRICING[DEFAULT_AI_MODEL];
292
+ const inputTokens = Math.ceil(rawLogLength / CHARS_PER_TOKEN);
293
+ const outputTokens = DEFAULT_ESTIMATED_OUTPUT_TOKENS;
294
+ const cost = (inputTokens * pricing.input + outputTokens * pricing.output) / 1e6;
295
+ return { inputTokens, outputTokens, cost };
296
+ }
297
+ var init_cost_estimator = __esm({
298
+ "src/core/cost-estimator.ts"() {
299
+ "use strict";
300
+ init_constants();
301
+ }
302
+ });
303
+
304
+ // src/core/git-context.ts
305
+ import { execSync } from "child_process";
306
+ function getGitContext(cwd) {
307
+ try {
308
+ const opts = { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] };
309
+ const branch = execSync("git rev-parse --abbrev-ref HEAD", opts).trim();
310
+ const commit = execSync("git rev-parse --short HEAD", opts).trim();
311
+ const statusOutput = execSync("git status --porcelain", opts).trim();
312
+ const dirty = statusOutput.length > 0;
313
+ return { branch, commit, dirty };
314
+ } catch {
315
+ return { branch: null, commit: null, dirty: null };
316
+ }
317
+ }
318
+ function getGitDiff(cwd, fromCommit, toCommit, maxSize) {
319
+ try {
320
+ const diff = execSync(`git diff ${fromCommit}..${toCommit}`, {
321
+ cwd,
322
+ encoding: "utf-8",
323
+ maxBuffer: maxSize
324
+ });
325
+ return diff.length > 0 ? diff.slice(0, maxSize) : null;
326
+ } catch {
327
+ return null;
328
+ }
329
+ }
330
+ var init_git_context = __esm({
331
+ "src/core/git-context.ts"() {
332
+ "use strict";
333
+ }
334
+ });
335
+
336
+ // src/db/schema.ts
337
+ var schema_exports = {};
338
+ __export(schema_exports, {
339
+ fixAttempts: () => fixAttempts,
340
+ issues: () => issues,
341
+ occurrences: () => occurrences,
342
+ projects: () => projects,
343
+ runLog: () => runLog
344
+ });
345
+ import { sqliteTable, text, integer, real, index, uniqueIndex } from "drizzle-orm/sqlite-core";
346
+ var projects, issues, occurrences, fixAttempts, runLog;
347
+ var init_schema = __esm({
348
+ "src/db/schema.ts"() {
349
+ "use strict";
350
+ projects = sqliteTable("projects", {
351
+ id: text("id").primaryKey(),
352
+ name: text("name").notNull(),
353
+ rootPath: text("root_path").notNull().unique(),
354
+ createdAt: text("created_at").notNull(),
355
+ updatedAt: text("updated_at").notNull()
356
+ });
357
+ issues = sqliteTable("issues", {
358
+ id: text("id").primaryKey(),
359
+ projectId: text("project_id").notNull().references(() => projects.id),
360
+ title: text("title").notNull(),
361
+ type: text("type", { enum: ["build", "runtime", "test", "lint", "unknown"] }).notNull().default("unknown"),
362
+ severity: text("severity", { enum: ["low", "medium", "high", "critical"] }).notNull().default("medium"),
363
+ status: text("status", { enum: ["open", "resolved", "ignored"] }).notNull().default("open"),
364
+ signature: text("signature").notNull(),
365
+ occurrenceCount: integer("occurrence_count").notNull().default(1),
366
+ estimatedTotalCost: real("estimated_total_cost").notNull().default(0),
367
+ firstSeenAt: text("first_seen_at").notNull(),
368
+ lastSeenAt: text("last_seen_at").notNull(),
369
+ resolvedAt: text("resolved_at"),
370
+ regressionFlag: integer("regression_flag", { mode: "boolean" }).notNull().default(false),
371
+ createdAt: text("created_at").notNull(),
372
+ updatedAt: text("updated_at").notNull()
373
+ }, (table) => [
374
+ uniqueIndex("idx_issues_project_signature").on(table.projectId, table.signature),
375
+ index("idx_issues_project_status").on(table.projectId, table.status)
376
+ ]);
377
+ occurrences = sqliteTable("occurrences", {
378
+ id: text("id").primaryKey(),
379
+ issueId: text("issue_id").notNull().references(() => issues.id),
380
+ rawLog: text("raw_log").notNull(),
381
+ command: text("command").notNull(),
382
+ exitCode: integer("exit_code"),
383
+ signal: text("signal"),
384
+ durationMs: integer("duration_ms"),
385
+ gitBranch: text("git_branch"),
386
+ gitCommit: text("git_commit"),
387
+ gitDirty: integer("git_dirty", { mode: "boolean" }),
388
+ appliedDiff: text("applied_diff"),
389
+ estimatedInputTokens: integer("estimated_input_tokens").notNull().default(0),
390
+ estimatedOutputTokens: integer("estimated_output_tokens").notNull().default(0),
391
+ estimatedCost: real("estimated_cost").notNull().default(0),
392
+ capturedFrom: text("captured_from", { enum: ["wrapper", "stream"] }).notNull().default("wrapper"),
393
+ createdAt: text("created_at").notNull()
394
+ }, (table) => [
395
+ index("idx_occurrences_issue_created").on(table.issueId, table.createdAt)
396
+ ]);
397
+ fixAttempts = sqliteTable("fix_attempts", {
398
+ id: text("id").primaryKey(),
399
+ issueId: text("issue_id").notNull().references(() => issues.id),
400
+ summary: text("summary"),
401
+ rootCause: text("root_cause"),
402
+ prevention: text("prevention"),
403
+ successful: integer("successful", { mode: "boolean" }),
404
+ source: text("source", { enum: ["agent", "manual", "api"] }).notNull().default("manual"),
405
+ createdAt: text("created_at").notNull()
406
+ }, (table) => [
407
+ index("idx_fix_attempts_issue").on(table.issueId)
408
+ ]);
409
+ runLog = sqliteTable("run_log", {
410
+ id: text("id").primaryKey(),
411
+ projectId: text("project_id").notNull().references(() => projects.id),
412
+ command: text("command").notNull(),
413
+ exitCode: integer("exit_code"),
414
+ durationMs: integer("duration_ms"),
415
+ createdAt: text("created_at").notNull()
416
+ }, (table) => [
417
+ index("idx_run_log_project_created").on(table.projectId, table.createdAt)
418
+ ]);
419
+ }
420
+ });
421
+
422
+ // src/utils/paths.ts
423
+ import { join, parse } from "path";
424
+ import { existsSync } from "fs";
425
+ function findProjectRoot(startDir) {
426
+ let dir = startDir;
427
+ const { root } = parse(dir);
428
+ while (dir !== root) {
429
+ if (existsSync(join(dir, VIBEBUG_DIR))) {
430
+ return dir;
431
+ }
432
+ dir = join(dir, "..");
433
+ }
434
+ return null;
435
+ }
436
+ function getVibeBugDir(projectRoot) {
437
+ return join(projectRoot, VIBEBUG_DIR);
438
+ }
439
+ function getDbPath(projectRoot) {
440
+ return join(projectRoot, VIBEBUG_DIR, DB_FILENAME);
441
+ }
442
+ var init_paths = __esm({
443
+ "src/utils/paths.ts"() {
444
+ "use strict";
445
+ init_constants();
446
+ }
447
+ });
448
+
449
+ // src/db/connection.ts
450
+ import Database from "better-sqlite3";
451
+ import { drizzle } from "drizzle-orm/better-sqlite3";
452
+ import { mkdirSync, existsSync as existsSync2 } from "fs";
453
+ function getDatabase(projectRoot) {
454
+ if (cachedDb) return cachedDb;
455
+ const vibebugDir = getVibeBugDir(projectRoot);
456
+ if (!existsSync2(vibebugDir)) {
457
+ mkdirSync(vibebugDir, { recursive: true });
458
+ }
459
+ const dbPath = getDbPath(projectRoot);
460
+ const sqlite = new Database(dbPath);
461
+ sqlite.pragma("journal_mode = WAL");
462
+ sqlite.pragma("foreign_keys = ON");
463
+ cachedSqlite = sqlite;
464
+ cachedDb = drizzle(sqlite, { schema: schema_exports });
465
+ return cachedDb;
466
+ }
467
+ var cachedDb, cachedSqlite;
468
+ var init_connection = __esm({
469
+ "src/db/connection.ts"() {
470
+ "use strict";
471
+ init_schema();
472
+ init_paths();
473
+ cachedDb = null;
474
+ cachedSqlite = null;
475
+ }
476
+ });
477
+
478
+ // src/db/migrate.ts
479
+ import Database2 from "better-sqlite3";
480
+ import { mkdirSync as mkdirSync2, existsSync as existsSync3 } from "fs";
481
+ function runMigrations(projectRoot) {
482
+ const vibebugDir = getVibeBugDir(projectRoot);
483
+ if (!existsSync3(vibebugDir)) {
484
+ mkdirSync2(vibebugDir, { recursive: true });
485
+ }
486
+ const dbPath = getDbPath(projectRoot);
487
+ const db = new Database2(dbPath);
488
+ db.pragma("journal_mode = WAL");
489
+ db.pragma("foreign_keys = ON");
490
+ db.exec(SCHEMA_SQL);
491
+ try {
492
+ db.exec(`ALTER TABLE fix_attempts ADD COLUMN source TEXT NOT NULL DEFAULT 'manual'`);
493
+ } catch {
494
+ }
495
+ db.exec(`
496
+ CREATE TABLE IF NOT EXISTS run_log (
497
+ id TEXT PRIMARY KEY,
498
+ project_id TEXT NOT NULL REFERENCES projects(id),
499
+ command TEXT NOT NULL,
500
+ exit_code INTEGER,
501
+ duration_ms INTEGER,
502
+ created_at TEXT NOT NULL
503
+ );
504
+
505
+ CREATE INDEX IF NOT EXISTS idx_run_log_project_created ON run_log(project_id, created_at);
506
+ `);
507
+ db.close();
508
+ }
509
+ var SCHEMA_SQL;
510
+ var init_migrate = __esm({
511
+ "src/db/migrate.ts"() {
512
+ "use strict";
513
+ init_paths();
514
+ SCHEMA_SQL = `
515
+ CREATE TABLE IF NOT EXISTS projects (
516
+ id TEXT PRIMARY KEY,
517
+ name TEXT NOT NULL,
518
+ root_path TEXT NOT NULL UNIQUE,
519
+ created_at TEXT NOT NULL,
520
+ updated_at TEXT NOT NULL
521
+ );
522
+
523
+ CREATE TABLE IF NOT EXISTS issues (
524
+ id TEXT PRIMARY KEY,
525
+ project_id TEXT NOT NULL REFERENCES projects(id),
526
+ title TEXT NOT NULL,
527
+ type TEXT NOT NULL DEFAULT 'unknown',
528
+ severity TEXT NOT NULL DEFAULT 'medium',
529
+ status TEXT NOT NULL DEFAULT 'open',
530
+ signature TEXT NOT NULL,
531
+ occurrence_count INTEGER NOT NULL DEFAULT 1,
532
+ estimated_total_cost REAL NOT NULL DEFAULT 0,
533
+ first_seen_at TEXT NOT NULL,
534
+ last_seen_at TEXT NOT NULL,
535
+ resolved_at TEXT,
536
+ regression_flag INTEGER NOT NULL DEFAULT 0,
537
+ created_at TEXT NOT NULL,
538
+ updated_at TEXT NOT NULL
539
+ );
540
+
541
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_issues_project_signature ON issues(project_id, signature);
542
+ CREATE INDEX IF NOT EXISTS idx_issues_project_status ON issues(project_id, status);
543
+
544
+ CREATE TABLE IF NOT EXISTS occurrences (
545
+ id TEXT PRIMARY KEY,
546
+ issue_id TEXT NOT NULL REFERENCES issues(id),
547
+ raw_log TEXT NOT NULL,
548
+ command TEXT NOT NULL,
549
+ exit_code INTEGER,
550
+ signal TEXT,
551
+ duration_ms INTEGER,
552
+ git_branch TEXT,
553
+ git_commit TEXT,
554
+ git_dirty INTEGER,
555
+ applied_diff TEXT,
556
+ estimated_input_tokens INTEGER NOT NULL DEFAULT 0,
557
+ estimated_output_tokens INTEGER NOT NULL DEFAULT 0,
558
+ estimated_cost REAL NOT NULL DEFAULT 0,
559
+ captured_from TEXT NOT NULL DEFAULT 'wrapper',
560
+ created_at TEXT NOT NULL
561
+ );
562
+
563
+ CREATE INDEX IF NOT EXISTS idx_occurrences_issue_created ON occurrences(issue_id, created_at);
564
+
565
+ CREATE TABLE IF NOT EXISTS fix_attempts (
566
+ id TEXT PRIMARY KEY,
567
+ issue_id TEXT NOT NULL REFERENCES issues(id),
568
+ summary TEXT,
569
+ root_cause TEXT,
570
+ prevention TEXT,
571
+ successful INTEGER,
572
+ source TEXT NOT NULL DEFAULT 'manual',
573
+ created_at TEXT NOT NULL
574
+ );
575
+
576
+ CREATE INDEX IF NOT EXISTS idx_fix_attempts_issue ON fix_attempts(issue_id);
577
+ `;
578
+ }
579
+ });
580
+
581
+ // src/db/queries.ts
582
+ import { basename } from "path";
583
+ import { eq, and, desc, sql } from "drizzle-orm";
584
+ import { nanoid } from "nanoid";
585
+ function ensureProject(rootPath) {
586
+ runMigrations(rootPath);
587
+ const db = getDatabase(rootPath);
588
+ const existing = db.select().from(projects).where(eq(projects.rootPath, rootPath)).get();
589
+ if (existing) {
590
+ return existing;
591
+ }
592
+ const now = (/* @__PURE__ */ new Date()).toISOString();
593
+ const project = {
594
+ id: nanoid(),
595
+ name: basename(rootPath),
596
+ rootPath,
597
+ createdAt: now,
598
+ updatedAt: now
599
+ };
600
+ db.insert(projects).values(project).run();
601
+ return project;
602
+ }
603
+ function findIssueBySignature(projectRoot, projectId, signature) {
604
+ const db = getDatabase(projectRoot);
605
+ return db.select().from(issues).where(and(eq(issues.projectId, projectId), eq(issues.signature, signature))).get();
606
+ }
607
+ function createIssue(projectRoot, data) {
608
+ const db = getDatabase(projectRoot);
609
+ const now = (/* @__PURE__ */ new Date()).toISOString();
610
+ const id = nanoid();
611
+ db.insert(issues).values({
612
+ id,
613
+ projectId: data.projectId,
614
+ title: data.title,
615
+ type: data.type,
616
+ severity: "medium",
617
+ status: "open",
618
+ signature: data.signature,
619
+ occurrenceCount: 1,
620
+ estimatedTotalCost: data.estimatedCost,
621
+ firstSeenAt: now,
622
+ lastSeenAt: now,
623
+ regressionFlag: false,
624
+ createdAt: now,
625
+ updatedAt: now
626
+ }).run();
627
+ return id;
628
+ }
629
+ function incrementIssue(projectRoot, issueId, additionalCost, wasResolved) {
630
+ const db = getDatabase(projectRoot);
631
+ const now = (/* @__PURE__ */ new Date()).toISOString();
632
+ const updateFields = {
633
+ occurrenceCount: sql`${issues.occurrenceCount} + 1`,
634
+ lastSeenAt: now,
635
+ estimatedTotalCost: sql`${issues.estimatedTotalCost} + ${additionalCost}`,
636
+ updatedAt: now
637
+ };
638
+ if (wasResolved) {
639
+ updateFields.status = "open";
640
+ updateFields.regressionFlag = true;
641
+ updateFields.resolvedAt = null;
642
+ }
643
+ db.update(issues).set(updateFields).where(eq(issues.id, issueId)).run();
644
+ }
645
+ function createOccurrence(projectRoot, data) {
646
+ const db = getDatabase(projectRoot);
647
+ const now = (/* @__PURE__ */ new Date()).toISOString();
648
+ db.insert(occurrences).values({
649
+ id: nanoid(),
650
+ issueId: data.issueId,
651
+ rawLog: data.rawLog,
652
+ command: data.command,
653
+ exitCode: data.exitCode,
654
+ signal: data.signal,
655
+ durationMs: data.durationMs,
656
+ gitBranch: data.gitContext.branch,
657
+ gitCommit: data.gitContext.commit,
658
+ gitDirty: data.gitContext.dirty,
659
+ appliedDiff: data.appliedDiff,
660
+ estimatedInputTokens: data.costEstimate.inputTokens,
661
+ estimatedOutputTokens: data.costEstimate.outputTokens,
662
+ estimatedCost: data.costEstimate.cost,
663
+ capturedFrom: data.capturedFrom,
664
+ createdAt: now
665
+ }).run();
666
+ }
667
+ function getOpenIssues(projectRoot, projectId) {
668
+ const db = getDatabase(projectRoot);
669
+ return db.select().from(issues).where(and(eq(issues.projectId, projectId), eq(issues.status, "open"))).orderBy(desc(issues.lastSeenAt)).all();
670
+ }
671
+ function getAllIssues(projectRoot, projectId) {
672
+ const db = getDatabase(projectRoot);
673
+ return db.select().from(issues).where(eq(issues.projectId, projectId)).orderBy(desc(issues.lastSeenAt)).all();
674
+ }
675
+ function getLastOccurrenceCommit(projectRoot, issueId) {
676
+ const db = getDatabase(projectRoot);
677
+ const last = db.select({ gitCommit: occurrences.gitCommit }).from(occurrences).where(eq(occurrences.issueId, issueId)).orderBy(desc(occurrences.createdAt)).limit(1).get();
678
+ return last?.gitCommit ?? null;
679
+ }
680
+ function getMostRecentOpenIssue(projectRoot, projectId) {
681
+ const db = getDatabase(projectRoot);
682
+ return db.select().from(issues).where(and(eq(issues.projectId, projectId), eq(issues.status, "open"))).orderBy(desc(issues.lastSeenAt)).limit(1).get();
683
+ }
684
+ function resolveIssue(projectRoot, issueId) {
685
+ const db = getDatabase(projectRoot);
686
+ const now = (/* @__PURE__ */ new Date()).toISOString();
687
+ db.update(issues).set({
688
+ status: "resolved",
689
+ resolvedAt: now,
690
+ updatedAt: now
691
+ }).where(eq(issues.id, issueId)).run();
692
+ }
693
+ function getIssueById(projectRoot, issueId) {
694
+ const db = getDatabase(projectRoot);
695
+ return db.select().from(issues).where(eq(issues.id, issueId)).get();
696
+ }
697
+ function createFixAttempt(projectRoot, data) {
698
+ const db = getDatabase(projectRoot);
699
+ const now = (/* @__PURE__ */ new Date()).toISOString();
700
+ const id = nanoid();
701
+ db.insert(fixAttempts).values({
702
+ id,
703
+ issueId: data.issueId,
704
+ summary: data.summary ?? null,
705
+ rootCause: data.rootCause ?? null,
706
+ prevention: data.prevention ?? null,
707
+ successful: null,
708
+ source: data.source ?? "manual",
709
+ createdAt: now
710
+ }).run();
711
+ return id;
712
+ }
713
+ function logRun(projectRoot, data) {
714
+ const db = getDatabase(projectRoot);
715
+ db.insert(runLog).values({
716
+ id: nanoid(),
717
+ projectId: data.projectId,
718
+ command: data.command,
719
+ exitCode: data.exitCode,
720
+ durationMs: data.durationMs,
721
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
722
+ }).run();
723
+ }
724
+ var init_queries = __esm({
725
+ "src/db/queries.ts"() {
726
+ "use strict";
727
+ init_connection();
728
+ init_schema();
729
+ init_migrate();
730
+ }
731
+ });
732
+
733
+ // src/core/capture.ts
734
+ import pc from "picocolors";
735
+ async function captureFailure(input2) {
736
+ const { project, commandStr, result, gitContext, skipSignatures } = input2;
737
+ const fullLog = result.stderr || result.stdout;
738
+ if (!fullLog.trim()) return;
739
+ const rawLog = tailLines(fullLog, DEFAULT_TAIL_LINES);
740
+ const signature = generateSignature(rawLog, commandStr);
741
+ if (skipSignatures?.has(signature)) return;
742
+ const costEstimate = estimateCost(rawLog.length);
743
+ const issueType = inferType(commandStr);
744
+ const existing = findIssueBySignature(project.rootPath, project.id, signature);
745
+ let issueId;
746
+ let occurrenceCount;
747
+ let appliedDiff = null;
748
+ let isRegression = false;
749
+ if (existing) {
750
+ issueId = existing.id;
751
+ const wasResolved = existing.status === "resolved";
752
+ isRegression = wasResolved;
753
+ if (gitContext.commit) {
754
+ const prevCommit = getLastOccurrenceCommit(project.rootPath, issueId);
755
+ if (prevCommit && prevCommit !== gitContext.commit) {
756
+ appliedDiff = getGitDiff(project.rootPath, prevCommit, gitContext.commit, MAX_DIFF_SIZE);
757
+ }
758
+ }
759
+ incrementIssue(project.rootPath, issueId, costEstimate.cost, wasResolved);
760
+ occurrenceCount = existing.occurrenceCount + 1;
761
+ } else {
762
+ const title2 = extractTitle(rawLog);
763
+ issueId = createIssue(project.rootPath, {
764
+ projectId: project.id,
765
+ title: title2,
766
+ type: issueType,
767
+ signature,
768
+ estimatedCost: costEstimate.cost
769
+ });
770
+ occurrenceCount = 1;
771
+ }
772
+ createOccurrence(project.rootPath, {
773
+ issueId,
774
+ rawLog,
775
+ command: commandStr,
776
+ exitCode: result.exitCode,
777
+ signal: result.signal,
778
+ durationMs: result.durationMs,
779
+ gitContext,
780
+ appliedDiff,
781
+ costEstimate,
782
+ capturedFrom: "wrapper"
783
+ });
784
+ const title = existing?.title ?? extractTitle(rawLog);
785
+ const shortTitle = title.length > 60 ? title.slice(0, 57) + "..." : title;
786
+ const costStr = formatCost(costEstimate.cost * occurrenceCount);
787
+ const countStr = occurrenceCount > 1 ? ` (seen ${occurrenceCount} times, ~${costStr} in AI fixes)` : "";
788
+ const regressionStr = isRegression ? pc.red(" [REGRESSION]") : "";
789
+ console.error(
790
+ `
791
+ ${pc.cyan("[vibebug]")} ${pc.bold(shortTitle)}${countStr}${regressionStr}`
792
+ );
793
+ }
794
+ function inferType(command) {
795
+ const lower = command.toLowerCase();
796
+ if (/\b(build|compile|tsc)\b/.test(lower)) return "build";
797
+ if (/\b(test|jest|vitest|mocha|pytest|cargo test|go test)\b/.test(lower)) return "test";
798
+ if (/\b(lint|eslint|prettier|biome)\b/.test(lower)) return "lint";
799
+ if (/\b(dev|start|serve|run)\b/.test(lower)) return "runtime";
800
+ return "unknown";
801
+ }
802
+ function extractTitle(rawLog) {
803
+ const lines = rawLog.split("\n").filter((l) => l.trim());
804
+ for (const line of lines) {
805
+ const stripped = line.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim();
806
+ if (/^(Error|TypeError|ReferenceError|SyntaxError|RangeError):/.test(stripped)) {
807
+ return stripped.slice(0, 200);
808
+ }
809
+ if (/^error(\[|\s|:)/i.test(stripped)) {
810
+ return stripped.slice(0, 200);
811
+ }
812
+ if (/Module not found|Cannot find module/.test(stripped)) {
813
+ return stripped.slice(0, 200);
814
+ }
815
+ if (/FAIL|FATAL|panic:/.test(stripped)) {
816
+ return stripped.slice(0, 200);
817
+ }
818
+ }
819
+ const last = lines.at(-1)?.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim() ?? "Unknown error";
820
+ return last.slice(0, 200);
821
+ }
822
+ function captureStreamError(input2) {
823
+ const { project, commandStr, block, gitContext } = input2;
824
+ const rawLog = block.lines.join("\n");
825
+ if (!rawLog.trim()) return;
826
+ const costEstimate = estimateCost(rawLog.length);
827
+ const issueType = inferType(commandStr);
828
+ const existing = findIssueBySignature(project.rootPath, project.id, block.signature);
829
+ let issueId;
830
+ let occurrenceCount;
831
+ let appliedDiff = null;
832
+ let isRegression = false;
833
+ if (existing) {
834
+ issueId = existing.id;
835
+ const wasResolved = existing.status === "resolved";
836
+ isRegression = wasResolved;
837
+ if (gitContext.commit) {
838
+ const prevCommit = getLastOccurrenceCommit(project.rootPath, issueId);
839
+ if (prevCommit && prevCommit !== gitContext.commit) {
840
+ appliedDiff = getGitDiff(project.rootPath, prevCommit, gitContext.commit, MAX_DIFF_SIZE);
841
+ }
842
+ }
843
+ incrementIssue(project.rootPath, issueId, costEstimate.cost, wasResolved);
844
+ occurrenceCount = existing.occurrenceCount + 1;
845
+ } else {
846
+ const title2 = extractTitle(rawLog);
847
+ issueId = createIssue(project.rootPath, {
848
+ projectId: project.id,
849
+ title: title2,
850
+ type: issueType,
851
+ signature: block.signature,
852
+ estimatedCost: costEstimate.cost
853
+ });
854
+ occurrenceCount = 1;
855
+ }
856
+ createOccurrence(project.rootPath, {
857
+ issueId,
858
+ rawLog,
859
+ command: commandStr,
860
+ exitCode: null,
861
+ signal: null,
862
+ durationMs: 0,
863
+ gitContext,
864
+ appliedDiff,
865
+ costEstimate,
866
+ capturedFrom: "stream"
867
+ });
868
+ const title = existing?.title ?? extractTitle(rawLog);
869
+ const shortTitle = title.length > 60 ? title.slice(0, 57) + "..." : title;
870
+ const costStr = formatCost(costEstimate.cost * occurrenceCount);
871
+ const countStr = occurrenceCount > 1 ? ` (seen ${occurrenceCount} times, ~${costStr} in AI fixes)` : "";
872
+ const regressionStr = isRegression ? pc.red(" [REGRESSION]") : "";
873
+ console.error(
874
+ `
875
+ ${pc.cyan("[vibebug:stream]")} ${pc.bold(shortTitle)}${countStr}${regressionStr}`
876
+ );
877
+ }
878
+ function formatCost(cost) {
879
+ if (cost < 0.01) return `$${cost.toFixed(4)}`;
880
+ return `$${cost.toFixed(2)}`;
881
+ }
882
+ function tailLines(text2, maxLines) {
883
+ const lines = text2.split("\n");
884
+ if (lines.length <= maxLines) return text2;
885
+ return lines.slice(-maxLines).join("\n");
886
+ }
887
+ var init_capture = __esm({
888
+ "src/core/capture.ts"() {
889
+ "use strict";
890
+ init_signature();
891
+ init_cost_estimator();
892
+ init_git_context();
893
+ init_queries();
894
+ init_constants();
895
+ }
896
+ });
897
+
898
+ // src/core/stream-detector.ts
899
+ var ERROR_MARKERS, StreamDetector;
900
+ var init_stream_detector = __esm({
901
+ "src/core/stream-detector.ts"() {
902
+ "use strict";
903
+ init_signature();
904
+ init_constants();
905
+ ERROR_MARKERS = [
906
+ /^(Error|TypeError|ReferenceError|SyntaxError|RangeError|URIError|EvalError):/,
907
+ /^Uncaught /,
908
+ /^Traceback \(most recent call last\)/,
909
+ /^panic:/,
910
+ /^FATAL/i,
911
+ /^npm ERR!/,
912
+ /^ERR!/,
913
+ /ELIFECYCLE/,
914
+ /Module not found/,
915
+ /Cannot find module/,
916
+ /Segmentation fault/,
917
+ /error\[E\d+\]/,
918
+ /error TS\d+/,
919
+ /^ERROR\s/,
920
+ /CompileError/,
921
+ /Build failed/i,
922
+ /Failed to compile/i
923
+ ];
924
+ StreamDetector = class {
925
+ state = "idle";
926
+ blockLines = [];
927
+ quietTimer = null;
928
+ recentSignatures = /* @__PURE__ */ new Map();
929
+ // signature → timestamp
930
+ onCapture;
931
+ command;
932
+ quietTimeoutMs;
933
+ cooldownMs;
934
+ constructor(command, onCapture, options) {
935
+ this.command = command;
936
+ this.onCapture = onCapture;
937
+ this.quietTimeoutMs = options?.quietTimeoutMs ?? DEFAULT_STREAM_QUIET_TIMEOUT_MS;
938
+ this.cooldownMs = options?.cooldownMs ?? DEFAULT_STREAM_COOLDOWN_MS;
939
+ }
940
+ /**
941
+ * Feed a chunk of output to the detector. Call this for each data event
942
+ * from stdout/stderr.
943
+ */
944
+ feed(chunk) {
945
+ const lines = chunk.split("\n");
946
+ for (const line of lines) {
947
+ this.processLine(line);
948
+ }
949
+ }
950
+ /**
951
+ * Call when the process exits to flush any pending error block.
952
+ */
953
+ flush() {
954
+ if (this.state === "collecting" && this.blockLines.length > 0) {
955
+ this.finalizeBlock();
956
+ }
957
+ this.clearQuietTimer();
958
+ }
959
+ destroy() {
960
+ this.clearQuietTimer();
961
+ }
962
+ /**
963
+ * Returns all signatures that were captured by the stream detector.
964
+ */
965
+ getCapturedSignatures() {
966
+ return new Set(this.recentSignatures.keys());
967
+ }
968
+ processLine(line) {
969
+ const stripped = line.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").trim();
970
+ if (this.state === "idle") {
971
+ if (this.isErrorMarker(stripped)) {
972
+ this.state = "collecting";
973
+ this.blockLines = [line];
974
+ this.resetQuietTimer();
975
+ }
976
+ } else if (this.state === "collecting") {
977
+ this.blockLines.push(line);
978
+ this.resetQuietTimer();
979
+ if (this.blockLines.length >= MAX_ERROR_BLOCK_LINES) {
980
+ this.finalizeBlock();
981
+ }
982
+ }
983
+ }
984
+ isErrorMarker(line) {
985
+ return ERROR_MARKERS.some((re) => re.test(line));
986
+ }
987
+ resetQuietTimer() {
988
+ this.clearQuietTimer();
989
+ this.quietTimer = setTimeout(() => {
990
+ if (this.state === "collecting") {
991
+ this.finalizeBlock();
992
+ }
993
+ }, this.quietTimeoutMs);
994
+ }
995
+ clearQuietTimer() {
996
+ if (this.quietTimer) {
997
+ clearTimeout(this.quietTimer);
998
+ this.quietTimer = null;
999
+ }
1000
+ }
1001
+ finalizeBlock() {
1002
+ this.clearQuietTimer();
1003
+ this.state = "idle";
1004
+ const rawBlock = this.blockLines.join("\n");
1005
+ const signature = generateSignature(rawBlock, this.command);
1006
+ const lastSeen = this.recentSignatures.get(signature);
1007
+ const now = Date.now();
1008
+ if (lastSeen && now - lastSeen < this.cooldownMs) {
1009
+ this.blockLines = [];
1010
+ return;
1011
+ }
1012
+ this.recentSignatures.set(signature, now);
1013
+ for (const [sig, ts] of this.recentSignatures) {
1014
+ if (now - ts > this.cooldownMs * 2) {
1015
+ this.recentSignatures.delete(sig);
1016
+ }
1017
+ }
1018
+ this.onCapture({ lines: this.blockLines, signature });
1019
+ this.blockLines = [];
1020
+ }
1021
+ };
1022
+ }
1023
+ });
1024
+
1025
+ // src/commands/wrap.ts
1026
+ var wrap_exports = {};
1027
+ __export(wrap_exports, {
1028
+ wrapCommand: () => wrapCommand
1029
+ });
1030
+ async function wrapCommand(args) {
1031
+ const commandStr = args.join(" ");
1032
+ const cwd = process.cwd();
1033
+ const project = ensureProject(cwd);
1034
+ const detector = new StreamDetector(commandStr, (block) => {
1035
+ const gitContext = getGitContext(cwd);
1036
+ captureStreamError({
1037
+ project,
1038
+ commandStr,
1039
+ block,
1040
+ gitContext
1041
+ });
1042
+ });
1043
+ const result = await runCommand(args, (chunk) => {
1044
+ detector.feed(chunk);
1045
+ });
1046
+ detector.flush();
1047
+ detector.destroy();
1048
+ logRun(cwd, {
1049
+ projectId: project.id,
1050
+ command: commandStr,
1051
+ exitCode: result.exitCode,
1052
+ durationMs: result.durationMs
1053
+ });
1054
+ if (result.exitCode !== 0) {
1055
+ const gitContext = getGitContext(cwd);
1056
+ await captureFailure({
1057
+ project,
1058
+ commandStr,
1059
+ result,
1060
+ gitContext,
1061
+ skipSignatures: detector.getCapturedSignatures()
1062
+ });
1063
+ }
1064
+ return result.exitCode ?? 1;
1065
+ }
1066
+ var init_wrap = __esm({
1067
+ "src/commands/wrap.ts"() {
1068
+ "use strict";
1069
+ init_runner();
1070
+ init_capture();
1071
+ init_git_context();
1072
+ init_queries();
1073
+ init_stream_detector();
1074
+ }
1075
+ });
1076
+
1077
+ // src/commands/init.ts
1078
+ var init_exports = {};
1079
+ __export(init_exports, {
1080
+ initCommand: () => initCommand
1081
+ });
1082
+ import pc2 from "picocolors";
1083
+ import { existsSync as existsSync4, appendFileSync, readFileSync } from "fs";
1084
+ import { join as join2 } from "path";
1085
+ async function initCommand() {
1086
+ const cwd = process.cwd();
1087
+ const vibebugDir = join2(cwd, VIBEBUG_DIR);
1088
+ if (existsSync4(vibebugDir)) {
1089
+ console.log(pc2.yellow("VibeBug is already initialized in this project."));
1090
+ return;
1091
+ }
1092
+ const project = ensureProject(cwd);
1093
+ const gitignorePath = join2(cwd, ".gitignore");
1094
+ if (existsSync4(gitignorePath)) {
1095
+ const content = readFileSync(gitignorePath, "utf-8");
1096
+ if (!content.includes(VIBEBUG_DIR)) {
1097
+ appendFileSync(gitignorePath, `
1098
+ # VibeBug local data
1099
+ ${VIBEBUG_DIR}/
1100
+ `);
1101
+ console.log(pc2.dim(`Added ${VIBEBUG_DIR}/ to .gitignore`));
1102
+ }
1103
+ }
1104
+ console.log(pc2.green(`VibeBug initialized for "${project.name}".`));
1105
+ console.log();
1106
+ console.log("Quick start:");
1107
+ console.log(` ${pc2.cyan("vb")} npm run build Run a command with failure capture`);
1108
+ console.log(` ${pc2.cyan("vb list")} View captured issues`);
1109
+ console.log(` ${pc2.cyan("vb dash")} Open the dashboard`);
1110
+ }
1111
+ var init_init = __esm({
1112
+ "src/commands/init.ts"() {
1113
+ "use strict";
1114
+ init_queries();
1115
+ init_constants();
1116
+ }
1117
+ });
1118
+
1119
+ // src/commands/list.ts
1120
+ var list_exports = {};
1121
+ __export(list_exports, {
1122
+ listCommand: () => listCommand
1123
+ });
1124
+ import pc3 from "picocolors";
1125
+ async function listCommand() {
1126
+ const cwd = process.cwd();
1127
+ const project = ensureProject(cwd);
1128
+ const issueList = getAllIssues(cwd, project.id);
1129
+ console.log();
1130
+ console.log(pc3.bold(project.name) + pc3.dim(` ${cwd}`));
1131
+ if (issueList.length === 0) {
1132
+ console.log(pc3.dim("No issues captured yet. Run commands with `vb` to start tracking."));
1133
+ return;
1134
+ }
1135
+ console.log();
1136
+ console.log(
1137
+ pc3.dim(
1138
+ pad("ID", 8) + pad("Title", 45) + pad("Type", 10) + pad("Status", 10) + pad("Seen", 6) + pad("Cost", 10) + "Last seen"
1139
+ )
1140
+ );
1141
+ console.log(pc3.dim("\u2500".repeat(110)));
1142
+ for (const issue of issueList) {
1143
+ const title = issue.title.length > 42 ? issue.title.slice(0, 39) + "..." : issue.title;
1144
+ const cost = formatCost2(issue.estimatedTotalCost);
1145
+ const lastSeen = timeAgo(issue.lastSeenAt);
1146
+ const statusColor = issue.status === "open" ? pc3.yellow : issue.status === "resolved" ? pc3.green : pc3.dim;
1147
+ const severityColor = issue.severity === "critical" ? pc3.red : issue.severity === "high" ? pc3.yellow : issue.severity === "medium" ? pc3.white : pc3.dim;
1148
+ const regression = issue.regressionFlag ? pc3.red(" !") : "";
1149
+ console.log(
1150
+ pad(issue.id.slice(0, 7), 8) + severityColor(pad(title, 45)) + pad(issue.type, 10) + statusColor(pad(issue.status, 10)) + pad(String(issue.occurrenceCount), 6) + pad(cost, 10) + lastSeen + regression
1151
+ );
1152
+ }
1153
+ console.log();
1154
+ console.log(pc3.dim(`${issueList.length} issue(s) total`));
1155
+ }
1156
+ function pad(str, width) {
1157
+ return str.padEnd(width);
1158
+ }
1159
+ function formatCost2(cost) {
1160
+ if (cost === 0) return "-";
1161
+ if (cost < 0.01) return `$${cost.toFixed(4)}`;
1162
+ return `$${cost.toFixed(2)}`;
1163
+ }
1164
+ function timeAgo(isoStr) {
1165
+ const diff = Date.now() - new Date(isoStr).getTime();
1166
+ const seconds = Math.floor(diff / 1e3);
1167
+ if (seconds < 60) return "just now";
1168
+ const minutes = Math.floor(seconds / 60);
1169
+ if (minutes < 60) return `${minutes}m ago`;
1170
+ const hours = Math.floor(minutes / 60);
1171
+ if (hours < 24) return `${hours}h ago`;
1172
+ const days = Math.floor(hours / 24);
1173
+ return `${days}d ago`;
1174
+ }
1175
+ var init_list = __esm({
1176
+ "src/commands/list.ts"() {
1177
+ "use strict";
1178
+ init_queries();
1179
+ }
1180
+ });
1181
+
1182
+ // src/server/routes/api.ts
1183
+ import { Hono } from "hono";
1184
+ import { eq as eq2, and as and2, desc as desc2, sql as sql2, like } from "drizzle-orm";
1185
+ import { nanoid as nanoid2 } from "nanoid";
1186
+ function createApiRoutes(projectRoot) {
1187
+ const api = new Hono();
1188
+ const db = getDatabase(projectRoot);
1189
+ api.get("/project", (c) => {
1190
+ const project = db.select().from(projects).where(eq2(projects.rootPath, projectRoot)).get();
1191
+ if (!project) return c.json({ error: "Project not found" }, 404);
1192
+ return c.json(project);
1193
+ });
1194
+ api.get("/stats", (c) => {
1195
+ const project = db.select().from(projects).where(eq2(projects.rootPath, projectRoot)).get();
1196
+ if (!project) return c.json({ error: "Project not found" }, 404);
1197
+ const totalIssues = db.select({ count: sql2`count(*)` }).from(issues).where(eq2(issues.projectId, project.id)).get()?.count ?? 0;
1198
+ const openIssues = db.select({ count: sql2`count(*)` }).from(issues).where(and2(eq2(issues.projectId, project.id), eq2(issues.status, "open"))).get()?.count ?? 0;
1199
+ const resolvedIssues = db.select({ count: sql2`count(*)` }).from(issues).where(and2(eq2(issues.projectId, project.id), eq2(issues.status, "resolved"))).get()?.count ?? 0;
1200
+ const totalOccurrences = db.select({ count: sql2`count(*)` }).from(occurrences).innerJoin(issues, eq2(occurrences.issueId, issues.id)).where(eq2(issues.projectId, project.id)).get()?.count ?? 0;
1201
+ const totalEstimatedCost = db.select({ total: sql2`coalesce(sum(${issues.estimatedTotalCost}), 0)` }).from(issues).where(eq2(issues.projectId, project.id)).get()?.total ?? 0;
1202
+ const regressions = db.select({ count: sql2`count(*)` }).from(issues).where(and2(eq2(issues.projectId, project.id), eq2(issues.regressionFlag, true))).get()?.count ?? 0;
1203
+ const severityDist = db.select({
1204
+ severity: issues.severity,
1205
+ count: sql2`count(*)`
1206
+ }).from(issues).where(eq2(issues.projectId, project.id)).groupBy(issues.severity).all();
1207
+ const typeDist = db.select({
1208
+ type: issues.type,
1209
+ count: sql2`count(*)`
1210
+ }).from(issues).where(eq2(issues.projectId, project.id)).groupBy(issues.type).all();
1211
+ const occurrencesPerDay = db.select({
1212
+ date: sql2`date(${occurrences.createdAt})`,
1213
+ count: sql2`count(*)`
1214
+ }).from(occurrences).innerJoin(issues, eq2(occurrences.issueId, issues.id)).where(and2(
1215
+ eq2(issues.projectId, project.id),
1216
+ sql2`${occurrences.createdAt} >= datetime('now', '-30 days')`
1217
+ )).groupBy(sql2`date(${occurrences.createdAt})`).orderBy(sql2`date(${occurrences.createdAt})`).all();
1218
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1219
+ const runsToday = db.select({ count: sql2`count(*)` }).from(runLog).where(and2(
1220
+ eq2(runLog.projectId, project.id),
1221
+ sql2`${runLog.createdAt} >= ${today}`
1222
+ )).get()?.count ?? 0;
1223
+ const failuresToday = db.select({ count: sql2`count(*)` }).from(runLog).where(and2(
1224
+ eq2(runLog.projectId, project.id),
1225
+ sql2`${runLog.createdAt} >= ${today}`,
1226
+ sql2`${runLog.exitCode} != 0`
1227
+ )).get()?.count ?? 0;
1228
+ const totalRuns = db.select({ count: sql2`count(*)` }).from(runLog).where(eq2(runLog.projectId, project.id)).get()?.count ?? 0;
1229
+ return c.json({
1230
+ totalIssues,
1231
+ openIssues,
1232
+ resolvedIssues,
1233
+ totalOccurrences,
1234
+ totalEstimatedCost,
1235
+ regressions,
1236
+ severityDistribution: severityDist,
1237
+ typeDistribution: typeDist,
1238
+ occurrencesPerDay,
1239
+ runsToday,
1240
+ failuresToday,
1241
+ totalRuns
1242
+ });
1243
+ });
1244
+ api.get("/issues", (c) => {
1245
+ const project = db.select().from(projects).where(eq2(projects.rootPath, projectRoot)).get();
1246
+ if (!project) return c.json({ error: "Project not found" }, 404);
1247
+ const status = c.req.query("status");
1248
+ const type = c.req.query("type");
1249
+ const severity = c.req.query("severity");
1250
+ const search = c.req.query("search");
1251
+ const sortBy = c.req.query("sort") ?? "lastSeenAt";
1252
+ let conditions = [eq2(issues.projectId, project.id)];
1253
+ if (status) conditions.push(eq2(issues.status, status));
1254
+ if (type) conditions.push(eq2(issues.type, type));
1255
+ if (severity) conditions.push(eq2(issues.severity, severity));
1256
+ if (search) conditions.push(like(issues.title, `%${search}%`));
1257
+ const orderCol = sortBy === "occurrences" ? desc2(issues.occurrenceCount) : sortBy === "cost" ? desc2(issues.estimatedTotalCost) : sortBy === "severity" ? desc2(issues.severity) : desc2(issues.lastSeenAt);
1258
+ const result = db.select().from(issues).where(and2(...conditions)).orderBy(orderCol).all();
1259
+ return c.json(result);
1260
+ });
1261
+ api.get("/issues/:id", (c) => {
1262
+ const issueId = c.req.param("id");
1263
+ const issue = db.select().from(issues).where(eq2(issues.id, issueId)).get();
1264
+ if (!issue) return c.json({ error: "Issue not found" }, 404);
1265
+ const issueOccurrences = db.select().from(occurrences).where(eq2(occurrences.issueId, issueId)).orderBy(desc2(occurrences.createdAt)).all();
1266
+ const fixes = db.select().from(fixAttempts).where(eq2(fixAttempts.issueId, issueId)).orderBy(desc2(fixAttempts.createdAt)).all();
1267
+ return c.json({ ...issue, occurrences: issueOccurrences, fixAttempts: fixes });
1268
+ });
1269
+ api.patch("/issues/:id", async (c) => {
1270
+ const issueId = c.req.param("id");
1271
+ const body = await c.req.json();
1272
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1273
+ const updateFields = { updatedAt: now };
1274
+ if (body.status) {
1275
+ updateFields.status = body.status;
1276
+ if (body.status === "resolved") updateFields.resolvedAt = now;
1277
+ if (body.status === "open") updateFields.resolvedAt = null;
1278
+ }
1279
+ if (body.severity) updateFields.severity = body.severity;
1280
+ db.update(issues).set(updateFields).where(eq2(issues.id, issueId)).run();
1281
+ const updated = db.select().from(issues).where(eq2(issues.id, issueId)).get();
1282
+ return c.json(updated);
1283
+ });
1284
+ api.post("/issues/:id/fix", async (c) => {
1285
+ const issueId = c.req.param("id");
1286
+ const issue = db.select().from(issues).where(eq2(issues.id, issueId)).get();
1287
+ if (!issue) return c.json({ error: "Issue not found" }, 404);
1288
+ const body = await c.req.json();
1289
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1290
+ const fixId = nanoid2();
1291
+ const source = body.source ?? "api";
1292
+ db.insert(fixAttempts).values({
1293
+ id: fixId,
1294
+ issueId,
1295
+ summary: body.summary ?? null,
1296
+ rootCause: body.rootCause ?? null,
1297
+ prevention: body.prevention ?? null,
1298
+ successful: null,
1299
+ source,
1300
+ createdAt: now
1301
+ }).run();
1302
+ db.update(issues).set({
1303
+ status: "resolved",
1304
+ resolvedAt: now,
1305
+ updatedAt: now
1306
+ }).where(eq2(issues.id, issueId)).run();
1307
+ const updated = db.select().from(issues).where(eq2(issues.id, issueId)).get();
1308
+ const fixes = db.select().from(fixAttempts).where(eq2(fixAttempts.issueId, issueId)).orderBy(desc2(fixAttempts.createdAt)).all();
1309
+ return c.json({ ...updated, fixAttempts: fixes }, 201);
1310
+ });
1311
+ api.get("/insights", (c) => {
1312
+ const project = db.select().from(projects).where(eq2(projects.rootPath, projectRoot)).get();
1313
+ if (!project) return c.json({ error: "Project not found" }, 404);
1314
+ const topRecurring = db.select().from(issues).where(eq2(issues.projectId, project.id)).orderBy(desc2(issues.occurrenceCount)).limit(10).all();
1315
+ const mostExpensive = db.select().from(issues).where(eq2(issues.projectId, project.id)).orderBy(desc2(issues.estimatedTotalCost)).limit(10).all();
1316
+ const regressionIssues = db.select().from(issues).where(and2(eq2(issues.projectId, project.id), eq2(issues.regressionFlag, true))).orderBy(desc2(issues.lastSeenAt)).all();
1317
+ const failingCommands = db.select({
1318
+ command: occurrences.command,
1319
+ count: sql2`count(*)`
1320
+ }).from(occurrences).innerJoin(issues, eq2(occurrences.issueId, issues.id)).where(eq2(issues.projectId, project.id)).groupBy(occurrences.command).orderBy(desc2(sql2`count(*)`)).limit(10).all();
1321
+ return c.json({
1322
+ topRecurring,
1323
+ mostExpensive,
1324
+ regressions: regressionIssues,
1325
+ failingCommands
1326
+ });
1327
+ });
1328
+ return api;
1329
+ }
1330
+ var init_api = __esm({
1331
+ "src/server/routes/api.ts"() {
1332
+ "use strict";
1333
+ init_connection();
1334
+ init_schema();
1335
+ }
1336
+ });
1337
+
1338
+ // src/server/index.ts
1339
+ import { Hono as Hono2 } from "hono";
1340
+ import { serveStatic } from "@hono/node-server/serve-static";
1341
+ import { fileURLToPath } from "url";
1342
+ import { dirname, join as join3 } from "path";
1343
+ function createServer(projectRoot) {
1344
+ const app = new Hono2();
1345
+ const api = createApiRoutes(projectRoot);
1346
+ app.route("/api", api);
1347
+ const staticRoot = join3(__dirname, "..", "static");
1348
+ app.use("/*", serveStatic({ root: staticRoot }));
1349
+ app.get("*", serveStatic({ root: staticRoot, path: "index.html" }));
1350
+ return app;
1351
+ }
1352
+ var __dirname;
1353
+ var init_server = __esm({
1354
+ "src/server/index.ts"() {
1355
+ "use strict";
1356
+ init_api();
1357
+ __dirname = dirname(fileURLToPath(import.meta.url));
1358
+ }
1359
+ });
1360
+
1361
+ // src/commands/dash.ts
1362
+ var dash_exports = {};
1363
+ __export(dash_exports, {
1364
+ dashCommand: () => dashCommand
1365
+ });
1366
+ import pc4 from "picocolors";
1367
+ import { serve } from "@hono/node-server";
1368
+ async function dashCommand(options) {
1369
+ const cwd = process.cwd();
1370
+ const project = ensureProject(cwd);
1371
+ const port = parseInt(options.port ?? String(DEFAULT_DASHBOARD_PORT), 10);
1372
+ const app = createServer(cwd);
1373
+ const server = serve({
1374
+ fetch: app.fetch,
1375
+ port
1376
+ }, (info) => {
1377
+ const url = `http://localhost:${info.port}`;
1378
+ console.log(pc4.green(`VibeBug dashboard running at ${pc4.bold(url)}`));
1379
+ console.log(pc4.dim(`Project: ${project.name} (${cwd})`));
1380
+ console.log(pc4.dim("Press Ctrl+C to stop.\n"));
1381
+ if (options.open !== false) {
1382
+ import("open").then(({ default: open }) => open(url)).catch(() => {
1383
+ });
1384
+ }
1385
+ });
1386
+ await new Promise(() => {
1387
+ });
1388
+ }
1389
+ var init_dash = __esm({
1390
+ "src/commands/dash.ts"() {
1391
+ "use strict";
1392
+ init_server();
1393
+ init_queries();
1394
+ init_constants();
1395
+ }
1396
+ });
1397
+
1398
+ // src/commands/fix.ts
1399
+ var fix_exports = {};
1400
+ __export(fix_exports, {
1401
+ fixCommand: () => fixCommand
1402
+ });
1403
+ import pc5 from "picocolors";
1404
+ import { input, select } from "@inquirer/prompts";
1405
+ async function fixCommand(issueId, options) {
1406
+ const cwd = process.cwd();
1407
+ const projectRoot = findProjectRoot(cwd);
1408
+ if (!projectRoot) {
1409
+ console.error(pc5.red("No VibeBug project found. Run `vb init` first."));
1410
+ process.exit(1);
1411
+ }
1412
+ const project = ensureProject(projectRoot);
1413
+ let targetIssue;
1414
+ if (options.last) {
1415
+ targetIssue = getMostRecentOpenIssue(projectRoot, project.id);
1416
+ if (!targetIssue) {
1417
+ console.log(pc5.yellow("No open issues found."));
1418
+ return;
1419
+ }
1420
+ } else if (issueId) {
1421
+ targetIssue = getIssueById(projectRoot, issueId);
1422
+ if (!targetIssue) {
1423
+ const allOpen = getOpenIssues(projectRoot, project.id);
1424
+ targetIssue = allOpen.find((i) => i.id.startsWith(issueId));
1425
+ if (!targetIssue) {
1426
+ console.error(pc5.red(`Issue "${issueId}" not found.`));
1427
+ return;
1428
+ }
1429
+ }
1430
+ } else {
1431
+ const openIssues = getOpenIssues(projectRoot, project.id);
1432
+ if (openIssues.length === 0) {
1433
+ console.log(pc5.yellow("No open issues found."));
1434
+ return;
1435
+ }
1436
+ const answer = await select({
1437
+ message: "Which issue did you fix?",
1438
+ choices: openIssues.map((issue) => ({
1439
+ name: `${issue.id.slice(0, 7)} ${issue.title.slice(0, 60)} (${issue.occurrenceCount}x)`,
1440
+ value: issue.id
1441
+ }))
1442
+ });
1443
+ targetIssue = openIssues.find((i) => i.id === answer);
1444
+ }
1445
+ const isNonInteractive = !!options.summary;
1446
+ let summary = options.summary;
1447
+ let rootCause = options.rootCause;
1448
+ let prevention = options.prevention;
1449
+ if (!isNonInteractive) {
1450
+ summary = await input({
1451
+ message: "Fix summary (what you changed):"
1452
+ });
1453
+ rootCause = await input({
1454
+ message: "Root cause (optional):"
1455
+ }) || void 0;
1456
+ prevention = await input({
1457
+ message: "Prevention (how to avoid in the future, optional):"
1458
+ }) || void 0;
1459
+ }
1460
+ const source = isNonInteractive ? "agent" : "manual";
1461
+ createFixAttempt(projectRoot, {
1462
+ issueId: targetIssue.id,
1463
+ summary,
1464
+ rootCause,
1465
+ prevention,
1466
+ source
1467
+ });
1468
+ resolveIssue(projectRoot, targetIssue.id);
1469
+ if (options.json) {
1470
+ console.log(JSON.stringify({
1471
+ status: "resolved",
1472
+ issueId: targetIssue.id,
1473
+ title: targetIssue.title,
1474
+ fixSummary: summary
1475
+ }));
1476
+ } else {
1477
+ const shortTitle = targetIssue.title.length > 50 ? targetIssue.title.slice(0, 47) + "..." : targetIssue.title;
1478
+ console.log(pc5.green(`Resolved: ${pc5.bold(shortTitle)}`));
1479
+ if (summary) {
1480
+ console.log(pc5.dim(` Fix: ${summary}`));
1481
+ }
1482
+ }
1483
+ }
1484
+ var init_fix = __esm({
1485
+ "src/commands/fix.ts"() {
1486
+ "use strict";
1487
+ init_paths();
1488
+ init_queries();
1489
+ }
1490
+ });
1491
+
1492
+ // src/commands/export.ts
1493
+ var export_exports = {};
1494
+ __export(export_exports, {
1495
+ exportCommand: () => exportCommand
1496
+ });
1497
+ import pc6 from "picocolors";
1498
+ import { writeFileSync } from "fs";
1499
+ import { eq as eq3, desc as desc3 } from "drizzle-orm";
1500
+ async function exportCommand(options) {
1501
+ const cwd = process.cwd();
1502
+ const projectRoot = findProjectRoot(cwd);
1503
+ if (!projectRoot) {
1504
+ console.error(pc6.red("No VibeBug project found. Run `vb init` first."));
1505
+ process.exit(1);
1506
+ }
1507
+ const project = ensureProject(projectRoot);
1508
+ const allIssues = getAllIssues(projectRoot, project.id);
1509
+ if (allIssues.length === 0) {
1510
+ console.log(pc6.yellow("No issues to export."));
1511
+ return;
1512
+ }
1513
+ const format = options.format ?? "json";
1514
+ if (format === "json") {
1515
+ const db = getDatabase(projectRoot);
1516
+ const data = allIssues.map((issue) => {
1517
+ const issueOccurrences = db.select().from(occurrences).where(eq3(occurrences.issueId, issue.id)).orderBy(desc3(occurrences.createdAt)).all();
1518
+ const fixes = db.select().from(fixAttempts).where(eq3(fixAttempts.issueId, issue.id)).orderBy(desc3(fixAttempts.createdAt)).all();
1519
+ return { ...issue, occurrences: issueOccurrences, fixAttempts: fixes };
1520
+ });
1521
+ const json = JSON.stringify({ project, issues: data }, null, 2);
1522
+ if (options.output) {
1523
+ writeFileSync(options.output, json, "utf-8");
1524
+ console.log(pc6.green(`Exported ${allIssues.length} issues to ${options.output}`));
1525
+ } else {
1526
+ process.stdout.write(json + "\n");
1527
+ }
1528
+ } else if (format === "csv") {
1529
+ const header = "id,title,type,severity,status,occurrences,estimated_cost,first_seen,last_seen,regression";
1530
+ const rows = allIssues.map(
1531
+ (issue) => [
1532
+ issue.id,
1533
+ `"${issue.title.replace(/"/g, '""')}"`,
1534
+ issue.type,
1535
+ issue.severity,
1536
+ issue.status,
1537
+ issue.occurrenceCount,
1538
+ issue.estimatedTotalCost.toFixed(4),
1539
+ issue.firstSeenAt,
1540
+ issue.lastSeenAt,
1541
+ issue.regressionFlag ? "true" : "false"
1542
+ ].join(",")
1543
+ );
1544
+ const csv = [header, ...rows].join("\n");
1545
+ if (options.output) {
1546
+ writeFileSync(options.output, csv, "utf-8");
1547
+ console.log(pc6.green(`Exported ${allIssues.length} issues to ${options.output}`));
1548
+ } else {
1549
+ process.stdout.write(csv + "\n");
1550
+ }
1551
+ } else {
1552
+ console.error(pc6.red(`Unknown format: "${format}". Use "json" or "csv".`));
1553
+ }
1554
+ }
1555
+ var init_export = __esm({
1556
+ "src/commands/export.ts"() {
1557
+ "use strict";
1558
+ init_paths();
1559
+ init_queries();
1560
+ init_connection();
1561
+ init_schema();
1562
+ }
1563
+ });
1564
+
1565
+ // src/commands/config.ts
1566
+ var config_exports = {};
1567
+ __export(config_exports, {
1568
+ configCommand: () => configCommand
1569
+ });
1570
+ import pc7 from "picocolors";
1571
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync5 } from "fs";
1572
+ import { join as join4 } from "path";
1573
+ function getConfigPath(projectRoot) {
1574
+ return join4(projectRoot, VIBEBUG_DIR, CONFIG_FILE);
1575
+ }
1576
+ function readConfig(projectRoot) {
1577
+ const configPath = getConfigPath(projectRoot);
1578
+ if (!existsSync5(configPath)) return {};
1579
+ try {
1580
+ return JSON.parse(readFileSync2(configPath, "utf-8"));
1581
+ } catch {
1582
+ return {};
1583
+ }
1584
+ }
1585
+ function writeConfig(projectRoot, config) {
1586
+ const configPath = getConfigPath(projectRoot);
1587
+ writeFileSync2(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
1588
+ }
1589
+ async function configCommand(action, key, value) {
1590
+ const cwd = process.cwd();
1591
+ const projectRoot = findProjectRoot(cwd);
1592
+ if (!projectRoot) {
1593
+ console.error(pc7.red("No VibeBug project found. Run `vb init` first."));
1594
+ process.exit(1);
1595
+ }
1596
+ const config = readConfig(projectRoot);
1597
+ switch (action) {
1598
+ case "get": {
1599
+ if (key) {
1600
+ const val = config[key];
1601
+ if (val !== void 0) {
1602
+ console.log(`${key} = ${val}`);
1603
+ } else {
1604
+ console.log(pc7.dim(`${key} is not set`));
1605
+ }
1606
+ } else {
1607
+ if (Object.keys(config).length === 0) {
1608
+ console.log(pc7.dim("No configuration set. Using defaults."));
1609
+ } else {
1610
+ for (const [k, v] of Object.entries(config)) {
1611
+ console.log(`${k} = ${v}`);
1612
+ }
1613
+ }
1614
+ }
1615
+ break;
1616
+ }
1617
+ case "set": {
1618
+ if (!key || value === void 0) {
1619
+ console.error(pc7.red("Usage: vb config set <key> <value>"));
1620
+ return;
1621
+ }
1622
+ const spec = VALID_KEYS[key];
1623
+ if (!spec) {
1624
+ console.error(pc7.red(`Unknown config key: "${key}"`));
1625
+ console.log(pc7.dim("Valid keys:"));
1626
+ for (const [k, v] of Object.entries(VALID_KEYS)) {
1627
+ console.log(pc7.dim(` ${k} \u2014 ${v.description}`));
1628
+ }
1629
+ return;
1630
+ }
1631
+ let parsed = value;
1632
+ if (spec.type === "number") {
1633
+ parsed = Number(value);
1634
+ if (Number.isNaN(parsed)) {
1635
+ console.error(pc7.red(`"${value}" is not a valid number.`));
1636
+ return;
1637
+ }
1638
+ }
1639
+ config[key] = parsed;
1640
+ writeConfig(projectRoot, config);
1641
+ console.log(pc7.green(`Set ${key} = ${parsed}`));
1642
+ break;
1643
+ }
1644
+ case "list": {
1645
+ console.log(pc7.bold("Available configuration keys:\n"));
1646
+ for (const [k, v] of Object.entries(VALID_KEYS)) {
1647
+ const current = config[k];
1648
+ const currentStr = current !== void 0 ? pc7.cyan(` (current: ${current})`) : "";
1649
+ console.log(` ${pc7.bold(k)}${currentStr}`);
1650
+ console.log(` ${pc7.dim(v.description)}`);
1651
+ }
1652
+ break;
1653
+ }
1654
+ default:
1655
+ console.error(pc7.red(`Unknown action: "${action}". Use "get", "set", or "list".`));
1656
+ }
1657
+ }
1658
+ var CONFIG_FILE, VALID_KEYS;
1659
+ var init_config = __esm({
1660
+ "src/commands/config.ts"() {
1661
+ "use strict";
1662
+ init_paths();
1663
+ init_constants();
1664
+ CONFIG_FILE = "config.json";
1665
+ VALID_KEYS = {
1666
+ aiModel: { type: "string", description: "AI model for cost estimation (claude-sonnet, claude-opus, claude-haiku, gpt-4o)" },
1667
+ aiInputPricePerMToken: { type: "number", description: "Custom input price per million tokens" },
1668
+ aiOutputPricePerMToken: { type: "number", description: "Custom output price per million tokens" },
1669
+ estimatedOutputTokens: { type: "number", description: "Heuristic for AI response size (default: 1000)" },
1670
+ ringBufferSize: { type: "number", description: "Ring buffer size in bytes (default: 204800)" },
1671
+ streamQuietTimeout: { type: "number", description: "Stream quiet timeout in ms (default: 2000)" },
1672
+ streamCooldown: { type: "number", description: "Stream cooldown in ms (default: 60000)" },
1673
+ dashboardPort: { type: "number", description: "Dashboard port (default: 7600)" },
1674
+ autoOpen: { type: "string", description: "Auto-open dashboard in browser (true/false)" }
1675
+ };
1676
+ }
1677
+ });
1678
+
1679
+ // src/index.ts
1680
+ init_constants();
1681
+ import { Command } from "commander";
1682
+ var KNOWN_COMMANDS = ["init", "list", "dash", "fix", "export", "config", "help"];
1683
+ var firstArg = process.argv[2];
1684
+ var isSubcommand = firstArg && (KNOWN_COMMANDS.includes(firstArg) || firstArg.startsWith("-"));
1685
+ if (!isSubcommand && firstArg) {
1686
+ const args = process.argv.slice(2);
1687
+ Promise.resolve().then(() => (init_wrap(), wrap_exports)).then(async ({ wrapCommand: wrapCommand2 }) => {
1688
+ const exitCode = await wrapCommand2(args);
1689
+ process.exitCode = exitCode;
1690
+ });
1691
+ } else {
1692
+ const program = new Command();
1693
+ program.name(APP_NAME).description("Automatic issue capture for vibe coding failures \u2014 without interrupting flow.").version(VERSION);
1694
+ program.command("init").description("Initialize VibeBug for the current project").action(async () => {
1695
+ const { initCommand: initCommand2 } = await Promise.resolve().then(() => (init_init(), init_exports));
1696
+ await initCommand2();
1697
+ });
1698
+ program.command("list").description("List open issues for the current project").action(async () => {
1699
+ const { listCommand: listCommand2 } = await Promise.resolve().then(() => (init_list(), list_exports));
1700
+ await listCommand2();
1701
+ });
1702
+ program.command("dash").description("Launch the local dashboard").option("--port <port>", "Dashboard port", String(7600)).option("--no-open", "Do not auto-open browser").action(async (options) => {
1703
+ const { dashCommand: dashCommand2 } = await Promise.resolve().then(() => (init_dash(), dash_exports));
1704
+ await dashCommand2(options);
1705
+ });
1706
+ program.command("fix [issueId]").description("Record what fixed an issue (retroactive annotation)").option("--last", "Target the most recent open issue").option("--summary <text>", "Fix summary (what was changed)").option("--root-cause <text>", "Root cause of the issue").option("--prevention <text>", "How to prevent in the future").option("--json", "Output result as JSON (for agent consumption)").action(async (issueId, options) => {
1707
+ const { fixCommand: fixCommand2 } = await Promise.resolve().then(() => (init_fix(), fix_exports));
1708
+ await fixCommand2(issueId, options);
1709
+ });
1710
+ program.command("export").description("Export issues to JSON or CSV").option("--format <format>", "Output format (json or csv)", "json").option("-o, --output <file>", "Write to file instead of stdout").action(async (options) => {
1711
+ const { exportCommand: exportCommand2 } = await Promise.resolve().then(() => (init_export(), export_exports));
1712
+ await exportCommand2(options);
1713
+ });
1714
+ program.command("config <action> [key] [value]").description("Manage VibeBug configuration").action(async (action, key, value) => {
1715
+ const { configCommand: configCommand2 } = await Promise.resolve().then(() => (init_config(), config_exports));
1716
+ await configCommand2(action, key, value);
1717
+ });
1718
+ program.parse();
1719
+ }
1720
+ //# sourceMappingURL=index.js.map