@xth26/dsh-plan-build-mode 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,146 +1,255 @@
1
- # dsh-plan-build-mode
2
-
3
- OpenCode-style Plan / Build hard permission model for DeepSeek Harness.
4
-
5
- This plugin adds an independent Plan/Build mode switch to DSH:
6
-
7
- - **Plan mode** enforces a `read-only` sandbox and blocks mutating tools (`write`, `edit`).
8
- - **Build mode** restores `workspace-write` and allows edits.
9
- - DSH's built-in `/plan` soft-guidance mode is left untouched.
10
-
11
- The tool denial only takes effect when Plan mode is configured as `read-only`. When the session has not been switched, Build mode is active by default.
12
-
13
- ## Version compatibility
14
-
15
- | Plugin version | DSH version | Notes |
16
- |---|---|---|
17
- | `0.4.x` | `>= 0.1.5-rc.1` | Uses `session.snapshotEvents()`; DSH 0.1.5 removed the `session.events` snapshot getter. |
18
- | `<= 0.3.1` | `0.1.0-rc.x` | Legacy releases; broken on DSH 0.1.5+ because of the API change above. |
19
-
20
- ## Installation
21
-
22
- DSH plugins are loaded through a DSH profile. Install the plugin into the profile(s) you use:
23
-
24
- ```bash
25
- # For the web profile
26
- dsh plugin --profile web add @xth26/dsh-plan-build-mode
27
-
28
- # For the TUI profile
29
- dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode
30
- ```
31
-
32
- The plugin ships with a `cordis.patch.yml` that auto-injects the required row
33
- when the package is loaded by a DSH profile.
34
-
35
- ## Using the same Plan/Build mode in both web and TUI
36
-
37
- Plan/Build mode state is stored in the session event log (`sandbox/mode` event). Both `dsh web` and `dsh --profile <name>` use the same session store when they share the same profile and session ID.
38
-
39
- - **Same profile**: if you run `dsh web --profile web` and `dsh --profile web`, the mode is shared because the session events are shared.
40
- - **Different profiles**: by default `web` and `dsh-tui` are separate profiles with separate session directories. Switching in one does **not** affect the other.
41
-
42
- To make web and TUI share the same mode and session history, use the **same profile** for both:
43
-
44
- ```bash
45
- # Use the web profile for both interfaces
46
- dsh web --profile web
47
- dsh --profile web
48
- ```
49
-
50
- Or, if you prefer to keep separate profiles but want to share only the session store, override the session root in each profile's `cordis.patch.yml` to point to the same directory:
51
-
52
- ```yaml
53
- - id: session-root
54
- config:
55
- root: /path/to/shared/sessions
56
- ```
57
-
58
- Note: sharing session directories across profiles requires both profiles to mount compatible service bundles; otherwise event interpretation may differ.
59
-
60
- ## Local development / link
61
-
62
- To try the plugin from a local checkout without publishing:
63
-
64
- ```bash
65
- # From the plugin checkout
66
- cd /path/to/dsh-plan-build-mode
67
- pnpm link --global
68
-
69
- # From your DSH profile directory
70
- pnpm link --global @xth26/dsh-plan-build-mode
71
- ```
72
-
73
- Then start DSH with that profile. The `dsh.bundle.patch` field in the plugin's
74
- `package.json` makes the patch apply automatically.
75
-
76
- ## Updating
77
-
78
- For `0.x` versions, the semver range `^0.1.0` only matches `0.1.x` and will **never** auto-update to `0.2.0`. Always bump the declaration explicitly and restart the DSH process:
79
-
80
- ```bash
81
- # Bump the installed version in the target profile
82
- # (run this outside a sandboxed agent session; the profile directory is not writable from DSH)
83
- dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@0.4.0
84
-
85
- # Restart the DSH process that uses that profile
86
- # (old Node process still holds the previous plugin code in memory)
87
- Stop-Process -Name dsh -Force # PowerShell
88
- # or: taskkill /IM dsh.exe /F # CMD
89
- ```
90
-
91
- Then start `dsh web` or `dsh tui` again. To always pull the latest published version:
92
-
93
- ```bash
94
- dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@latest
95
- ```
96
-
97
- ## Usage
98
-
99
- | Command | Effect |
100
- |---|---|
101
- | `/plan-build` | Toggle between Plan and Build modes |
102
- | `/plan-build status` | Show the current mode |
103
- | `/plan-build switch plan` | Enter Plan mode (read-only) |
104
- | `/plan-build switch build` | Enter Build mode (writable) |
105
- | `/plan-build switch` | Same as `/plan-build`; toggles between modes |
106
-
107
- ## Configuration
108
-
109
- ```yaml
110
- - id: plan-build-mode
111
- name: '@xth26/dsh-plan-build-mode'
112
- config:
113
- planSandbox: read-only
114
- buildSandbox: workspace-write
115
- denyWriteTools: true
116
- section: true
117
- ```
118
-
119
- `planSandbox` and `buildSandbox` must differ.
120
-
121
- ## Plan mode: command-line Python for inspection
122
-
123
- While Plan mode is read-only and blocks `write`/`edit` tools, it is **encouraged** to run short, read-only command-line Python snippets to inspect data and validate assumptions. For example:
124
-
125
- ```bash
126
- python -B -c "import json, sys; data = json.load(open('data/sample.json')); print(len(data))"
127
- python -B -c "import pandas as pd; print(pd.read_csv('data.csv').describe())"
128
- pytest -p no:cacheprovider -q tests/test_sanity.py
129
- ```
130
-
131
- Use `-B` (or set `PYTHONDONTWRITEBYTECODE=1`) and `pytest -p no:cacheprovider` to avoid writing `__pycache__` or `.pytest_cache`. The read-only sandbox is the final guardrail; any command that tries to write files will still be blocked at the sandbox layer.
132
-
133
- ## Local integration check
134
-
135
- After installing/linking the plugin in a DSH profile:
136
-
137
- 1. Start DSH with that profile.
138
- 2. Run `/plan-build` to enter Plan mode (read-only). You should see a confirmation.
139
- 3. Ask the agent to call the `write` tool. It should be denied with a reason.
140
- 4. Run `/plan-build` again to enter Build mode (writable). You should see a confirmation.
141
- 5. Ask the agent to call `write` again. It should now be allowed.
142
- 6. Run `/plan-build status` to check the current mode without switching.
143
-
144
- ## License
145
-
146
- MIT
1
+ # dsh-plan-build-mode
2
+
3
+ OpenCode-style Plan / Build hard permission model for DeepSeek Harness.
4
+
5
+ This plugin adds an independent Plan/Build mode switch to DSH and gates tool
6
+ access with an OpenCode-style permission engine (`allow | ask | deny`):
7
+
8
+ - **Plan mode** is read-only *by policy*: every write-category tool is denied
9
+ except writes under the configured plans directory (`.opencode/plans/`), so
10
+ you can persist plan files while the agent stays read-only everywhere else.
11
+ - **Build mode** is writable: the approved plan can be executed.
12
+ - **Reads are unrestricted in both modes**, including files outside the
13
+ workspace matching DSH, where reads pass through every sandbox mode. Only
14
+ *modifications* are gated (OpenCode `external_directory`).
15
+ - DSH's built-in `/plan` soft-guidance mode is left untouched.
16
+
17
+ The shipped `cordis.patch.yml` configures both sandboxes as `workspace-write`;
18
+ "read-only" in Plan mode is enforced by the permission gate, not by the
19
+ sandbox. This is what makes plan files persist under `.opencode/plans/` while
20
+ everything else stays read-only.
21
+
22
+ ## Version compatibility
23
+
24
+ | Plugin version | DSH version | Notes |
25
+ |---|---|---|
26
+ | `0.5.x` | `>= 0.1.5-rc.1` | Adds the OpenCode permission gate and `/permission`; mode is carried by the `plan-build/mode` session event (legacy sessions are inferred from `sandbox/mode`). |
27
+ | `0.4.x` | `>= 0.1.5-rc.1` | Uses `session.snapshotEvents()`; DSH 0.1.5 removed the `session.events` snapshot getter. |
28
+ | `<= 0.3.1` | `0.1.0-rc.x` | Legacy releases; broken on DSH 0.1.5+ because of the API change above. |
29
+
30
+ ## Installation
31
+
32
+ DSH plugins are loaded through a DSH profile. Install the plugin into the profile(s) you use:
33
+
34
+ ```bash
35
+ # For the web profile
36
+ dsh plugin --profile web add @xth26/dsh-plan-build-mode
37
+
38
+ # For the TUI profile
39
+ dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode
40
+ ```
41
+
42
+ The plugin ships with a `cordis.patch.yml` that auto-injects the required row
43
+ when the package is loaded by a DSH profile.
44
+
45
+ ## Using the same Plan/Build mode in both web and TUI
46
+
47
+ Plan/Build mode state is stored in the session event log (the plugin-owned
48
+ `plan-build/mode` event, plus `sandbox/mode` for sandbox enforcement). Both
49
+ `dsh web` and `dsh --profile <name>` use the same session store when they share
50
+ the same profile and session ID.
51
+
52
+ - **Same profile**: if you run `dsh web --profile web` and `dsh --profile web`, the mode is shared because the session events are shared.
53
+ - **Different profiles**: by default `web` and `dsh-tui` are separate profiles with separate session directories. Switching in one does **not** affect the other.
54
+
55
+ To make web and TUI share the same mode and session history, use the **same profile** for both:
56
+
57
+ ```bash
58
+ # Use the web profile for both interfaces
59
+ dsh web --profile web
60
+ dsh --profile web
61
+ ```
62
+
63
+ Or, if you prefer to keep separate profiles but want to share only the session store, override the session root in each profile's `cordis.patch.yml` to point to the same directory:
64
+
65
+ ```yaml
66
+ - id: session-root
67
+ config:
68
+ root: /path/to/shared/sessions
69
+ ```
70
+
71
+ Note: sharing session directories across profiles requires both profiles to mount compatible service bundles; otherwise event interpretation may differ.
72
+
73
+ ## Local development / link
74
+
75
+ To try the plugin from a local checkout without publishing:
76
+
77
+ ```bash
78
+ # From the plugin checkout
79
+ cd /path/to/dsh-plan-build-mode
80
+ pnpm link --global
81
+
82
+ # From your DSH profile directory
83
+ pnpm link --global @xth26/dsh-plan-build-mode
84
+ ```
85
+
86
+ Then start DSH with that profile. The `dsh.bundle.patch` field in the plugin's
87
+ `package.json` makes the patch apply automatically.
88
+
89
+ ## Updating
90
+
91
+ For `0.x` versions, the semver range `^0.1.0` only matches `0.1.x` and will **never** auto-update to `0.2.0`. Always bump the declaration explicitly and restart the DSH process:
92
+
93
+ ```bash
94
+ # Bump the installed version in the target profile
95
+ # (run this outside a sandboxed agent session; the profile directory is not writable from DSH)
96
+ dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@0.5.0
97
+
98
+ # Restart the DSH process that uses that profile
99
+ # (old Node process still holds the previous plugin code in memory)
100
+ Stop-Process -Name dsh -Force # PowerShell
101
+ # or: taskkill /IM dsh.exe /F # CMD
102
+ ```
103
+
104
+ Then start `dsh web` or `dsh tui` again. To always pull the latest published version:
105
+
106
+ ```bash
107
+ dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@latest
108
+ ```
109
+
110
+ ### Upgrade notes (0.4.x → 0.5.x)
111
+
112
+ - `planSandbox` and `buildSandbox` **may now be equal** (the shipped patch uses
113
+ `workspace-write` for both); the permission gate enforces the read-only
114
+ policy in Plan mode. The old "must differ" requirement is gone.
115
+ - Mode state moves to the plugin-owned `plan-build/mode` event. Sessions that
116
+ only have `sandbox/mode` events are inferred on load and pinned to a real
117
+ `plan-build/mode` event at session start, so no history is lost.
118
+ - The legacy `denyWriteTools` option still works: setting it to `false` lets
119
+ the agent write in Plan mode (equivalent to overriding `modes.plan.edit =
120
+ allow`).
121
+
122
+ ## Usage
123
+
124
+ | Command | Effect |
125
+ |---|---|
126
+ | `/plan-build` | Toggle between Plan and Build modes |
127
+ | `/plan-build status` | Show the current mode |
128
+ | `/plan-build switch plan` | Enter Plan mode (read-only by policy) |
129
+ | `/plan-build switch build` | Enter Build mode (writable) |
130
+ | `/plan-build switch` | Same as `/plan-build`; toggles between modes |
131
+ | `/permission status` | Show the external-path default, rules, and the session allowlist |
132
+ | `/permission allow <pattern>` | Add a pattern to this session's allowlist (OpenCode "always") |
133
+ | `/permission deny <pattern>` | Remove a pattern from this session's allowlist |
134
+
135
+ ## Configuration
136
+
137
+ ```yaml
138
+ - id: plan-build-mode
139
+ name: '@xth26/dsh-plan-build-mode'
140
+ config:
141
+ planSandbox: workspace-write
142
+ buildSandbox: workspace-write
143
+ denyWriteTools: true
144
+ section: true
145
+ plansDirectory: .opencode/plans
146
+ externalDirectory:
147
+ default: allow # allow | ask | deny — reads outside the workspace
148
+ ```
149
+
150
+ `planSandbox` and `buildSandbox` may be equal or differ. When they differ, mode
151
+ switches also move the sandbox (`read-only` → `workspace-write` etc.). When
152
+ they are equal (shipped default), the permission gate is what enforces the
153
+ Plan-mode policy.
154
+
155
+ ### Permission model
156
+
157
+ Each tool maps to a category:
158
+
159
+ | Category | Tools |
160
+ |---|---|
161
+ | `read` | `read`, `read_image`, `pdf_read`, `docx_read`, `pptx_read`, `xlsx_read` |
162
+ | `edit` | `write`, `edit`, `pdf_create`, `docx_create`, `pptx_create`, `xlsx_write`, `xlsx_edit` |
163
+ | `glob` | `glob` |
164
+ | `grep` | `grep` |
165
+ | `bash` | `bash` |
166
+ | `pwsh` | `pwsh` |
167
+ | `webfetch` | `web_fetch` |
168
+ | `websearch` | `web_search` |
169
+
170
+ A category value is either a scalar (`allow | ask | deny`) or an object of
171
+ ordered rules, like OpenCode:
172
+
173
+ ```yaml
174
+ permission:
175
+ read:
176
+ '*': allow
177
+ '**/.env': deny
178
+ '**/.env.*': deny
179
+ '**/.env.example': allow # last matching rule wins
180
+ bash: ask
181
+ edit: deny
182
+ ```
183
+
184
+ Patterns support OpenCode wildcards (`*` matches any characters including `/`,
185
+ `?` matches exactly one character, `**` behaves like `*`) and `~`/`$HOME`
186
+ expansion. Rules are evaluated last-match-wins. The default rules mirror
187
+ OpenCode: most categories `allow`; `.env*` reads are denied (`.env.example`
188
+ allowed back).
189
+
190
+ Plan mode overrides (unless overridden by `modes.plan` or `denyWriteTools:
191
+ false`): `edit: deny`, `bash: ask`, `pwsh: ask` — mirroring OpenCode's plan
192
+ agent defaults. `modes.plan` / `modes.build` override the global permission for
193
+ that mode, like OpenCode's agent-specific overrides.
194
+
195
+ ```yaml
196
+ modes:
197
+ plan:
198
+ bash: deny # instead of the default ask
199
+ build:
200
+ read: allow
201
+ ```
202
+
203
+ ### Plans carve-out
204
+
205
+ In Plan mode, `edit`-category writes under `plansDirectory` (default
206
+ `.opencode/plans/`, relative to the session workspace) are always allowed, so
207
+ `/plan-build` and plan files keep working. Every other write in Plan mode is
208
+ denied with a reason that includes `forbidden in Plan Mode`.
209
+
210
+ ### External paths
211
+
212
+ - **Reads** outside the workspace are allowed by default in both modes
213
+ (`externalDirectory.default: allow`). Set it to `ask` for full OpenCode
214
+ behavior: the agent asks for approval on external reads.
215
+ - **Writes** outside the workspace are governed by the sandbox. Under
216
+ `workspace-write` they cannot be permitted, so instead of prompting (which
217
+ would just fail at the sandbox), the plugin denies with an escalation hint —
218
+ retry the same tool with `sandbox_permissions` + `justification`.
219
+ - `/permission allow <pattern>` adds a session-scoped allowlist entry
220
+ (OpenCode "always"), persisted in memory for the session's lifetime.
221
+
222
+ ## Plan mode: command-line Python for inspection
223
+
224
+ While Plan mode denies `write`/`edit` tools, it is **encouraged** to run short,
225
+ read-only command-line Python snippets to inspect data and validate
226
+ assumptions. For example:
227
+
228
+ ```bash
229
+ python -B -c "import json, sys; data = json.load(open('data/sample.json')); print(len(data))"
230
+ python -B -c "import pandas as pd; print(pd.read_csv('data.csv').describe())"
231
+ pytest -p no:cacheprovider -q tests/test_sanity.py
232
+ ```
233
+
234
+ Use `-B` (or set `PYTHONDONTWRITEBYTECODE=1`) and `pytest -p no:cacheprovider`
235
+ to avoid writing `__pycache__` or `.pytest_cache`. The permission gate and the
236
+ sandbox are the final guardrails; anything that tries to write files is blocked
237
+ at the gate (Plan mode) or the sandbox layer (Build mode).
238
+
239
+ ## Local integration check
240
+
241
+ After installing/linking the plugin in a DSH profile:
242
+
243
+ 1. Start DSH with that profile.
244
+ 2. Run `/plan-build` to enter Plan mode (read-only by policy). You should see a confirmation.
245
+ 3. Ask the agent to call the `write` tool on `src/x.ts`. It should be denied with a reason containing `forbidden in Plan Mode`.
246
+ 4. Ask the agent to call `write` on `.opencode/plans/x.md`. It should be **allowed** (plans carve-out).
247
+ 5. Run `/plan-build` again to enter Build mode (writable). You should see a confirmation.
248
+ 6. Ask the agent to call `write` on `src/x.ts`. It should now be allowed.
249
+ 7. Ask the agent to read a file outside the workspace. It should be allowed without prompting (default `external_directory: allow`).
250
+ 8. Run `/permission status` to inspect the gate; try `/permission allow ~/projects/**` to add a session allowlist entry.
251
+ 9. Run `/plan-build status` to check the current mode without switching.
252
+
253
+ ## License
254
+
255
+ MIT