dsh-permission-rules 0.6.7 → 0.6.9

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/CHANGELOG.md CHANGED
@@ -1,3 +1,22 @@
1
+ ## v0.6.9 - 2026-09-03
2
+
3
+ ### Changed
4
+
5
+ - Vendor the `dsh-auto-review 0.10.2` integration tarball (its runtime dependencies now pin the published dsh `0.1.2-alpha.5` line; `dsh-agent-spine-demo` moved to devDependencies) and update the `file:` devDependency.
6
+ - Dev pins `@deepseek-ai/cordis-plugin-loader ^1.0.3` / `@deepseek-ai/cordis-plugin-include ^1.0.7` aligned with the `cordis 4.0.2` peer ranges.
7
+
8
+ ## v0.6.8 - 2026-09-03
9
+
10
+ ### Fixed
11
+
12
+ - Never recursively watch a workspace root for an expected-but-absent rule
13
+ file (issue #13): candidate watchers are depth-limited to the immediate
14
+ children of the deepest existing ancestor, ignore `node_modules`/`.git`,
15
+ upgrade the watch when the missing path component (e.g. `.dsh/`)
16
+ appears, and close when their watched directory disappears. A missing
17
+ `.dsh/rules.yaml` used to watch the entire workspace tree — 245k inotify
18
+ watches, multi-GiB RSS, and stalled tool calls on large workspaces.
19
+
1
20
  ## v0.6.7 - 2026-09-02
2
21
 
3
22
  ### Docs
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createHash } from "node:crypto";
3
3
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
- import { dirname, isAbsolute, join, resolve } from "node:path";
4
+ import { dirname, isAbsolute, join, resolve, sep } from "node:path";
5
5
  import chokidar from "chokidar";
6
6
  import z from "@deepseek-ai/schemastery";
7
7
  import { parse } from "yaml";
