@getmikk/watcher 1.2.0 → 1.3.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 ADDED
@@ -0,0 +1,265 @@
1
+ # @getmikk/watcher
2
+
3
+ > Chokidar-powered file watcher daemon with incremental analysis, debouncing, race-condition protection, and atomic lock file updates.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@getmikk/watcher)](https://www.npmjs.com/package/@getmikk/watcher)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](../../LICENSE)
7
+
8
+ `@getmikk/watcher` keeps the `mikk.lock.json` file in sync with your codebase in real time. When files change, the watcher debounces events, incrementally re-parses only the affected files, updates the dependency graph, recomputes Merkle hashes, and writes the lock file atomically — all without requiring a full re-analysis.
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install @getmikk/watcher
16
+ # or
17
+ bun add @getmikk/watcher
18
+ ```
19
+
20
+ **Peer dependency:** `@getmikk/core`
21
+
22
+ ---
23
+
24
+ ## Quick Start
25
+
26
+ ```typescript
27
+ import { WatcherDaemon } from '@getmikk/watcher'
28
+
29
+ const daemon = new WatcherDaemon({
30
+ projectRoot: process.cwd(),
31
+ include: ['src/**/*.ts', 'src/**/*.tsx'],
32
+ exclude: ['node_modules', 'dist', '.mikk'],
33
+ debounceMs: 100,
34
+ })
35
+
36
+ daemon.on((event) => {
37
+ switch (event.type) {
38
+ case 'file:changed':
39
+ console.log(`Changed: ${event.path}`)
40
+ break
41
+ case 'graph:updated':
42
+ console.log('Dependency graph rebuilt')
43
+ break
44
+ case 'sync:clean':
45
+ console.log('Lock file is in sync')
46
+ break
47
+ case 'sync:drifted':
48
+ console.log('Lock file has drifted')
49
+ break
50
+ }
51
+ })
52
+
53
+ await daemon.start()
54
+ // Lock file is now kept in sync automatically
55
+
56
+ // Later...
57
+ await daemon.stop()
58
+ ```
59
+
60
+ ---
61
+
62
+ ## Architecture
63
+
64
+ ```
65
+ Filesystem Events (Chokidar)
66
+
67
+
68
+ ┌─────────────┐
69
+ │ FileWatcher │ ← Hash computation, deduplication
70
+ └──────┬──────┘
71
+ │ FileChangeEvent[]
72
+
73
+ ┌──────────────────┐
74
+ │ WatcherDaemon │ ← Debouncing (100ms), batching
75
+ └──────┬───────────┘
76
+ │ Batch of events
77
+
78
+ ┌─────────────────────┐
79
+ │ IncrementalAnalyzer │ ← Re-parse, graph patch, hash update
80
+ └──────────┬──────────┘
81
+
82
+
83
+ Atomic lock file write
84
+ ```
85
+
86
+ ---
87
+
88
+ ## API Reference
89
+
90
+ ### WatcherDaemon
91
+
92
+ The main entry point — a long-running process that keeps the lock file in sync.
93
+
94
+ ```typescript
95
+ import { WatcherDaemon } from '@getmikk/watcher'
96
+
97
+ const daemon = new WatcherDaemon(config)
98
+ ```
99
+
100
+ **`WatcherConfig`:**
101
+
102
+ | Field | Type | Default | Description |
103
+ |-------|------|---------|-------------|
104
+ | `projectRoot` | `string` | — | Absolute path to the project |
105
+ | `include` | `string[]` | `['**/*.ts']` | Glob patterns for watched files |
106
+ | `exclude` | `string[]` | `['node_modules']` | Glob patterns to ignore |
107
+ | `debounceMs` | `number` | `100` | Debounce window in milliseconds |
108
+
109
+ **Methods:**
110
+
111
+ | Method | Description |
112
+ |--------|-------------|
113
+ | `start()` | Start watching. Creates PID file at `.mikk/watcher.pid` for single-instance enforcement |
114
+ | `stop()` | Stop watching. Cleans up PID file |
115
+ | `on(handler)` | Register event handler |
116
+
117
+ **Features:**
118
+
119
+ - **Debouncing** — Batches rapid file changes (e.g., save-all) into a single analysis pass
120
+ - **PID file** — Prevents multiple watcher instances via `.mikk/watcher.pid`
121
+ - **Atomic writes** — Lock file is written atomically to prevent corruption
122
+ - **Sync state** — Emits `sync:clean` or `sync:drifted` after each cycle
123
+
124
+ ---
125
+
126
+ ### FileWatcher
127
+
128
+ Lower-level wrapper around Chokidar with hash-based change detection:
129
+
130
+ ```typescript
131
+ import { FileWatcher } from '@getmikk/watcher'
132
+
133
+ const watcher = new FileWatcher(config)
134
+
135
+ watcher.on((event) => {
136
+ console.log(event.type) // 'added' | 'changed' | 'deleted'
137
+ console.log(event.path) // Absolute file path
138
+ console.log(event.oldHash) // Previous content hash (undefined for 'added')
139
+ console.log(event.newHash) // New content hash (undefined for 'deleted')
140
+ console.log(event.timestamp) // Event timestamp
141
+ console.log(event.affectedModuleIds) // Modules containing this file
142
+ })
143
+
144
+ await watcher.start()
145
+
146
+ // Seed with known hashes to detect only actual content changes
147
+ watcher.setHash('/src/index.ts', 'abc123...')
148
+
149
+ await watcher.stop()
150
+ ```
151
+
152
+ **Hash-based deduplication:** Even if the OS reports a file change, the watcher computes a SHA-256 hash and only emits an event if the content actually changed. This prevents redundant re-analysis from editor auto-saves or format-on-save.
153
+
154
+ ---
155
+
156
+ ### IncrementalAnalyzer
157
+
158
+ Incrementally updates the dependency graph and lock file for a batch of changed files:
159
+
160
+ ```typescript
161
+ import { IncrementalAnalyzer } from '@getmikk/watcher'
162
+
163
+ const analyzer = new IncrementalAnalyzer(graph, lock, contract, projectRoot)
164
+
165
+ const result = await analyzer.analyzeBatch(events)
166
+
167
+ console.log(result.graph) // Updated DependencyGraph
168
+ console.log(result.lock) // Updated MikkLock
169
+ console.log(result.impactResult) // ImpactResult from @getmikk/core
170
+ console.log(result.mode) // 'incremental' | 'full'
171
+ ```
172
+
173
+ **How it works:**
174
+
175
+ 1. **Small batches (≤15 files)** → Incremental mode:
176
+ - Re-parse only changed files
177
+ - Patch the existing graph (remove old nodes/edges, add new ones)
178
+ - Recompute affected hashes only
179
+ - Run impact analysis on changed nodes
180
+
181
+ 2. **Large batches (>15 files)** → Full re-analysis:
182
+ - Re-parse all files from scratch
183
+ - Rebuild entire graph
184
+ - Recompute all hashes
185
+
186
+ **Race-condition protection:** After parsing a file, the analyzer re-hashes it. If the hash changed during parsing (the file was modified again), it retries up to 3 times before falling back to the latest parsed version.
187
+
188
+ ---
189
+
190
+ ### Events
191
+
192
+ All events emitted through the `on()` handler:
193
+
194
+ ```typescript
195
+ type WatcherEvent =
196
+ | { type: 'file:changed'; event: FileChangeEvent }
197
+ | { type: 'module:updated'; moduleId: string }
198
+ | { type: 'graph:updated'; stats: { nodes: number; edges: number } }
199
+ | { type: 'sync:clean' }
200
+ | { type: 'sync:drifted'; driftedModules: string[] }
201
+ ```
202
+
203
+ **`FileChangeEvent`:**
204
+
205
+ ```typescript
206
+ type FileChangeEvent = {
207
+ type: 'added' | 'changed' | 'deleted'
208
+ path: string
209
+ oldHash?: string
210
+ newHash?: string
211
+ timestamp: number
212
+ affectedModuleIds: string[]
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## Usage with the CLI
219
+
220
+ The `mikk watch` command starts the watcher daemon:
221
+
222
+ ```bash
223
+ mikk watch
224
+ # Watching src/**/*.ts, src/**/*.tsx...
225
+ # [sync:clean] Lock file is up to date
226
+ # [file:changed] src/auth/login.ts
227
+ # [graph:updated] 142 nodes, 87 edges
228
+ # [sync:clean] Lock file updated
229
+ ```
230
+
231
+ Press `Ctrl+C` to stop.
232
+
233
+ ---
234
+
235
+ ## Single-Instance Enforcement
236
+
237
+ The daemon writes a PID file to `.mikk/watcher.pid` on start and removes it on stop. If another watcher is already running, `start()` will throw an error. This prevents multiple watchers from fighting over the lock file.
238
+
239
+ ```typescript
240
+ try {
241
+ await daemon.start()
242
+ } catch (err) {
243
+ if (err.message.includes('already running')) {
244
+ console.log('Another watcher is already running')
245
+ }
246
+ }
247
+ ```
248
+
249
+ ---
250
+
251
+ ## Types
252
+
253
+ ```typescript
254
+ import type {
255
+ FileChangeEvent,
256
+ WatcherConfig,
257
+ WatcherEvent,
258
+ } from '@getmikk/watcher'
259
+ ```
260
+
261
+ ---
262
+
263
+ ## License
264
+
265
+ [MIT](../../LICENSE)
package/package.json CHANGED
@@ -1,27 +1,31 @@
1
- {
2
- "name": "@getmikk/watcher",
3
- "version": "1.2.0",
4
- "type": "module",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "exports": {
8
- ".": {
9
- "import": "./dist/index.js",
10
- "types": "./dist/index.d.ts"
11
- }
12
- },
13
- "scripts": {
14
- "build": "tsc",
15
- "test": "bun test",
16
- "publish": "npm publish --access public",
17
- "dev": "tsc --watch"
18
- },
19
- "dependencies": {
20
- "@getmikk/core": "workspace:*",
21
- "chokidar": "^4.0.0"
22
- },
23
- "devDependencies": {
24
- "typescript": "^5.7.0",
25
- "@types/node": "^22.0.0"
26
- }
1
+ {
2
+ "name": "@getmikk/watcher",
3
+ "version": "1.3.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Ansh-dhanani/mikk"
8
+ },
9
+ "type": "module",
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "import": "./dist/index.js",
15
+ "types": "./dist/index.d.ts"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "test": "bun test",
21
+ "dev": "tsc --watch"
22
+ },
23
+ "dependencies": {
24
+ "@getmikk/core": "workspace:*",
25
+ "chokidar": "^4.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "typescript": "^5.7.0",
29
+ "@types/node": "^22.0.0"
30
+ }
27
31
  }