opencode-branch-memory-manager 0.1.3 → 0.1.5

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.
@@ -0,0 +1,2270 @@
1
+ // .opencode/branch-memory/storage.ts
2
+ import * as fs from "fs/promises";
3
+ import * as path from "path";
4
+ import { existsSync } from "fs";
5
+
6
+ class ContextStorage {
7
+ storageDir;
8
+ maxBackups;
9
+ constructor(storageDir, config) {
10
+ this.storageDir = storageDir;
11
+ this.maxBackups = config?.storage?.maxBackups ?? 5;
12
+ }
13
+ getBranchFile(branch) {
14
+ return path.join(this.storageDir, `${this.sanitizeBranchName(branch)}.json`);
15
+ }
16
+ getBackupFile(branch, timestamp) {
17
+ const safeBranch = this.sanitizeBranchName(branch);
18
+ return path.join(this.storageDir, `${safeBranch}.backup.${timestamp}.json`);
19
+ }
20
+ sanitizeBranchName(branch) {
21
+ return branch.replace(/[\/\\:*?"<>|]/g, "_").replace(/\s+/g, "-").substring(0, 255);
22
+ }
23
+ async ensureStorageDir() {
24
+ try {
25
+ await fs.mkdir(this.storageDir, { recursive: true });
26
+ } catch (error) {
27
+ console.error("Failed to create storage directory:", error);
28
+ throw error;
29
+ }
30
+ }
31
+ async saveContext(branch, context) {
32
+ await this.ensureStorageDir();
33
+ const filePath = this.getBranchFile(branch);
34
+ const tempFile = `${filePath}.tmp.${Date.now()}.${Math.random().toString(36).substring(2, 9)}`;
35
+ try {
36
+ if (existsSync(filePath)) {
37
+ await this.createBackup(branch, filePath);
38
+ }
39
+ await fs.writeFile(tempFile, JSON.stringify(context, null, 2), "utf8");
40
+ await fs.rename(tempFile, filePath);
41
+ } catch (error) {
42
+ try {
43
+ await fs.unlink(tempFile).catch(() => {});
44
+ } catch {}
45
+ throw error;
46
+ }
47
+ }
48
+ async loadContext(branch) {
49
+ const filePath = this.getBranchFile(branch);
50
+ if (!existsSync(filePath)) {
51
+ return null;
52
+ }
53
+ try {
54
+ const content = await fs.readFile(filePath, "utf8");
55
+ const data = JSON.parse(content);
56
+ if (data.metadata?.version !== "1.0.0") {
57
+ console.warn(`Context version mismatch for branch '${branch}': ${data.metadata?.version}`);
58
+ }
59
+ return data;
60
+ } catch (error) {
61
+ console.error(`Failed to load context for branch '${branch}':`, error);
62
+ const backup = await this.restoreFromBackup(branch);
63
+ if (backup) {
64
+ console.info(`Restored from backup for branch '${branch}'`);
65
+ return backup;
66
+ }
67
+ return null;
68
+ }
69
+ }
70
+ async createBackup(branch, filePath) {
71
+ try {
72
+ if (!existsSync(filePath)) {
73
+ return;
74
+ }
75
+ const backupFile = this.getBackupFile(branch, Date.now());
76
+ await fs.copyFile(filePath, backupFile);
77
+ await this.cleanOldBackups(branch);
78
+ } catch (error) {
79
+ console.warn("Failed to create backup:", error);
80
+ }
81
+ }
82
+ async cleanOldBackups(branch) {
83
+ try {
84
+ const files = await fs.readdir(this.storageDir);
85
+ const safeBranch = this.sanitizeBranchName(branch);
86
+ const backups = files.filter((f) => f.startsWith(`${safeBranch}.backup.`) && f.endsWith(".json")).map((f) => {
87
+ const match = f.match(/\.backup\.(\d+)\.json$/);
88
+ return { name: f, timestamp: match ? parseInt(match[1], 10) : 0 };
89
+ }).sort((a, b) => b.timestamp - a.timestamp).slice(this.maxBackups);
90
+ for (const backup of backups) {
91
+ await fs.unlink(path.join(this.storageDir, backup.name)).catch(() => {});
92
+ }
93
+ } catch (error) {
94
+ console.warn("Failed to clean old backups:", error);
95
+ }
96
+ }
97
+ async restoreFromBackup(branch) {
98
+ try {
99
+ const files = await fs.readdir(this.storageDir);
100
+ const safeBranch = this.sanitizeBranchName(branch);
101
+ const backups = files.filter((f) => f.startsWith(`${safeBranch}.backup.`) && f.endsWith(".json")).map((f) => {
102
+ const match = f.match(/\.backup\.(\d+)\.json$/);
103
+ return { name: f, timestamp: match ? parseInt(match[1], 10) : 0 };
104
+ }).sort((a, b) => b.timestamp - a.timestamp);
105
+ for (const backup of backups) {
106
+ try {
107
+ const content = await fs.readFile(path.join(this.storageDir, backup.name), "utf8");
108
+ return JSON.parse(content);
109
+ } catch {
110
+ continue;
111
+ }
112
+ }
113
+ } catch (error) {
114
+ console.error("Failed to restore from backup:", error);
115
+ }
116
+ return null;
117
+ }
118
+ async listBranches() {
119
+ try {
120
+ await this.ensureStorageDir();
121
+ const files = await fs.readdir(this.storageDir);
122
+ const branches = [];
123
+ for (const file of files) {
124
+ if (file.endsWith(".json") && !file.includes(".backup.")) {
125
+ try {
126
+ const filePath = path.join(this.storageDir, file);
127
+ const content = await fs.readFile(filePath, "utf8");
128
+ const data = JSON.parse(content);
129
+ branches.push(data.branch);
130
+ } catch {
131
+ continue;
132
+ }
133
+ }
134
+ }
135
+ return branches;
136
+ } catch (error) {
137
+ console.error("Failed to list branches:", error);
138
+ return [];
139
+ }
140
+ }
141
+ async getMetadata(branch) {
142
+ const filePath = this.getBranchFile(branch);
143
+ if (!existsSync(filePath)) {
144
+ return {
145
+ size: "0KB",
146
+ modified: "Never",
147
+ messageCount: 0,
148
+ todoCount: 0,
149
+ fileCount: 0
150
+ };
151
+ }
152
+ try {
153
+ const content = await fs.readFile(filePath, "utf8");
154
+ const data = JSON.parse(content);
155
+ let size;
156
+ if (data.metadata?.size !== undefined) {
157
+ size = `${(data.metadata.size / 1024).toFixed(1)}KB`;
158
+ } else {
159
+ const stats = await fs.stat(filePath);
160
+ size = `${(stats.size / 1024).toFixed(1)}KB`;
161
+ }
162
+ return {
163
+ size,
164
+ modified: data.savedAt || new Date().toISOString(),
165
+ messageCount: data.metadata?.messageCount || 0,
166
+ todoCount: data.metadata?.todoCount || 0,
167
+ fileCount: data.metadata?.fileCount || 0
168
+ };
169
+ } catch (error) {
170
+ console.error("Failed to get metadata:", error);
171
+ return {
172
+ size: "Error",
173
+ modified: "Error",
174
+ messageCount: 0,
175
+ todoCount: 0,
176
+ fileCount: 0
177
+ };
178
+ }
179
+ }
180
+ async deleteContext(branch) {
181
+ const filePath = this.getBranchFile(branch);
182
+ if (existsSync(filePath)) {
183
+ await fs.unlink(filePath).catch(() => {});
184
+ }
185
+ try {
186
+ const files = await fs.readdir(this.storageDir);
187
+ const safeBranch = this.sanitizeBranchName(branch);
188
+ const backups = files.filter((f) => f.startsWith(`${safeBranch}.backup.`) && f.endsWith(".json"));
189
+ for (const backup of backups) {
190
+ await fs.unlink(path.join(this.storageDir, backup)).catch(() => {});
191
+ }
192
+ } catch (error) {
193
+ console.error("Failed to delete backups:", error);
194
+ }
195
+ }
196
+ }
197
+ // .opencode/branch-memory/git.ts
198
+ import { spawn } from "child_process";
199
+
200
+ class GitOperations {
201
+ static async runGitCommand(...args) {
202
+ return new Promise((resolve, reject) => {
203
+ const git = spawn("git", args, {
204
+ cwd: process.cwd(),
205
+ stdio: ["pipe", "pipe", "pipe"]
206
+ });
207
+ let stdout = "";
208
+ let stderr = "";
209
+ git.stdout.on("data", (data) => {
210
+ stdout += data.toString();
211
+ });
212
+ git.stderr.on("data", (data) => {
213
+ stderr += data.toString();
214
+ });
215
+ git.on("close", (code) => {
216
+ resolve({
217
+ stdout: stdout.trim(),
218
+ stderr: stderr.trim(),
219
+ exitCode: code ?? 0
220
+ });
221
+ });
222
+ git.on("error", (err) => {
223
+ resolve({
224
+ stdout: stdout.trim(),
225
+ stderr: stderr.trim(),
226
+ exitCode: 1
227
+ });
228
+ });
229
+ });
230
+ }
231
+ static async getCurrentBranch() {
232
+ const result = await this.runGitCommand("symbolic-ref", "--short", "HEAD");
233
+ if (result.exitCode !== 0 || result.stderr.length > 0) {
234
+ return null;
235
+ }
236
+ const branch = result.stdout;
237
+ if (branch === "HEAD" || branch === "") {
238
+ return null;
239
+ }
240
+ return branch;
241
+ }
242
+ static async getGitDir() {
243
+ const result = await this.runGitCommand("rev-parse", "--git-dir");
244
+ if (result.exitCode !== 0 || result.stderr.length > 0) {
245
+ return null;
246
+ }
247
+ return result.stdout;
248
+ }
249
+ static async isGitRepo() {
250
+ const gitDir = await this.getGitDir();
251
+ return gitDir !== null;
252
+ }
253
+ static async isBareRepo() {
254
+ const result = await this.runGitCommand("rev-parse", "--is-bare-repository");
255
+ return result.stdout === "true" && result.exitCode === 0;
256
+ }
257
+ static async getModifiedFiles() {
258
+ const result = await this.runGitCommand("diff", "--name-only");
259
+ if (result.exitCode !== 0) {
260
+ return [];
261
+ }
262
+ if (result.stdout.length === 0) {
263
+ return [];
264
+ }
265
+ return result.stdout.split(`
266
+ `).filter((f) => f.length > 0);
267
+ }
268
+ static async getAllBranches() {
269
+ const result = await this.runGitCommand("branch", "--format=%(refname:short)");
270
+ if (result.exitCode !== 0) {
271
+ return [];
272
+ }
273
+ if (result.stdout.length === 0) {
274
+ return [];
275
+ }
276
+ return result.stdout.split(`
277
+ `).filter((f) => f.length > 0);
278
+ }
279
+ static sanitizeBranchName(branch) {
280
+ return branch.replace(/[\/\\:*?"<>|]/g, "_").replace(/\s+/g, "-").replace(/[\u0000-\u001F\u007F-\u009F]/g, "").replace(/[\uE000-\uF8FF\uFFF0-\uFFFF]/g, "").substring(0, 255).trim();
281
+ }
282
+ static async getCurrentCommit() {
283
+ const result = await this.runGitCommand("rev-parse", "HEAD");
284
+ if (result.exitCode !== 0 || result.stderr.length > 0) {
285
+ return null;
286
+ }
287
+ return result.stdout;
288
+ }
289
+ static async isGitAvailable() {
290
+ const result = await this.runGitCommand("--version");
291
+ return result.exitCode === 0;
292
+ }
293
+ static async getRemoteUrl() {
294
+ const branch = await this.getCurrentBranch();
295
+ if (!branch) {
296
+ return null;
297
+ }
298
+ const remoteResult = await this.runGitCommand("config", `branch.${branch}.remote`);
299
+ if (remoteResult.exitCode !== 0) {
300
+ return null;
301
+ }
302
+ const remote = remoteResult.stdout;
303
+ if (remote.length === 0) {
304
+ return null;
305
+ }
306
+ const urlResult = await this.runGitCommand("remote", "get-url", remote);
307
+ if (urlResult.exitCode !== 0) {
308
+ return null;
309
+ }
310
+ return urlResult.stdout;
311
+ }
312
+ static async hasUncommittedChanges() {
313
+ const result = await this.runGitCommand("status", "--porcelain");
314
+ if (result.exitCode !== 0) {
315
+ return false;
316
+ }
317
+ return result.stdout.length > 0;
318
+ }
319
+ }
320
+ // .opencode/branch-memory/collector.ts
321
+ class ContextCollector {
322
+ config;
323
+ client;
324
+ constructor(config, client) {
325
+ this.config = config;
326
+ this.client = client;
327
+ }
328
+ async collectContext(includeMessages = true, includeTodos = true, includeFiles = true, description = "") {
329
+ const currentBranch = await GitOperations.getCurrentBranch();
330
+ if (!currentBranch) {
331
+ throw new Error("Not on a git branch");
332
+ }
333
+ const data = {
334
+ description: description || ""
335
+ };
336
+ if (includeMessages) {
337
+ data.messages = await this.collectMessages();
338
+ }
339
+ if (includeTodos) {
340
+ data.todos = await this.collectTodos();
341
+ }
342
+ if (includeFiles) {
343
+ data.files = await GitOperations.getModifiedFiles();
344
+ }
345
+ const context = {
346
+ branch: currentBranch,
347
+ savedAt: new Date().toISOString(),
348
+ metadata: {
349
+ version: "1.0.0",
350
+ platform: process.platform,
351
+ size: JSON.stringify(data).length,
352
+ messageCount: data.messages?.length || 0,
353
+ todoCount: data.todos?.length || 0,
354
+ fileCount: data.files?.length || 0
355
+ },
356
+ data
357
+ };
358
+ return context;
359
+ }
360
+ async collectMessages() {
361
+ if (!this.client) {
362
+ console.warn("Client not available, skipping message collection");
363
+ return [];
364
+ }
365
+ try {
366
+ const messages = await this.client.session?.getMessages?.() || [];
367
+ const limited = messages.slice(-this.config.context.maxMessages);
368
+ return limited.map((msg) => ({
369
+ role: msg.role || "user",
370
+ content: msg.content || "",
371
+ timestamp: msg.timestamp || new Date().toISOString()
372
+ }));
373
+ } catch (error) {
374
+ console.error("Failed to collect messages:", error);
375
+ return [];
376
+ }
377
+ }
378
+ async collectTodos() {
379
+ if (!this.client) {
380
+ console.warn("Client not available, skipping todo collection");
381
+ return [];
382
+ }
383
+ try {
384
+ const todos = await this.client.session?.getTodos?.() || [];
385
+ const limited = todos.slice(0, this.config.context.maxTodos);
386
+ return limited.map((todo) => ({
387
+ id: todo.id || String(Date.now()),
388
+ content: todo.content || "",
389
+ status: todo.status || "pending"
390
+ }));
391
+ } catch (error) {
392
+ console.error("Failed to collect todos:", error);
393
+ return [];
394
+ }
395
+ }
396
+ }
397
+ // .opencode/node_modules/chokidar/esm/index.js
398
+ import { stat as statcb } from "fs";
399
+ import { stat as stat4, readdir as readdir3 } from "fs/promises";
400
+ import { EventEmitter } from "events";
401
+ import * as sysPath2 from "path";
402
+
403
+ // .opencode/node_modules/readdirp/esm/index.js
404
+ import { stat as stat2, lstat, readdir as readdir2, realpath } from "node:fs/promises";
405
+ import { Readable } from "node:stream";
406
+ import { resolve as presolve, relative as prelative, join as pjoin, sep as psep } from "node:path";
407
+ var EntryTypes = {
408
+ FILE_TYPE: "files",
409
+ DIR_TYPE: "directories",
410
+ FILE_DIR_TYPE: "files_directories",
411
+ EVERYTHING_TYPE: "all"
412
+ };
413
+ var defaultOptions = {
414
+ root: ".",
415
+ fileFilter: (_entryInfo) => true,
416
+ directoryFilter: (_entryInfo) => true,
417
+ type: EntryTypes.FILE_TYPE,
418
+ lstat: false,
419
+ depth: 2147483648,
420
+ alwaysStat: false,
421
+ highWaterMark: 4096
422
+ };
423
+ Object.freeze(defaultOptions);
424
+ var RECURSIVE_ERROR_CODE = "READDIRP_RECURSIVE_ERROR";
425
+ var NORMAL_FLOW_ERRORS = new Set(["ENOENT", "EPERM", "EACCES", "ELOOP", RECURSIVE_ERROR_CODE]);
426
+ var ALL_TYPES = [
427
+ EntryTypes.DIR_TYPE,
428
+ EntryTypes.EVERYTHING_TYPE,
429
+ EntryTypes.FILE_DIR_TYPE,
430
+ EntryTypes.FILE_TYPE
431
+ ];
432
+ var DIR_TYPES = new Set([
433
+ EntryTypes.DIR_TYPE,
434
+ EntryTypes.EVERYTHING_TYPE,
435
+ EntryTypes.FILE_DIR_TYPE
436
+ ]);
437
+ var FILE_TYPES = new Set([
438
+ EntryTypes.EVERYTHING_TYPE,
439
+ EntryTypes.FILE_DIR_TYPE,
440
+ EntryTypes.FILE_TYPE
441
+ ]);
442
+ var isNormalFlowError = (error) => NORMAL_FLOW_ERRORS.has(error.code);
443
+ var wantBigintFsStats = process.platform === "win32";
444
+ var emptyFn = (_entryInfo) => true;
445
+ var normalizeFilter = (filter) => {
446
+ if (filter === undefined)
447
+ return emptyFn;
448
+ if (typeof filter === "function")
449
+ return filter;
450
+ if (typeof filter === "string") {
451
+ const fl = filter.trim();
452
+ return (entry) => entry.basename === fl;
453
+ }
454
+ if (Array.isArray(filter)) {
455
+ const trItems = filter.map((item) => item.trim());
456
+ return (entry) => trItems.some((f) => entry.basename === f);
457
+ }
458
+ return emptyFn;
459
+ };
460
+
461
+ class ReaddirpStream extends Readable {
462
+ constructor(options = {}) {
463
+ super({
464
+ objectMode: true,
465
+ autoDestroy: true,
466
+ highWaterMark: options.highWaterMark
467
+ });
468
+ const opts = { ...defaultOptions, ...options };
469
+ const { root, type } = opts;
470
+ this._fileFilter = normalizeFilter(opts.fileFilter);
471
+ this._directoryFilter = normalizeFilter(opts.directoryFilter);
472
+ const statMethod = opts.lstat ? lstat : stat2;
473
+ if (wantBigintFsStats) {
474
+ this._stat = (path2) => statMethod(path2, { bigint: true });
475
+ } else {
476
+ this._stat = statMethod;
477
+ }
478
+ this._maxDepth = opts.depth ?? defaultOptions.depth;
479
+ this._wantsDir = type ? DIR_TYPES.has(type) : false;
480
+ this._wantsFile = type ? FILE_TYPES.has(type) : false;
481
+ this._wantsEverything = type === EntryTypes.EVERYTHING_TYPE;
482
+ this._root = presolve(root);
483
+ this._isDirent = !opts.alwaysStat;
484
+ this._statsProp = this._isDirent ? "dirent" : "stats";
485
+ this._rdOptions = { encoding: "utf8", withFileTypes: this._isDirent };
486
+ this.parents = [this._exploreDir(root, 1)];
487
+ this.reading = false;
488
+ this.parent = undefined;
489
+ }
490
+ async _read(batch) {
491
+ if (this.reading)
492
+ return;
493
+ this.reading = true;
494
+ try {
495
+ while (!this.destroyed && batch > 0) {
496
+ const par = this.parent;
497
+ const fil = par && par.files;
498
+ if (fil && fil.length > 0) {
499
+ const { path: path2, depth } = par;
500
+ const slice = fil.splice(0, batch).map((dirent) => this._formatEntry(dirent, path2));
501
+ const awaited = await Promise.all(slice);
502
+ for (const entry of awaited) {
503
+ if (!entry)
504
+ continue;
505
+ if (this.destroyed)
506
+ return;
507
+ const entryType = await this._getEntryType(entry);
508
+ if (entryType === "directory" && this._directoryFilter(entry)) {
509
+ if (depth <= this._maxDepth) {
510
+ this.parents.push(this._exploreDir(entry.fullPath, depth + 1));
511
+ }
512
+ if (this._wantsDir) {
513
+ this.push(entry);
514
+ batch--;
515
+ }
516
+ } else if ((entryType === "file" || this._includeAsFile(entry)) && this._fileFilter(entry)) {
517
+ if (this._wantsFile) {
518
+ this.push(entry);
519
+ batch--;
520
+ }
521
+ }
522
+ }
523
+ } else {
524
+ const parent = this.parents.pop();
525
+ if (!parent) {
526
+ this.push(null);
527
+ break;
528
+ }
529
+ this.parent = await parent;
530
+ if (this.destroyed)
531
+ return;
532
+ }
533
+ }
534
+ } catch (error) {
535
+ this.destroy(error);
536
+ } finally {
537
+ this.reading = false;
538
+ }
539
+ }
540
+ async _exploreDir(path2, depth) {
541
+ let files;
542
+ try {
543
+ files = await readdir2(path2, this._rdOptions);
544
+ } catch (error) {
545
+ this._onError(error);
546
+ }
547
+ return { files, depth, path: path2 };
548
+ }
549
+ async _formatEntry(dirent, path2) {
550
+ let entry;
551
+ const basename = this._isDirent ? dirent.name : dirent;
552
+ try {
553
+ const fullPath = presolve(pjoin(path2, basename));
554
+ entry = { path: prelative(this._root, fullPath), fullPath, basename };
555
+ entry[this._statsProp] = this._isDirent ? dirent : await this._stat(fullPath);
556
+ } catch (err) {
557
+ this._onError(err);
558
+ return;
559
+ }
560
+ return entry;
561
+ }
562
+ _onError(err) {
563
+ if (isNormalFlowError(err) && !this.destroyed) {
564
+ this.emit("warn", err);
565
+ } else {
566
+ this.destroy(err);
567
+ }
568
+ }
569
+ async _getEntryType(entry) {
570
+ if (!entry && this._statsProp in entry) {
571
+ return "";
572
+ }
573
+ const stats = entry[this._statsProp];
574
+ if (stats.isFile())
575
+ return "file";
576
+ if (stats.isDirectory())
577
+ return "directory";
578
+ if (stats && stats.isSymbolicLink()) {
579
+ const full = entry.fullPath;
580
+ try {
581
+ const entryRealPath = await realpath(full);
582
+ const entryRealPathStats = await lstat(entryRealPath);
583
+ if (entryRealPathStats.isFile()) {
584
+ return "file";
585
+ }
586
+ if (entryRealPathStats.isDirectory()) {
587
+ const len = entryRealPath.length;
588
+ if (full.startsWith(entryRealPath) && full.substr(len, 1) === psep) {
589
+ const recursiveError = new Error(`Circular symlink detected: "${full}" points to "${entryRealPath}"`);
590
+ recursiveError.code = RECURSIVE_ERROR_CODE;
591
+ return this._onError(recursiveError);
592
+ }
593
+ return "directory";
594
+ }
595
+ } catch (error) {
596
+ this._onError(error);
597
+ return "";
598
+ }
599
+ }
600
+ }
601
+ _includeAsFile(entry) {
602
+ const stats = entry && entry[this._statsProp];
603
+ return stats && this._wantsEverything && !stats.isDirectory();
604
+ }
605
+ }
606
+ function readdirp(root, options = {}) {
607
+ let type = options.entryType || options.type;
608
+ if (type === "both")
609
+ type = EntryTypes.FILE_DIR_TYPE;
610
+ if (type)
611
+ options.type = type;
612
+ if (!root) {
613
+ throw new Error("readdirp: root argument is required. Usage: readdirp(root, options)");
614
+ } else if (typeof root !== "string") {
615
+ throw new TypeError("readdirp: root argument must be a string. Usage: readdirp(root, options)");
616
+ } else if (type && !ALL_TYPES.includes(type)) {
617
+ throw new Error(`readdirp: Invalid type passed. Use one of ${ALL_TYPES.join(", ")}`);
618
+ }
619
+ options.root = root;
620
+ return new ReaddirpStream(options);
621
+ }
622
+
623
+ // .opencode/node_modules/chokidar/esm/handler.js
624
+ import { watchFile, unwatchFile, watch as fs_watch } from "fs";
625
+ import { open, stat as stat3, lstat as lstat2, realpath as fsrealpath } from "fs/promises";
626
+ import * as sysPath from "path";
627
+ import { type as osType } from "os";
628
+ var STR_DATA = "data";
629
+ var STR_END = "end";
630
+ var STR_CLOSE = "close";
631
+ var EMPTY_FN = () => {};
632
+ var pl = process.platform;
633
+ var isWindows = pl === "win32";
634
+ var isMacos = pl === "darwin";
635
+ var isLinux = pl === "linux";
636
+ var isFreeBSD = pl === "freebsd";
637
+ var isIBMi = osType() === "OS400";
638
+ var EVENTS = {
639
+ ALL: "all",
640
+ READY: "ready",
641
+ ADD: "add",
642
+ CHANGE: "change",
643
+ ADD_DIR: "addDir",
644
+ UNLINK: "unlink",
645
+ UNLINK_DIR: "unlinkDir",
646
+ RAW: "raw",
647
+ ERROR: "error"
648
+ };
649
+ var EV = EVENTS;
650
+ var THROTTLE_MODE_WATCH = "watch";
651
+ var statMethods = { lstat: lstat2, stat: stat3 };
652
+ var KEY_LISTENERS = "listeners";
653
+ var KEY_ERR = "errHandlers";
654
+ var KEY_RAW = "rawEmitters";
655
+ var HANDLER_KEYS = [KEY_LISTENERS, KEY_ERR, KEY_RAW];
656
+ var binaryExtensions = new Set([
657
+ "3dm",
658
+ "3ds",
659
+ "3g2",
660
+ "3gp",
661
+ "7z",
662
+ "a",
663
+ "aac",
664
+ "adp",
665
+ "afdesign",
666
+ "afphoto",
667
+ "afpub",
668
+ "ai",
669
+ "aif",
670
+ "aiff",
671
+ "alz",
672
+ "ape",
673
+ "apk",
674
+ "appimage",
675
+ "ar",
676
+ "arj",
677
+ "asf",
678
+ "au",
679
+ "avi",
680
+ "bak",
681
+ "baml",
682
+ "bh",
683
+ "bin",
684
+ "bk",
685
+ "bmp",
686
+ "btif",
687
+ "bz2",
688
+ "bzip2",
689
+ "cab",
690
+ "caf",
691
+ "cgm",
692
+ "class",
693
+ "cmx",
694
+ "cpio",
695
+ "cr2",
696
+ "cur",
697
+ "dat",
698
+ "dcm",
699
+ "deb",
700
+ "dex",
701
+ "djvu",
702
+ "dll",
703
+ "dmg",
704
+ "dng",
705
+ "doc",
706
+ "docm",
707
+ "docx",
708
+ "dot",
709
+ "dotm",
710
+ "dra",
711
+ "DS_Store",
712
+ "dsk",
713
+ "dts",
714
+ "dtshd",
715
+ "dvb",
716
+ "dwg",
717
+ "dxf",
718
+ "ecelp4800",
719
+ "ecelp7470",
720
+ "ecelp9600",
721
+ "egg",
722
+ "eol",
723
+ "eot",
724
+ "epub",
725
+ "exe",
726
+ "f4v",
727
+ "fbs",
728
+ "fh",
729
+ "fla",
730
+ "flac",
731
+ "flatpak",
732
+ "fli",
733
+ "flv",
734
+ "fpx",
735
+ "fst",
736
+ "fvt",
737
+ "g3",
738
+ "gh",
739
+ "gif",
740
+ "graffle",
741
+ "gz",
742
+ "gzip",
743
+ "h261",
744
+ "h263",
745
+ "h264",
746
+ "icns",
747
+ "ico",
748
+ "ief",
749
+ "img",
750
+ "ipa",
751
+ "iso",
752
+ "jar",
753
+ "jpeg",
754
+ "jpg",
755
+ "jpgv",
756
+ "jpm",
757
+ "jxr",
758
+ "key",
759
+ "ktx",
760
+ "lha",
761
+ "lib",
762
+ "lvp",
763
+ "lz",
764
+ "lzh",
765
+ "lzma",
766
+ "lzo",
767
+ "m3u",
768
+ "m4a",
769
+ "m4v",
770
+ "mar",
771
+ "mdi",
772
+ "mht",
773
+ "mid",
774
+ "midi",
775
+ "mj2",
776
+ "mka",
777
+ "mkv",
778
+ "mmr",
779
+ "mng",
780
+ "mobi",
781
+ "mov",
782
+ "movie",
783
+ "mp3",
784
+ "mp4",
785
+ "mp4a",
786
+ "mpeg",
787
+ "mpg",
788
+ "mpga",
789
+ "mxu",
790
+ "nef",
791
+ "npx",
792
+ "numbers",
793
+ "nupkg",
794
+ "o",
795
+ "odp",
796
+ "ods",
797
+ "odt",
798
+ "oga",
799
+ "ogg",
800
+ "ogv",
801
+ "otf",
802
+ "ott",
803
+ "pages",
804
+ "pbm",
805
+ "pcx",
806
+ "pdb",
807
+ "pdf",
808
+ "pea",
809
+ "pgm",
810
+ "pic",
811
+ "png",
812
+ "pnm",
813
+ "pot",
814
+ "potm",
815
+ "potx",
816
+ "ppa",
817
+ "ppam",
818
+ "ppm",
819
+ "pps",
820
+ "ppsm",
821
+ "ppsx",
822
+ "ppt",
823
+ "pptm",
824
+ "pptx",
825
+ "psd",
826
+ "pya",
827
+ "pyc",
828
+ "pyo",
829
+ "pyv",
830
+ "qt",
831
+ "rar",
832
+ "ras",
833
+ "raw",
834
+ "resources",
835
+ "rgb",
836
+ "rip",
837
+ "rlc",
838
+ "rmf",
839
+ "rmvb",
840
+ "rpm",
841
+ "rtf",
842
+ "rz",
843
+ "s3m",
844
+ "s7z",
845
+ "scpt",
846
+ "sgi",
847
+ "shar",
848
+ "snap",
849
+ "sil",
850
+ "sketch",
851
+ "slk",
852
+ "smv",
853
+ "snk",
854
+ "so",
855
+ "stl",
856
+ "suo",
857
+ "sub",
858
+ "swf",
859
+ "tar",
860
+ "tbz",
861
+ "tbz2",
862
+ "tga",
863
+ "tgz",
864
+ "thmx",
865
+ "tif",
866
+ "tiff",
867
+ "tlz",
868
+ "ttc",
869
+ "ttf",
870
+ "txz",
871
+ "udf",
872
+ "uvh",
873
+ "uvi",
874
+ "uvm",
875
+ "uvp",
876
+ "uvs",
877
+ "uvu",
878
+ "viv",
879
+ "vob",
880
+ "war",
881
+ "wav",
882
+ "wax",
883
+ "wbmp",
884
+ "wdp",
885
+ "weba",
886
+ "webm",
887
+ "webp",
888
+ "whl",
889
+ "wim",
890
+ "wm",
891
+ "wma",
892
+ "wmv",
893
+ "wmx",
894
+ "woff",
895
+ "woff2",
896
+ "wrm",
897
+ "wvx",
898
+ "xbm",
899
+ "xif",
900
+ "xla",
901
+ "xlam",
902
+ "xls",
903
+ "xlsb",
904
+ "xlsm",
905
+ "xlsx",
906
+ "xlt",
907
+ "xltm",
908
+ "xltx",
909
+ "xm",
910
+ "xmind",
911
+ "xpi",
912
+ "xpm",
913
+ "xwd",
914
+ "xz",
915
+ "z",
916
+ "zip",
917
+ "zipx"
918
+ ]);
919
+ var isBinaryPath = (filePath) => binaryExtensions.has(sysPath.extname(filePath).slice(1).toLowerCase());
920
+ var foreach = (val, fn) => {
921
+ if (val instanceof Set) {
922
+ val.forEach(fn);
923
+ } else {
924
+ fn(val);
925
+ }
926
+ };
927
+ var addAndConvert = (main, prop, item) => {
928
+ let container = main[prop];
929
+ if (!(container instanceof Set)) {
930
+ main[prop] = container = new Set([container]);
931
+ }
932
+ container.add(item);
933
+ };
934
+ var clearItem = (cont) => (key) => {
935
+ const set = cont[key];
936
+ if (set instanceof Set) {
937
+ set.clear();
938
+ } else {
939
+ delete cont[key];
940
+ }
941
+ };
942
+ var delFromSet = (main, prop, item) => {
943
+ const container = main[prop];
944
+ if (container instanceof Set) {
945
+ container.delete(item);
946
+ } else if (container === item) {
947
+ delete main[prop];
948
+ }
949
+ };
950
+ var isEmptySet = (val) => val instanceof Set ? val.size === 0 : !val;
951
+ var FsWatchInstances = new Map;
952
+ function createFsWatchInstance(path2, options, listener, errHandler, emitRaw) {
953
+ const handleEvent = (rawEvent, evPath) => {
954
+ listener(path2);
955
+ emitRaw(rawEvent, evPath, { watchedPath: path2 });
956
+ if (evPath && path2 !== evPath) {
957
+ fsWatchBroadcast(sysPath.resolve(path2, evPath), KEY_LISTENERS, sysPath.join(path2, evPath));
958
+ }
959
+ };
960
+ try {
961
+ return fs_watch(path2, {
962
+ persistent: options.persistent
963
+ }, handleEvent);
964
+ } catch (error) {
965
+ errHandler(error);
966
+ return;
967
+ }
968
+ }
969
+ var fsWatchBroadcast = (fullPath, listenerType, val1, val2, val3) => {
970
+ const cont = FsWatchInstances.get(fullPath);
971
+ if (!cont)
972
+ return;
973
+ foreach(cont[listenerType], (listener) => {
974
+ listener(val1, val2, val3);
975
+ });
976
+ };
977
+ var setFsWatchListener = (path2, fullPath, options, handlers) => {
978
+ const { listener, errHandler, rawEmitter } = handlers;
979
+ let cont = FsWatchInstances.get(fullPath);
980
+ let watcher;
981
+ if (!options.persistent) {
982
+ watcher = createFsWatchInstance(path2, options, listener, errHandler, rawEmitter);
983
+ if (!watcher)
984
+ return;
985
+ return watcher.close.bind(watcher);
986
+ }
987
+ if (cont) {
988
+ addAndConvert(cont, KEY_LISTENERS, listener);
989
+ addAndConvert(cont, KEY_ERR, errHandler);
990
+ addAndConvert(cont, KEY_RAW, rawEmitter);
991
+ } else {
992
+ watcher = createFsWatchInstance(path2, options, fsWatchBroadcast.bind(null, fullPath, KEY_LISTENERS), errHandler, fsWatchBroadcast.bind(null, fullPath, KEY_RAW));
993
+ if (!watcher)
994
+ return;
995
+ watcher.on(EV.ERROR, async (error) => {
996
+ const broadcastErr = fsWatchBroadcast.bind(null, fullPath, KEY_ERR);
997
+ if (cont)
998
+ cont.watcherUnusable = true;
999
+ if (isWindows && error.code === "EPERM") {
1000
+ try {
1001
+ const fd = await open(path2, "r");
1002
+ await fd.close();
1003
+ broadcastErr(error);
1004
+ } catch (err) {}
1005
+ } else {
1006
+ broadcastErr(error);
1007
+ }
1008
+ });
1009
+ cont = {
1010
+ listeners: listener,
1011
+ errHandlers: errHandler,
1012
+ rawEmitters: rawEmitter,
1013
+ watcher
1014
+ };
1015
+ FsWatchInstances.set(fullPath, cont);
1016
+ }
1017
+ return () => {
1018
+ delFromSet(cont, KEY_LISTENERS, listener);
1019
+ delFromSet(cont, KEY_ERR, errHandler);
1020
+ delFromSet(cont, KEY_RAW, rawEmitter);
1021
+ if (isEmptySet(cont.listeners)) {
1022
+ cont.watcher.close();
1023
+ FsWatchInstances.delete(fullPath);
1024
+ HANDLER_KEYS.forEach(clearItem(cont));
1025
+ cont.watcher = undefined;
1026
+ Object.freeze(cont);
1027
+ }
1028
+ };
1029
+ };
1030
+ var FsWatchFileInstances = new Map;
1031
+ var setFsWatchFileListener = (path2, fullPath, options, handlers) => {
1032
+ const { listener, rawEmitter } = handlers;
1033
+ let cont = FsWatchFileInstances.get(fullPath);
1034
+ const copts = cont && cont.options;
1035
+ if (copts && (copts.persistent < options.persistent || copts.interval > options.interval)) {
1036
+ unwatchFile(fullPath);
1037
+ cont = undefined;
1038
+ }
1039
+ if (cont) {
1040
+ addAndConvert(cont, KEY_LISTENERS, listener);
1041
+ addAndConvert(cont, KEY_RAW, rawEmitter);
1042
+ } else {
1043
+ cont = {
1044
+ listeners: listener,
1045
+ rawEmitters: rawEmitter,
1046
+ options,
1047
+ watcher: watchFile(fullPath, options, (curr, prev) => {
1048
+ foreach(cont.rawEmitters, (rawEmitter2) => {
1049
+ rawEmitter2(EV.CHANGE, fullPath, { curr, prev });
1050
+ });
1051
+ const currmtime = curr.mtimeMs;
1052
+ if (curr.size !== prev.size || currmtime > prev.mtimeMs || currmtime === 0) {
1053
+ foreach(cont.listeners, (listener2) => listener2(path2, curr));
1054
+ }
1055
+ })
1056
+ };
1057
+ FsWatchFileInstances.set(fullPath, cont);
1058
+ }
1059
+ return () => {
1060
+ delFromSet(cont, KEY_LISTENERS, listener);
1061
+ delFromSet(cont, KEY_RAW, rawEmitter);
1062
+ if (isEmptySet(cont.listeners)) {
1063
+ FsWatchFileInstances.delete(fullPath);
1064
+ unwatchFile(fullPath);
1065
+ cont.options = cont.watcher = undefined;
1066
+ Object.freeze(cont);
1067
+ }
1068
+ };
1069
+ };
1070
+
1071
+ class NodeFsHandler {
1072
+ constructor(fsW) {
1073
+ this.fsw = fsW;
1074
+ this._boundHandleError = (error) => fsW._handleError(error);
1075
+ }
1076
+ _watchWithNodeFs(path2, listener) {
1077
+ const opts = this.fsw.options;
1078
+ const directory = sysPath.dirname(path2);
1079
+ const basename2 = sysPath.basename(path2);
1080
+ const parent = this.fsw._getWatchedDir(directory);
1081
+ parent.add(basename2);
1082
+ const absolutePath = sysPath.resolve(path2);
1083
+ const options = {
1084
+ persistent: opts.persistent
1085
+ };
1086
+ if (!listener)
1087
+ listener = EMPTY_FN;
1088
+ let closer;
1089
+ if (opts.usePolling) {
1090
+ const enableBin = opts.interval !== opts.binaryInterval;
1091
+ options.interval = enableBin && isBinaryPath(basename2) ? opts.binaryInterval : opts.interval;
1092
+ closer = setFsWatchFileListener(path2, absolutePath, options, {
1093
+ listener,
1094
+ rawEmitter: this.fsw._emitRaw
1095
+ });
1096
+ } else {
1097
+ closer = setFsWatchListener(path2, absolutePath, options, {
1098
+ listener,
1099
+ errHandler: this._boundHandleError,
1100
+ rawEmitter: this.fsw._emitRaw
1101
+ });
1102
+ }
1103
+ return closer;
1104
+ }
1105
+ _handleFile(file, stats, initialAdd) {
1106
+ if (this.fsw.closed) {
1107
+ return;
1108
+ }
1109
+ const dirname2 = sysPath.dirname(file);
1110
+ const basename2 = sysPath.basename(file);
1111
+ const parent = this.fsw._getWatchedDir(dirname2);
1112
+ let prevStats = stats;
1113
+ if (parent.has(basename2))
1114
+ return;
1115
+ const listener = async (path2, newStats) => {
1116
+ if (!this.fsw._throttle(THROTTLE_MODE_WATCH, file, 5))
1117
+ return;
1118
+ if (!newStats || newStats.mtimeMs === 0) {
1119
+ try {
1120
+ const newStats2 = await stat3(file);
1121
+ if (this.fsw.closed)
1122
+ return;
1123
+ const at = newStats2.atimeMs;
1124
+ const mt = newStats2.mtimeMs;
1125
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
1126
+ this.fsw._emit(EV.CHANGE, file, newStats2);
1127
+ }
1128
+ if ((isMacos || isLinux || isFreeBSD) && prevStats.ino !== newStats2.ino) {
1129
+ this.fsw._closeFile(path2);
1130
+ prevStats = newStats2;
1131
+ const closer2 = this._watchWithNodeFs(file, listener);
1132
+ if (closer2)
1133
+ this.fsw._addPathCloser(path2, closer2);
1134
+ } else {
1135
+ prevStats = newStats2;
1136
+ }
1137
+ } catch (error) {
1138
+ this.fsw._remove(dirname2, basename2);
1139
+ }
1140
+ } else if (parent.has(basename2)) {
1141
+ const at = newStats.atimeMs;
1142
+ const mt = newStats.mtimeMs;
1143
+ if (!at || at <= mt || mt !== prevStats.mtimeMs) {
1144
+ this.fsw._emit(EV.CHANGE, file, newStats);
1145
+ }
1146
+ prevStats = newStats;
1147
+ }
1148
+ };
1149
+ const closer = this._watchWithNodeFs(file, listener);
1150
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && this.fsw._isntIgnored(file)) {
1151
+ if (!this.fsw._throttle(EV.ADD, file, 0))
1152
+ return;
1153
+ this.fsw._emit(EV.ADD, file, stats);
1154
+ }
1155
+ return closer;
1156
+ }
1157
+ async _handleSymlink(entry, directory, path2, item) {
1158
+ if (this.fsw.closed) {
1159
+ return;
1160
+ }
1161
+ const full = entry.fullPath;
1162
+ const dir = this.fsw._getWatchedDir(directory);
1163
+ if (!this.fsw.options.followSymlinks) {
1164
+ this.fsw._incrReadyCount();
1165
+ let linkPath;
1166
+ try {
1167
+ linkPath = await fsrealpath(path2);
1168
+ } catch (e) {
1169
+ this.fsw._emitReady();
1170
+ return true;
1171
+ }
1172
+ if (this.fsw.closed)
1173
+ return;
1174
+ if (dir.has(item)) {
1175
+ if (this.fsw._symlinkPaths.get(full) !== linkPath) {
1176
+ this.fsw._symlinkPaths.set(full, linkPath);
1177
+ this.fsw._emit(EV.CHANGE, path2, entry.stats);
1178
+ }
1179
+ } else {
1180
+ dir.add(item);
1181
+ this.fsw._symlinkPaths.set(full, linkPath);
1182
+ this.fsw._emit(EV.ADD, path2, entry.stats);
1183
+ }
1184
+ this.fsw._emitReady();
1185
+ return true;
1186
+ }
1187
+ if (this.fsw._symlinkPaths.has(full)) {
1188
+ return true;
1189
+ }
1190
+ this.fsw._symlinkPaths.set(full, true);
1191
+ }
1192
+ _handleRead(directory, initialAdd, wh, target, dir, depth, throttler) {
1193
+ directory = sysPath.join(directory, "");
1194
+ throttler = this.fsw._throttle("readdir", directory, 1000);
1195
+ if (!throttler)
1196
+ return;
1197
+ const previous = this.fsw._getWatchedDir(wh.path);
1198
+ const current = new Set;
1199
+ let stream = this.fsw._readdirp(directory, {
1200
+ fileFilter: (entry) => wh.filterPath(entry),
1201
+ directoryFilter: (entry) => wh.filterDir(entry)
1202
+ });
1203
+ if (!stream)
1204
+ return;
1205
+ stream.on(STR_DATA, async (entry) => {
1206
+ if (this.fsw.closed) {
1207
+ stream = undefined;
1208
+ return;
1209
+ }
1210
+ const item = entry.path;
1211
+ let path2 = sysPath.join(directory, item);
1212
+ current.add(item);
1213
+ if (entry.stats.isSymbolicLink() && await this._handleSymlink(entry, directory, path2, item)) {
1214
+ return;
1215
+ }
1216
+ if (this.fsw.closed) {
1217
+ stream = undefined;
1218
+ return;
1219
+ }
1220
+ if (item === target || !target && !previous.has(item)) {
1221
+ this.fsw._incrReadyCount();
1222
+ path2 = sysPath.join(dir, sysPath.relative(dir, path2));
1223
+ this._addToNodeFs(path2, initialAdd, wh, depth + 1);
1224
+ }
1225
+ }).on(EV.ERROR, this._boundHandleError);
1226
+ return new Promise((resolve2, reject) => {
1227
+ if (!stream)
1228
+ return reject();
1229
+ stream.once(STR_END, () => {
1230
+ if (this.fsw.closed) {
1231
+ stream = undefined;
1232
+ return;
1233
+ }
1234
+ const wasThrottled = throttler ? throttler.clear() : false;
1235
+ resolve2(undefined);
1236
+ previous.getChildren().filter((item) => {
1237
+ return item !== directory && !current.has(item);
1238
+ }).forEach((item) => {
1239
+ this.fsw._remove(directory, item);
1240
+ });
1241
+ stream = undefined;
1242
+ if (wasThrottled)
1243
+ this._handleRead(directory, false, wh, target, dir, depth, throttler);
1244
+ });
1245
+ });
1246
+ }
1247
+ async _handleDir(dir, stats, initialAdd, depth, target, wh, realpath2) {
1248
+ const parentDir = this.fsw._getWatchedDir(sysPath.dirname(dir));
1249
+ const tracked = parentDir.has(sysPath.basename(dir));
1250
+ if (!(initialAdd && this.fsw.options.ignoreInitial) && !target && !tracked) {
1251
+ this.fsw._emit(EV.ADD_DIR, dir, stats);
1252
+ }
1253
+ parentDir.add(sysPath.basename(dir));
1254
+ this.fsw._getWatchedDir(dir);
1255
+ let throttler;
1256
+ let closer;
1257
+ const oDepth = this.fsw.options.depth;
1258
+ if ((oDepth == null || depth <= oDepth) && !this.fsw._symlinkPaths.has(realpath2)) {
1259
+ if (!target) {
1260
+ await this._handleRead(dir, initialAdd, wh, target, dir, depth, throttler);
1261
+ if (this.fsw.closed)
1262
+ return;
1263
+ }
1264
+ closer = this._watchWithNodeFs(dir, (dirPath, stats2) => {
1265
+ if (stats2 && stats2.mtimeMs === 0)
1266
+ return;
1267
+ this._handleRead(dirPath, false, wh, target, dir, depth, throttler);
1268
+ });
1269
+ }
1270
+ return closer;
1271
+ }
1272
+ async _addToNodeFs(path2, initialAdd, priorWh, depth, target) {
1273
+ const ready = this.fsw._emitReady;
1274
+ if (this.fsw._isIgnored(path2) || this.fsw.closed) {
1275
+ ready();
1276
+ return false;
1277
+ }
1278
+ const wh = this.fsw._getWatchHelpers(path2);
1279
+ if (priorWh) {
1280
+ wh.filterPath = (entry) => priorWh.filterPath(entry);
1281
+ wh.filterDir = (entry) => priorWh.filterDir(entry);
1282
+ }
1283
+ try {
1284
+ const stats = await statMethods[wh.statMethod](wh.watchPath);
1285
+ if (this.fsw.closed)
1286
+ return;
1287
+ if (this.fsw._isIgnored(wh.watchPath, stats)) {
1288
+ ready();
1289
+ return false;
1290
+ }
1291
+ const follow = this.fsw.options.followSymlinks;
1292
+ let closer;
1293
+ if (stats.isDirectory()) {
1294
+ const absPath = sysPath.resolve(path2);
1295
+ const targetPath = follow ? await fsrealpath(path2) : path2;
1296
+ if (this.fsw.closed)
1297
+ return;
1298
+ closer = await this._handleDir(wh.watchPath, stats, initialAdd, depth, target, wh, targetPath);
1299
+ if (this.fsw.closed)
1300
+ return;
1301
+ if (absPath !== targetPath && targetPath !== undefined) {
1302
+ this.fsw._symlinkPaths.set(absPath, targetPath);
1303
+ }
1304
+ } else if (stats.isSymbolicLink()) {
1305
+ const targetPath = follow ? await fsrealpath(path2) : path2;
1306
+ if (this.fsw.closed)
1307
+ return;
1308
+ const parent = sysPath.dirname(wh.watchPath);
1309
+ this.fsw._getWatchedDir(parent).add(wh.watchPath);
1310
+ this.fsw._emit(EV.ADD, wh.watchPath, stats);
1311
+ closer = await this._handleDir(parent, stats, initialAdd, depth, path2, wh, targetPath);
1312
+ if (this.fsw.closed)
1313
+ return;
1314
+ if (targetPath !== undefined) {
1315
+ this.fsw._symlinkPaths.set(sysPath.resolve(path2), targetPath);
1316
+ }
1317
+ } else {
1318
+ closer = this._handleFile(wh.watchPath, stats, initialAdd);
1319
+ }
1320
+ ready();
1321
+ if (closer)
1322
+ this.fsw._addPathCloser(path2, closer);
1323
+ return false;
1324
+ } catch (error) {
1325
+ if (this.fsw._handleError(error)) {
1326
+ ready();
1327
+ return path2;
1328
+ }
1329
+ }
1330
+ }
1331
+ }
1332
+
1333
+ // .opencode/node_modules/chokidar/esm/index.js
1334
+ /*! chokidar - MIT License (c) 2012 Paul Miller (paulmillr.com) */
1335
+ var SLASH = "/";
1336
+ var SLASH_SLASH = "//";
1337
+ var ONE_DOT = ".";
1338
+ var TWO_DOTS = "..";
1339
+ var STRING_TYPE = "string";
1340
+ var BACK_SLASH_RE = /\\/g;
1341
+ var DOUBLE_SLASH_RE = /\/\//;
1342
+ var DOT_RE = /\..*\.(sw[px])$|~$|\.subl.*\.tmp/;
1343
+ var REPLACER_RE = /^\.[/\\]/;
1344
+ function arrify(item) {
1345
+ return Array.isArray(item) ? item : [item];
1346
+ }
1347
+ var isMatcherObject = (matcher) => typeof matcher === "object" && matcher !== null && !(matcher instanceof RegExp);
1348
+ function createPattern(matcher) {
1349
+ if (typeof matcher === "function")
1350
+ return matcher;
1351
+ if (typeof matcher === "string")
1352
+ return (string) => matcher === string;
1353
+ if (matcher instanceof RegExp)
1354
+ return (string) => matcher.test(string);
1355
+ if (typeof matcher === "object" && matcher !== null) {
1356
+ return (string) => {
1357
+ if (matcher.path === string)
1358
+ return true;
1359
+ if (matcher.recursive) {
1360
+ const relative3 = sysPath2.relative(matcher.path, string);
1361
+ if (!relative3) {
1362
+ return false;
1363
+ }
1364
+ return !relative3.startsWith("..") && !sysPath2.isAbsolute(relative3);
1365
+ }
1366
+ return false;
1367
+ };
1368
+ }
1369
+ return () => false;
1370
+ }
1371
+ function normalizePath(path2) {
1372
+ if (typeof path2 !== "string")
1373
+ throw new Error("string expected");
1374
+ path2 = sysPath2.normalize(path2);
1375
+ path2 = path2.replace(/\\/g, "/");
1376
+ let prepend = false;
1377
+ if (path2.startsWith("//"))
1378
+ prepend = true;
1379
+ const DOUBLE_SLASH_RE2 = /\/\//;
1380
+ while (path2.match(DOUBLE_SLASH_RE2))
1381
+ path2 = path2.replace(DOUBLE_SLASH_RE2, "/");
1382
+ if (prepend)
1383
+ path2 = "/" + path2;
1384
+ return path2;
1385
+ }
1386
+ function matchPatterns(patterns, testString, stats) {
1387
+ const path2 = normalizePath(testString);
1388
+ for (let index = 0;index < patterns.length; index++) {
1389
+ const pattern = patterns[index];
1390
+ if (pattern(path2, stats)) {
1391
+ return true;
1392
+ }
1393
+ }
1394
+ return false;
1395
+ }
1396
+ function anymatch(matchers, testString) {
1397
+ if (matchers == null) {
1398
+ throw new TypeError("anymatch: specify first argument");
1399
+ }
1400
+ const matchersArray = arrify(matchers);
1401
+ const patterns = matchersArray.map((matcher) => createPattern(matcher));
1402
+ if (testString == null) {
1403
+ return (testString2, stats) => {
1404
+ return matchPatterns(patterns, testString2, stats);
1405
+ };
1406
+ }
1407
+ return matchPatterns(patterns, testString);
1408
+ }
1409
+ var unifyPaths = (paths_) => {
1410
+ const paths = arrify(paths_).flat();
1411
+ if (!paths.every((p) => typeof p === STRING_TYPE)) {
1412
+ throw new TypeError(`Non-string provided as watch path: ${paths}`);
1413
+ }
1414
+ return paths.map(normalizePathToUnix);
1415
+ };
1416
+ var toUnix = (string) => {
1417
+ let str = string.replace(BACK_SLASH_RE, SLASH);
1418
+ let prepend = false;
1419
+ if (str.startsWith(SLASH_SLASH)) {
1420
+ prepend = true;
1421
+ }
1422
+ while (str.match(DOUBLE_SLASH_RE)) {
1423
+ str = str.replace(DOUBLE_SLASH_RE, SLASH);
1424
+ }
1425
+ if (prepend) {
1426
+ str = SLASH + str;
1427
+ }
1428
+ return str;
1429
+ };
1430
+ var normalizePathToUnix = (path2) => toUnix(sysPath2.normalize(toUnix(path2)));
1431
+ var normalizeIgnored = (cwd = "") => (path2) => {
1432
+ if (typeof path2 === "string") {
1433
+ return normalizePathToUnix(sysPath2.isAbsolute(path2) ? path2 : sysPath2.join(cwd, path2));
1434
+ } else {
1435
+ return path2;
1436
+ }
1437
+ };
1438
+ var getAbsolutePath = (path2, cwd) => {
1439
+ if (sysPath2.isAbsolute(path2)) {
1440
+ return path2;
1441
+ }
1442
+ return sysPath2.join(cwd, path2);
1443
+ };
1444
+ var EMPTY_SET = Object.freeze(new Set);
1445
+
1446
+ class DirEntry {
1447
+ constructor(dir, removeWatcher) {
1448
+ this.path = dir;
1449
+ this._removeWatcher = removeWatcher;
1450
+ this.items = new Set;
1451
+ }
1452
+ add(item) {
1453
+ const { items } = this;
1454
+ if (!items)
1455
+ return;
1456
+ if (item !== ONE_DOT && item !== TWO_DOTS)
1457
+ items.add(item);
1458
+ }
1459
+ async remove(item) {
1460
+ const { items } = this;
1461
+ if (!items)
1462
+ return;
1463
+ items.delete(item);
1464
+ if (items.size > 0)
1465
+ return;
1466
+ const dir = this.path;
1467
+ try {
1468
+ await readdir3(dir);
1469
+ } catch (err) {
1470
+ if (this._removeWatcher) {
1471
+ this._removeWatcher(sysPath2.dirname(dir), sysPath2.basename(dir));
1472
+ }
1473
+ }
1474
+ }
1475
+ has(item) {
1476
+ const { items } = this;
1477
+ if (!items)
1478
+ return;
1479
+ return items.has(item);
1480
+ }
1481
+ getChildren() {
1482
+ const { items } = this;
1483
+ if (!items)
1484
+ return [];
1485
+ return [...items.values()];
1486
+ }
1487
+ dispose() {
1488
+ this.items.clear();
1489
+ this.path = "";
1490
+ this._removeWatcher = EMPTY_FN;
1491
+ this.items = EMPTY_SET;
1492
+ Object.freeze(this);
1493
+ }
1494
+ }
1495
+ var STAT_METHOD_F = "stat";
1496
+ var STAT_METHOD_L = "lstat";
1497
+
1498
+ class WatchHelper {
1499
+ constructor(path2, follow, fsw) {
1500
+ this.fsw = fsw;
1501
+ const watchPath = path2;
1502
+ this.path = path2 = path2.replace(REPLACER_RE, "");
1503
+ this.watchPath = watchPath;
1504
+ this.fullWatchPath = sysPath2.resolve(watchPath);
1505
+ this.dirParts = [];
1506
+ this.dirParts.forEach((parts) => {
1507
+ if (parts.length > 1)
1508
+ parts.pop();
1509
+ });
1510
+ this.followSymlinks = follow;
1511
+ this.statMethod = follow ? STAT_METHOD_F : STAT_METHOD_L;
1512
+ }
1513
+ entryPath(entry) {
1514
+ return sysPath2.join(this.watchPath, sysPath2.relative(this.watchPath, entry.fullPath));
1515
+ }
1516
+ filterPath(entry) {
1517
+ const { stats } = entry;
1518
+ if (stats && stats.isSymbolicLink())
1519
+ return this.filterDir(entry);
1520
+ const resolvedPath = this.entryPath(entry);
1521
+ return this.fsw._isntIgnored(resolvedPath, stats) && this.fsw._hasReadPermissions(stats);
1522
+ }
1523
+ filterDir(entry) {
1524
+ return this.fsw._isntIgnored(this.entryPath(entry), entry.stats);
1525
+ }
1526
+ }
1527
+
1528
+ class FSWatcher extends EventEmitter {
1529
+ constructor(_opts = {}) {
1530
+ super();
1531
+ this.closed = false;
1532
+ this._closers = new Map;
1533
+ this._ignoredPaths = new Set;
1534
+ this._throttled = new Map;
1535
+ this._streams = new Set;
1536
+ this._symlinkPaths = new Map;
1537
+ this._watched = new Map;
1538
+ this._pendingWrites = new Map;
1539
+ this._pendingUnlinks = new Map;
1540
+ this._readyCount = 0;
1541
+ this._readyEmitted = false;
1542
+ const awf = _opts.awaitWriteFinish;
1543
+ const DEF_AWF = { stabilityThreshold: 2000, pollInterval: 100 };
1544
+ const opts = {
1545
+ persistent: true,
1546
+ ignoreInitial: false,
1547
+ ignorePermissionErrors: false,
1548
+ interval: 100,
1549
+ binaryInterval: 300,
1550
+ followSymlinks: true,
1551
+ usePolling: false,
1552
+ atomic: true,
1553
+ ..._opts,
1554
+ ignored: _opts.ignored ? arrify(_opts.ignored) : arrify([]),
1555
+ awaitWriteFinish: awf === true ? DEF_AWF : typeof awf === "object" ? { ...DEF_AWF, ...awf } : false
1556
+ };
1557
+ if (isIBMi)
1558
+ opts.usePolling = true;
1559
+ if (opts.atomic === undefined)
1560
+ opts.atomic = !opts.usePolling;
1561
+ const envPoll = process.env.CHOKIDAR_USEPOLLING;
1562
+ if (envPoll !== undefined) {
1563
+ const envLower = envPoll.toLowerCase();
1564
+ if (envLower === "false" || envLower === "0")
1565
+ opts.usePolling = false;
1566
+ else if (envLower === "true" || envLower === "1")
1567
+ opts.usePolling = true;
1568
+ else
1569
+ opts.usePolling = !!envLower;
1570
+ }
1571
+ const envInterval = process.env.CHOKIDAR_INTERVAL;
1572
+ if (envInterval)
1573
+ opts.interval = Number.parseInt(envInterval, 10);
1574
+ let readyCalls = 0;
1575
+ this._emitReady = () => {
1576
+ readyCalls++;
1577
+ if (readyCalls >= this._readyCount) {
1578
+ this._emitReady = EMPTY_FN;
1579
+ this._readyEmitted = true;
1580
+ process.nextTick(() => this.emit(EVENTS.READY));
1581
+ }
1582
+ };
1583
+ this._emitRaw = (...args) => this.emit(EVENTS.RAW, ...args);
1584
+ this._boundRemove = this._remove.bind(this);
1585
+ this.options = opts;
1586
+ this._nodeFsHandler = new NodeFsHandler(this);
1587
+ Object.freeze(opts);
1588
+ }
1589
+ _addIgnoredPath(matcher) {
1590
+ if (isMatcherObject(matcher)) {
1591
+ for (const ignored of this._ignoredPaths) {
1592
+ if (isMatcherObject(ignored) && ignored.path === matcher.path && ignored.recursive === matcher.recursive) {
1593
+ return;
1594
+ }
1595
+ }
1596
+ }
1597
+ this._ignoredPaths.add(matcher);
1598
+ }
1599
+ _removeIgnoredPath(matcher) {
1600
+ this._ignoredPaths.delete(matcher);
1601
+ if (typeof matcher === "string") {
1602
+ for (const ignored of this._ignoredPaths) {
1603
+ if (isMatcherObject(ignored) && ignored.path === matcher) {
1604
+ this._ignoredPaths.delete(ignored);
1605
+ }
1606
+ }
1607
+ }
1608
+ }
1609
+ add(paths_, _origAdd, _internal) {
1610
+ const { cwd } = this.options;
1611
+ this.closed = false;
1612
+ this._closePromise = undefined;
1613
+ let paths = unifyPaths(paths_);
1614
+ if (cwd) {
1615
+ paths = paths.map((path2) => {
1616
+ const absPath = getAbsolutePath(path2, cwd);
1617
+ return absPath;
1618
+ });
1619
+ }
1620
+ paths.forEach((path2) => {
1621
+ this._removeIgnoredPath(path2);
1622
+ });
1623
+ this._userIgnored = undefined;
1624
+ if (!this._readyCount)
1625
+ this._readyCount = 0;
1626
+ this._readyCount += paths.length;
1627
+ Promise.all(paths.map(async (path2) => {
1628
+ const res = await this._nodeFsHandler._addToNodeFs(path2, !_internal, undefined, 0, _origAdd);
1629
+ if (res)
1630
+ this._emitReady();
1631
+ return res;
1632
+ })).then((results) => {
1633
+ if (this.closed)
1634
+ return;
1635
+ results.forEach((item) => {
1636
+ if (item)
1637
+ this.add(sysPath2.dirname(item), sysPath2.basename(_origAdd || item));
1638
+ });
1639
+ });
1640
+ return this;
1641
+ }
1642
+ unwatch(paths_) {
1643
+ if (this.closed)
1644
+ return this;
1645
+ const paths = unifyPaths(paths_);
1646
+ const { cwd } = this.options;
1647
+ paths.forEach((path2) => {
1648
+ if (!sysPath2.isAbsolute(path2) && !this._closers.has(path2)) {
1649
+ if (cwd)
1650
+ path2 = sysPath2.join(cwd, path2);
1651
+ path2 = sysPath2.resolve(path2);
1652
+ }
1653
+ this._closePath(path2);
1654
+ this._addIgnoredPath(path2);
1655
+ if (this._watched.has(path2)) {
1656
+ this._addIgnoredPath({
1657
+ path: path2,
1658
+ recursive: true
1659
+ });
1660
+ }
1661
+ this._userIgnored = undefined;
1662
+ });
1663
+ return this;
1664
+ }
1665
+ close() {
1666
+ if (this._closePromise) {
1667
+ return this._closePromise;
1668
+ }
1669
+ this.closed = true;
1670
+ this.removeAllListeners();
1671
+ const closers = [];
1672
+ this._closers.forEach((closerList) => closerList.forEach((closer) => {
1673
+ const promise = closer();
1674
+ if (promise instanceof Promise)
1675
+ closers.push(promise);
1676
+ }));
1677
+ this._streams.forEach((stream) => stream.destroy());
1678
+ this._userIgnored = undefined;
1679
+ this._readyCount = 0;
1680
+ this._readyEmitted = false;
1681
+ this._watched.forEach((dirent) => dirent.dispose());
1682
+ this._closers.clear();
1683
+ this._watched.clear();
1684
+ this._streams.clear();
1685
+ this._symlinkPaths.clear();
1686
+ this._throttled.clear();
1687
+ this._closePromise = closers.length ? Promise.all(closers).then(() => {
1688
+ return;
1689
+ }) : Promise.resolve();
1690
+ return this._closePromise;
1691
+ }
1692
+ getWatched() {
1693
+ const watchList = {};
1694
+ this._watched.forEach((entry, dir) => {
1695
+ const key = this.options.cwd ? sysPath2.relative(this.options.cwd, dir) : dir;
1696
+ const index = key || ONE_DOT;
1697
+ watchList[index] = entry.getChildren().sort();
1698
+ });
1699
+ return watchList;
1700
+ }
1701
+ emitWithAll(event, args) {
1702
+ this.emit(event, ...args);
1703
+ if (event !== EVENTS.ERROR)
1704
+ this.emit(EVENTS.ALL, event, ...args);
1705
+ }
1706
+ async _emit(event, path2, stats) {
1707
+ if (this.closed)
1708
+ return;
1709
+ const opts = this.options;
1710
+ if (isWindows)
1711
+ path2 = sysPath2.normalize(path2);
1712
+ if (opts.cwd)
1713
+ path2 = sysPath2.relative(opts.cwd, path2);
1714
+ const args = [path2];
1715
+ if (stats != null)
1716
+ args.push(stats);
1717
+ const awf = opts.awaitWriteFinish;
1718
+ let pw;
1719
+ if (awf && (pw = this._pendingWrites.get(path2))) {
1720
+ pw.lastChange = new Date;
1721
+ return this;
1722
+ }
1723
+ if (opts.atomic) {
1724
+ if (event === EVENTS.UNLINK) {
1725
+ this._pendingUnlinks.set(path2, [event, ...args]);
1726
+ setTimeout(() => {
1727
+ this._pendingUnlinks.forEach((entry, path3) => {
1728
+ this.emit(...entry);
1729
+ this.emit(EVENTS.ALL, ...entry);
1730
+ this._pendingUnlinks.delete(path3);
1731
+ });
1732
+ }, typeof opts.atomic === "number" ? opts.atomic : 100);
1733
+ return this;
1734
+ }
1735
+ if (event === EVENTS.ADD && this._pendingUnlinks.has(path2)) {
1736
+ event = EVENTS.CHANGE;
1737
+ this._pendingUnlinks.delete(path2);
1738
+ }
1739
+ }
1740
+ if (awf && (event === EVENTS.ADD || event === EVENTS.CHANGE) && this._readyEmitted) {
1741
+ const awfEmit = (err, stats2) => {
1742
+ if (err) {
1743
+ event = EVENTS.ERROR;
1744
+ args[0] = err;
1745
+ this.emitWithAll(event, args);
1746
+ } else if (stats2) {
1747
+ if (args.length > 1) {
1748
+ args[1] = stats2;
1749
+ } else {
1750
+ args.push(stats2);
1751
+ }
1752
+ this.emitWithAll(event, args);
1753
+ }
1754
+ };
1755
+ this._awaitWriteFinish(path2, awf.stabilityThreshold, event, awfEmit);
1756
+ return this;
1757
+ }
1758
+ if (event === EVENTS.CHANGE) {
1759
+ const isThrottled = !this._throttle(EVENTS.CHANGE, path2, 50);
1760
+ if (isThrottled)
1761
+ return this;
1762
+ }
1763
+ if (opts.alwaysStat && stats === undefined && (event === EVENTS.ADD || event === EVENTS.ADD_DIR || event === EVENTS.CHANGE)) {
1764
+ const fullPath = opts.cwd ? sysPath2.join(opts.cwd, path2) : path2;
1765
+ let stats2;
1766
+ try {
1767
+ stats2 = await stat4(fullPath);
1768
+ } catch (err) {}
1769
+ if (!stats2 || this.closed)
1770
+ return;
1771
+ args.push(stats2);
1772
+ }
1773
+ this.emitWithAll(event, args);
1774
+ return this;
1775
+ }
1776
+ _handleError(error) {
1777
+ const code = error && error.code;
1778
+ if (error && code !== "ENOENT" && code !== "ENOTDIR" && (!this.options.ignorePermissionErrors || code !== "EPERM" && code !== "EACCES")) {
1779
+ this.emit(EVENTS.ERROR, error);
1780
+ }
1781
+ return error || this.closed;
1782
+ }
1783
+ _throttle(actionType, path2, timeout) {
1784
+ if (!this._throttled.has(actionType)) {
1785
+ this._throttled.set(actionType, new Map);
1786
+ }
1787
+ const action = this._throttled.get(actionType);
1788
+ if (!action)
1789
+ throw new Error("invalid throttle");
1790
+ const actionPath = action.get(path2);
1791
+ if (actionPath) {
1792
+ actionPath.count++;
1793
+ return false;
1794
+ }
1795
+ let timeoutObject;
1796
+ const clear = () => {
1797
+ const item = action.get(path2);
1798
+ const count = item ? item.count : 0;
1799
+ action.delete(path2);
1800
+ clearTimeout(timeoutObject);
1801
+ if (item)
1802
+ clearTimeout(item.timeoutObject);
1803
+ return count;
1804
+ };
1805
+ timeoutObject = setTimeout(clear, timeout);
1806
+ const thr = { timeoutObject, clear, count: 0 };
1807
+ action.set(path2, thr);
1808
+ return thr;
1809
+ }
1810
+ _incrReadyCount() {
1811
+ return this._readyCount++;
1812
+ }
1813
+ _awaitWriteFinish(path2, threshold, event, awfEmit) {
1814
+ const awf = this.options.awaitWriteFinish;
1815
+ if (typeof awf !== "object")
1816
+ return;
1817
+ const pollInterval = awf.pollInterval;
1818
+ let timeoutHandler;
1819
+ let fullPath = path2;
1820
+ if (this.options.cwd && !sysPath2.isAbsolute(path2)) {
1821
+ fullPath = sysPath2.join(this.options.cwd, path2);
1822
+ }
1823
+ const now = new Date;
1824
+ const writes = this._pendingWrites;
1825
+ function awaitWriteFinishFn(prevStat) {
1826
+ statcb(fullPath, (err, curStat) => {
1827
+ if (err || !writes.has(path2)) {
1828
+ if (err && err.code !== "ENOENT")
1829
+ awfEmit(err);
1830
+ return;
1831
+ }
1832
+ const now2 = Number(new Date);
1833
+ if (prevStat && curStat.size !== prevStat.size) {
1834
+ writes.get(path2).lastChange = now2;
1835
+ }
1836
+ const pw = writes.get(path2);
1837
+ const df = now2 - pw.lastChange;
1838
+ if (df >= threshold) {
1839
+ writes.delete(path2);
1840
+ awfEmit(undefined, curStat);
1841
+ } else {
1842
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval, curStat);
1843
+ }
1844
+ });
1845
+ }
1846
+ if (!writes.has(path2)) {
1847
+ writes.set(path2, {
1848
+ lastChange: now,
1849
+ cancelWait: () => {
1850
+ writes.delete(path2);
1851
+ clearTimeout(timeoutHandler);
1852
+ return event;
1853
+ }
1854
+ });
1855
+ timeoutHandler = setTimeout(awaitWriteFinishFn, pollInterval);
1856
+ }
1857
+ }
1858
+ _isIgnored(path2, stats) {
1859
+ if (this.options.atomic && DOT_RE.test(path2))
1860
+ return true;
1861
+ if (!this._userIgnored) {
1862
+ const { cwd } = this.options;
1863
+ const ign = this.options.ignored;
1864
+ const ignored = (ign || []).map(normalizeIgnored(cwd));
1865
+ const ignoredPaths = [...this._ignoredPaths];
1866
+ const list = [...ignoredPaths.map(normalizeIgnored(cwd)), ...ignored];
1867
+ this._userIgnored = anymatch(list, undefined);
1868
+ }
1869
+ return this._userIgnored(path2, stats);
1870
+ }
1871
+ _isntIgnored(path2, stat5) {
1872
+ return !this._isIgnored(path2, stat5);
1873
+ }
1874
+ _getWatchHelpers(path2) {
1875
+ return new WatchHelper(path2, this.options.followSymlinks, this);
1876
+ }
1877
+ _getWatchedDir(directory) {
1878
+ const dir = sysPath2.resolve(directory);
1879
+ if (!this._watched.has(dir))
1880
+ this._watched.set(dir, new DirEntry(dir, this._boundRemove));
1881
+ return this._watched.get(dir);
1882
+ }
1883
+ _hasReadPermissions(stats) {
1884
+ if (this.options.ignorePermissionErrors)
1885
+ return true;
1886
+ return Boolean(Number(stats.mode) & 256);
1887
+ }
1888
+ _remove(directory, item, isDirectory) {
1889
+ const path2 = sysPath2.join(directory, item);
1890
+ const fullPath = sysPath2.resolve(path2);
1891
+ isDirectory = isDirectory != null ? isDirectory : this._watched.has(path2) || this._watched.has(fullPath);
1892
+ if (!this._throttle("remove", path2, 100))
1893
+ return;
1894
+ if (!isDirectory && this._watched.size === 1) {
1895
+ this.add(directory, item, true);
1896
+ }
1897
+ const wp = this._getWatchedDir(path2);
1898
+ const nestedDirectoryChildren = wp.getChildren();
1899
+ nestedDirectoryChildren.forEach((nested) => this._remove(path2, nested));
1900
+ const parent = this._getWatchedDir(directory);
1901
+ const wasTracked = parent.has(item);
1902
+ parent.remove(item);
1903
+ if (this._symlinkPaths.has(fullPath)) {
1904
+ this._symlinkPaths.delete(fullPath);
1905
+ }
1906
+ let relPath = path2;
1907
+ if (this.options.cwd)
1908
+ relPath = sysPath2.relative(this.options.cwd, path2);
1909
+ if (this.options.awaitWriteFinish && this._pendingWrites.has(relPath)) {
1910
+ const event = this._pendingWrites.get(relPath).cancelWait();
1911
+ if (event === EVENTS.ADD)
1912
+ return;
1913
+ }
1914
+ this._watched.delete(path2);
1915
+ this._watched.delete(fullPath);
1916
+ const eventName = isDirectory ? EVENTS.UNLINK_DIR : EVENTS.UNLINK;
1917
+ if (wasTracked && !this._isIgnored(path2))
1918
+ this._emit(eventName, path2);
1919
+ this._closePath(path2);
1920
+ }
1921
+ _closePath(path2) {
1922
+ this._closeFile(path2);
1923
+ const dir = sysPath2.dirname(path2);
1924
+ this._getWatchedDir(dir).remove(sysPath2.basename(path2));
1925
+ }
1926
+ _closeFile(path2) {
1927
+ const closers = this._closers.get(path2);
1928
+ if (!closers)
1929
+ return;
1930
+ closers.forEach((closer) => closer());
1931
+ this._closers.delete(path2);
1932
+ }
1933
+ _addPathCloser(path2, closer) {
1934
+ if (!closer)
1935
+ return;
1936
+ let list = this._closers.get(path2);
1937
+ if (!list) {
1938
+ list = [];
1939
+ this._closers.set(path2, list);
1940
+ }
1941
+ list.push(closer);
1942
+ }
1943
+ _readdirp(root, opts) {
1944
+ if (this.closed)
1945
+ return;
1946
+ const options = { type: EVENTS.ALL, alwaysStat: true, lstat: true, ...opts, depth: 0 };
1947
+ let stream = readdirp(root, options);
1948
+ this._streams.add(stream);
1949
+ stream.once(STR_CLOSE, () => {
1950
+ stream = undefined;
1951
+ });
1952
+ stream.once(STR_END, () => {
1953
+ if (stream) {
1954
+ this._streams.delete(stream);
1955
+ stream = undefined;
1956
+ }
1957
+ });
1958
+ return stream;
1959
+ }
1960
+ }
1961
+ function watch(paths, options = {}) {
1962
+ const watcher = new FSWatcher(options);
1963
+ watcher.add(paths);
1964
+ return watcher;
1965
+ }
1966
+ var esm_default = { watch, FSWatcher };
1967
+
1968
+ // .opencode/branch-memory/monitor.ts
1969
+ class BranchMonitor {
1970
+ onBranchChange;
1971
+ config;
1972
+ watcher;
1973
+ pollingInterval;
1974
+ currentBranch;
1975
+ lastPoll;
1976
+ changeCallbacks = [];
1977
+ isMonitoring = false;
1978
+ constructor(onBranchChange, config) {
1979
+ this.onBranchChange = onBranchChange;
1980
+ this.config = config;
1981
+ this.changeCallbacks.push(this.onBranchChange);
1982
+ }
1983
+ async start() {
1984
+ if (this.isMonitoring) {
1985
+ return;
1986
+ }
1987
+ const gitDir = await GitOperations.getGitDir();
1988
+ if (!gitDir) {
1989
+ console.warn("Not in a git repository, branch monitoring disabled");
1990
+ return;
1991
+ }
1992
+ const branch = await GitOperations.getCurrentBranch();
1993
+ this.currentBranch = branch || undefined;
1994
+ if (!this.currentBranch) {
1995
+ console.warn("Not on a git branch, branch monitoring disabled");
1996
+ return;
1997
+ }
1998
+ if (this.config.monitoring.method === "watcher" || this.config.monitoring.method === "both") {
1999
+ this.startWatcher(gitDir);
2000
+ }
2001
+ if (this.config.monitoring.method === "polling" || this.config.monitoring.method === "both") {
2002
+ this.startPolling();
2003
+ }
2004
+ this.isMonitoring = true;
2005
+ console.log(`✓ Branch monitoring started for: ${this.currentBranch}`);
2006
+ console.log(` Method: ${this.config.monitoring.method}`);
2007
+ console.log(` Polling interval: ${this.config.monitoring.pollingInterval}ms`);
2008
+ }
2009
+ startWatcher(gitDir) {
2010
+ try {
2011
+ const headFile = `${gitDir}/HEAD`;
2012
+ this.watcher = esm_default.watch(headFile, {
2013
+ ignoreInitial: true,
2014
+ persistent: true,
2015
+ awaitWriteFinish: { stabilityThreshold: 100 },
2016
+ usePolling: false
2017
+ });
2018
+ this.watcher.on("change", async () => {
2019
+ await this.checkBranchChange();
2020
+ });
2021
+ this.watcher.on("error", (error) => {
2022
+ console.error("Watcher error:", error);
2023
+ if (this.config.monitoring.method === "watcher") {
2024
+ console.info("Watcher failed, falling back to polling");
2025
+ this.stopWatcher();
2026
+ this.startPolling();
2027
+ }
2028
+ });
2029
+ console.log(`✓ File watcher started: ${headFile}`);
2030
+ } catch (error) {
2031
+ console.error("Failed to start watcher:", error);
2032
+ }
2033
+ }
2034
+ startPolling() {
2035
+ const interval = this.config.monitoring.pollingInterval || 1000;
2036
+ this.pollingInterval = setInterval(async () => {
2037
+ await this.checkBranchChange();
2038
+ }, interval);
2039
+ console.log(`✓ Polling started (interval: ${interval}ms)`);
2040
+ }
2041
+ stop() {
2042
+ this.stopWatcher();
2043
+ this.stopPolling();
2044
+ this.isMonitoring = false;
2045
+ console.log("✓ Branch monitoring stopped");
2046
+ }
2047
+ stopWatcher() {
2048
+ if (this.watcher) {
2049
+ this.watcher.close();
2050
+ this.watcher = undefined;
2051
+ }
2052
+ }
2053
+ stopPolling() {
2054
+ if (this.pollingInterval) {
2055
+ clearInterval(this.pollingInterval);
2056
+ this.pollingInterval = undefined;
2057
+ }
2058
+ }
2059
+ async checkBranchChange() {
2060
+ try {
2061
+ const newBranch = await GitOperations.getCurrentBranch();
2062
+ if (newBranch && newBranch !== this.currentBranch) {
2063
+ const oldBranch = this.currentBranch;
2064
+ this.currentBranch = newBranch;
2065
+ console.log(`\uD83D\uDD04 Branch changed: ${oldBranch || "(none)"} → ${newBranch}`);
2066
+ for (const callback of this.changeCallbacks) {
2067
+ try {
2068
+ await callback(oldBranch, newBranch);
2069
+ } catch (error) {
2070
+ console.error("Error in branch change callback:", error);
2071
+ }
2072
+ }
2073
+ }
2074
+ } catch (error) {
2075
+ console.error("Error checking branch:", error);
2076
+ }
2077
+ }
2078
+ onChange(callback) {
2079
+ this.changeCallbacks.push(callback);
2080
+ }
2081
+ offChange(callback) {
2082
+ const index = this.changeCallbacks.indexOf(callback);
2083
+ if (index > -1) {
2084
+ this.changeCallbacks.splice(index, 1);
2085
+ }
2086
+ }
2087
+ getCurrentBranch() {
2088
+ return this.currentBranch;
2089
+ }
2090
+ isActive() {
2091
+ return this.isMonitoring;
2092
+ }
2093
+ async _testTriggerCheck() {
2094
+ await this.checkBranchChange();
2095
+ }
2096
+ }
2097
+ // .opencode/branch-memory/config.ts
2098
+ import * as fs2 from "fs/promises";
2099
+ import * as path2 from "path";
2100
+ import { existsSync as existsSync2 } from "fs";
2101
+ var DEFAULT_CONFIG = {
2102
+ autoSave: {
2103
+ enabled: true,
2104
+ onMessageChange: true,
2105
+ onBranchChange: true,
2106
+ onToolExecute: true,
2107
+ throttleMs: 5000,
2108
+ periodicIntervalMs: 60000
2109
+ },
2110
+ contextLoading: "auto",
2111
+ context: {
2112
+ defaultInclude: ["messages", "todos", "files"],
2113
+ maxMessages: 50,
2114
+ maxTodos: 20,
2115
+ compression: false
2116
+ },
2117
+ storage: {
2118
+ maxBackups: 5,
2119
+ retentionDays: 90
2120
+ },
2121
+ monitoring: {
2122
+ method: "both",
2123
+ pollingInterval: 1000
2124
+ }
2125
+ };
2126
+
2127
+ class ConfigManager {
2128
+ configPath;
2129
+ projectPath;
2130
+ constructor(projectPath) {
2131
+ this.projectPath = projectPath;
2132
+ this.configPath = path2.join(projectPath, ".opencode", "config", "branch-memory.json");
2133
+ }
2134
+ async load() {
2135
+ if (existsSync2(this.configPath)) {
2136
+ try {
2137
+ const content = await fs2.readFile(this.configPath, "utf8");
2138
+ const userConfig = JSON.parse(content);
2139
+ return this.deepMerge(DEFAULT_CONFIG, userConfig);
2140
+ } catch (error) {
2141
+ console.warn("Failed to load config, using defaults:", error instanceof Error ? error.message : error);
2142
+ return { ...DEFAULT_CONFIG };
2143
+ }
2144
+ }
2145
+ return { ...DEFAULT_CONFIG };
2146
+ }
2147
+ getDefault() {
2148
+ return JSON.parse(JSON.stringify(DEFAULT_CONFIG));
2149
+ }
2150
+ getStorageDir() {
2151
+ return path2.join(this.projectPath, ".opencode", "branch-memory");
2152
+ }
2153
+ async save(config) {
2154
+ const configDir = path2.dirname(this.configPath);
2155
+ try {
2156
+ await fs2.mkdir(configDir, { recursive: true });
2157
+ await fs2.writeFile(this.configPath, JSON.stringify(config, null, 2), "utf8");
2158
+ } catch (error) {
2159
+ console.error("Failed to save configuration:", error);
2160
+ throw error;
2161
+ }
2162
+ }
2163
+ deepMerge(target, source) {
2164
+ const result = { ...target };
2165
+ for (const key in source) {
2166
+ const sourceValue = source[key];
2167
+ const targetValue = result[key];
2168
+ if (sourceValue !== undefined) {
2169
+ if (typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(sourceValue) && typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
2170
+ result[key] = this.deepMerge(targetValue, sourceValue);
2171
+ } else {
2172
+ result[key] = sourceValue;
2173
+ }
2174
+ }
2175
+ }
2176
+ return result;
2177
+ }
2178
+ }
2179
+ // .opencode/plugin/branch-memory-plugin.ts
2180
+ var BranchMemoryPlugin = async ({ project, client, $, directory, worktree }) => {
2181
+ console.log("\uD83E\uDDE0 Branch Memory Plugin initializing...");
2182
+ const configManager = new ConfigManager(directory);
2183
+ const isGitRepo = await GitOperations.isGitRepo();
2184
+ if (!isGitRepo) {
2185
+ console.log("⚠️ Not in a git repository, branch memory disabled");
2186
+ return {};
2187
+ }
2188
+ const config = await configManager.load();
2189
+ const storage = new ContextStorage(configManager.getStorageDir(), config);
2190
+ const collector = new ContextCollector(config, client);
2191
+ let lastAutoSave = 0;
2192
+ const autoSave = async (reason) => {
2193
+ const currentConfig = await configManager.load();
2194
+ if (currentConfig.autoSave.enabled) {
2195
+ const now = Date.now();
2196
+ if (now - lastAutoSave > currentConfig.autoSave.throttleMs) {
2197
+ try {
2198
+ const currentBranch = await GitOperations.getCurrentBranch();
2199
+ if (currentBranch) {
2200
+ const context = await collector.collectContext(currentConfig.context.defaultInclude.includes("messages"), currentConfig.context.defaultInclude.includes("todos"), currentConfig.context.defaultInclude.includes("files"), reason);
2201
+ await storage.saveContext(currentBranch, context);
2202
+ lastAutoSave = now;
2203
+ console.log(`\uD83D\uDCBE Auto-saved context for branch '${currentBranch}' (${reason})`);
2204
+ }
2205
+ } catch (error) {
2206
+ console.error("Auto-save failed:", error);
2207
+ }
2208
+ }
2209
+ }
2210
+ };
2211
+ const branchMonitor = new BranchMonitor(async (oldBranch, newBranch) => {
2212
+ console.log(`\uD83D\uDD04 Branch changed: ${oldBranch || "(none)"} → ${newBranch}`);
2213
+ const currentConfig = await configManager.load();
2214
+ if (oldBranch && currentConfig.autoSave.onBranchChange) {
2215
+ const context = await collector.collectContext(currentConfig.context.defaultInclude.includes("messages"), currentConfig.context.defaultInclude.includes("todos"), currentConfig.context.defaultInclude.includes("files"), "branch change");
2216
+ await storage.saveContext(oldBranch, context);
2217
+ console.log(`\uD83D\uDCBE Saved context for old branch '${oldBranch}'`);
2218
+ }
2219
+ if (currentConfig.contextLoading === "auto") {
2220
+ const branchContext = await storage.loadContext(newBranch);
2221
+ if (branchContext) {
2222
+ console.log(`\uD83D\uDCE5 Found context for branch '${newBranch}'`);
2223
+ console.log(" Use @branch-memory_load to restore it");
2224
+ } else {
2225
+ console.log(`ℹ️ No saved context for branch '${newBranch}'`);
2226
+ }
2227
+ } else if (currentConfig.contextLoading === "ask") {
2228
+ console.log(`ℹ️ Context available for branch '${newBranch}'`);
2229
+ console.log(` Use @branch-memory_load to restore it`);
2230
+ }
2231
+ }, config);
2232
+ await branchMonitor.start();
2233
+ return {
2234
+ "session.created": async (input, output) => {
2235
+ console.log("\uD83D\uDE80 Session created - checking for saved context...");
2236
+ const currentConfig = await configManager.load();
2237
+ const branch = await GitOperations.getCurrentBranch();
2238
+ if (branch && currentConfig.contextLoading === "auto") {
2239
+ const branchContext = await storage.loadContext(branch);
2240
+ if (branchContext) {
2241
+ console.log(`\uD83D\uDCE5 Found context for branch '${branch}'`);
2242
+ console.log(" Use @branch-memory_load to restore it");
2243
+ }
2244
+ }
2245
+ },
2246
+ "tool.execute.before": async (input, output) => {
2247
+ await autoSave("tool execution");
2248
+ },
2249
+ "session.updated": async (input, output) => {
2250
+ const currentConfig = await configManager.load();
2251
+ if (currentConfig.autoSave.enabled) {
2252
+ const now = Date.now();
2253
+ if (now - lastAutoSave > currentConfig.autoSave.periodicIntervalMs) {
2254
+ await autoSave("session update");
2255
+ }
2256
+ }
2257
+ },
2258
+ unload: () => {
2259
+ console.log("\uD83E\uDDE0 Branch Memory Plugin shutting down...");
2260
+ branchMonitor.stop();
2261
+ autoSave("plugin unload").catch((error) => {
2262
+ console.error("Final save failed:", error);
2263
+ });
2264
+ console.log("✅ Plugin stopped");
2265
+ }
2266
+ };
2267
+ };
2268
+ export {
2269
+ BranchMemoryPlugin
2270
+ };