@flotiarenor/dsh-tool-text-editor 1.0.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/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,177 @@
1
+ # dsh-tool-text-editor
2
+
3
+ [中文](README.zh.md) | English
4
+
5
+ Model-facing tools for [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) that edit text
6
+ files **byte-faithfully**: `edit_text` and `write_text`.
7
+
8
+ They exist because the built-in tools lose Windows file conventions:
9
+
10
+ | Case (file is UTF-8 **BOM + CRLF**) | built-in `edit` | built-in `write` | this plugin |
11
+ |---|---|---|---|
12
+ | change one line | CRLF kept / **BOM lost** | — | BOM + CRLF kept |
13
+ | full overwrite | — | **BOM lost + CRLF flattened to LF** | BOM + CRLF kept |
14
+
15
+ `@deepseek-ai/dsh-fs-local` has no BOM handling at all (Node's `TextDecoder` strips a leading BOM
16
+ byte by default) and `writeText` does not restore a file's line-ending style.
17
+
18
+ On top of fidelity: **unified diffs** (with a `dry_run` preview), **automatic backups**, an **edit
19
+ ledger**, **`grep` / `lines` anchors** so old text never has to be copied by hand, **ambiguity
20
+ refusal**, and **near-miss candidates** when an anchor does not match.
21
+
22
+ ## Implementation and requirements
23
+
24
+ The implementation is **in-process Node** (`lib/core.mjs`): `node:` builtins only, no subprocess, no
25
+ build step, no third-party package.
26
+
27
+ | Requirement | Notes |
28
+ |---|---|
29
+ | Node | **The only dependency** — no interpreter, no external runtime, no process-startup cost per call. |
30
+
31
+ The package installs nothing of its own. Its single `peerDependencies` entry, `@deepseek-ai/dsh-tools`,
32
+ is the host contract — "needs this dsh or newer" — and resolves from the dsh installation rather than
33
+ being installed beside the plugin.
34
+
35
+ ## Install
36
+
37
+ ### Option 1 — preset (recommended, tightly scoped)
38
+
39
+ Only sessions that select this preset see the two tools. Run this from the root of a clone of this
40
+ repository:
41
+
42
+ ```powershell
43
+ node scripts/install-preset.mjs
44
+ # flags: --id <preset-id> (default texteditor) / --base <shipped-preset> (default standard)
45
+ # / --force / --dry-run / --from <agent.cordis.yml path>
46
+ ```
47
+
48
+ Then restart `dsh web` and start a new session on preset `texteditor`; a preset is a session-creation
49
+ fact, so a running session cannot switch to it.
50
+
51
+ ### Option 2 — install into the profile (available to every session)
52
+
53
+ `dsh plugin add` accepts several kinds of source, and every one of them works here: the package is
54
+ prebuilt, dependency-free ESM, so there is no `prepare`/`build` step to authorize or run.
55
+
56
+ | Source | Command |
57
+ |---|---|
58
+ | a local checkout | `dsh plugin --profile web add link:<absolute-path-to-this-checkout>` |
59
+ | npm, once published | `dsh plugin --profile web add @flotiarenor/dsh-tool-text-editor` |
60
+ | a packed tarball | `pnpm pack`, then `dsh plugin --profile web add ./<the-file-it-printed>.tgz` |
61
+ | a git repository (tracks the default branch) | `dsh plugin --profile web add github:Flotiarenor/dsh-tool-text-editor` |
62
+
63
+ Verify the layer without starting anything, then restart:
64
+
65
+ ```powershell
66
+ dsh --profile web --dump-config # look for the "# == @flotiarenor/dsh-tool-text-editor" layer
67
+ ```
68
+
69
+ The tool names do not collide with the built-ins, so a host-plane insert is safe. The trade-off is
70
+ that both tools (and their guidance section) show up in **every** session. Uninstall with
71
+ `dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`.
72
+
73
+ Both installs may coexist: the preset layer shadows the host layer with an identical definition.
74
+
75
+ ## Tools
76
+
77
+ ### `edit_text` — targeted replacement
78
+
79
+ `file_path` and `new_text` are required; give **exactly one** anchor: `old_text` (literal, copied from
80
+ `read`), `grep` (regex; the matched line/block including its trailing newline), or `lines` (e.g.
81
+ `"263:270"`). `mode` is `replace` (default) / `after` / `before` / `append` / `prepend`; also `count`
82
+ (require exactly N occurrences and replace all), `nth` (k-th occurrence), `strict`, `dry_run`, `note`.
83
+ `count` and `nth` are mutually exclusive.
84
+
85
+ ### `write_text` — create or fully replace a file
86
+
87
+ `file_path` + `content`; creation needs no flag, an overwrite is backed up first, and a brand-new file
88
+ follows the **majority** line-ending style of its siblings (same extension first) with no BOM by
89
+ default.
90
+
91
+ Both **write by default** (like the built-ins); pass `dry_run: true` to preview. The returned text
92
+ always includes the diff.
93
+
94
+ ## Deliberate limitations
95
+
96
+ These are design choices, not defects to be fixed; check them against your use case before relying on
97
+ the tools.
98
+
99
+ - **Writes bypass `ctx.fs`.** The file is written by the plugin itself, so the fs-observation policy
100
+ (read-before-write, version freshness), the sandbox, `sandbox_permissions` escalation and Windows
101
+ DACL preservation are all skipped — and the Web UI therefore shows no diff card (the model still
102
+ receives the textual diff).
103
+ - **Line anchors are not content-verified.** `lines` and `before` / `after <line>` locate text by line
104
+ number alone: a wrong number does not fail, it edits somewhere else. When the anchor has to be
105
+ verifiable, use `old_text` or `grep`.
106
+ - **Per-target serialization is per process.** An in-process queue per target plus an atomic write
107
+ keeps parallel tool calls from overwriting each other, but another dsh instance, an editor or any
108
+ other process writing the same file still can, and external changes are not detected.
109
+ - **UTF-8 text only.** Files containing NUL bytes (binary) or invalid UTF-8 are refused, as are paths
110
+ inside `.git/` or `.dsh/` and paths outside the workspace.
111
+
112
+ ## Configuration
113
+
114
+ There is no Config schema: the preset row's `config:` mapping is passed through as-is.
115
+
116
+ | Key | Default | Meaning |
117
+ |---|---|---|
118
+ | `backup` | `true` | copy the previous content into `artifactsDir/backups` before writing |
119
+ | `ledger` | `true` | append a JSONL record to `artifactsDir/edits.log` |
120
+ | `artifactsDir` | `<workspace>/.dsh` | where backups and the ledger live |
121
+ | `newFileBom` | `false` | write a UTF-8 BOM when creating a new file |
122
+ | `context` | `3` | context lines in the unified diff |
123
+ | `root` | `process.cwd()` | fallback workspace when a call has no agent session |
124
+
125
+ `DSH_TEXT_EDITOR_EOL` (`lf` \| `crlf`) overrides the line-ending inference for **new** files.
126
+
127
+ ## Self-test and gates
128
+
129
+ ```powershell
130
+ # run from the root of a clone of this repository
131
+ node tools/selftest.mjs # 75/75 on Windows + Node 24
132
+ node tools/check-license.mjs # license / dependency / Node-only gate
133
+ node tools/gen-schema.mjs # embedded schemas still match the DSL
134
+ ```
135
+
136
+ These three live in the repository only: `tools/` is deliberately outside the `files` whitelist, so
137
+ the published package is just the plugin, its preset installer, the docs and the license.
138
+
139
+ `tools/selftest.mjs` covers BOM/EOL fidelity, `dry_run`, all four anchor kinds, `count`, ambiguity
140
+ refusal, usage errors, binary/invalid-UTF-8 refusal, `.dsh/` and outside-workspace guards, majority
141
+ EOL inference, multi-hunk diffs, end-of-file newline changes and concurrent writes — **plus a
142
+ plugin-layer suite** that drives `apply()` with a fake context and asserts tool registration, the
143
+ guidance section, that every returned value satisfies `OUTPUT_SCHEMA`, the `render()` text, and the
144
+ config plumbing (`root` / `backup` / `ledger` / `newFileBom`).
145
+
146
+ `tools/gen-schema.mjs` needs an installed `@deepseek-ai/dsh-tools`: it looks for one under the dsh
147
+ profile's `node_modules` and under the npm global prefix, and `DSH_TOOLS_ENTRY` overrides that lookup.
148
+ It exits 2 when it cannot find one.
149
+
150
+ ## Layout
151
+
152
+ ```
153
+ lib/core.mjs # the core: BOM/EOL, anchors, matching, diff, backups, ledger, atomic write, per-target lock
154
+ lib/editor.mjs # the plugin: schemas, validation, tool registration (zero-dep ESM, no build)
155
+ preset/preset.yml # preset name/description, as dsh lists it
156
+ scripts/install-preset.mjs # derives the user preset from the local dsh installation
157
+ cordis.patch.yml # host-plane bundle patch
158
+ tools/selftest.mjs # end-to-end self-test (core + plugin layer)
159
+ tools/check-license.mjs # license / dependency / Node-only hygiene gate
160
+ tools/gen-schema.mjs # authoritative source and checker for the embedded JSON Schemas
161
+ ```
162
+
163
+ Backups and the ledger use fixed, documented names and fields: one file per edit under
164
+ `.dsh/backups/`, named `<flattened-absolute-path>@<timestamp>`, and one JSON object per line in
165
+ `.dsh/edits.log` (`time`, `id`, `tool`, `file`, `abspath`, `action`, `kinds`, `line_start`,
166
+ `line_end`, `added`, `removed`, `bom`, `eol`, `backup`, `summary`).
167
+
168
+ ## License
169
+
170
+ **Apache-2.0** — see [LICENSE](LICENSE). Copyright 2026 Flotiarenor. The package has **no runtime
171
+ dependencies**, so it carries no third-party license obligations of its own. Three notes:
172
+
173
+ - The embedded JSON Schemas in `lib/editor.mjs` are generated *output* of the `@deepseek-ai/dsh-tools`
174
+ converter (MIT, Copyright (c) 2026 DeepSeek) via `tools/gen-schema.mjs`.
175
+ - The preset composition is **not** part of this package: `scripts/install-preset.mjs` reads the one
176
+ shipped with the user's own dsh installation at install time.
177
+ - Source files carry an `SPDX-License-Identifier` header, so the license is machine-readable per file.
package/README.zh.md ADDED
@@ -0,0 +1,167 @@
1
+ # dsh-tool-text-editor
2
+
3
+ 中文 | [English](README.md)
4
+
5
+ [DeepSeek Harness](https://github.com/deepseek-ai)(dsh)的模型工具插件,提供两个**字节保真**的
6
+ 文本编辑工具:`edit_text` 与 `write_text`。
7
+
8
+ 原生工具在 Windows 上存在两处缺陷:
9
+
10
+ | 场景(文件为 UTF-8 **BOM + CRLF**) | 原生 `edit` | 原生 `write` | 本插件 |
11
+ | ---------------------------------------- | ----------------------------- | ----------------------------------- | ----------------- |
12
+ | 改一行 | CRLF 保住 / **BOM 丢失** | — | BOM + CRLF 都保住 |
13
+ | 整篇覆盖 | — | **BOM 丢失 + CRLF 被拍成 LF** | BOM + CRLF 都保住 |
14
+
15
+ 原因在于 `@deepseek-ai/dsh-fs-local` 完全没有 BOM 处理(Node 的 `TextDecoder` 默认吞掉前导
16
+ BOM 字节),且 `writeText` 不按原文件风格还原行尾。
17
+
18
+ 在保真之外,本插件还提供:**统一 diff**(可用 dry-run 预览)、**写入前自动备份**、**编辑台账**、
19
+ **`grep` / `lines` 锚点**(无需人工誊抄原文)、**歧义时拒绝写入**,以及**最接近候选**提示。
20
+
21
+ ## 实现与依赖
22
+
23
+ 实现是**进程内 Node**(`lib/core.mjs`):只用 `node:` 内置模块,不启动任何子进程,无构建步骤、
24
+ 无第三方依赖。
25
+
26
+ | 依赖 | 说明 |
27
+ | ---- | ------------------------------------------------------------------------------ |
28
+ | Node | 插件唯一的依赖。不启动解释器、不引入外部运行时;每次调用均无进程启动开销。 |
29
+
30
+ ## 安装
31
+
32
+ ### 方式一:preset(推荐,作用域最小)
33
+
34
+ 仅选定该 preset 的会话可见这两个工具,其它项目与会话的工具表不受影响。**在本仓库根目录**执行:
35
+
36
+ ```powershell
37
+ node scripts/install-preset.mjs
38
+ # 可选:--id <preset-id>(默认 texteditor)/ --base <自带 preset>(默认 standard)
39
+ # / --force / --dry-run / --from <agent.cordis.yml 路径>
40
+ ```
41
+
42
+ 随后重启 `dsh web`,新建会话时选择 preset `texteditor`;preset 属会话创建期事实,已在运行的会话无法切换。
43
+
44
+ ### 方式二:安装到 profile(所有会话可用)
45
+
46
+ `dsh plugin add` 支持多种来源,**以下来源均受支持**:本包为预构建的零依赖 ESM,无
47
+ `prepare` / `build` 步骤,因此既无需用户授权构建,安装时也不会执行任何构建脚本。
48
+
49
+ | 来源 | 命令 |
50
+ | -------------- | --------------------------------------------------------------------------- |
51
+ | 本地仓库 | `dsh plugin --profile web add link:<本仓库的绝对路径>` |
52
+ | npm(发布后) | `dsh plugin --profile web add @flotiarenor/dsh-tool-text-editor` |
53
+ | 打包的 tarball | 先 `pnpm pack`,再 `dsh plugin --profile web add ./<pnpm pack 输出的文件名>.tgz` |
54
+ | git 仓库(跟随默认分支) | `dsh plugin --profile web add github:Flotiarenor/dsh-tool-text-editor` |
55
+
56
+ 可在启动前用以下命令确认该层是否生效:
57
+
58
+ ```powershell
59
+ dsh --profile web --dump-config # 应当能看到 "# == @flotiarenor/dsh-tool-text-editor"
60
+ ```
61
+
62
+ `edit_text` / `write_text` 与原生工具不重名,插入宿主组合不会产生注册冲突。
63
+
64
+ 卸载:`dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`。
65
+
66
+ 两种方式可以并存:同名时 preset 层的注册会遮蔽宿主层的注册,二者定义相同、行为一致。
67
+
68
+ ## 工具契约
69
+
70
+ ### `edit_text` —— 局部替换
71
+
72
+ | 参数 | 必填 | 说明 |
73
+ | ------------- | ---- | ------------------------------------------------------------------------ |
74
+ | `file_path` | ✅ | 目标文件;相对路径按会话工作区解析。 |
75
+ | `new_text` | ✅ | 替换或插入的内容。 |
76
+ | `old_text` | - | 字面量锚点(可直接复制 `read` 的输出)。 |
77
+ | `grep` | - | 正则锚点:命中的行或行块作为锚点(含行尾换行符)。 |
78
+ | `lines` | - | 行号锚点,如 `"263:270"` 或 `"120"`。 |
79
+ | `mode` | - | `replace`(默认)/ `after` / `before` / `append` / `prepend`。 |
80
+ | `count` | - | 要求恰好 N 处命中并全部替换(不符则拒绝写入)。 |
81
+ | `nth` | - | 只替换第 k 处(1-based)。 |
82
+ | `strict` | - | 禁用宽松匹配(只接受精确匹配)。 |
83
+ | `dry_run` | - | 只输出 diff,不写入文件。 |
84
+ | `note` | - | 一行说明,记入编辑台账。 |
85
+
86
+ `old_text` / `grep` / `lines` **必须且只能提供一个**;`append` / `prepend` 不接受锚点,`after` / `before`
87
+ 只能与 `grep` / `lines` 搭配。数量不符将拒绝写入。`count` 与 `nth` 互斥。
88
+
89
+ ### `write_text` —— 整文件新建/覆盖
90
+
91
+ `file_path` + `content`;目标不存在时自动新建,覆盖前先备份。新建文件的行尾风格取自
92
+ 同目录的多数派(同扩展名优先),默认不写 BOM。
93
+
94
+ ## 已知限制
95
+
96
+ 以下均为有意的设计取舍,而非缺陷;采用前请对照自身场景确认。
97
+
98
+ - **写入不经由 `ctx.fs`。** 文件由本插件直接写入,因此不经过 fs 观察策略(先读后写、版本新鲜度校验)、
99
+ 沙箱与 `sandbox_permissions` 审批升权,也不保留 Windows DACL。对应地,Web UI 不会生成 diff 卡片
100
+ (模型仍可读到文本 diff)。
101
+ - **行号锚点不做内容校验。** `lines` 与 `before` / `after <行号>` 仅按行号定位:行号有误不会报错,
102
+ 改动会落在非预期位置;定位需要可校验时,请改用 `old_text` 或 `grep`。
103
+ - **同目标串行仅限本进程。** 进程内按目标路径排队,并配合原子写,故并行的工具调用不会相互覆盖;
104
+ 但另一个 dsh 实例、编辑器或其它进程同时修改同一文件时,仍可能相互覆盖,本插件也不检测外部改动。
105
+ - **仅处理 UTF-8 文本。** 含 NUL 字节的二进制文件与非法 UTF-8 文件一律拒绝;`.git/`、`.dsh/` 内部
106
+ 以及工作区之外的路径一律拒绝写入。
107
+
108
+ ## 配置
109
+
110
+ 本插件不定义 Config schema:preset 行的 `config:` 字段原样透传。
111
+
112
+ | 键 | 默认 | 含义 |
113
+ | ---------------- | ----------------- | ------------------------------------------------ |
114
+ | `backup` | `true` | 写入前把原内容复制到 `artifactsDir/backups` |
115
+ | `ledger` | `true` | 往 `artifactsDir/edits.log` 追加一条 JSONL 记录 |
116
+ | `artifactsDir` | `<工作区>/.dsh` | 备份与台账所在目录 |
117
+ | `newFileBom` | `false` | 新建文件时是否写 UTF-8 BOM |
118
+ | `context` | `3` | unified diff 的上下文行数 |
119
+ | `root` | `process.cwd()` | 无 agent 会话时的回退工作区 |
120
+
121
+ 环境变量 `DSH_TEXT_EDITOR_EOL`(`lf` \| `crlf`)可覆盖**新建文件**的行尾推断。
122
+
123
+ ## 自测与门禁
124
+
125
+ ```powershell
126
+ # 在本仓库根目录执行
127
+ node tools/selftest.mjs # Windows + Node 24 参考结果 75/75
128
+ node tools/check-license.mjs # 许可证 / 依赖 / 纯 Node 门禁
129
+ node tools/gen-schema.mjs # 内嵌 schema 是否仍与作者 DSL 一致
130
+ ```
131
+
132
+ `tools/selftest.mjs` 覆盖:BOM 与行尾保真、`dry_run`、四种锚点、`count`、歧义时拒绝写入、用法错误、
133
+ 二进制与非法 UTF-8、路径护栏(`.dsh/`、工作区之外)、行尾多数派推断、跨多个 hunk、末尾换行差异、
134
+ 并发写入不产生半截文件;**并含一层插件层断言**:以模拟 ctx 驱动 `apply()`,验证工具注册、引导段
135
+ 身份、每个返回值均满足 `OUTPUT_SCHEMA`、`render()` 输出,以及 config 透传(`root` / `backup` /
136
+ `ledger` / `newFileBom`)。
137
+
138
+ `tools/gen-schema.mjs` 需要一份装有 `@deepseek-ai/dsh-tools` 的 dsh:它会在 dsh profile 的
139
+ `node_modules` 与 npm 全局目录中自动查找,也可用 `DSH_TOOLS_ENTRY` 显式指定;找不到入口时退出码为 2。
140
+
141
+ ## 目录结构
142
+
143
+ ```
144
+ lib/core.mjs # 编辑核心:BOM/行尾、锚点、匹配、diff、备份、台账、原子写、同目标串行
145
+ lib/editor.mjs # 插件本体:schema、参数校验、工具注册(零依赖 ESM,无构建)
146
+ preset/preset.yml # preset 的名字/描述(dsh 列表里显示的内容)
147
+ scripts/install-preset.mjs # 从本机 dsh 派生用户 preset
148
+ cordis.patch.yml # 宿主平面安装用的 bundle patch
149
+ tools/selftest.mjs # 端到端自测(核心 + 插件层)
150
+ tools/check-license.mjs # 许可证 / 依赖 / 纯 Node 卫生门禁
151
+ tools/gen-schema.mjs # 内嵌 schema 的权威来源与校验器
152
+ ```
153
+
154
+ 备份与台账采用固定的命名与字段:每次编辑在 `.dsh/backups/` 下留存一个文件,命名为
155
+ `<绝对路径扁平化>@<时间戳>`;`.dsh/edits.log` 每行一个 JSON 对象(`time`、`id`、`tool`、`file`、
156
+ `abspath`、`action`、`kinds`、`line_start`、`line_end`、`added`、`removed`、`bom`、`eol`、
157
+ `backup`、`summary`)。
158
+
159
+ ## License
160
+
161
+ **Apache-2.0**,见 [LICENSE](LICENSE)。Copyright 2026 Flotiarenor。本包**零运行时依赖**,因此不承担
162
+ 任何第三方许可证义务。
163
+
164
+ - `lib/editor.mjs` 内嵌的 JSON Schema 是 `@deepseek-ai/dsh-tools`(MIT,Copyright (c) 2026 DeepSeek)
165
+ 转换器的**生成产物**(由 `tools/gen-schema.mjs` 离线生成)。
166
+ - preset 组合**不在本包内**:`scripts/install-preset.mjs` 在安装时读取使用者所装 dsh 自带的组合。
167
+ - 源文件均带 `SPDX-License-Identifier` 头,许可证**逐文件机器可读**。
@@ -0,0 +1,22 @@
1
+ # SPDX-FileCopyrightText: 2026 Flotiarenor
2
+ # SPDX-License-Identifier: Apache-2.0
3
+ # dsh-tool-text-editor bundle patch:把 edit_text / write_text 两个工具行插进 profile 的宿主组合,
4
+ # 从而对**所有**会话可见(不限于某一个 preset)。
5
+ #
6
+ # dsh plugin --profile web add link:<本仓库的绝对路径>
7
+ # # 然后重启 dsh web
8
+ #
9
+ # 为什么可以走宿主平面:本插件注册的是 `edit_text` / `write_text` 两个**不与原生工具重名**的工具,
10
+ # 不存在同名注册冲突;它也消费宿主服务(tools / systemPrompt)、不发布任何服务,实现是纯 Node
11
+ # (进程内、零依赖、不启动外部进程),因此宿主平面插入没有额外依赖。
12
+ #
13
+ # 与 preset 安装方式的区别:
14
+ # * 这里(宿主层)= 每个会话都有这两个工具,任何 preset 都能用;
15
+ # * `scripts/install-preset.mjs`(preset 层)= 只有选了这个 preset 的会话有,
16
+ # 其它项目/会话的工具表保持干净。
17
+ # 两种方式可以同时存在:preset 层的注册在同名时遮蔽宿主层的,定义相同、行为一致。
18
+ #
19
+ # 卸载:`dsh plugin --profile web remove @flotiarenor/dsh-tool-text-editor`,再重启 dsh web。
20
+ - insert:
21
+ - id: tool-text-editor
22
+ name: '@flotiarenor/dsh-tool-text-editor'