dsh-hashline-edit 0.0.0-stage → 0.1.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.
Files changed (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +247 -2
  3. package/cordis.patch.yml +8 -0
  4. package/lib/index.js +14182 -0
  5. package/lib/types/dsh/contracts.d.ts +142 -0
  6. package/lib/types/dsh/encoding-bridge.d.ts +25 -0
  7. package/lib/types/dsh/encoding.d.ts +144 -0
  8. package/lib/types/dsh/fs.d.ts +60 -0
  9. package/lib/types/dsh/guidance.d.ts +119 -0
  10. package/lib/types/dsh/host-compat.d.ts +82 -0
  11. package/lib/types/dsh/index.d.ts +36 -0
  12. package/lib/types/dsh/metrics/events.d.ts +33 -0
  13. package/lib/types/dsh/metrics/instrument.d.ts +76 -0
  14. package/lib/types/dsh/sandbox.d.ts +58 -0
  15. package/lib/types/dsh/shell-hook.d.ts +53 -0
  16. package/lib/types/dsh/shell-view.d.ts +87 -0
  17. package/lib/types/dsh/tools/context.d.ts +37 -0
  18. package/lib/types/dsh/tools/edit.d.ts +10 -0
  19. package/lib/types/dsh/tools/errors.d.ts +60 -0
  20. package/lib/types/dsh/tools/output.d.ts +81 -0
  21. package/lib/types/dsh/tools/read.d.ts +99 -0
  22. package/lib/types/dsh/tools/undo.d.ts +10 -0
  23. package/lib/types/dsh/write-hook.d.ts +99 -0
  24. package/lib/types/src/config-dir.d.ts +57 -0
  25. package/lib/types/src/constants.d.ts +19 -0
  26. package/lib/types/src/cut-repair.d.ts +2 -0
  27. package/lib/types/src/diagnostics.d.ts +28 -0
  28. package/lib/types/src/domain-errors.d.ts +274 -0
  29. package/lib/types/src/drift.d.ts +51 -0
  30. package/lib/types/src/edit-diff.d.ts +34 -0
  31. package/lib/types/src/edit-response.d.ts +190 -0
  32. package/lib/types/src/edit-tool.d.ts +41 -0
  33. package/lib/types/src/edit-undo.d.ts +70 -0
  34. package/lib/types/src/error-envelope.d.ts +78 -0
  35. package/lib/types/src/file-content/admission.d.ts +61 -0
  36. package/lib/types/src/file-content/detection.d.ts +41 -0
  37. package/lib/types/src/file-content/index.d.ts +84 -0
  38. package/lib/types/src/file-content/line-walker.d.ts +63 -0
  39. package/lib/types/src/file-content/loader.d.ts +67 -0
  40. package/lib/types/src/file-content/preview.d.ts +69 -0
  41. package/lib/types/src/file-content/served-header.d.ts +36 -0
  42. package/lib/types/src/file-content/types.d.ts +11 -0
  43. package/lib/types/src/file-kind.d.ts +6 -0
  44. package/lib/types/src/file-reader.d.ts +6 -0
  45. package/lib/types/src/fs-write.d.ts +16 -0
  46. package/lib/types/src/hash-store.d.ts +107 -0
  47. package/lib/types/src/hashline/alphabet.d.ts +12 -0
  48. package/lib/types/src/hashline/apply.d.ts +81 -0
  49. package/lib/types/src/hashline/hash-identity.d.ts +145 -0
  50. package/lib/types/src/hashline/hasher.d.ts +7 -0
  51. package/lib/types/src/hashline/lease-resolve.d.ts +73 -0
  52. package/lib/types/src/hashline/parse.d.ts +7 -0
  53. package/lib/types/src/hashline/patience-pairing.d.ts +104 -0
  54. package/lib/types/src/hashline/resolve.d.ts +162 -0
  55. package/lib/types/src/io-port.d.ts +69 -0
  56. package/lib/types/src/json-config.d.ts +54 -0
  57. package/lib/types/src/mutation-engine/batch-span-gate.d.ts +61 -0
  58. package/lib/types/src/mutation-engine/edit-source.d.ts +69 -0
  59. package/lib/types/src/mutation-engine/engine.d.ts +31 -0
  60. package/lib/types/src/mutation-engine/pipeline.d.ts +14 -0
  61. package/lib/types/src/mutation-engine/types.d.ts +138 -0
  62. package/lib/types/src/mutation-queue.d.ts +10 -0
  63. package/lib/types/src/noop-guard.d.ts +31 -0
  64. package/lib/types/src/paths.d.ts +2 -0
  65. package/lib/types/src/payload-contract.d.ts +124 -0
  66. package/lib/types/src/prompts.d.ts +2 -0
  67. package/lib/types/src/read-expansion/ast-provider.d.ts +25 -0
  68. package/lib/types/src/read-expansion/ast-span.d.ts +50 -0
  69. package/lib/types/src/read-expansion/audit.d.ts +49 -0
  70. package/lib/types/src/read-expansion/bash-provider.d.ts +9 -0
  71. package/lib/types/src/read-expansion/go-decoration.d.ts +16 -0
  72. package/lib/types/src/read-expansion/grammar-cache.d.ts +45 -0
  73. package/lib/types/src/read-expansion/grammar-manifest.d.ts +25 -0
  74. package/lib/types/src/read-expansion/grammar-runtime.d.ts +20 -0
  75. package/lib/types/src/read-expansion/line-arithmetic.d.ts +25 -0
  76. package/lib/types/src/read-expansion/markdown-provider.d.ts +9 -0
  77. package/lib/types/src/read-expansion/markdown-section.d.ts +15 -0
  78. package/lib/types/src/read-expansion/paragraph-provider.d.ts +11 -0
  79. package/lib/types/src/read-expansion/paragraph-section.d.ts +8 -0
  80. package/lib/types/src/read-expansion/python-decoration.d.ts +7 -0
  81. package/lib/types/src/read-expansion/qualified-names.d.ts +19 -0
  82. package/lib/types/src/read-expansion/read-provider.d.ts +12 -0
  83. package/lib/types/src/read-expansion/rust-decoration.d.ts +15 -0
  84. package/lib/types/src/read-expansion/scope-decoration.d.ts +56 -0
  85. package/lib/types/src/read-expansion/span-expansion.d.ts +161 -0
  86. package/lib/types/src/read-expansion/span-kind.d.ts +37 -0
  87. package/lib/types/src/read-expansion/text-lines.d.ts +2 -0
  88. package/lib/types/src/read-expansion/typescript-decoration.d.ts +28 -0
  89. package/lib/types/src/read-expansion-config.d.ts +21 -0
  90. package/lib/types/src/served-session/drift-helpers.d.ts +1 -0
  91. package/lib/types/src/served-session/index.d.ts +11 -0
  92. package/lib/types/src/served-session/serve-render.d.ts +263 -0
  93. package/lib/types/src/served-session/session.d.ts +119 -0
  94. package/lib/types/src/served-session/types.d.ts +82 -0
  95. package/lib/types/src/served-spans.d.ts +69 -0
  96. package/lib/types/src/snapshot-store/index.d.ts +147 -0
  97. package/lib/types/src/snapshot-store/migrate.d.ts +7 -0
  98. package/lib/types/src/snapshot-store/snapshot-io.d.ts +76 -0
  99. package/lib/types/src/snapshot-store/vacuum.d.ts +38 -0
  100. package/lib/types/src/state-migration.d.ts +7 -0
  101. package/lib/types/src/store-kernel/busy.d.ts +80 -0
  102. package/lib/types/src/store-kernel/constants.d.ts +14 -0
  103. package/lib/types/src/store-kernel/index.d.ts +14 -0
  104. package/lib/types/src/store-kernel/open-gate.d.ts +24 -0
  105. package/lib/types/src/store-kernel/periodic.d.ts +15 -0
  106. package/lib/types/src/undo-store.d.ts +81 -0
  107. package/lib/types/src/utils.d.ts +12 -0
  108. package/lib/types/src/validation.d.ts +8 -0
  109. package/package.json +119 -4
  110. package/prompts/edit-guidelines.md +10 -0
  111. package/prompts/edit-snippet.md +1 -0
  112. package/prompts/edit.md +1 -0
  113. package/prompts/read-guidelines.md +6 -0
  114. package/prompts/read-snippet.md +1 -0
  115. package/prompts/read.md +1 -0
  116. package/prompts/undo-last-edit-guidelines.md +2 -0
  117. package/prompts/undo-last-edit-snippet.md +1 -0
  118. package/prompts/undo-last-edit.md +1 -0
  119. package/src/bash-classifier.ts +1626 -0
  120. package/src/bash-conversion/decide.ts +812 -0
  121. package/src/bash-display-transform.ts +50 -0
  122. package/src/bash-multifile-config.ts +239 -0
  123. package/src/bash-view-banner.ts +30 -0
  124. package/src/bash-view-config.ts +158 -0
  125. package/src/config-dir.ts +184 -0
  126. package/src/constants.ts +118 -0
  127. package/src/cut-repair.ts +201 -0
  128. package/src/diagnostics.ts +220 -0
  129. package/src/domain-errors.ts +794 -0
  130. package/src/drift.ts +404 -0
  131. package/src/edit-diff.ts +229 -0
  132. package/src/edit-normalize.ts +8 -0
  133. package/src/edit-render.ts +161 -0
  134. package/src/edit-response.ts +561 -0
  135. package/src/edit-tool.ts +116 -0
  136. package/src/edit-undo.ts +906 -0
  137. package/src/edit.ts +136 -0
  138. package/src/error-envelope.ts +175 -0
  139. package/src/file-content/admission.ts +243 -0
  140. package/src/file-content/detection.ts +318 -0
  141. package/src/file-content/index.ts +321 -0
  142. package/src/file-content/line-walker.ts +118 -0
  143. package/src/file-content/loader.ts +213 -0
  144. package/src/file-content/preview.ts +786 -0
  145. package/src/file-content/served-header.ts +72 -0
  146. package/src/file-content/types.ts +13 -0
  147. package/src/file-kind.ts +6 -0
  148. package/src/file-reader.ts +6 -0
  149. package/src/fs-write.ts +218 -0
  150. package/src/hash-store.ts +1309 -0
  151. package/src/hashline/alphabet.ts +27 -0
  152. package/src/hashline/apply.ts +779 -0
  153. package/src/hashline/hash-identity.ts +695 -0
  154. package/src/hashline/hasher.ts +41 -0
  155. package/src/hashline/lease-resolve.ts +269 -0
  156. package/src/hashline/parse.ts +78 -0
  157. package/src/hashline/patience-pairing.ts +542 -0
  158. package/src/hashline/resolve.ts +576 -0
  159. package/src/integrations/pi-lens/command.ts +712 -0
  160. package/src/integrations/pi-lens/config.ts +148 -0
  161. package/src/integrations/pi-lens/io-bridge-adapter.ts +234 -0
  162. package/src/integrations/pi-lens/io-bridge.ts +112 -0
  163. package/src/integrations/pi-lens/settings-overlay.ts +272 -0
  164. package/src/io-port.ts +87 -0
  165. package/src/json-config.ts +316 -0
  166. package/src/lifecycle-hooks/index.ts +1992 -0
  167. package/src/lifecycle-hooks/types.ts +64 -0
  168. package/src/mutated-files.ts +75 -0
  169. package/src/mutation-engine/batch-span-gate.ts +265 -0
  170. package/src/mutation-engine/edit-source.ts +211 -0
  171. package/src/mutation-engine/engine.ts +173 -0
  172. package/src/mutation-engine/index.ts +17 -0
  173. package/src/mutation-engine/pipeline.ts +1518 -0
  174. package/src/mutation-engine/types.ts +153 -0
  175. package/src/mutation-queue.ts +20 -0
  176. package/src/noop-guard.ts +149 -0
  177. package/src/paths.ts +30 -0
  178. package/src/payload-contract.ts +637 -0
  179. package/src/preview-controller.ts +84 -0
  180. package/src/prompts.ts +53 -0
  181. package/src/read-expansion/ast-provider.ts +236 -0
  182. package/src/read-expansion/ast-span.ts +86 -0
  183. package/src/read-expansion/audit.ts +146 -0
  184. package/src/read-expansion/bash-provider.ts +136 -0
  185. package/src/read-expansion/go-decoration.ts +47 -0
  186. package/src/read-expansion/grammar-cache.ts +209 -0
  187. package/src/read-expansion/grammar-manifest.ts +73 -0
  188. package/src/read-expansion/grammar-runtime.ts +75 -0
  189. package/src/read-expansion/line-arithmetic.ts +62 -0
  190. package/src/read-expansion/markdown-provider.ts +49 -0
  191. package/src/read-expansion/markdown-section.ts +133 -0
  192. package/src/read-expansion/paragraph-provider.ts +56 -0
  193. package/src/read-expansion/paragraph-section.ts +48 -0
  194. package/src/read-expansion/python-decoration.ts +34 -0
  195. package/src/read-expansion/qualified-names.ts +81 -0
  196. package/src/read-expansion/read-provider.ts +52 -0
  197. package/src/read-expansion/rust-decoration.ts +55 -0
  198. package/src/read-expansion/scope-decoration.ts +127 -0
  199. package/src/read-expansion/span-expansion.ts +481 -0
  200. package/src/read-expansion/span-kind.ts +193 -0
  201. package/src/read-expansion/text-lines.ts +10 -0
  202. package/src/read-expansion/typescript-decoration.ts +50 -0
  203. package/src/read-expansion-config.ts +121 -0
  204. package/src/read.ts +865 -0
  205. package/src/search-hook.ts +41 -0
  206. package/src/served-session/drift-helpers.ts +38 -0
  207. package/src/served-session/index.ts +33 -0
  208. package/src/served-session/serve-render.ts +810 -0
  209. package/src/served-session/session.ts +1327 -0
  210. package/src/served-session/types.ts +97 -0
  211. package/src/served-spans.ts +152 -0
  212. package/src/snapshot-store/index.ts +1034 -0
  213. package/src/snapshot-store/migrate.ts +96 -0
  214. package/src/snapshot-store/snapshot-io.ts +91 -0
  215. package/src/snapshot-store/vacuum.ts +236 -0
  216. package/src/state-migration.ts +227 -0
  217. package/src/store-config.ts +73 -0
  218. package/src/store-janitor-warning.ts +169 -0
  219. package/src/store-janitor.ts +539 -0
  220. package/src/store-kernel/busy.ts +214 -0
  221. package/src/store-kernel/constants.ts +39 -0
  222. package/src/store-kernel/index.ts +20 -0
  223. package/src/store-kernel/open-gate.ts +67 -0
  224. package/src/store-kernel/periodic.ts +35 -0
  225. package/src/store-kernel/settings.ts +233 -0
  226. package/src/store.ts +154 -0
  227. package/src/tui-presenter.ts +134 -0
  228. package/src/undo-store.ts +321 -0
  229. package/src/utils.ts +76 -0
  230. package/src/validation.ts +53 -0
  231. package/src/write-hook.ts +130 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 RimuruW and Yugimob
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,248 @@
1
- # Temporary Holding Version
1
+ # dsh-hashline-edit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Hash-anchored file editing for [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) on Windows.
4
+
5
+ `dsh-hashline-edit` is a dsh bundle that shadows the host's built-in `read` and `edit` tools with hashline equivalents and adds `undo_last_edit`. Every line a served read shows carries a stable 4-character base62 anchor (`A–Z`, `a–z`, `0–9`) derived from the file's canonical path and the line's content. An edit names two anchors and new text; it never re-types existing code. Anchors for unchanged lines stay stable across edits, and every successful edit returns fresh anchors for the changed lines, so chained edits need no re-read.
6
+
7
+ The semantic core is the [pi-better-edit](https://github.com/Rianico/pi-better-edit) v2.16.0 hashline engine (MIT). The `dsh/` tree is this repository's DeepSeek Harness host layer: tool registration, filesystem bridge, encoding governance, prompt guidance, shell integration, write integration and telemetry. See [`dsh/README.md`](dsh/README.md) for the host-layer map.
8
+
9
+ ## Requirements
10
+
11
+ | Requirement | Version |
12
+ | --- | --- |
13
+ | OS | Windows — this branch targets Windows only (see [`dsh/UPSTREAM.md`](dsh/UPSTREAM.md)) |
14
+ | Node.js | ≥ 24.0.0 |
15
+ | pnpm | 12.x |
16
+ | DeepSeek Harness | plugin-capable build; the repository is developed against `@deepseek-ai/dsh-*@0.2.1-rc.2` |
17
+
18
+ ## Install
19
+
20
+ Build the plugin first: the dsh loader reads the package through `exports`/`main`, and `lib/` is gitignored.
21
+
22
+ ```sh
23
+ pnpm install
24
+ pnpm run build
25
+ dsh plugin add <path-to-repo-or-tgz>
26
+ ```
27
+
28
+ To keep the install isolated, use a throwaway profile:
29
+
30
+ ```sh
31
+ dsh plugin --profile scratch-hashline add <path-to-repo>
32
+ dsh --profile scratch-hashline --dump-config # the bundle row is composed
33
+ ```
34
+
35
+ For a tarball, pack first and add the archive:
36
+
37
+ ```sh
38
+ pnpm pack
39
+ dsh plugin add ./dsh-hashline-edit-0.1.0.tgz
40
+ ```
41
+
42
+ The bundle ships `lib/index.js` plus `lib/types/**`; `prompts/` must sit next to `lib/` in the installed package (it is in `files`). Guidance override files are materialized under `<DSH_HOME>/plugins/dsh-hashline-edit/guidance/` and are shared between profiles.
43
+
44
+ ## The anchored model
45
+
46
+ A served read looks like this (`│` is U+2502, the separator):
47
+
48
+ ```text
49
+ ve7x│function calculateTotal(items: CartItem[]): number {
50
+ szJx│ let total = 0;
51
+ kQmx│ for (const item of items) {
52
+ m2zX│ total += item.price;
53
+ d2xX│ }
54
+ p9rX│ return total;
55
+ a1bX│}
56
+ ```
57
+
58
+ Copy only the 4 characters before `│` into `anchor_from` / `anchor_to`. An edit targets an inclusive range and returns a diff whose modified and context rows carry fresh anchors:
59
+
60
+ ```text
61
+ - szJx│ let total = 0;
62
+ - kQmx│ for (const item of items) {
63
+ - m2zX│ total += item.price;
64
+ +w4kQ│ let total = items.reduce((sum, item) => sum + item.price, 0);
65
+ d2xX│ }
66
+ ```
67
+
68
+ Served anchors are leased per session and per file. A stale, unknown, retired or foreign anchor is refused with a `[MODEL] [E_*]` message; range refusals return the current rows as a fresh read (`Current range (fresh read):`) so the model can retry from the rejection alone. An ordinary refusal writes nothing; a foreign-source cut is ordered and repair-backed rather than filesystem-atomic across a crash.
69
+
70
+ ## Tools
71
+
72
+ ### `read`
73
+
74
+ ```ts
75
+ read({
76
+ path: string, // relative or absolute; the dsh tool takes `path`, not `file`
77
+ offset?: number, // 1-indexed start; a bare offset reads a window (default limit 50)
78
+ limit?: number,
79
+ windows?: Array< // up to 16 disjoint windows in one call
80
+ | { offset: number; limit: number }
81
+ | { around_anchor: string; radius?: number } // radius defaults to 10
82
+ >,
83
+ mode?: "served" | "verbatim", // default "served"
84
+ encoding?: string, // reopen a non-UTF-8 file, e.g. "gbk", "big5", "shift_jis", "utf16le"
85
+ })
86
+ ```
87
+
88
+ - **Served mode** returns one `HASH│content` row per line and records every shown row as a lease for this session. A windowed read also includes a `[Lines A-B of N total]` page header. Every row shown is usable by one subsequent edit, even when the windows are far apart.
89
+ - **Verbatim mode** returns the same page as plain text with no anchors and serves nothing; an edit cannot target those lines.
90
+ - A small requested window may widen to its enclosing function, method, class, section or paragraph; a `[widened …]` note names the block that was served. Expansion applies in both modes.
91
+ - `offset`/`limit` and `windows` are page requests, not anchors: only `served` output grants edit leases.
92
+
93
+ ### `edit`
94
+
95
+ ```ts
96
+ edit({
97
+ file: string, // one text file per call
98
+ edits: [{
99
+ anchor_from: string, // bare 4-char anchor of the first range line (inclusive)
100
+ anchor_to: string, // bare 4-char anchor of the last range line (inclusive)
101
+ text?: string, // by-value content; "" deletes an in-place range
102
+ text_ref?: { // by-reference content from a served span
103
+ anchor_from: string,
104
+ anchor_to: string,
105
+ file?: string, // another served file (foreign-source copy/cut)
106
+ mode: "copy" | "cut", // required: copy keeps the source, cut retires the span
107
+ },
108
+ at?: "in-place" | "before" | "after", // default "in-place"
109
+ }],
110
+ mode?: "general" | "literal", // "general" refuses served-row echoes; "literal" declares them intended
111
+ })
112
+ ```
113
+
114
+ Each item carries exactly one of `text` or `text_ref`; supplying both or neither is refused. `before`/`after` insert at the range boundary and require a single-line resolved target. A foreign-source `cut` writes both files as one correlated transaction; `undo_last_edit` reverts both members. An edit never silently rewrites submitted text — a refused edit writes nothing.
115
+
116
+ The result is the core's unified diff with fresh anchors for the changed lines, so the next edit can chain from them without a `read`. The host's built-in `write` is not replaced: the plugin guards it against served-row echoes and appends an anchored auto-read so following edits can use its output.
117
+
118
+ ### `undo_last_edit`
119
+
120
+ ```ts
121
+ undo_last_edit({ path: string })
122
+ ```
123
+
124
+ Restores the previous file content, BOM, line endings and original anchors from a snapshot-pinned undo record. The record survives agent restarts. A file changed externally after the edit is refused with `[E_UNDO_STALE]`. A correlated cut's undo restores every member of the transaction, not only the named path.
125
+
126
+ ## Shell view
127
+
128
+ The plugin installs a `tools/post-execute` listener for the host's `pwsh` and `bash` tools. When the command is a whitelisted read-only file view, the captured stdout is replaced with the aligned file's `HASH│content` rows, and those rows are served (leased) exactly like a read — a following `edit` can use them with no re-read.
129
+
130
+ | Command shape | Served as |
131
+ | --- | --- |
132
+ | `Get-Content <file>`, `type <file>`, `cat <file>` | the file's lines |
133
+ | `Get-Content <file> \| Select-Object -First/-Last/-Skip <N>` | the bounded slice |
134
+ | `Select-String <pattern> <file>` | the matched lines, with their line numbers |
135
+ | `rg <pattern> <file>` (explicit single file, no switches) | the matched lines, with their line numbers |
136
+
137
+ Alignment is strict: stdout lines are mapped to 1-based disk line numbers with a monotonic, unique match (search output's own line numbers are used as hints). Anything the view cannot prove — an unknown pipeline, a wildcard path, formatted output, a truncated capture, a failed command — passes through byte-for-byte. The hook never guesses and never serves the wrong occurrence of a duplicated line.
138
+
139
+ ## Encoding governance
140
+
141
+ The dsh host reintroduces a VS Code-style encoding model on top of the UTF-8 core. Read admission is deterministic:
142
+
143
+ 1. explicit `read({ encoding })` hint;
144
+ 2. BOM sniff (UTF-8, UTF-16LE/BE and UTF-32LE/BE BOMs);
145
+ 3. strict UTF-8 decode;
146
+ 4. the `autoGuessEncoding` gate (off by default);
147
+ 5. Top-3 candidate preview on refusal, e.g. `not readable UTF-8 text — top guesses: gbk("你好"); try read({encoding: "<encoding>"}) or enable autoGuessEncoding to decode automatically`.
148
+
149
+ Text encodings offered to the model are `gbk`, `big5`, `shift_jis`, `euc-kr`, `windows-1251` and `utf16le` (case-insensitive), in addition to UTF-8 and BOM-detected Unicode.
150
+
151
+ A governed read records the file's encoding state (encoding, BOM, line ending, file version). Both the `edit` lane and the built-in `write` lane invert that state on save through a single codec:
152
+
153
+ - a GBK file edited through `read` → `edit` stays GBK on disk (byte comparison, not text equality);
154
+ - a UTF-16LE file with a BOM keeps its BOM and its byte order;
155
+ - a UTF-8 BOM file round-trips byte-for-byte.
156
+
157
+ When a legacy code page cannot represent the new text, the save degrades to UTF-8 and appends a model-facing warning instead of writing lossy bytes:
158
+
159
+ > Warning: the file was written as UTF-8 because the recorded gbk encoding cannot represent the edited text. Re-read the file before the next edit.
160
+
161
+ Undo also inverts the recorded state for every member of a correlated cut — but only when a governed read recorded that file's encoding state in the same plugin process. Without a recorded state, undo keeps the core's UTF-8 behavior; on a legacy file that can surface as `[E_UNDO_STALE]` rather than a guessed re-encode.
162
+
163
+ ## Sandbox behavior
164
+
165
+ `edit` and `undo_last_edit` carry the host's optional escalation fields `sandbox_permissions` and `justification` only when a confining filesystem backend is mounted. `dsh/sandbox.ts` mirrors the built-in `@deepseek-ai/dsh-tool-fs` controller: it validates the field pairing, repeats the standing mode without approval, and resolves a strictly wider mode through the host's approval channel before touching the filesystem. A provider `FS_SANDBOX_DENIED` error is mapped to the standard model-facing denial marker plus escalation hint; other errors pass through unchanged.
166
+
167
+ ### Host-side `0xC0000142` workspace-write finding
168
+
169
+ On this Windows host, the default `workspace-write` pwsh sandbox cannot start `pwsh`: `dsh-pwsh-sandbox` launches it through the Windows ACL restricted-token runner, and the child dies at process start with exit code `3221225794` (`0xC0000142`, `STATUS_DLL_INIT_FAILED`). It reproduces in a headless profile with **no hashline plugin installed**, so the failure belongs to `dsh-tool-pwsh` + `dsh-sandbox-windows-acl`, not to this plugin.
170
+
171
+ The M3/M4 smoke runs therefore used `DSH_PERMISSION_MODE=danger-full-access`; this is an environment setup requirement, not a plugin behavior. Evidence: `dsh/baseline/m4-behaviour.json` (`sandboxFinding`) and `dsh/baseline/m3.json`.
172
+
173
+ ## Build and verify
174
+
175
+ | Command | What it does |
176
+ | --- | --- |
177
+ | `pnpm install` | install dependencies |
178
+ | `pnpm run build` | `node scripts/build-dsh.mjs`: bundle `lib/index.js` and emit `lib/types/**` from `tsconfig.dsh.json` |
179
+ | `pnpm run typecheck` | `tsc --noEmit` (full upstream program) and `tsc --noEmit -p tsconfig.dsh.json` (dsh build program) |
180
+ | `pnpm run test:win` | the Windows test suite (`vitest.config.windows.ts`); the primary gate for this branch |
181
+ | `pnpm test` | the default vitest suite |
182
+ | `pnpm run format` | `oxfmt --check .` (use `pnpm run format:fix` to write) |
183
+ | `pnpm run lint` | `oxlint .` + `knip --max-issues 0` |
184
+ | `pnpm run verify` | lint + format + typecheck + Windows coverage |
185
+
186
+ The build enforces its own packaging contract: host packages stay external, the pi utility package is aliased to `dsh/host-compat.ts` (the bundle must not reference pi), and no bare import outside the allowlist can slip in. See [`dsh/README.md`](dsh/README.md#build-and-packaging).
187
+
188
+ ## Error and warning codes
189
+
190
+ Every refusal carries a bracketed code; `src/domain-errors.ts` is the source of truth. `E_*` are refusals (nothing is written), `W_*` are warnings carried beside an accepted result.
191
+
192
+ | Code | Meaning |
193
+ | --- | --- |
194
+ | `[E_ACCESS]` | The filesystem denied access to the target path. |
195
+ | `[E_BAD_PAYLOAD]` | Malformed or unknown tool payload; nothing is written. |
196
+ | `[E_BATCH_ABORT]` | A batch item failed; the whole batch was not applied. |
197
+ | `[E_EMPTY_RANGE]` | The edit would empty a non-empty file; use `write`. |
198
+ | `[E_FOREIGN_ANCHOR]` | The anchor belongs to a different file. |
199
+ | `[E_INTERNAL_INVARIANT]` | An internal invariant was violated. |
200
+ | `[E_LARGE_FILE]` | The file exceeds the served line or byte budget. |
201
+ | `[E_LOSSY_TEXT]` | The file's bytes do not round-trip through UTF-8; editing would corrupt it. |
202
+ | `[E_MALFORMED_ANCHOR]` | The anchor token cannot be parsed. |
203
+ | `[E_NOOP_LOOP]` | Repeated identical no-op edits; refused with the current range served. |
204
+ | `[E_NOT_FOUND]` | The target file does not exist or is unreachable. |
205
+ | `[E_STALE_ANCHOR]` | A boundary anchor no longer resolves against the file. |
206
+ | `[E_STALE_RANGE]` | The served range changed between serve and edit. |
207
+ | `[E_SUSPICIOUS_TEXT]` | The replacement reproduces served anchor text (a copied `HASH│` echo). |
208
+ | `[E_TARGET_LOST]` | The target line identity can no longer be resolved. |
209
+ | `[E_UNDO_REVERT_FAILED]` | A correlated revert failed mid-way; a repair run finishes it. |
210
+ | `[E_UNDO_STALE]` | The undo record is stale relative to the file. |
211
+ | `[E_UNDO_UNAVAILABLE]` | There is no undo history for the path. |
212
+ | `[E_UNKNOWN]` | An unclassified failure; the message carries the cause. |
213
+ | `[E_UNKNOWN_ANCHOR]` | The anchor was never served in this session. |
214
+ | `[E_UNSUPPORTED_FILE]` | Binary, image or directory targets are not editable text. |
215
+ | `[E_UNVERIFIED_RANGE]` | The range interior cannot be verified (never served). |
216
+ | `[W_LITERAL_BYPASS]` | Literal `HASH│` bytes were written through `edit` and accepted with a warning. |
217
+ | `[W_NEVER_SERVED_SHAPE]` | A never-served hint was attached to an accepted edit. |
218
+ | `[W_NOOP]` | The edit produced no content change and was accepted as a no-op. |
219
+ | `[W_NOOP_INSERT]` | An insert-shaped edit produced no change. |
220
+ | `[W_REVERSED_ANCHORS]` | Boundaries were reversed and healed. |
221
+ | `[W_SERVED_PREFIX_MISMATCH]` | A served prefix does not match; fresh rows were served. |
222
+ | `[W_UNICODE_LITERAL]` | A Unicode literal in the payload looks like a copied anchor. |
223
+
224
+ ## Current limitations
225
+
226
+ - **`ctx.fs` is read-only.** The read path can consume a mounted `ctx.fs` through the `CoreReadIo` port, but the mutation seams — `writeAtomic`, the edit engine and undo — remain local-only. `edit` and `undo_last_edit` fail closed with `E_NOT_FOUND` when the target is reachable only through `ctx.fs`.
227
+ - **`edit`, `undo_last_edit` and `writeAtomic` are local-only.** A remote or sandboxed backend needs target-keyed store state and versioned writes; that is deferred to M6 (see [`dsh/README.md`](dsh/README.md#ctxfs-bridge-m5-read-only)).
228
+ - **Undo preserves a legacy encoding only with recorded state.** The encoding memo lives in the plugin process and is version-gated; a fresh process or a file never governed-read in that process has no state to invert, so undo keeps the core's UTF-8 behavior.
229
+ - **Windows-only.** The branch targets Windows; POSIX-only suites are excluded in `vitest.config.windows.ts`, and that exclusion list must shrink, never grow (see [`dsh/UPSTREAM.md`](dsh/UPSTREAM.md)).
230
+
231
+ ## Repository layout
232
+
233
+ | Path | Role |
234
+ | --- | --- |
235
+ | `src/` | upstream pi-better-edit core (semantic source; synced from upstream) |
236
+ | `dsh/` | DeepSeek Harness host layer — the only code that differs from upstream; map in [`dsh/README.md`](dsh/README.md) |
237
+ | `dsh/UPSTREAM.md` | upstream sync cursor, policy and Windows adaptation patches |
238
+ | `dsh/baseline/` | milestone measurement baselines (M0–M5) |
239
+ | `scripts/build-dsh.mjs` | dsh bundle builder and packaging guards |
240
+ | `tsconfig.dsh.json` | dsh build typecheck/declaration program |
241
+
242
+ ## Upstream attribution
243
+
244
+ The hashline engine is [**pi-better-edit**](https://github.com/Rianico/pi-better-edit) by [Rianico](https://github.com/Rianico), used under the MIT license and synced at upstream `673964c` (`v2.16.0`; see [`dsh/UPSTREAM.md`](dsh/UPSTREAM.md)). This repository keeps the upstream history and tree; the `dsh/` adapter is ours, is never merged upstream, and contains only host wiring.
245
+
246
+ ## License
247
+
248
+ [MIT](LICENSE)
@@ -0,0 +1,8 @@
1
+ # dsh-hashline-edit bundle patch: mount the host-plane plugin row.
2
+ # Installed via `dsh plugin --profile <name> add dsh-hashline-edit` (or a
3
+ # profile's bundle list). The plugin hooks agent/created and shadows the
4
+ # built-in read/edit tools on each agent's own scope layer, so no preset
5
+ # editing or user config is needed.
6
+ - insert:
7
+ - id: dsh-hashline-edit
8
+ name: dsh-hashline-edit