@mnemonik/scanner 5.136.2 → 5.151.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/watcher.js DELETED
@@ -1,214 +0,0 @@
1
- import { watch } from 'fs';
2
- import { join, relative } from 'path';
3
- import { readdir, stat } from 'fs/promises';
4
- import { isGitBoundary } from '@mnemonik/shared';
5
- const SKIP_DIRS = new Set([
6
- 'node_modules',
7
- '.git',
8
- 'dist',
9
- 'build',
10
- '.next',
11
- '.nuxt',
12
- '.output',
13
- '__pycache__',
14
- '.venv',
15
- 'venv',
16
- '.tox',
17
- 'target',
18
- '.cache',
19
- 'coverage',
20
- '.turbo',
21
- '.vercel',
22
- '.svelte-kit',
23
- ]);
24
- export class FileWatcher {
25
- rootPath;
26
- onChange;
27
- // Keyed by directory so a watcher can be closed and re-attached when the
28
- // directory is deleted and recreated at the same path.
29
- watchers = new Map();
30
- watchedDirs = new Set();
31
- // Inode per watched directory: a delete+recreate faster than the delete
32
- // event's stat() presents as "directory exists and is already watched",
33
- // but the live watcher is bound to the OLD inode and is inert. Comparing
34
- // inodes at event time detects the swap so the subtree can re-attach.
35
- watchedDirInodes = new Map();
36
- pendingFiles = new Set();
37
- flushTimer = null;
38
- debounceMs;
39
- onError;
40
- constructor(rootPath, onChange, debounceMs = 500, onError) {
41
- this.rootPath = rootPath;
42
- this.onChange = onChange;
43
- this.debounceMs = debounceMs;
44
- this.onError = onError;
45
- }
46
- async start() {
47
- await this.watchDir(this.rootPath);
48
- console.log(`[scanner] Watching ${this.rootPath} for changes`);
49
- }
50
- stop() {
51
- for (const w of this.watchers.values()) {
52
- w.close();
53
- }
54
- this.watchers.clear();
55
- this.watchedDirs.clear();
56
- this.watchedDirInodes.clear();
57
- if (this.flushTimer) {
58
- clearTimeout(this.flushTimer);
59
- this.flushTimer = null;
60
- }
61
- this.pendingFiles.clear();
62
- }
63
- scheduleFlush() {
64
- if (this.flushTimer)
65
- clearTimeout(this.flushTimer);
66
- this.flushTimer = setTimeout(() => {
67
- this.flushTimer = null;
68
- if (this.pendingFiles.size === 0)
69
- return;
70
- const batch = [...this.pendingFiles];
71
- this.pendingFiles.clear();
72
- this.onChange(batch);
73
- }, this.debounceMs);
74
- }
75
- async watchDir(dir) {
76
- const dirName = dir.split('/').pop() ?? '';
77
- if (SKIP_DIRS.has(dirName))
78
- return;
79
- // Dedup guard — the change callback re-enters watchDir for new
80
- // subdirectories, and a rapid create/rename burst can resolve the same
81
- // path twice before the first watch is registered.
82
- if (this.watchedDirs.has(dir))
83
- return;
84
- this.watchedDirs.add(dir);
85
- try {
86
- // Record the inode BEFORE attaching so watchNewDir can distinguish a
87
- // benign event on this directory from a recreate-at-same-path.
88
- this.watchedDirInodes.set(dir, (await stat(dir)).ino);
89
- const watcher = watch(dir, { persistent: true }, (event, filename) => {
90
- if (!filename)
91
- return;
92
- const fullPath = join(dir, filename);
93
- const relPath = relative(this.rootPath, fullPath);
94
- this.pendingFiles.add(relPath);
95
- this.scheduleFlush();
96
- // fs.watch is non-recursive: the initial recursion below only covers
97
- // directories that existed at start(). When this event is a newly
98
- // created directory, attach a watcher to it too — otherwise the
99
- // subtree is a permanent blind spot until restart. Fire-and-forget;
100
- // watchNewDir no-ops for plain files, skip dirs, git boundaries, and
101
- // already-watched paths.
102
- void this.watchNewDir(fullPath, event);
103
- });
104
- watcher.on('error', (err) => {
105
- console.warn(`[scanner] Watcher error for ${dir}:`, err.message);
106
- if (dir === this.rootPath && this.onError) {
107
- this.onError(err);
108
- }
109
- });
110
- this.watchers.set(dir, watcher);
111
- const entries = await readdir(dir, { withFileTypes: true });
112
- for (const entry of entries) {
113
- if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
114
- const child = join(dir, entry.name);
115
- // Nested-git-boundary rule (see @mnemonik/shared isGitBoundary):
116
- // linked worktrees / nested clones are other repositories — don't
117
- // watch their subtrees as part of this project.
118
- if (await isGitBoundary(child))
119
- continue;
120
- await this.watchDir(child);
121
- }
122
- }
123
- }
124
- catch (err) {
125
- // Watch registration failed. Never fatal for the subtree's parent, but
126
- // the *reason* matters: inotify/fd limit exhaustion means silently
127
- // growing blind spots, while permission-denied is a benign property of
128
- // the directory itself. Drop the bookkeeping (and any watcher that did
129
- // attach before the failure) so a later retry can re-enter cleanly.
130
- this.watchedDirs.delete(dir);
131
- this.watchedDirInodes.delete(dir);
132
- const stale = this.watchers.get(dir);
133
- if (stale) {
134
- stale.close();
135
- this.watchers.delete(dir);
136
- }
137
- const code = err.code;
138
- if (code === 'ENOSPC' || code === 'EMFILE' || code === 'ENFILE') {
139
- console.warn(`[scanner] Watch limit reached (${code}) — subtree unwatched: ${dir}. ` +
140
- 'Raise fs.inotify.max_user_watches or trim scanner roots.');
141
- // Surface to the daemon only for the root — losing the root means the
142
- // whole project is blind; a subtree gap must not tear the project down
143
- // (the daemon's onError removes the project entirely).
144
- if (dir === this.rootPath && this.onError) {
145
- this.onError(err);
146
- }
147
- }
148
- else {
149
- // Permission denied or inaccessible directory
150
- console.warn(`[scanner] Cannot watch ${dir}: ${err.message}`);
151
- }
152
- }
153
- }
154
- /**
155
- * Attach a watcher to a directory created after start(). Called from the
156
- * per-directory change callback with every event path; stats the path and
157
- * only recurses when it is a genuinely new, watchable directory.
158
- */
159
- async watchNewDir(fullPath, eventType) {
160
- try {
161
- // Stat before the dedup check: a delete event arrives with the same
162
- // path as the original create, and the stale watchedDirs entry must
163
- // not short-circuit the vanish detection below.
164
- const s = await stat(fullPath);
165
- if (!s.isDirectory())
166
- return;
167
- if (this.watchedDirs.has(fullPath)) {
168
- // Already watched — but a delete+recreate faster than this stat
169
- // presents exactly like this, with the live watcher bound to the
170
- // OLD (dead) inode and inert. A 'rename' event means the entry's
171
- // identity changed (created / deleted / moved), so re-attach
172
- // unconditionally — comparing inodes is NOT sufficient there
173
- // because ext4 routinely hands the freed inode straight back to
174
- // the recreated directory. 'change' events are attrib noise (a
175
- // write inside the child bumps its mtime, which fires on the
176
- // parent), so the cheap inode check keeps those churn-free.
177
- if (eventType !== 'rename' && this.watchedDirInodes.get(fullPath) === s.ino)
178
- return;
179
- this.unwatchSubtree(fullPath);
180
- }
181
- if (await isGitBoundary(fullPath))
182
- return;
183
- await this.watchDir(fullPath);
184
- }
185
- catch {
186
- // Path vanished between event and stat. If it (or anything under it)
187
- // was a watched directory, drop the bookkeeping and close the dead
188
- // watchers — otherwise the dedup guards in watchDir/watchNewDir block
189
- // re-attachment forever when the path is recreated (build-output
190
- // wipes, codegen, rm -rf && mkdir).
191
- this.unwatchSubtree(fullPath);
192
- }
193
- }
194
- /**
195
- * Forget a deleted directory and everything watched beneath it. fs.watch
196
- * emits no per-descendant events on a recursive delete, so the whole
197
- * prefix must be purged here for a recreate to re-watch the full subtree.
198
- */
199
- unwatchSubtree(root) {
200
- const prefix = root + '/';
201
- for (const dir of this.watchedDirs) {
202
- if (dir !== root && !dir.startsWith(prefix))
203
- continue;
204
- this.watchedDirs.delete(dir);
205
- this.watchedDirInodes.delete(dir);
206
- const w = this.watchers.get(dir);
207
- if (w) {
208
- w.close();
209
- this.watchers.delete(dir);
210
- }
211
- }
212
- }
213
- }
214
- //# sourceMappingURL=watcher.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"watcher.js","sourceRoot":"","sources":["../src/watcher.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAkB,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,cAAc;IACd,MAAM;IACN,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,SAAS;IACT,aAAa;IACb,OAAO;IACP,MAAM;IACN,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,UAAU;IACV,QAAQ;IACR,SAAS;IACT,aAAa;CACd,CAAC,CAAC;AAKH,MAAM,OAAO,WAAW;IAgBZ;IACA;IAhBV,yEAAyE;IACzE,uDAAuD;IAC/C,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IACxC,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,wEAAwE;IACxE,wEAAwE;IACxE,yEAAyE;IACzE,sEAAsE;IAC9D,gBAAgB,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC7C,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IACjC,UAAU,GAAyC,IAAI,CAAC;IACxD,UAAU,CAAS;IACnB,OAAO,CAA2B;IAE1C,YACU,QAAgB,EAChB,QAAuB,EAC/B,UAAU,GAAG,GAAG,EAChB,OAAsB;QAHd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,aAAQ,GAAR,QAAQ,CAAe;QAI/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnC,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,CAAC,QAAQ,cAAc,CAAC,CAAC;IACjE,CAAC;IAED,IAAI;QACF,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC;YACvC,CAAC,CAAC,KAAK,EAAE,CAAC;QACZ,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;QACtB,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YAC9B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAEO,aAAa;QACnB,IAAI,IAAI,CAAC,UAAU;YAAE,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;YACvB,IAAI,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,CAAC;gBAAE,OAAO;YACzC,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC;YACrC,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;YAC1B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACtB,CAAC;IAEO,KAAK,CAAC,QAAQ,CAAC,GAAW;QAChC,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC;QAC3C,IAAI,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,OAAO;QACnC,+DAA+D;QAC/D,uEAAuE;QACvE,mDAAmD;QACnD,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QACtC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAE1B,IAAI,CAAC;YACH,qEAAqE;YACrE,+DAA+D;YAC/D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YACtD,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;gBACnE,IAAI,CAAC,QAAQ;oBAAE,OAAO;gBACtB,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;gBACrC,MAAM,OAAO,GAAG,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;gBAClD,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;gBAC/B,IAAI,CAAC,aAAa,EAAE,CAAC;gBACrB,qEAAqE;gBACrE,kEAAkE;gBAClE,gEAAgE;gBAChE,oEAAoE;gBACpE,qEAAqE;gBACrE,yBAAyB;gBACzB,KAAK,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC;YAEH,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBAC1B,OAAO,CAAC,IAAI,CAAC,+BAA+B,GAAG,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;gBACjE,IAAI,GAAG,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBAC1C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;gBACpB,CAAC;YACH,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YAEhC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;gBAC5B,IAAI,KAAK,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;oBACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;oBACpC,iEAAiE;oBACjE,kEAAkE;oBAClE,gDAAgD;oBAChD,IAAI,MAAM,aAAa,CAAC,KAAK,CAAC;wBAAE,SAAS;oBACzC,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,uEAAuE;YACvE,mEAAmE;YACnE,uEAAuE;YACvE,uEAAuE;YACvE,oEAAoE;YACpE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACrC,IAAI,KAAK,EAAE,CAAC;gBACV,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;YACD,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAChE,OAAO,CAAC,IAAI,CACV,kCAAkC,IAAI,0BAA0B,GAAG,IAAI;oBACrE,0DAA0D,CAC7D,CAAC;gBACF,sEAAsE;gBACtE,uEAAuE;gBACvE,uDAAuD;gBACvD,IAAI,GAAG,KAAK,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;oBAC1C,IAAI,CAAC,OAAO,CAAC,GAAY,CAAC,CAAC;gBAC7B,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,8CAA8C;gBAC9C,OAAO,CAAC,IAAI,CAAC,0BAA0B,GAAG,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;YAC3E,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,WAAW,CAAC,QAAgB,EAAE,SAAiB;QAC3D,IAAI,CAAC;YACH,oEAAoE;YACpE,oEAAoE;YACpE,gDAAgD;YAChD,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC/B,IAAI,CAAC,CAAC,CAAC,WAAW,EAAE;gBAAE,OAAO;YAC7B,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACnC,gEAAgE;gBAChE,iEAAiE;gBACjE,iEAAiE;gBACjE,6DAA6D;gBAC7D,6DAA6D;gBAC7D,gEAAgE;gBAChE,+DAA+D;gBAC/D,6DAA6D;gBAC7D,4DAA4D;gBAC5D,IAAI,SAAS,KAAK,QAAQ,IAAI,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,GAAG;oBAAE,OAAO;gBACpF,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;YAChC,CAAC;YACD,IAAI,MAAM,aAAa,CAAC,QAAQ,CAAC;gBAAE,OAAO;YAC1C,MAAM,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;YACrE,mEAAmE;YACnE,sEAAsE;YACtE,iEAAiE;YACjE,oCAAoC;YACpC,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,CAAC;QAChC,CAAC;IACH,CAAC;IAED;;;;OAIG;IACK,cAAc,CAAC,IAAY;QACjC,MAAM,MAAM,GAAG,IAAI,GAAG,GAAG,CAAC;QAC1B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YACnC,IAAI,GAAG,KAAK,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAS;YACtD,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7B,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAClC,MAAM,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACjC,IAAI,CAAC,EAAE,CAAC;gBACN,CAAC,CAAC,KAAK,EAAE,CAAC;gBACV,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC5B,CAAC;QACH,CAAC;IACH,CAAC;CACF"}