@@ -3513,10 +3513,22 @@ var PermissionRulesRuntime = class {
3513
3513
  * relevant event re-checks existence and only triggers a reload once the
3514
3514
  * candidate actually appeared, so unrelated workspace activity while the
3515
3515
  * file is absent costs a stat, not a reload.
3516
+ *
3517
+ * Candidate watchers never recurse past the immediate children of that
3518
+ * ancestor: when the walk-up landed on the workspace root (the
3519
+ * candidate's parent chain is missing, e.g. no `.dsh/` directory),
3520
+ * recursion would watch the entire workspace tree — issue #13 measured
3521
+ * 245k inotify watches and multi-GiB RSS from exactly this path. The
3522
+ * watcher therefore runs with `depth: 0` plus `node_modules`/`.git`
3523
+ * ignores in that case, and the `addDir` handler upgrades the watch to
3524
+ * the newly appeared ancestor level, preserving mid-session adoption.
3525
+ * A candidate watcher whose directory itself disappears closes instead
3526
+ * of lingering on a dead path.
3516
3527
  */
3517
3528
  attachCandidateWatch(cwd, candidate) {
3518
3529
  if (!this.config.watch) return;
3519
- const dir = this.deepestExistingDir(dirname(candidate));
3530
+ const parent = dirname(candidate);
3531
+ const dir = this.deepestExistingDir(parent);
3520
3532
  const existing = this.watchers.get(dir);
3521
3533
  if (existing !== void 0) {
3522
3534
  existing.cwds.add(cwd);
@@ -3525,16 +3537,38 @@ var PermissionRulesRuntime = class {
3525
3537
  }
3526
3538
  const cwds = /* @__PURE__ */ new Set([cwd]);
3527
3539
  const candidates = /* @__PURE__ */ new Map([[cwd, candidate]]);
3528
- const watcher = chokidar.watch(dir, {
3540
+ const watchOptions = {
3529
3541
  persistent: true,
3530
- ignoreInitial: true
3542
+ ignoreInitial: true,
3543
+ ignored: (path) => path.split(sep).includes("node_modules") || path.split(sep).includes(".git")
3544
+ };
3545
+ const watcher = dir === parent ? chokidar.watch(dir, watchOptions) : chokidar.watch(dir, {
3546
+ ...watchOptions,
3547
+ depth: 0
3531
3548
  });
3532
- const onEvent = () => this.scheduleReload(dir);
3533
- watcher.on("add", onEvent);
3534
- watcher.on("addDir", onEvent);
3535
- watcher.on("change", onEvent);
3536
- watcher.on("unlink", onEvent);
3537
- watcher.on("unlinkDir", onEvent);
3549
+ const samePath = (a, b) => process.platform === "win32" ? a.toLowerCase() === b.toLowerCase() : a === b;
3550
+ const startsWithPath = (a, b) => process.platform === "win32" ? a.toLowerCase().startsWith(b.toLowerCase()) : a.startsWith(b);
3551
+ const onEvent = (event, path) => {
3552
+ if (event === "unlinkDir" && samePath(path, dir)) {
3553
+ this.watchers.delete(dir);
3554
+ const timer = this.timers.get(dir);
3555
+ if (timer !== void 0) {
3556
+ clearTimeout(timer);
3557
+ this.timers.delete(dir);
3558
+ }
3559
+ watcher.close().catch((error) => {
3560
+ this.ctx.logger.warn(`permission-rules: failed to close watcher on ${dir}: ${String(error)}`);
3561
+ });
3562
+ return;
3563
+ }
3564
+ if (dir !== parent && event === "addDir" && (samePath(path, parent) || startsWithPath(path, parent + sep))) this.attachCandidateWatch(cwd, candidate);
3565
+ this.scheduleReload(dir);
3566
+ };
3567
+ watcher.on("add", (path) => onEvent("add", path));
3568
+ watcher.on("addDir", (path) => onEvent("addDir", path));
3569
+ watcher.on("change", (path) => onEvent("change", path));
3570
+ watcher.on("unlink", (path) => onEvent("unlink", path));
3571
+ watcher.on("unlinkDir", (path) => onEvent("unlinkDir", path));
3538
3572
  watcher.on("error", (error) => {
3539
3573
  this.ctx.logger.warn(`permission-rules: watcher error on ${dir}: ${String(error)}`);
3540
3574
  });
@@ -370,6 +370,17 @@ export declare class PermissionRulesRuntime {
370
370
  * relevant event re-checks existence and only triggers a reload once the
371
371
  * candidate actually appeared, so unrelated workspace activity while the
372
372
  * file is absent costs a stat, not a reload.
373
+ *
374
+ * Candidate watchers never recurse past the immediate children of that
375
+ * ancestor: when the walk-up landed on the workspace root (the
376
+ * candidate's parent chain is missing, e.g. no `.dsh/` directory),
377
+ * recursion would watch the entire workspace tree — issue #13 measured
378
+ * 245k inotify watches and multi-GiB RSS from exactly this path. The
379
+ * watcher therefore runs with `depth: 0` plus `node_modules`/`.git`
380
+ * ignores in that case, and the `addDir` handler upgrades the watch to
381
+ * the newly appeared ancestor level, preserving mid-session adoption.
382
+ * A candidate watcher whose directory itself disappears closes instead
383
+ * of lingering on a dead path.
373
384
  */
374
385
  private attachCandidateWatch;
375
386
  /** The deepest existing ancestor directory of `dir` (candidate watchers target directories, never missing files). */
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,KAAK,EAAE,iBAAiB,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AACjF,OAAO,KAAK,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAG5E,OAAO,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGzD,OAAO,KAAK,EAAgC,eAAe,EAA+B,OAAO,EAAE,MAAM,YAAY,CAAA;AAIrH,OAAO,KAAK,EAAiC,WAAW,EAAE,MAAM,cAAc,CAAA;AAQ9E,OAAO,KAAK,EAAE,kBAAkB,EAAoB,MAAM,YAAY,CAAA;AAItE,OAAO,KAAK,EAAiD,eAAe,EAAE,MAAM,aAAa,CAAA;AAEjG,eAAO,MAAM,IAAI,qBAAqB,CAAA;AAEtC,kDAAkD;AAClD,eAAO,MAAM,MAAM,UAAwB,CAAA;AAE3C,iDAAiD;AACjD,UAAU,WAAW;IACnB,wDAAwD;IACxD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,wFAAwF;IACxF,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;IAClC,iGAAiG;IACjG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,IAAI,CAAA;CACvB;AAuBD;;;GAGG;AACH,qBAAa,sBAAsB;IA6B/B,OAAO,CAAC,QAAQ,CAAC,GAAG;IA5BtB,0FAA0F;IAC1F,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiC;IAEvD,uHAAuH;IACvH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA6D;IAE1F,6GAA6G;IAC7G,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAE3D,0CAA0C;IAC1C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmD;IAE1E,iIAAiI;IACjI,OAAO,CAAC,YAAY,CAAqD;IAEzE,mGAAmG;IACnG,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAE5D,iEAAiE;IACjE,OAAO,CAAC,YAAY,CAA0B;IAE9C,6EAA6E;IAC7E,OAAO,CAAC,UAAU,CAA0B;IAE5C,6GAA6G;IAC7G,OAAO,CAAC,YAAY,CAAsB;gBAGvB,GAAG,EAAE,OAAO,EAC7B,MAAM,EAAE,cAAc;IAKxB,gFAAgF;IAChF,IAAI,MAAM,IAAI,cAAc,CAE3B;IAED,+FAA+F;IAC/F,eAAe,CAAC,MAAM,EAAE,MAAM,cAAc,GAAG,IAAI;IAInD,sDAAsD;IACtD,OAAO,CAAC,cAAc;IAUtB;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE;IAMrC,iEAAiE;IACjE,OAAO,CAAC,aAAa;IAIrB,iGAAiG;IACjG,OAAO,CAAC,kBAAkB;IAkB1B,8DAA8D;IAC9D,OAAO,CAAC,eAAe;IASvB;;;;;;;OAOG;IACH,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW;IAiB9B;;;;;;;;OAQG;IACH,OAAO,CAAC,iBAAiB;IAezB,oIAAoI;IACpI,OAAO,CAAC,iBAAiB;IASzB;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ;IAKhB;;;;;;;;;OASG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW;IA0BlC;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAoBzB;;;;;;OAMG;IACH,OAAO,CAAC,YAAY;IAIpB;;;;;;;;;;;;;;;;;OAiBG;IACG,UAAU,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IAmCrG;;;;;OAKG;IACH,OAAO,CAAC,eAAe;IAQvB;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,KAAK,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,EAAE,OAAO,GAAG,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,UAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAgCvJ,gHAAgH;IAChH,OAAO,CAAC,gBAAgB;IAWxB,wGAAwG;IACxG,OAAO,CAAC,WAAW;IAMnB;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;KAAE;IAS7F;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IASzB,sEAAsE;IACtE,OAAO,CAAC,WAAW;IAQnB,iFAAiF;IACjF,OAAO,CAAC,SAAS;IAIjB,gDAAgD;IAChD,WAAW,CAAC,IAAI,EAAE,aAAa,GAAG,IAAI;IAItC,mGAAmG;IACnG,OAAO,CAAC,gBAAgB;IAkBxB;;;;;OAKG;IACH,OAAO,CAAC,iBAAiB;IAmCzB;;;;;;;;OAQG;IACG,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC;IAiCzC;;;;;OAKG;IACG,sBAAsB,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7C,0EAA0E;IAC1E,eAAe,IAAI;QACjB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;QACzB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAA;QAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,WAAW,CAAA;QAC7C,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,SAAS,CAAA;QACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;QAC1B,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;QAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;QAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,kBAAkB,EAAE,CAAA;KAC/C;IAkBD;;;;;;;OAOG;IACH,gBAAgB,IAAI,SAAS,MAAM,EAAE;IAgBrC,0EAA0E;IAC1E,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAY3F,0GAA0G;IAC1G,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAO7C,8EAA8E;IAC9E,SAAS,IAAI,IAAI;IAIjB;;;;;;OAMG;IACH,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE;IA+B5F,sHAAsH;IACtH,OAAO,CAAC,WAAW;IASnB,0FAA0F;IAC1F,OAAO,CAAC,qBAAqB;IAM7B;;;;;;;;;OASG;IACH,OAAO,CAAC,UAAU,EAAE,iBAAiB,GAAG,aAAa;IA4DrD;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,WAAW;IA0DnB,mHAAmH;IACnH,OAAO,CAAC,aAAa;IAMrB,gFAAgF;IAChF,OAAO,CAAC,gBAAgB;IAiBxB,8FAA8F;IAC9F,OAAO,CAAC,cAAc;IAmBtB;;;;;;OAMG;IACH,uBAAuB,IAAI,IAAI;IAmB/B,0EAA0E;IAC1E,OAAO,CAAC,iBAAiB;IAUzB,+EAA+E;IAC/E,OAAO,CAAC,WAAW;IAmCnB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,cAAc;IAetB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAOxB;;;;;;;;;;;;;;;;;;OAkBG;IACH,OAAO,CAAC,oBAAoB;IAwE5B,qHAAqH;IACrH,OAAO,CAAC,kBAAkB;IAU1B,8FAA8F;IAC9F,OAAO,CAAC,aAAa;IAarB;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAYnB,4DAA4D;IAC5D,kBAAkB,IAAI,MAAM;IAI5B,2EAA2E;IAC3E,mBAAmB,IAAI,MAAM;IAI7B,sEAAsE;IACtE,kBAAkB,IAAI,MAAM;IAI5B;;;;;OAKG;IACH,OAAO,CAAC,cAAc;CAevB;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BvE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAK9D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-permission-rules",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "description": "Declarative Claude Code-style permission rules plus a Codex-style process-level network policy for DeepSeek Harness: ordered allow/deny/ask rules with tool-name, argument (glob/regex), workspace-path, and network-target (domain/ip/port/scheme) matching on the tools/pre-execute waterfall, a built-in local HTTP/CONNECT proxy governing shell subprocess traffic, three network modes mapped onto the official sandbox presets, full session-log audit, HMR rule reload, and a settings page with a rule editor, block counters, and recent interception lists.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -157,8 +157,8 @@
157
157
  },
158
158
  "devDependencies": {"@deepseek-ai/dsh-attachment": "0.1.2-alpha.5",
159
159
  "@deepseek-ai/cordis": "^4.0.2",
160
- "@deepseek-ai/cordis-plugin-include": "1.0.6",
161
- "@deepseek-ai/cordis-plugin-loader": "1.0.2",
160
+ "@deepseek-ai/cordis-plugin-include": "^1.0.7",
161
+ "@deepseek-ai/cordis-plugin-loader": "^1.0.3",
162
162
  "@deepseek-ai/dsh-agent": "0.1.2-alpha.5",
163
163
  "@deepseek-ai/dsh-brand": "0.1.2-alpha.5",
164
164
  "@deepseek-ai/dsh-client-connection": "0.1.2-alpha.5",
@@ -179,7 +179,7 @@
179
179
  "@eslint/js": "^10.0.1",
180
180
  "@types/node": "^22.19.0",
181
181
  "@vitest/coverage-v8": "^3.2.0",
182
- "dsh-auto-review": "file:vendor/dsh-auto-review-0.10.0.tgz",
182
+ "dsh-auto-review": "file:vendor/dsh-auto-review-0.10.2.tgz",
183
183
  "eslint": "^10.8.1",
184
184
  "globals": "^17.11.0",
185
185
  "react": "18.3.1",
package/src/runtime.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  import { createHash } from 'node:crypto'
15
15
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
16
16
  import { createRequire } from 'node:module'
17
- import { dirname, isAbsolute, join, resolve } from 'node:path'
17
+ import { dirname, isAbsolute, join, resolve, sep } from 'node:path'
18
18
  import chokidar from 'chokidar'
19
19
  import type { Context } from '@deepseek-ai/cordis'
20
20
  import type { CommandInvocation, CommandResult } from '@deepseek-ai/dsh-commands'
@@ -1094,10 +1094,22 @@ export class PermissionRulesRuntime {
1094
1094
  * relevant event re-checks existence and only triggers a reload once the
1095
1095
  * candidate actually appeared, so unrelated workspace activity while the
1096
1096
  * file is absent costs a stat, not a reload.
1097
+ *
1098
+ * Candidate watchers never recurse past the immediate children of that
1099
+ * ancestor: when the walk-up landed on the workspace root (the
1100
+ * candidate's parent chain is missing, e.g. no `.dsh/` directory),
1101
+ * recursion would watch the entire workspace tree — issue #13 measured
1102
+ * 245k inotify watches and multi-GiB RSS from exactly this path. The
1103
+ * watcher therefore runs with `depth: 0` plus `node_modules`/`.git`
1104
+ * ignores in that case, and the `addDir` handler upgrades the watch to
1105
+ * the newly appeared ancestor level, preserving mid-session adoption.
1106
+ * A candidate watcher whose directory itself disappears closes instead
1107
+ * of lingering on a dead path.
1097
1108
  */
1098
1109
  private attachCandidateWatch(cwd: string, candidate: string): void {
1099
1110
  if (!this.config.watch) return
1100
- const dir = this.deepestExistingDir(dirname(candidate))
1111
+ const parent = dirname(candidate)
1112
+ const dir = this.deepestExistingDir(parent)
1101
1113
  const existing = this.watchers.get(dir)
1102
1114
  if (existing !== undefined) {
1103
1115
  existing.cwds.add(cwd)
@@ -1106,13 +1118,43 @@ export class PermissionRulesRuntime {
1106
1118
  }
1107
1119
  const cwds = new Set([cwd])
1108
1120
  const candidates = new Map([[cwd, candidate]])
1109
- const watcher = chokidar.watch(dir, { persistent: true, ignoreInitial: true })
1110
- const onEvent = (): void => this.scheduleReload(dir)
1111
- watcher.on('add', onEvent)
1112
- watcher.on('addDir', onEvent)
1113
- watcher.on('change', onEvent)
1114
- watcher.on('unlink', onEvent)
1115
- watcher.on('unlinkDir', onEvent)
1121
+ const watchOptions = {
1122
+ persistent: true,
1123
+ ignoreInitial: true,
1124
+ ignored: (path: string): boolean => path.split(sep).includes('node_modules') || path.split(sep).includes('.git'),
1125
+ }
1126
+ const watcher = dir === parent
1127
+ ? chokidar.watch(dir, watchOptions)
1128
+ : chokidar.watch(dir, { ...watchOptions, depth: 0 })
1129
+ // Windows paths may reach the watcher with a different ASCII case than
1130
+ // the header-derived candidate paths (the session layer case-folds the
1131
+ // cache key); compare case-insensitively there or the upgrade/close
1132
+ // handlers silently never fire.
1133
+ const samePath = (a: string, b: string): boolean => (process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b)
1134
+ const startsWithPath = (a: string, b: string): boolean => (process.platform === 'win32' ? a.toLowerCase().startsWith(b.toLowerCase()) : a.startsWith(b))
1135
+ const onEvent = (event: string, path: string): void => {
1136
+ if (event === 'unlinkDir' && samePath(path, dir)) {
1137
+ this.watchers.delete(dir)
1138
+ const timer = this.timers.get(dir)
1139
+ if (timer !== undefined) {
1140
+ clearTimeout(timer)
1141
+ this.timers.delete(dir)
1142
+ }
1143
+ void watcher.close().catch((error: unknown) => {
1144
+ this.ctx.logger.warn(`permission-rules: failed to close watcher on ${dir}: ${String(error)}`)
1145
+ })
1146
+ return
1147
+ }
1148
+ if (dir !== parent && event === 'addDir' && (samePath(path, parent) || startsWithPath(path, parent + sep))) {
1149
+ this.attachCandidateWatch(cwd, candidate)
1150
+ }
1151
+ this.scheduleReload(dir)
1152
+ }
1153
+ watcher.on('add', (path: string) => onEvent('add', path))
1154
+ watcher.on('addDir', (path: string) => onEvent('addDir', path))
1155
+ watcher.on('change', (path: string) => onEvent('change', path))
1156
+ watcher.on('unlink', (path: string) => onEvent('unlink', path))
1157
+ watcher.on('unlinkDir', (path: string) => onEvent('unlinkDir', path))
1116
1158
  watcher.on('error', (error: unknown) => {
1117
1159
  this.ctx.logger.warn(`permission-rules: watcher error on ${dir}: ${String(error)}`)
1118
1160
  })