dsh-permission-rules 0.6.6 → 0.6.8

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,21 @@
1
+ ## v0.6.8 - 2026-09-03
2
+
3
+ ### Fixed
4
+
5
+ - Never recursively watch a workspace root for an expected-but-absent rule
6
+ file (issue #13): candidate watchers are depth-limited to the immediate
7
+ children of the deepest existing ancestor, ignore `node_modules`/`.git`,
8
+ upgrade the watch when the missing path component (e.g. `.dsh/`)
9
+ appears, and close when their watched directory disappears. A missing
10
+ `.dsh/rules.yaml` used to watch the entire workspace tree — 245k inotify
11
+ watches, multi-GiB RSS, and stalled tool calls on large workspaces.
12
+
13
+ ## v0.6.7 - 2026-09-02
14
+
15
+ ### Docs
16
+
17
+ - Sync the five-language READMEs to the 0.1.2-alpha.5 facts; no behavior change.
18
+
1
19
  # Changelog
2
20
 
3
21
  All notable changes to dsh-permission-rules are recorded here, newest first.
package/README.es.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  | Surface | Status |
27
27
  |---|---|
28
- | Harness | DeepSeek Harness `0.1.1-rc.2` 0.1.2-alpha.5 (adaptado el 2026-09-02): el sobre de sesión conserva su campo ignorable solo para compatibilidad de lectura de logs almacenados - Session.append aún no puede estamparlo, por lo que el comportamiento de la puerta no cambia. |
28
+ | Harness | DeepSeek Harness `0.1.2-alpha.5` (adaptado el 2026-09-02): el sobre de sesión conserva su campo ignorable solo para compatibilidad de lectura de logs almacenados - Session.append aún no puede estamparlo, por lo que el comportamiento de la puerta no cambia. |
29
29
  | Node | `^22.19.0 || >=24.0.0` |
30
30
  | Platforms | Todas (host + cliente web de settings) |
31
31
  | Model | Cualquiera (las razones deny/ask se muestran a través de los resultados de herramienta) |
package/README.hi.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  | Surface | Status |
27
27
  |---|---|
28
- | Harness | DeepSeek Harness `0.1.1-rc.2` 0.1.2-alpha.5 (2026-09-02 को अनुकूलित): सत्र लिफ़ाफ़ा अपना ignorable फ़ील्ड केवल संग्रहीत-लॉग पठन संगतता के लिए रखता है - Session.append अभी भी इसे स्टैम्प नहीं कर सकता, इसलिए गेट व्यवहार अपरिवर्तित है। |
28
+ | Harness | DeepSeek Harness `0.1.2-alpha.5` (2026-09-02 को अनुकूलित): सत्र लिफ़ाफ़ा अपना ignorable फ़ील्ड केवल संग्रहीत-लॉग पठन संगतता के लिए रखता है - Session.append अभी भी इसे स्टैम्प नहीं कर सकता, इसलिए गेट व्यवहार अपरिवर्तित है। |
29
29
  | Node | `^22.19.0 || >=24.0.0` |
30
30
  | Platforms | सभी (host + वेब settings क्लाइंट) |
31
31
  | Model | कोई भी (deny/ask कारण टूल परिणामों के माध्यम से दिखते हैं) |
package/README.md CHANGED
@@ -26,7 +26,7 @@
26
26
 
27
27
  | Surface | Status |
28
28
  |---|---|
29
- | Harness | DeepSeek Harness `0.1.1-rc.2` 0.1.2-alpha.5 (adapted 2026-09-02): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. |
29
+ | Harness | DeepSeek Harness `0.1.2-alpha.5` (adapted 2026-09-02): the session envelope keeps its ignorable field for stored-log read compatibility only - Session.append still cannot stamp it, so audit-gate behavior is unchanged. |
30
30
  | Node | `^22.19.0 || >=24.0.0` |
31
31
  | Platforms | All (host + web settings client) |
32
32
  | Model | Any (deny/ask reasons surface through tool results) |
package/README.pt.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  | Surface | Status |
27
27
  |---|---|
28
- | Harness | DeepSeek Harness `0.1.1-rc.2` 0.1.2-alpha.5 (adaptado em 2026-09-02): o envelope de sessão mantém seu campo ignorable apenas para compatibilidade de leitura de logs armazenados - o Session.append ainda não consegue estampá-lo, então o comportamento da porta não muda. |
28
+ | Harness | DeepSeek Harness `0.1.2-alpha.5` (adaptado em 2026-09-02): o envelope de sessão mantém seu campo ignorable apenas para compatibilidade de leitura de logs armazenados - o Session.append ainda não consegue estampá-lo, então o comportamento da porta não muda. |
29
29
  | Node | `^22.19.0 || >=24.0.0` |
30
30
  | Platforms | Todas (host + cliente web de settings) |
31
31
  | Model | Qualquer (razões deny/ask aparecem pelos resultados de ferramenta) |
package/README.zh.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  | Surface | Status |
27
27
  |---|---|
28
- | Harness | DeepSeek Harness `0.1.1-rc.2` 0.1.2-alpha.5(2026-09-02 已适配):会话信封保留 ignorable 字段但仅用于存量日志读取兼容——Session.append 仍无法盖章,门控行为不变。 |
28
+ | Harness | DeepSeek Harness `0.1.2-alpha.5`(2026-09-02 已适配):会话信封保留 ignorable 字段但仅用于存量日志读取兼容——Session.append 仍无法盖章,门控行为不变。 |
29
29
  | Node | `^22.19.0 || >=24.0.0` |
30
30
  | Platforms | 全部(host + Web 设置客户端) |
31
31
  | Model | 任意(deny/ask 原因经工具结果呈现) |
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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-permission-rules",
3
- "version": "0.6.6",
3
+ "version": "0.6.8",
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",
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
  })