watchfix 0.2.1 → 0.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.
@@ -259,6 +259,13 @@ declare const configSchema: z.ZodObject<{
259
259
  context_max_age_days?: number | undefined;
260
260
  context_max_size_kb?: number | undefined;
261
261
  }>>;
262
+ deduplication: z.ZodDefault<z.ZodObject<{
263
+ fixed_grace_period: z.ZodDefault<z.ZodEffects<z.ZodEffects<z.ZodString, string, string>, string, string>>;
264
+ }, "strip", z.ZodTypeAny, {
265
+ fixed_grace_period: string;
266
+ }, {
267
+ fixed_grace_period?: string | undefined;
268
+ }>>;
262
269
  patterns: z.ZodDefault<z.ZodObject<{
263
270
  match: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
264
271
  ignore: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
@@ -315,6 +322,9 @@ declare const configSchema: z.ZodObject<{
315
322
  context_max_age_days: number;
316
323
  context_max_size_kb: number;
317
324
  };
325
+ deduplication: {
326
+ fixed_grace_period: string;
327
+ };
318
328
  patterns: {
319
329
  ignore: string[];
320
330
  match: string[];
@@ -365,6 +375,9 @@ declare const configSchema: z.ZodObject<{
365
375
  context_max_age_days?: number | undefined;
366
376
  context_max_size_kb?: number | undefined;
367
377
  } | undefined;
378
+ deduplication?: {
379
+ fixed_grace_period?: string | undefined;
380
+ } | undefined;
368
381
  patterns?: {
369
382
  ignore?: string[] | undefined;
370
383
  match?: string[] | undefined;
@@ -89,6 +89,11 @@ const configSchema = z.object({
89
89
  context_max_size_kb: z.number().int().min(64).default(256),
90
90
  })
91
91
  .default({}),
92
+ deduplication: z
93
+ .object({
94
+ fixed_grace_period: durationSchema.default('10m'),
95
+ })
96
+ .default({}),
92
97
  patterns: z
93
98
  .object({
94
99
  match: z.array(patternSchema).default([]),
@@ -1,5 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import { getErrorByHash, insertError, logActivity } from '../db/queries.js';
3
+ import { parseDuration } from '../utils/duration.js';
3
4
  import { Logger } from '../utils/logger.js';
4
5
  import { ErrorParser } from './parser.js';
5
6
  import { CommandSource } from './sources/command.js';
@@ -186,6 +187,22 @@ export class WatcherOrchestrator {
186
187
  });
187
188
  return;
188
189
  }
190
+ // Deduplicate fixed errors within grace period to prevent re-detection
191
+ // after a fix when the log file is re-read
192
+ if (existing && existing.status === 'fixed') {
193
+ const gracePeriodMs = parseDuration(this.config.deduplication.fixed_grace_period);
194
+ const fixedAt = new Date(existing.updatedAt).getTime();
195
+ if (Date.now() - fixedAt < gracePeriodMs) {
196
+ logActivity(this.db, 'error_deduplicated', existing.id, `status=${existing.status} grace_period=true`);
197
+ this.logger.info(`Deduplicated error ${error.hash} (within grace period after fix)`);
198
+ this.emitter.emit('error_deduplicated', {
199
+ errorId: existing.id,
200
+ error,
201
+ status: existing.status,
202
+ });
203
+ return;
204
+ }
205
+ }
189
206
  const newId = insertError(this.db, {
190
207
  hash: error.hash,
191
208
  source: error.source,
@@ -125,14 +125,12 @@ export class FileSource {
125
125
  }
126
126
  const inode = typeof stats.ino === 'number' ? stats.ino : null;
127
127
  const mtimeMs = stats.mtimeMs;
128
- const shouldForceRead = this.forceRead;
129
128
  this.forceRead = false;
130
129
  const inodeChanged = this.lastInode !== null && inode !== null && inode !== this.lastInode;
131
- const mtimeChanged = this.lastMtimeMs !== null && mtimeMs !== this.lastMtimeMs;
132
- if (inodeChanged ||
133
- stats.size < this.position ||
134
- (shouldForceRead && stats.size === this.position && this.position > 0) ||
135
- (mtimeChanged && stats.size === this.position && this.position > 0)) {
130
+ // Only reset position on actual file replacement (inode change) or truncation (size shrunk)
131
+ // Do NOT reset on mtime changes without content changes - this prevents re-reading
132
+ // old errors when the file is touched but no new content is added
133
+ if (inodeChanged || stats.size < this.position) {
136
134
  this.position = 0;
137
135
  this.partialLine = '';
138
136
  }
package/package.json CHANGED
@@ -1,7 +1,21 @@
1
1
  {
2
2
  "name": "watchfix",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "CLI tool that watches logs, detects errors, and dispatches AI agents to fix them",
5
+ "keywords": [
6
+ "cli",
7
+ "log-watcher",
8
+ "error-detection",
9
+ "ai",
10
+ "ai-agent",
11
+ "automation",
12
+ "auto-fix",
13
+ "llm",
14
+ "devtools",
15
+ "monitoring",
16
+ "logs",
17
+ "debugging"
18
+ ],
5
19
  "type": "module",
6
20
  "bin": {
7
21
  "watchfix": "./dist/cli/index.js"