dsh-rewind-plugin 0.2.6 → 0.2.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/README.md CHANGED
@@ -1,44 +1,26 @@
1
1
  # dsh-rewind
2
2
 
3
- [简体中文](README.zh.md)
4
-
5
- In-place conversation rewind for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): the Claude Code `/rewind` semantics inside the **same session window** — cut the model context back to an earlier user message, and optionally restore workspace files from **disk-persisted before-backups**.
6
-
7
- > **Status:** published to npm (`dsh-rewind-plugin`, v0.2.6) via GitHub Actions Trusted Publishing + Sigstore provenance. Targets the web profile (`dsh --profile web`). Interaction mirrors Claude Code's rewind, adapted to dsh's real web UI.
3
+ In-place conversation rewind for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness): Claude Code's `/rewind` semantics inside the **same session window** — cut the model context back to an earlier user message, and optionally restore workspace files from disk-persisted before-backups.
8
4
 
9
5
  [![npm version](https://img.shields.io/npm/v/dsh-rewind-plugin.svg)](https://www.npmjs.com/package/dsh-rewind-plugin)
10
6
  [![npm license](https://img.shields.io/npm/l/dsh-rewind-plugin.svg)](https://github.com/SiriLee/dsh-rewind/blob/main/LICENSE)
11
7
 
12
- ## Table of contents
13
-
14
- - [✨ Features](#-features)
15
- - [📸 Screenshots](#-screenshots)
16
- - [How it works](#how-it-works)
17
- - [📦 Install](#-install)
18
- - [Usage](#usage)
19
- - [Behavior details & limitations](#behavior-details--limitations)
20
- - [Not included](#not-included)
21
- - [Compatibility](#compatibility)
22
- - [Development](#development)
23
- - [Publishing](#publishing)
24
- - [Directory layout](#directory-layout)
25
- - [License](#license)
26
-
27
- ## Features
28
-
29
- | Feature | Description |
30
- | --- | --- |
31
- | In-place rewind | Rewind to **any** user message from a per-message ↶ button: the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript — no new session, no window switch |
32
- | Time-travel semantics | Rewinding to a message withdraws **that message too**; its text is offered back in the composer so you can edit and re-send it |
33
- | Claude-Code-style file restore | Write-class edits are backed up **before** they happen and persisted on disk; "conversation and code" restores files to their pre-edit content and deletes files created after the target |
34
- | Impact preview | "Conversation and code" first shows the exact restore / delete list for confirmation (the option is hidden when there are no tracked changes — like Claude Code's code-restore visibility) |
35
- | Approval-plugin coexistence | Capture runs at the `tools/execute` around-dispatch stage, so another plugin's pre-execute approval short-circuit (e.g. dsh-edit-approval) cannot skip the backup, and a denied call never records |
36
- | Paths resolved by session cwd | Relative paths are backed up and restored against the **real** file using the fs-tools session-cwd rule; the resolved display path is what gets recorded |
37
- | Restores write real files | Restore goes through plain `node:fs` directly to the filesystem; symbolic and hard links are skipped with a warning (no clobber through a shared inode) |
38
- | Survives host restart | Backups live on disk under `~/.dsh/rewind-snapshots/<session>/<anchor seq>/`, newest 100 message groups per session |
39
- | Localized | `zh` / `en` copy registered into the dsh locale system |
40
-
41
- ## 📸 Screenshots
8
+ > English | [中文](README.zh.md)
9
+
10
+ A deliberately focused plugin with one job: rewind to any earlier user message, in place.
11
+
12
+ | Mode | Conversation | Workspace files |
13
+ | --- | --- | --- |
14
+ | **Rewind conversation only** | Cut back to the target message | Untouched |
15
+ | **Rewind conversation and code** | Cut back to the target message | Restored to their state before it (modified files written back, later-created files deleted) |
16
+
17
+ Rewinding is time-travel: the target message and everything after it (agent replies, tool calls) are withdrawn from the model context *and* the rendered transcript — no new session, no window switch — and the target's text is offered back in the composer so you can edit and re-send it.
18
+
19
+ The plugin never rewrites the append-only session log and never touches your git repository.
20
+
21
+ ## Preview
22
+
23
+ Each user message gains a compact **↶ rewind** action in its action row. Clicking it opens a mode-selection popover; "conversation and code" first shows the exact restore / delete list for confirmation (the option is hidden when there are no tracked changes — like Claude Code's code-restore visibility).
42
24
 
43
25
  <table>
44
26
  <tr>
@@ -51,131 +33,78 @@ In-place conversation rewind for [DeepSeek Harness](https://github.com/deepseek-
51
33
  </tr>
52
34
  </table>
53
35
 
54
- ## How it works
55
-
56
- Two halves work together: the **conversation rewind** (in-place, same window) and the **checkpoint file restore** (Claude-Code-style before-backups).
36
+ Manual `/rewind` input in the composer is intercepted — submitting shows a transient hint pointing at the ↶ button.
57
37
 
58
- ### 1. Conversation rewind
59
-
60
- The plugin appends an **empty-content marker** `assistant/message` into the session log whose `surfaceOp: { op: 'replace', start, end }` replaces every surface node after the target message with the marker:
61
-
62
- - The marker carries `sourceEventSeqs` covering every shadowed node, and the `Session.append` surface rules validate the cut (only a contiguous range on the current surface).
63
- - Because the marker is **empty**, the harness derives it to `null` — it never enters the model context and never renders as conversation content. The agent and the user both see the conversation exactly as it was at the target.
64
- - The marker's **turn number reuses the LAST STARTED turn** (`markerTurnOf`), never `lastTurn + 1`: the harness's agent loop numbers its next real turn exactly `last turn/start + 1`, so a marker numbered the same way would leave an `assistant/message` BEFORE the `turn/start` of the same turn — the client conversation-context builder rejects that ordering with `conversation Context …:turn-tail… received an update before its start Match`, history load fails, and the whole conversation disappears from the UI (the real defect in ≤ 0.2.4, fixed in 0.2.5). Reusing an already-consumed turn makes the marker a harmless trailing update on the previous completed turn's tail — it can never collide with a future turn.
65
- - The append-only log is **untouched** — the audit trail keeps every withdrawn event; only the model-visible surface is cut, so the next request derives its context from the target onward.
66
-
67
- A running turn (LLM thinking / streaming) is force-stopped first (`cancel({ kind: 'user' })`) and the rewind waits for quiescence; if it can't stop, the rewind is aborted with an error.
68
-
69
- ### 2. Checkpoint file restore
70
-
71
- The plugin tracks the write-class tools — `write`, `edit`, `str_replace_editor` (mutating commands `create` / `str_replace` / `insert`):
72
-
73
- 1. **Before-capture** at `tools/execute` (the around-dispatch stage): the target file is read and the resolved path + content are held in a pending map. This stage only runs after any pre-execute approval gate let the call through — so an `ask` short-circuit (dsh-edit-approval) **cannot skip** the backup, and a denied call never records.
74
- 2. **Disk commit** at `tools/post-execute`: the before-backup is written under the turn's **anchor message seq** (`~/.dsh/rewind-snapshots/<session>/<anchor seq>/<callId>.json`).
75
- 3. **Restore** (`/rewind @<seq> both`): every backup anchored at or after the target applies — modified files are written back to their **earliest** captured before-state, files created after the target are deleted, symbolic / hard links are skipped. Writes go through plain `node:fs`, independent of the fs service.
76
- 4. A failed tool body that **throws** skips `tools/post-execute`; a `tools/result` safety net clears the pending capture so nothing leaks in memory.
77
-
78
- Backups persist across host restarts, bounded to the newest 100 anchor groups per session.
79
-
80
- ## 🔧 Troubleshooting: history load failure (`…turn-tail… received an update before its start Match`)
81
-
82
- Versions ≤ 0.2.4 corrupted client replay when a rewind was **followed by further
83
- conversation**: the marker's turn number collided with the next real turn's
84
- `turn/start`, so reopening the session showed
85
- `Failed to load history: conversation Context …:turn-tail… received an update before its start Match (internal)`
86
- and the history vanished. Rewinds created from 0.2.5 on no longer produce the
87
- collision, but **already-corrupted sessions need an offline repair** (the log is
88
- append-only — it cannot be rewritten in memory).
89
-
90
- The repair tool ships **inside the npm package** (`dsh-rewind-repair`) — no
91
- source checkout needed:
38
+ ## Install
92
39
 
93
40
  ```sh
94
- # 1. Fully quit dsh web / host first (while a session is resident in memory,
95
- # a disk repair is overwritten by the next checkpoint)
96
- # 2. Run the offline repair (scans every session under ~/.dsh/sessions,
97
- # rewriting each marker's turn back to the last started turn)
98
- npm exec --yes --package=dsh-rewind-plugin -- dsh-rewind-repair
99
- npm exec --yes --package=dsh-rewind-plugin -- dsh-rewind-repair -- --dry-run # preview only
100
- # 3. Restart dsh web — the repaired sessions load their history again
41
+ dsh plugin --profile web add dsh-rewind-plugin
101
42
  ```
102
43
 
103
- Or install it globally once (`npm i -g dsh-rewind-plugin`) and run
104
- `dsh-rewind-repair` directly; from a source checkout the same tool is
105
- `node scripts/repair-markers.mjs` (identical flags).
44
+ Restart `dsh web` (`--profile web`) after installing.
106
45
 
107
- The tool only rewrites the `data.turn` of `dsh-rewind` empty-marker events
108
- (keeping seqs, order, and the zstd frame structure intact), backs up the original
109
- file to `session.jsonl.zstd.bak-<timestamp>` before writing, and never touches
110
- any other event — safe to run repeatedly.
46
+ > ⚠️ The npm name `dsh-rewind` belongs to another author's package — install with `dsh-rewind-plugin`.
111
47
 
112
- ## 📦 Install
48
+ From a local checkout or a pinned GitHub commit:
113
49
 
114
- Published to npm — the registry path is the recommended one. **Restart dsh web (`--profile web`) after installing.**
50
+ ```sh
51
+ dsh plugin --profile web add /path/to/dsh-rewind # local checkout
52
+ dsh plugin --profile web add github:SiriLee/dsh-rewind#<sha> # pinned commit
53
+ ```
115
54
 
116
- > ⚠️ Note: the npm name `dsh-rewind` belongs to another author's packageinstall with `dsh-rewind-plugin`.
55
+ A git install fails on first run: pnpm blocks git dependencies from running build scripts. Follow the CLI hint to add an `allowBuilds` key to the profile's `pnpm-workspace.yaml`, then retry pnpm runs the plugin's `prepare` (full build) and installs it.
117
56
 
118
- ### Option A: registry (recommended)
57
+ ## Usage
119
58
 
120
- ```sh
121
- dsh plugin --profile web add dsh-rewind-plugin
122
- ```
59
+ 1. **Hover** any user message you sent — a **↶ rewind** button appears in its action row.
60
+ 2. **Click it.** The target is that message; a small popover offers the two modes ("conversation and code" is hidden when no tracked file changes exist after the target).
61
+ 3. The rewind executes as an in-session command; a result message confirms, and the withdrawn message's text is filled back into the composer for editing and re-sending.
123
62
 
124
- ### Option B: local checkout (authors / contributors)
63
+ The button appears on user messages rendered in the **current session view** — switch to another session before rewinding it. A rewind can itself be rewound (its marker enters the log), but the file-restore action is not re-backed up.
125
64
 
126
- ```sh
127
- cd dsh-rewind
128
- npm install # devDeps come from the npm registry; no harness checkout needed
129
- npm run build # full build: lib/ (host ESM + client bundle + .d.ts)
130
- dsh plugin --profile web add /path/to/dsh-rewind # link install
131
- ```
65
+ ## How it works
132
66
 
133
- ### Option C: GitHub (pin a commit for reproducibility)
67
+ ### 1. Conversation rewind (in-place)
134
68
 
135
- ```sh
136
- dsh plugin --profile web add github:SiriLee/dsh-rewind#<commit-sha>
137
- ```
69
+ The plugin appends an **empty-content marker** `assistant/message` into the session log whose `surfaceOp: { op: 'replace', start, end }` replaces every surface node after the target message with the marker:
138
70
 
139
- First run fails: pnpm blocks git dependencies from running build scripts. Follow
140
- the CLI hint to add an `allowBuilds` key to the profile's `pnpm-workspace.yaml`
141
- (e.g. `$DSH_HOME/profiles/web/pnpm-workspace.yaml`), then retry. pnpm then runs
142
- the plugin's `prepare` (full build) and installs it into the profile.
71
+ - The marker carries `sourceEventSeqs` covering every shadowed node, and `Session.append`'s surface rules validate the cut (a contiguous range on the current surface).
72
+ - Because the marker is **empty**, the harness derives it to `null` it never enters the model context and never renders as conversation content. Agent and user both see the conversation exactly as it was at the target.
73
+ - The marker's **turn number reuses the last started turn** (`markerTurnOf`), never `lastTurn + 1`: the harness numbers its next real turn exactly `last turn/start + 1`, so a `maxTurn + 1` marker would sit *before* that `turn/start` — the client conversation builder rejects the ordering (`…turn-tail… received an update before its start Match`), history load fails, and the whole conversation disappears from the UI (the real defect in ≤ 0.2.4, fixed in 0.2.5). Reusing an already-consumed turn makes the marker a harmless trailing update on the previous completed turn's tail — it can never collide with a future turn.
74
+ - The append-only log is **untouched** — every withdrawn event stays in the audit trail; only the model-visible surface is cut, so the next request derives its context from the target onward.
143
75
 
144
- ## Usage
76
+ A running turn (LLM thinking / streaming) is force-stopped first (`cancel({ kind: 'user' })`) and the rewind waits for quiescence; if it can't stop, the rewind is aborted with an error.
145
77
 
146
- ### Rewind via the per-message button
78
+ ### 2. Checkpoint file restore
147
79
 
148
- 1. **Hover** any user message you sent a **↶ rewind** button appears in its action row.
149
- 2. **Click it.** The target is that message (step one is done). A small popover opens (step two):
150
- - **Rewind conversation only** — cut the model context back to before the message; workspace files stay untouched.
151
- - **Rewind conversation and code** — same context cut, plus workspace files restored to their state before the message. An impact list (files to restore / delete) is shown first, then you confirm.
152
- - The "conversation and code" option is **hidden** when no tracked file changes exist after the target (matching Claude Code's behavior).
153
- 3. The rewind executes as an in-session command; a result message confirms (e.g. "已撤回 seq N 及之后内容;还原 M 个文件"), and the withdrawn message's text is filled back into the composer for editing and re-sending.
80
+ The plugin tracks the write-class tools`write`, `edit`, `str_replace_editor` (mutating commands `create` / `str_replace` / `insert`):
154
81
 
155
- ### Rewinds are time-travel
82
+ 1. **Before-capture** at `tools/execute` (the around-dispatch stage): the target file is read; the resolved path + content are held in a pending map. This stage runs only after any pre-execute approval gate let the call through — an `ask` short-circuit (dsh-edit-approval) **cannot skip** the backup, and a denied call never records. If the read fails (e.g. a permission error), the change is simply not backed up — the plugin warns in the log but **does not block the write**.
83
+ 2. **Disk commit** at `tools/post-execute`: the before-backup is written under the turn's anchor message seq (`~/.dsh/rewind-snapshots/<session>/<anchor seq>/<callId>.json`).
84
+ 3. **Restore** (`/rewind @<seq> both`): every backup anchored at or after the target applies — modified files are written back to their **earliest** captured before-state, files created after the target are deleted, symbolic / hard links are skipped (they share an inode with another name; restoring through one would clobber both). Writes go through plain `node:fs`, independent of the fs service — under sandbox / remote backends, path resolution may be restricted.
85
+ 4. A tool body that **throws** skips `tools/post-execute`; a `tools/result` safety net clears the pending capture so nothing leaks in memory.
156
86
 
157
- Rewinding to a message **withdraws** it and everything after it — the transcript and the agent's context both return to before the message. The command result says so, and the message's text is offered back in the composer.
87
+ Backups persist across host restarts, bounded to the newest 100 anchor groups per session.
158
88
 
159
- ### Manual `/rewind` is blocked
89
+ ## What it deliberately does NOT do
160
90
 
161
- `/rewind` exists only as the button's internal channel. Typing `/rewind` (bare or with arguments) into the composer is **intercepted** submitting shows a transient hint pointing at the button.
91
+ - **Whole-tree / git-first snapshots** — only write-class tool edits are backed up. `bash`, other tools, and external edits are not tracked and cannot be restored: the same limitation as Claude Code, which defers such rollbacks to the user's git.
92
+ - **Subagent edits** — not tracked (same as Claude Code): a subagent runs its own session, so its backups could never be restored by a rewind of the parent session.
93
+ - **Fork / branch rewind and `/compact`** — the harness already provides these ("branch in new chat", compact).
94
+ - **Keyboard shortcuts** (esc+esc rewind menu) — planned as a follow-up.
162
95
 
163
- ## Behavior details & limitations
96
+ ## Comparison with similar projects
164
97
 
165
- - Only **write-class tools** running while the plugin is active are tracked (`write` / `edit` / `str_replace_editor`). Changes made by `bash`, other tools, or external programs are not backed up and cannot be restored the same limitation as Claude Code, which also defers such rollbacks to the user's git.
166
- - If a before-capture read fails (e.g. a permission error), that change is simply not backed up and a `both` rewind cannot restore it — the plugin logs a warning but **does not block the write**.
167
- - File restore/delete writes through the **real local filesystem**; under sandbox / remote backends path resolution may be restricted.
168
- - Symbolic links and hard links are not written through (they share the inode with another name; a restore would clobber both) — they are skipped and reported.
169
- - A rewind can itself be rewound (its marker enters the log), but the file-restore action is not re-backed up.
170
- - The ↶ button is injected on user messages rendered in the **current session view**; switch to another session before rewinding it.
171
- - When no tracked file changes exist after the target, the mode popover offers only "conversation only" (Claude Code hides code-restore options the same way).
98
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) also has [Anionex/dsh-turn-rewind](https://github.com/Anionex/dsh-turn-rewind) a rewind plugin with the same user-facing idea (a per-message action that rolls the conversation back and restores workspace files), but a different approach:
172
99
 
173
- ## Not included
100
+ | Dimension | dsh-rewind (this plugin) | Anionex dsh-turn-rewind |
101
+ | --- | --- | --- |
102
+ | Conversation rollback | **In-place, same session/window** — the model-visible surface is cut back to the target; the append-only log is untouched | **Forks a new Session** at the previous `turn/end`; the original session is always retained |
103
+ | File-restore engine | **Lightweight before-backups** of write-class tools only, restored via plain `node:fs` | **Change Ledger** — a durable restore-point engine with Git fences, an approval gate, rescue points and crash reconciliation |
104
+ | Tracked-change scope | Only write-class tool edits (like Claude Code) | Any Git-managed file (Git worktree required) |
105
+ | Public service API | No — a focused single-purpose plugin | Yes — `ctx.changeLedger` service + `/turn-rewind` HTTP endpoint |
174
106
 
175
- - Keyboard shortcuts (esc+esc to open the rewind menu)planned as a follow-up.
176
- - `/compact` — provided by the harness.
177
- - Fork / branch rewind — the harness's built-in "branch in new chat".
178
- - Whole-tree / git-first snapshots covering bash and external edits — **deliberately not implemented**, in line with Claude Code's native rewind (which also defers such rollbacks to the user's git).
107
+ The essential difference: dsh-turn-rewind keeps the log immutable and therefore forks a new session; this plugin cuts the model-visible surface in place with an empty marker, so the original conversation continues in the same window the non-trivial part (see [Known issues](#known-issues)) that dsh-turn-rewind sidesteps.
179
108
 
180
109
  ## Compatibility
181
110
 
@@ -186,6 +115,16 @@ Rewinding to a message **withdraws** it and everything after it — the transcri
186
115
  > This project and DeepSeek Harness are both in developer preview. Pin exact
187
116
  > versions in reproducible environments and review the behavior notes above.
188
117
 
118
+ ## Known issues
119
+
120
+ Rewinds created with versions `≤ v0.2.4` could corrupt client replay when followed by more conversation (a marker turn collides with the next `turn/start`). The offline repair tool ships **inside the npm package** (`dsh-rewind-repair`). This only affects sessions you already had before upgrading — a fresh install never hits it.
121
+
122
+ Full instructions: [docs/troubleshooting.md](docs/troubleshooting.md)
123
+
124
+ ## Security
125
+
126
+ This plugin only appends rewind-marker events to the session log; it never deletes or rewrites logged history. File writes happen only when you choose "conversation and code" — before-backups and restores stay under `~/.dsh/rewind-snapshots/`. It never touches your git repository, makes no network requests, and accesses no credentials.
127
+
189
128
  ## Development
190
129
 
191
130
  ```sh
@@ -196,55 +135,19 @@ npm run build # esbuild: lib/index.js (host ESM) + lib/client.js (loade
196
135
  node scripts/verify-host.mjs # boot the BUILT host artifact end-to-end (18 checks)
197
136
  ```
198
137
 
199
- `prepare` runs the full build, so git installs and `npm pack` / `npm publish`
200
- always produce a complete `lib/` and the `LICENSE`.
138
+ `prepare` runs the full build, so git installs and `npm pack` / `npm publish` always produce a complete `lib/` and the `LICENSE`.
201
139
 
202
- Maintainers: see [docs/harness-reference.md](docs/harness-reference.md) for the
203
- DeepSeek Harness interface reference (subsystem docs + key source index).
140
+ Maintainers: the module map and harness interface reference live in [docs/harness-reference.md](docs/harness-reference.md); publishing steps in [docs/release.md](docs/release.md).
204
141
 
205
- ## Publishing
142
+ ## Release
206
143
 
207
- Releases go out through GitHub Actions Trusted Publishing (OIDC, no stored
208
- `NPM_TOKEN`):
144
+ Releases go out through GitHub Actions Trusted Publishing (OIDC, no stored `NPM_TOKEN`): push a `v<version>` tag and CI publishes with Sigstore provenance.
209
145
 
210
146
  ```sh
211
- npm version patch && git push origin main --tags # triggers .github/workflows/publish.yml
147
+ npm version patch && git push origin main --tags
212
148
  ```
213
149
 
214
- - The workflow verifies the tag matches `package.json`, runs typecheck + tests +
215
- a full build + artifact verification, publishes with `--provenance`
216
- (Sigstore), and creates a GitHub Release. It is **idempotent** — an already
217
- published version is skipped. CI (`.github/workflows/ci.yml`) runs the same
218
- checks on every push / PR, plus a `npm pack --dry-run` sanity check that the
219
- tarball carries `lib/` and `LICENSE`.
220
- - One-time npm-side configuration (cannot be done from this repo): open
221
- [dsh-rewind-plugin](https://www.npmjs.com/package/dsh-rewind-plugin) →
222
- **settings → Trusted Publisher → Add**, with Provider **GitHub Actions** ·
223
- Organization or user **`SiriLee`** · Repository **`dsh-rewind`** (the GitHub
224
- repo, not the npm name) · Workflow filename **`publish.yml`** · Environment
225
- **empty** · Allowed actions **`npm publish`**. Once configured, pushes of
226
- `v<version>` tags publish automatically.
227
-
228
- ## Directory layout
229
-
230
- ```
231
- src/index.ts host plugin: /rewind command + checkpoint pipeline (tools/execute|post-execute)
232
- src/rewind.ts pure planning: target resolution, surface range, candidate listing
233
- src/snapshot.ts checkpoint store (disk before-backups, restore/preview, bounded prune)
234
- src/session-cwd.ts session-cwd resolution (fs-tools rule)
235
- src/client/index.ts client plugin: per-message ↶ button + manual /rewind guard
236
- src/client/popover.ts mode-selection popover (both-mode impact confirm)
237
- src/client/hidden.ts withdrawn-span computation (hiddenSeqsOf), pure
238
- src/client/locales.ts zh / en copy (LocaleNamespaceMap)
239
- src/client/styles.ts injected styles (dsh design tokens)
240
- scripts/build.mjs esbuild: lib/index.js (host ESM) + lib/client.js (loader closure) + .d.ts
241
- scripts/verify-host.mjs end-to-end host verification (18 checks)
242
- tests/ vitest suites (rewind / snapshot / hidden / session-cwd / integration, 46 cases)
243
- docs/harness-reference.md maintainer docs: DeepSeek Harness interface reference
244
- assets/screenshots/ UI screenshots
245
- cordis.patch.yml bundle patch (mounts the dual-face plugin row)
246
- package.json dsh.bundle + dsh.client manifests, optional peerDependencies
247
- ```
150
+ One-time npm-side setup and the full workflow details: [docs/release.md](docs/release.md).
248
151
 
249
152
  ## License
250
153