watchfix 0.1.0 → 0.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.
package/README.md CHANGED
@@ -94,6 +94,8 @@ watchfix fix <error-id>
94
94
 
95
95
  For detailed configuration options and advanced usage, see the [specification document](./spec/watchfix-spec-v8.md).
96
96
 
97
+ For a working example project, see [watchfix-example](https://github.com/CaseyHaralson/watchfix-example).
98
+
97
99
  ## License
98
100
 
99
101
  [MIT](./LICENSE)
@@ -39,13 +39,34 @@ const parseStoredAnalysis = (value) => {
39
39
  const parsed = JSON.parse(value);
40
40
  if (!parsed ||
41
41
  typeof parsed.summary !== 'string' ||
42
- typeof parsed.root_cause !== 'string' ||
43
- typeof parsed.suggested_fix !== 'string' ||
44
- !Array.isArray(parsed.files_to_modify) ||
45
42
  typeof parsed.confidence !== 'string') {
46
43
  return null;
47
44
  }
48
- return parsed;
45
+ // For already_fixed analyses, root_cause/suggested_fix/files_to_modify may be empty
46
+ if (parsed.already_fixed === true) {
47
+ return {
48
+ already_fixed: true,
49
+ summary: parsed.summary,
50
+ root_cause: parsed.root_cause ?? '',
51
+ suggested_fix: parsed.suggested_fix ?? '',
52
+ files_to_modify: parsed.files_to_modify ?? [],
53
+ confidence: parsed.confidence,
54
+ };
55
+ }
56
+ // For regular analyses, ensure all fields are present
57
+ if (typeof parsed.root_cause !== 'string' ||
58
+ typeof parsed.suggested_fix !== 'string' ||
59
+ !Array.isArray(parsed.files_to_modify)) {
60
+ return null;
61
+ }
62
+ return {
63
+ already_fixed: parsed.already_fixed ?? false,
64
+ summary: parsed.summary,
65
+ root_cause: parsed.root_cause,
66
+ suggested_fix: parsed.suggested_fix,
67
+ files_to_modify: parsed.files_to_modify,
68
+ confidence: parsed.confidence,
69
+ };
49
70
  }
50
71
  catch {
51
72
  return null;
@@ -54,6 +75,11 @@ const parseStoredAnalysis = (value) => {
54
75
  const formatAnalysisSummary = (analysis) => {
55
76
  const lines = ['Analysis summary:'];
56
77
  lines.push(` Summary: ${analysis.summary}`);
78
+ if (analysis.already_fixed) {
79
+ lines.push(' Status: Issue already fixed (no action needed)');
80
+ lines.push(` Confidence: ${analysis.confidence}`);
81
+ return lines;
82
+ }
57
83
  lines.push(' Root cause:');
58
84
  lines.push(...analysis.root_cause.split('\n').map((line) => ` ${line}`));
59
85
  lines.push(' Suggested fix:');
@@ -87,11 +113,90 @@ const promptForConfirmation = async (label) => {
87
113
  rl.close();
88
114
  }
89
115
  };
90
- const formatFixFailure = (result) => {
91
- if (result.verification?.failure) {
92
- return result.verification.failure.message;
116
+ const formatFilesChanged = (files, indent = ' ') => {
117
+ if (!files || files.length === 0) {
118
+ return [`${indent}(no files changed)`];
119
+ }
120
+ return files.map((f) => `${indent}- ${f.path}: ${f.change}`);
121
+ };
122
+ const formatVerificationSummary = (result, verbosity) => {
123
+ if (!result) {
124
+ return [' Verification: not run'];
125
+ }
126
+ if (result.success) {
127
+ return [' Verification: PASSED'];
128
+ }
129
+ const lines = [' Verification: FAILED'];
130
+ if (result.failure) {
131
+ lines.push(` ${result.failure.message}`);
132
+ if (verbosity === 'verbose' && result.failure.type === 'command') {
133
+ const stdout = result.failure.stdout.trim();
134
+ const stderr = result.failure.stderr.trim();
135
+ if (stdout) {
136
+ lines.push(' stdout:');
137
+ lines.push(...stdout.split('\n').map((line) => ` ${line}`));
138
+ }
139
+ if (stderr) {
140
+ lines.push(' stderr:');
141
+ lines.push(...stderr.split('\n').map((line) => ` ${line}`));
142
+ }
143
+ }
144
+ }
145
+ return lines;
146
+ };
147
+ const formatStatusLine = (result, maxAttempts) => {
148
+ if (result.status === 'fixed') {
149
+ return 'Status: fixed';
93
150
  }
94
- return result.message ?? 'Fix did not complete successfully.';
151
+ if (result.status === 'resolved') {
152
+ return 'Status: resolved (issue already fixed)';
153
+ }
154
+ const retryInfo = result.attempts < maxAttempts ? ', will retry' : ', max attempts reached';
155
+ return `Status: ${result.status} (attempt ${result.attempts} of ${maxAttempts}${retryInfo})`;
156
+ };
157
+ const formatFixOutcome = (result, verbosity, maxAttempts) => {
158
+ if (verbosity === 'quiet') {
159
+ if (result.status === 'fixed') {
160
+ return [`Error #${result.errorId}: fixed`];
161
+ }
162
+ if (result.status === 'resolved') {
163
+ return [`Error #${result.errorId}: resolved (already fixed)`];
164
+ }
165
+ const mode = result.fix?.success ? 'verification' : 'agent';
166
+ return [`Error #${result.errorId}: failed (${mode})`];
167
+ }
168
+ const lines = [];
169
+ // Header
170
+ if (result.status === 'fixed') {
171
+ lines.push(`Error #${result.errorId}: Fix verified successfully`);
172
+ }
173
+ else if (result.status === 'resolved') {
174
+ lines.push(`Error #${result.errorId}: Issue already fixed`);
175
+ }
176
+ else if (!result.fix?.success) {
177
+ lines.push(`Error #${result.errorId}: Agent could not apply fix`);
178
+ }
179
+ else {
180
+ lines.push(`Error #${result.errorId}: Fix attempt completed`);
181
+ }
182
+ // Agent details
183
+ if (result.fix) {
184
+ lines.push(` Agent applied fix: ${result.fix.success ? 'yes' : 'no'}`);
185
+ if (result.fix.files_changed?.length) {
186
+ lines.push(' Files changed:');
187
+ lines.push(...formatFilesChanged(result.fix.files_changed, ' '));
188
+ }
189
+ if (result.fix.notes && (verbosity === 'verbose' || !result.fix.success)) {
190
+ lines.push(` Agent notes: ${result.fix.notes}`);
191
+ }
192
+ }
193
+ // Verification
194
+ if (result.verification) {
195
+ lines.push(...formatVerificationSummary(result.verification, verbosity));
196
+ }
197
+ lines.push('');
198
+ lines.push(formatStatusLine(result, maxAttempts));
199
+ return lines;
95
200
  };
96
201
  const checkDaemonConflict = (db, logger) => {
97
202
  const state = getWatcherState(db);
@@ -138,20 +243,23 @@ const reportAnalysisFromResult = (db, errorId, result, note) => {
138
243
  parseStoredAnalysis(getError(db, errorId)?.suggestion ?? null);
139
244
  reportAnalysis(errorId, analysis, note);
140
245
  };
141
- const reportFixResult = (result) => {
246
+ const reportFixResult = (result, verbosity = 'normal', maxAttempts = 3) => {
142
247
  if (!result.lockAcquired) {
143
248
  process.stdout.write(`Skipped error #${result.errorId}: already locked by another process.\n`);
144
249
  return 'skipped';
145
250
  }
251
+ const lines = formatFixOutcome(result, verbosity, maxAttempts);
252
+ process.stdout.write(`${lines.join('\n')}\n`);
146
253
  if (result.status === 'fixed') {
147
- process.stdout.write(`✓ Error #${result.errorId} fixed successfully.\n`);
148
254
  return 'fixed';
149
255
  }
150
- const message = formatFixFailure(result);
151
- process.stdout.write(`✗ Error #${result.errorId} not fixed (status=${result.status}).\n${message}\n`);
256
+ if (result.status === 'resolved') {
257
+ return 'resolved';
258
+ }
152
259
  return 'failed';
153
260
  };
154
- const runSingleFix = async (db, error, options, orchestrator) => {
261
+ const runSingleFix = async (db, error, options, orchestrator, ctx) => {
262
+ const { verbosity, maxAttempts } = ctx;
155
263
  const reanalyze = Boolean(options.reanalyze);
156
264
  const analyzeOnly = Boolean(options.analyzeOnly);
157
265
  const shouldPrompt = !options.yes && !analyzeOnly;
@@ -163,11 +271,11 @@ const runSingleFix = async (db, error, options, orchestrator) => {
163
271
  });
164
272
  if (!result.lockAcquired) {
165
273
  process.exitCode = EXIT_CODES.NOT_ACTIONABLE;
166
- return reportFixResult(result);
274
+ return reportFixResult(result, verbosity, maxAttempts);
167
275
  }
168
276
  if (result.status !== 'suggested') {
169
277
  process.exitCode = EXIT_CODES.GENERAL_ERROR;
170
- reportFixResult(result);
278
+ reportFixResult(result, verbosity, maxAttempts);
171
279
  return 'failed';
172
280
  }
173
281
  reportAnalysis(error.id, result.analysis ??
@@ -184,7 +292,7 @@ const runSingleFix = async (db, error, options, orchestrator) => {
184
292
  });
185
293
  if (!analysisResult.lockAcquired) {
186
294
  process.exitCode = EXIT_CODES.NOT_ACTIONABLE;
187
- return reportFixResult(analysisResult);
295
+ return reportFixResult(analysisResult, verbosity, maxAttempts);
188
296
  }
189
297
  analysisNote = analysisResult.message;
190
298
  analysis =
@@ -192,7 +300,7 @@ const runSingleFix = async (db, error, options, orchestrator) => {
192
300
  parseStoredAnalysis(getError(db, error.id)?.suggestion ?? null);
193
301
  if (analysisResult.status !== 'suggested') {
194
302
  process.exitCode = EXIT_CODES.GENERAL_ERROR;
195
- return reportFixResult(analysisResult);
303
+ return reportFixResult(analysisResult, verbosity, maxAttempts);
196
304
  }
197
305
  reanalyzeForFix = false;
198
306
  }
@@ -213,15 +321,16 @@ const runSingleFix = async (db, error, options, orchestrator) => {
213
321
  });
214
322
  if (!result.lockAcquired) {
215
323
  process.exitCode = EXIT_CODES.NOT_ACTIONABLE;
216
- return reportFixResult(result);
324
+ return reportFixResult(result, verbosity, maxAttempts);
217
325
  }
218
326
  reportAnalysisFromResult(db, error.id, result);
219
- if (result.status !== 'fixed') {
327
+ if (result.status !== 'fixed' && result.status !== 'resolved') {
220
328
  process.exitCode = EXIT_CODES.GENERAL_ERROR;
221
329
  }
222
- return reportFixResult(result);
330
+ return reportFixResult(result, verbosity, maxAttempts);
223
331
  };
224
- const runAllFixes = async (db, options, orchestrator) => {
332
+ const runAllFixes = async (db, options, orchestrator, ctx) => {
333
+ const { verbosity, maxAttempts } = ctx;
225
334
  const errors = getErrorsByStatus(db, FIXABLE_STATUSES);
226
335
  if (errors.length === 0) {
227
336
  process.stdout.write('No pending or suggested errors to fix.\n');
@@ -234,6 +343,7 @@ const runAllFixes = async (db, options, orchestrator) => {
234
343
  let failedCount = 0;
235
344
  let skippedCount = 0;
236
345
  let analyzedCount = 0;
346
+ let resolvedCount = 0;
237
347
  for (const error of errors) {
238
348
  const fixabilityIssue = ensureFixable(error);
239
349
  if (fixabilityIssue) {
@@ -248,12 +358,17 @@ const runAllFixes = async (db, options, orchestrator) => {
248
358
  });
249
359
  if (!result.lockAcquired) {
250
360
  skippedCount += 1;
251
- reportFixResult(result);
361
+ reportFixResult(result, verbosity, maxAttempts);
362
+ continue;
363
+ }
364
+ if (result.status === 'resolved') {
365
+ resolvedCount += 1;
366
+ reportFixResult(result, verbosity, maxAttempts);
252
367
  continue;
253
368
  }
254
369
  if (result.status !== 'suggested') {
255
370
  failedCount += 1;
256
- reportFixResult(result);
371
+ reportFixResult(result, verbosity, maxAttempts);
257
372
  continue;
258
373
  }
259
374
  reportAnalysis(error.id, result.analysis ?? parseStoredAnalysis(getError(db, error.id)?.suggestion ?? null), result.message);
@@ -271,16 +386,21 @@ const runAllFixes = async (db, options, orchestrator) => {
271
386
  });
272
387
  if (!analysisResult.lockAcquired) {
273
388
  skippedCount += 1;
274
- reportFixResult(analysisResult);
389
+ reportFixResult(analysisResult, verbosity, maxAttempts);
275
390
  continue;
276
391
  }
277
392
  note = analysisResult.message;
278
393
  analysis =
279
394
  analysisResult.analysis ??
280
395
  parseStoredAnalysis(getError(db, error.id)?.suggestion ?? null);
396
+ if (analysisResult.status === 'resolved') {
397
+ resolvedCount += 1;
398
+ reportFixResult(analysisResult, verbosity, maxAttempts);
399
+ continue;
400
+ }
281
401
  if (analysisResult.status !== 'suggested') {
282
402
  failedCount += 1;
283
- reportFixResult(analysisResult);
403
+ reportFixResult(analysisResult, verbosity, maxAttempts);
284
404
  continue;
285
405
  }
286
406
  reanalyzeForFix = false;
@@ -302,36 +422,43 @@ const runAllFixes = async (db, options, orchestrator) => {
302
422
  });
303
423
  if (!result.lockAcquired) {
304
424
  skippedCount += 1;
305
- reportFixResult(result);
425
+ reportFixResult(result, verbosity, maxAttempts);
306
426
  continue;
307
427
  }
308
428
  if (result.status === 'fixed') {
309
429
  fixedCount += 1;
310
430
  }
431
+ else if (result.status === 'resolved') {
432
+ resolvedCount += 1;
433
+ }
311
434
  else {
312
435
  failedCount += 1;
313
436
  }
314
- reportFixResult(result);
437
+ reportFixResult(result, verbosity, maxAttempts);
315
438
  continue;
316
439
  }
317
440
  const result = await orchestrator.fixError(error.id, { reanalyze });
318
441
  if (!result.lockAcquired) {
319
442
  skippedCount += 1;
320
- reportFixResult(result);
443
+ reportFixResult(result, verbosity, maxAttempts);
321
444
  continue;
322
445
  }
323
446
  reportAnalysisFromResult(db, error.id, result);
324
447
  if (result.status === 'fixed') {
325
448
  fixedCount += 1;
326
449
  }
450
+ else if (result.status === 'resolved') {
451
+ resolvedCount += 1;
452
+ }
327
453
  else {
328
454
  failedCount += 1;
329
455
  }
330
- reportFixResult(result);
456
+ reportFixResult(result, verbosity, maxAttempts);
331
457
  }
458
+ const resolvedSuffix = resolvedCount > 0 ? `, resolved ${resolvedCount}` : '';
332
459
  const summary = analyzeOnly
333
- ? `Analyzed ${analyzedCount} errors, failed ${failedCount}, skipped ${skippedCount}.`
334
- : `Summary: fixed ${fixedCount}, failed ${failedCount}, skipped ${skippedCount}.`;
460
+ ? `Analyzed ${analyzedCount} errors, failed ${failedCount}, skipped ${skippedCount}${resolvedSuffix}.`
461
+ : `Summary: fixed ${fixedCount}, failed ${failedCount}, skipped ${skippedCount}${resolvedSuffix}.`;
335
462
  process.stdout.write(`${summary}\n`);
336
463
  if (!analyzeOnly && failedCount > 0) {
337
464
  process.exitCode = EXIT_CODES.GENERAL_ERROR;
@@ -366,8 +493,12 @@ export const fixCommand = async (id, options) => {
366
493
  logger,
367
494
  terminalEnabled: true,
368
495
  });
496
+ const ctx = {
497
+ verbosity,
498
+ maxAttempts: config.limits.max_attempts_per_error,
499
+ };
369
500
  if (options.all) {
370
- await runAllFixes(db, options, orchestrator);
501
+ await runAllFixes(db, options, orchestrator, ctx);
371
502
  return;
372
503
  }
373
504
  const errorId = parsePositiveInt(id);
@@ -383,7 +514,7 @@ export const fixCommand = async (id, options) => {
383
514
  process.exitCode = EXIT_CODES.NOT_ACTIONABLE;
384
515
  return;
385
516
  }
386
- await runSingleFix(db, error, options, orchestrator);
517
+ await runSingleFix(db, error, options, orchestrator, ctx);
387
518
  }
388
519
  finally {
389
520
  db.close();
@@ -197,6 +197,7 @@ export const watchCommand = async (options) => {
197
197
  clearWatcherState: () => clearWatcherState(db),
198
198
  });
199
199
  await watcher.start();
200
+ logger.info('Watching for errors... (Ctrl+C to stop)');
200
201
  if (options.autonomous) {
201
202
  void fixQueue?.processQueueIfReady();
202
203
  }
@@ -35,7 +35,7 @@ export type ErrorInsert = {
35
35
  createdAt?: string;
36
36
  updatedAt?: string;
37
37
  };
38
- export type ActivityAction = 'watcher_start' | 'watcher_stop' | 'error_detected' | 'error_deduplicated' | 'analysis_start' | 'analysis_complete' | 'analysis_failed' | 'analysis_timeout' | 'fix_start' | 'fix_complete' | 'fix_failed' | 'fix_timeout' | 'verification_start' | 'verification_pass' | 'verification_fail' | 'error_ignored' | 'lock_acquired' | 'lock_released' | 'lock_expired' | 'stale_recovery';
38
+ export type ActivityAction = 'watcher_start' | 'watcher_stop' | 'error_detected' | 'error_deduplicated' | 'analysis_start' | 'analysis_complete' | 'analysis_failed' | 'analysis_timeout' | 'already_fixed_detected' | 'fix_start' | 'fix_complete' | 'fix_failed' | 'fix_timeout' | 'verification_start' | 'verification_pass' | 'verification_fail' | 'error_ignored' | 'lock_acquired' | 'lock_released' | 'lock_expired' | 'stale_recovery';
39
39
  export declare function insertError(db: Database, error: ErrorInsert): number;
40
40
  export declare function getError(db: Database, id: number): ErrorRecord | null;
41
41
  export declare function getErrorByHash(db: Database, hash: string): ErrorRecord | null;
@@ -127,16 +127,24 @@ ${options.contextBlock}
127
127
 
128
128
  ## Instructions
129
129
 
130
- 1. Investigate the project structure to understand the codebase
131
- 2. Identify the root cause of this error
132
- 3. Determine what files need to be modified
133
- 4. Assess your confidence in the fix
130
+ 1. **First, check if this issue still exists in the code**
131
+ - Look at the file(s) mentioned in the stack trace
132
+ - Determine if the error condition is still present
133
+ - If the code has been modified and the issue is gone, report it as already_fixed
134
+
135
+ 2. If the issue still exists:
136
+ - Investigate the project structure to understand the codebase
137
+ - Identify the root cause of this error
138
+ - Determine what files need to be modified
139
+ - Assess your confidence in the fix
134
140
 
135
141
  Write your analysis to: \`${analysisPath}\`
136
142
 
137
143
  Use this exact YAML format:
138
144
  \`\`\`yaml
139
- summary: One sentence summary of the problem
145
+ already_fixed: true | false
146
+ summary: One sentence summary of the problem (or that it was already fixed)
147
+ # The following fields are only required if already_fixed is false:
140
148
  root_cause: |
141
149
  Detailed explanation of root cause
142
150
  Can be multiple lines
@@ -153,6 +161,7 @@ confidence: high | medium | low
153
161
  - Do NOT modify any files during analysis
154
162
  - If you cannot determine the cause, set confidence to "low"
155
163
  - Be specific about file paths relative to project root
164
+ - Set already_fixed to true if the issue no longer exists in the code (e.g., fixed by a previous error fix)
156
165
  - WARNING: If a fix fails and is retried, any file modifications from previous attempts will persist
157
166
  `;
158
167
  };
@@ -39,13 +39,34 @@ const parseSuggestion = (value) => {
39
39
  const parsed = JSON.parse(value);
40
40
  if (!parsed ||
41
41
  typeof parsed.summary !== 'string' ||
42
- typeof parsed.root_cause !== 'string' ||
43
- typeof parsed.suggested_fix !== 'string' ||
44
- !Array.isArray(parsed.files_to_modify) ||
45
42
  typeof parsed.confidence !== 'string') {
46
43
  throw new UserError('Stored analysis output is invalid');
47
44
  }
48
- return parsed;
45
+ // For already_fixed analyses, other fields may be empty
46
+ if (parsed.already_fixed === true) {
47
+ return {
48
+ already_fixed: true,
49
+ summary: parsed.summary,
50
+ root_cause: parsed.root_cause ?? '',
51
+ suggested_fix: parsed.suggested_fix ?? '',
52
+ files_to_modify: parsed.files_to_modify ?? [],
53
+ confidence: parsed.confidence,
54
+ };
55
+ }
56
+ // For regular analyses, ensure all fields are present
57
+ if (typeof parsed.root_cause !== 'string' ||
58
+ typeof parsed.suggested_fix !== 'string' ||
59
+ !Array.isArray(parsed.files_to_modify)) {
60
+ throw new UserError('Stored analysis output is invalid');
61
+ }
62
+ return {
63
+ already_fixed: parsed.already_fixed ?? false,
64
+ summary: parsed.summary,
65
+ root_cause: parsed.root_cause,
66
+ suggested_fix: parsed.suggested_fix,
67
+ files_to_modify: parsed.files_to_modify,
68
+ confidence: parsed.confidence,
69
+ };
49
70
  };
50
71
  export class FixOrchestrator {
51
72
  db;
@@ -140,6 +161,30 @@ export class FixOrchestrator {
140
161
  const analysisResult = await this.processAgentOutput(agentResult, outputPath, parseAnalysisOutput);
141
162
  if (analysisResult.success) {
142
163
  analysisOutput = analysisResult.data;
164
+ if (analysisOutput.already_fixed) {
165
+ if (!transitionStatus(this.db, errorId, 'analyzing', 'resolved', lockId)) {
166
+ throw new UserError(`Failed to transition error ${errorId} into resolved status`);
167
+ }
168
+ this.db.run('UPDATE errors SET suggestion = ?, updated_at = ? WHERE id = ?', [
169
+ JSON.stringify(analysisOutput),
170
+ new Date().toISOString(),
171
+ errorId,
172
+ ]);
173
+ logActivity(this.db, 'already_fixed_detected', errorId, JSON.stringify({
174
+ attempt: attempts,
175
+ summary: analysisOutput.summary,
176
+ }));
177
+ await releaseLock(this.db, errorId, lockId);
178
+ lockHeld = false;
179
+ return {
180
+ errorId,
181
+ status: 'resolved',
182
+ lockAcquired: true,
183
+ attempts,
184
+ analysis: analysisOutput,
185
+ message: 'Issue already fixed',
186
+ };
187
+ }
143
188
  if (!transitionStatus(this.db, errorId, 'analyzing', 'suggested', lockId)) {
144
189
  throw new UserError(`Failed to transition error ${errorId} into suggested status`);
145
190
  }
@@ -1,5 +1,6 @@
1
1
  type ConfidenceLevel = 'high' | 'medium' | 'low';
2
2
  type AnalysisOutput = {
3
+ already_fixed: boolean;
3
4
  summary: string;
4
5
  root_cause: string;
5
6
  suggested_fix: string;
@@ -69,12 +69,24 @@ const validateConfidence = (value) => {
69
69
  const parseAnalysisOutput = (content) => {
70
70
  const raw = parseYaml(content);
71
71
  const data = assertRecord(raw);
72
+ const already_fixed = data.already_fixed === true;
72
73
  const summary = requireStringField(data, 'summary');
74
+ const confidence = validateConfidence(data.confidence);
75
+ if (already_fixed) {
76
+ return {
77
+ already_fixed: true,
78
+ summary,
79
+ root_cause: '',
80
+ suggested_fix: '',
81
+ files_to_modify: [],
82
+ confidence,
83
+ };
84
+ }
73
85
  const root_cause = requireStringField(data, 'root_cause');
74
86
  const suggested_fix = requireStringField(data, 'suggested_fix');
75
87
  const files_to_modify = requireStringArrayField(data, 'files_to_modify');
76
- const confidence = validateConfidence(data.confidence);
77
88
  return {
89
+ already_fixed: false,
78
90
  summary,
79
91
  root_cause,
80
92
  suggested_fix,
@@ -6,7 +6,7 @@ export declare class InternalError extends Error {
6
6
  cause?: unknown;
7
7
  });
8
8
  }
9
- export type ErrorStatus = 'pending' | 'analyzing' | 'suggested' | 'fixing' | 'fixed' | 'failed' | 'ignored';
9
+ export type ErrorStatus = 'pending' | 'analyzing' | 'suggested' | 'fixing' | 'fixed' | 'failed' | 'ignored' | 'resolved';
10
10
  export declare const EXIT_CODES: {
11
11
  readonly SUCCESS: 0;
12
12
  readonly GENERAL_ERROR: 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "watchfix",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI tool that watches logs, detects errors, and dispatches AI agents to fix them",
5
5
  "type": "module",
6
6
  "bin": {