dsh-hashline-edit 0.2.0 → 0.2.1

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Hash-anchored file editing for [DeepSeek Harness](https://github.com/deepseek-ai) (dsh) on Windows.
4
4
 
5
- `dsh-hashline-edit` is a dsh bundle that shadows the host's built-in `read` with hash-anchored output, registers `apply_patch` as the only file-mutation entry point, and adds `undo_last_edit`. There is no `edit` tool and no pi host support: this package target is DeepSeek Harness only. 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 `apply_patch` hunk either names served anchors or matches file content; the model never re-types existing code. Anchors for unchanged lines stay stable across edits, and every successful `apply_patch` call returns fresh anchors for the changed lines, so chained calls need no re-read.
5
+ `dsh-hashline-edit` is a dsh bundle that shadows the host's built-in `read` with hash-anchored output, registers `apply_patch` as the only file-mutation entry point, and adds `undo_last_edit`. The native `write`/`edit`/`str_replace_editor` surface is hidden; there is no pi host support, because this package targets DeepSeek Harness only. 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 `apply_patch` hunk either names served anchors or matches file content; the model never re-types existing code. Anchors for unchanged lines stay stable across edits, and every successful `apply_patch` call returns fresh anchors for the changed lines, so chained calls need no re-read.
6
6
 
7
7
  The semantic core is the [pi-better-edit](https://github.com/Rianico/pi-better-edit) v2.16.0 hashline engine (MIT), carried as an internal engine; the pi host integration has been removed. The `dsh/` tree is this repository's DeepSeek Harness host layer: tool registration, filesystem bridge, UTF-8-only admission, prompt guidance, shell integration, write integration and telemetry. See [`dsh/README.md`](dsh/README.md) for the host-layer map.
8
8
 
@@ -13,7 +13,7 @@ The semantic core is the [pi-better-edit](https://github.com/Rianico/pi-better-e
13
13
  | OS | Windows — this branch targets Windows only (see [`dsh/UPSTREAM.md`](dsh/UPSTREAM.md)) |
14
14
  | Node.js | ≥ 24.0.0 |
15
15
  | pnpm | 12.x |
16
- | DeepSeek Harness | plugin-capable build; the repository is developed against `@deepseek-ai/dsh-*@0.2.2-alpha.3` |
16
+ | DeepSeek Harness | plugin-capable build; the repository is developed against `@deepseek-ai/dsh-*@0.2.2-rc.1` (host packages are declared as peer dependencies) |
17
17
 
18
18
  ## Install
19
19
 
@@ -36,7 +36,7 @@ For a tarball, pack first and add the archive:
36
36
 
37
37
  ```sh
38
38
  pnpm pack
39
- dsh plugin add ./dsh-hashline-edit-0.2.0.tgz
39
+ dsh plugin add ./dsh-hashline-edit-0.2.1.tgz
40
40
  ```
41
41
 
42
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.
@@ -124,7 +124,7 @@ Addressing works in two modes, and one patch may mix them:
124
124
  characters is identity mode. Any rejection leaves every file byte-identical and reports every
125
125
  independent problem with its patch line number and `[E_*`] code. Success returns a file-level summary
126
126
  (A/M/R/D) followed by fresh `HASH│content` anchors for every changed line, so the next call can chain
127
- without a `read`. The host's built-in `write` remains for whole-file rewrites.
127
+ without a `read`. To replace a whole file, use one `*** Update File:` identity hunk spanning every served line.
128
128
  ### `undo_last_edit`
129
129
 
130
130
  ```ts
@@ -158,7 +158,7 @@ The dsh host reads and writes UTF-8. Read admission is one rule with one exempti
158
158
 
159
159
  A file that is not valid UTF-8 is refused with `[E_UNSUPPORTED_FILE]` naming the file path and telling the model to ask the user how to handle it — for example a GBK-encoded source file or a BOM-less UTF-16LE file. The host does not guess a code page, offers no reopen-with-encoding parameter, keeps no encoding/BOM memo, and does not re-encode on save. UTF-16/UTF-32 BOMs are still recognized by kind detection: they are named in the refusal (e.g. `UTF-16LE encoded text`) instead of being decoded. Convert a non-UTF-8 file with an external tool before editing it here.
160
160
 
161
- The built-in `write` integration and `undo_last_edit` speak the same contract: they write UTF-8, and every file this plugin serves is UTF-8.
161
+ `apply_patch` and `undo_last_edit` speak the same contract: they write UTF-8, and every file this plugin serves is UTF-8. The built-in `write` integration keeps the same contract for a profile that deliberately keeps `write` visible.
162
162
  ## Sandbox behavior
163
163
 
164
164
  `apply_patch` 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.
package/cordis.patch.yml CHANGED
@@ -1,8 +1,8 @@
1
1
  # dsh-hashline-edit bundle patch: mount the host-plane plugin row.
2
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 tool and hides its native edit surface on each agent's own scope layer, so no preset
5
- # editing or user config is needed.
3
+ # profile's bundle list). The plugin hooks agent/created, shadows the built-in
4
+ # read tool, and hides the native write/edit/str_replace_editor surface on each
5
+ # agent's own scope layer, so no preset editing or user config is needed.
6
6
  - insert:
7
7
  - id: dsh-hashline-edit
8
8
  name: dsh-hashline-edit
package/lib/index.js CHANGED
@@ -1373,8 +1373,8 @@ function assertRange(offset, length) {
1373
1373
  }
1374
1374
 
1375
1375
  // dsh/hide.ts
1376
- var NATIVE_WRITE_TOOLS = ["edit", "str_replace_editor"];
1377
- var GUIDANCE_SECTIONS = /* @__PURE__ */ new Set(["tool:edit"]);
1376
+ var NATIVE_WRITE_TOOLS = ["write", "edit", "str_replace_editor"];
1377
+ var GUIDANCE_SECTIONS = /* @__PURE__ */ new Set(["tool:write", "tool:edit"]);
1378
1378
  function readBooleanSource(value, fallback) {
1379
1379
  if (typeof value === "boolean") return value;
1380
1380
  if (typeof value === "function") {
@@ -1412,7 +1412,7 @@ function hiddenNames(config) {
1412
1412
  return names.filter((name2) => name2 !== config.toolName);
1413
1413
  }
1414
1414
  function nativeWriteDenial(toolName, name2) {
1415
- return `\`${name2}\` is disabled in this profile: precise and batch text changes go through \`${toolName}\` operations. Collect every planned file change into one call. Reading stays with \`read\`, \`glob\`, and \`grep\`; whole-file replacement stays with \`write\`.`;
1415
+ return `\`${name2}\` is disabled in this profile: precise and batch text changes go through \`${toolName}\` operations. Collect every planned file change into one call. Reading stays with \`read\`, \`glob\`, and \`grep\`; whole-file replacement is one \`*** Update File:\` identity hunk spanning every served line.`;
1416
1416
  }
1417
1417
  function transformAssembly(assembly, hidden) {
1418
1418
  if (hidden.size === 0) return;
@@ -1509,14 +1509,24 @@ function registerNativeToolHiding(rootCtx, agentCtx, config) {
1509
1509
  configured.filter((name2) => toolExists(rootCtx, agentCtx, name2, config.scope))
1510
1510
  );
1511
1511
  const disposers = [];
1512
- if (restrictable.size > 0) {
1513
- try {
1514
- const dispose = agent.tools?.restrict?.({ deny: [...restrictable] });
1515
- if (dispose !== void 0) disposers.push(dispose);
1516
- } catch (error) {
1512
+ if (restrictable.size > 0 || configured.length > 0) {
1513
+ let restrictError;
1514
+ const attemptRestrict = (names) => {
1515
+ if (names.length === 0) return void 0;
1516
+ try {
1517
+ const dispose2 = agent.tools?.restrict?.({ deny: [...names] });
1518
+ return dispose2 === void 0 ? void 0 : dispose2;
1519
+ } catch (error) {
1520
+ restrictError = error;
1521
+ return void 0;
1522
+ }
1523
+ };
1524
+ const dispose = attemptRestrict([...restrictable, config.toolName]) ?? attemptRestrict([...restrictable]);
1525
+ if (dispose !== void 0) disposers.push(dispose);
1526
+ else if (restrictable.size > 0) {
1517
1527
  warn(
1518
1528
  rootCtx,
1519
- `could not restrict native tools (${restrictable.size}): ${error instanceof Error ? error.message : String(error)}`
1529
+ `could not restrict native tools (${restrictable.size}): ${restrictError instanceof Error ? restrictError.message : String(restrictError)}`
1520
1530
  );
1521
1531
  }
1522
1532
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
- * Hide the native edit-class tools so every precise text change goes through
2
+ * Hide the native file-mutation tools so every text change goes through
3
3
  * `apply_patch` — the one mutation entry point of the v4 surface.
4
4
  *
5
- * DSH 0.2.2-alpha.3 owns tool declarations in the tool registry, not in the
5
+ * DSH 0.2.2-rc.1 owns tool declarations in the tool registry, not in the
6
6
  * `system-prompt/assemble` waterfall: the waterfall receives sections, contexts
7
7
  * and variables only. The authoritative schema filter is therefore the
8
8
  * per-agent registry restriction (`tools.restrict({ deny })`), which removes a
@@ -20,14 +20,15 @@
20
20
  * 3. `tools.guard` is the monotonic execution backstop for a stale schema or
21
21
  * any residual binding.
22
22
  *
23
- * `write` stays visible on purpose: whole-file replacement has no operation in
24
- * `apply_patch` (`*** Add File:` refuses to overwrite), so hiding it would leave
25
- * a rewrite with nowhere to go. Callers that want it gone can list it in
26
- * `hiddenToolNames`.
23
+ * `write` is hidden with `edit`/`str_replace_editor`. A whole-file replacement
24
+ * is one `*** Update File:` identity hunk spanning every served line with `+`
25
+ * lines for the new content, so no native mutation tool has to stay visible.
26
+ * The native `apply_patch` name is also denied in the inherited/preset layer
27
+ * when it exists; the plugin's own scoped registration shadows it otherwise.
27
28
  *
28
29
  * The layer is installed only when `apply_patch` actually registered: hiding
29
- * the native editors while owning no replacement would leave the profile with
30
- * no way to change a file.
30
+ * the native mutation tools while owning no replacement would leave the
31
+ * profile with no way to change a file.
31
32
  *
32
33
  * @module dsh-hashline-edit/hide
33
34
  */
@@ -37,7 +38,7 @@ type BooleanSource = boolean | (() => boolean);
37
38
  export interface NativeHideConfig {
38
39
  /** The registered replacement tool name, protected from `hiddenToolNames`. */
39
40
  readonly toolName: string;
40
- /** Hide `edit`/`str_replace_editor`; defaults to true. */
41
+ /** Hide `write`/`edit`/`str_replace_editor`; defaults to true. */
41
42
  readonly hideNativeWriteTools?: BooleanSource;
42
43
  /** Extra global tool names to hide alongside the native set. */
43
44
  readonly hiddenToolNames?: readonly string[];
@@ -4,8 +4,10 @@
4
4
  * On every `agent/created`, install the hashline tools on the AGENT's
5
5
  * own scope layer so they shadow the preset's built-in `read` for that
6
6
  * agent (nearest layer wins in dsh's tool registry) and unwind automatically
7
- * when the agent is disposed. The built-in `write` stays in place; the
8
- * platform lane's write hook integrates with it.
7
+ * when the agent is disposed. The native `write`, `edit` and
8
+ * `str_replace_editor` schemas are hidden on the same scope so `apply_patch`
9
+ * is the only file-mutation entry point; the write hook still integrates a
10
+ * built-in `write` when a profile chooses to keep it visible.
9
11
  *
10
12
  * `inject` deliberately lists only `tools` and `systemPrompt`: the read-path
11
13
  * `ctx.fs` bridge is optional and resolved with `ctx.get`, so a minimal
@@ -26,9 +28,9 @@ export declare const inject: string[];
26
28
  /** The plugin configuration the loader passes as the second `apply` argument. */
27
29
  export interface PluginConfig {
28
30
  /**
29
- * Hide the native `edit`/`str_replace_editor` tools so every precise text
30
- * change goes through `apply_patch`. Defaults to true. `write` is not in
31
- * that set on purpose: whole-file replacement has no apply_patch operation.
31
+ * Hide the native `write`/`edit`/`str_replace_editor` tools so every file
32
+ * mutation goes through `apply_patch`. Defaults to true. A whole-file
33
+ * replacement is one `*** Update File:` identity hunk over the served range.
32
34
  */
33
35
  hideNativeWriteTools?: boolean;
34
36
  /** Extra global tool names to hide alongside the native write set. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-hashline-edit",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Hash-anchored read/apply_patch tools for DeepSeek Harness (dsh). Every line gets a unique 4-char hash (A-Za-z0-9) that stays stable across edits; apply_patch is the only file-mutation entry point and commits one patch batch per transaction. Undo persists across restarts.",
5
5
  "keywords": [
6
6
  "anchors",
@@ -72,12 +72,12 @@
72
72
  "@babel/parser": "7.29.8",
73
73
  "@commitlint/cli": "^21.2.2",
74
74
  "@commitlint/config-conventional": "^21.2.2",
75
- "@deepseek-ai/cordis": "4.0.6-alpha.2",
76
- "@deepseek-ai/dsh-agent": "0.2.2-alpha.3",
77
- "@deepseek-ai/dsh-fs": "0.2.2-alpha.3",
78
- "@deepseek-ai/dsh-home-paths": "0.2.2-alpha.3",
79
- "@deepseek-ai/dsh-sandbox": "0.2.2-alpha.3",
80
- "@deepseek-ai/dsh-tools": "0.2.2-alpha.3",
75
+ "@deepseek-ai/cordis": "4.1.0",
76
+ "@deepseek-ai/dsh-agent": "0.2.2-rc.1",
77
+ "@deepseek-ai/dsh-fs": "0.2.2-rc.1",
78
+ "@deepseek-ai/dsh-home-paths": "0.2.2-rc.1",
79
+ "@deepseek-ai/dsh-sandbox": "0.2.2-rc.1",
80
+ "@deepseek-ai/dsh-tools": "0.2.2-rc.1",
81
81
  "@semantic-release/git": "^11.0.1",
82
82
  "@semantic-release/github": "^12.0.9",
83
83
  "@semantic-release/npm": "^13.1.5",
@@ -93,11 +93,19 @@
93
93
  "vite": "^8.3.5",
94
94
  "vitest": "^4.1.8"
95
95
  },
96
+ "peerDependencies": {
97
+ "@deepseek-ai/cordis": "~4.1.0",
98
+ "@deepseek-ai/dsh-agent": ">=0.2.2-rc.1 <0.2.3-0",
99
+ "@deepseek-ai/dsh-fs": ">=0.2.2-rc.1 <0.2.3-0",
100
+ "@deepseek-ai/dsh-home-paths": ">=0.2.2-rc.1 <0.2.3-0",
101
+ "@deepseek-ai/dsh-sandbox": ">=0.2.2-rc.1 <0.2.3-0",
102
+ "@deepseek-ai/dsh-tools": ">=0.2.2-rc.1 <0.2.3-0"
103
+ },
96
104
  "optionalDependencies": {
97
105
  "web-tree-sitter": "0.27.1"
98
106
  },
99
107
  "engines": {
100
- "dsh": ">=0.2.2-alpha.3 <0.2.3-0",
108
+ "dsh": ">=0.2.2-rc.1 <0.2.3-0",
101
109
  "node": ">=24.0.0"
102
110
  },
103
111
  "packageManager": "pnpm@12.0.0",
@@ -7,4 +7,4 @@
7
7
  - apply_patch: lifecycle joins the same transaction and undo. `*** Add File:` content lines all start with `+`; `*** Delete File:` removes the file; `*** Move to:` must immediately follow `*** Update File:` and renames before its hunks apply; an existing move target rejects.
8
8
  - apply_patch: a rejection writes nothing. Read the `- patch line N ([E_*], path): ...` lines, then use the immediately following `HASH│content` block — those are the current rows and anchors; retry directly without issuing a separate `read`.
9
9
  - apply_patch: a success starts with `Updated N files:` and ends with the changed lines' fresh anchors per file; use those anchors directly in the next identity hunk. `[W_*]` lines are informational.
10
- - apply_patch: paths are workspace-relative or workspace-absolute; `..`, `.git`, and symlink targets reject. Whole-file replacement stays with `write`.
10
+ - apply_patch: paths are workspace-relative or workspace-absolute; `..`, `.git`, and symlink targets reject. Whole-file replacement is one `*** Update File:` identity hunk spanning every served line with `+` lines for the new content.
@@ -1 +1 @@
1
- Apply one Codex-format patch to one or more files in a single transaction: `{ input: string }`. `input` is the complete patch text: `*** Begin Patch` ... `*** End Patch`, with `*** Add File:`, `*** Delete File:`, `*** Update File:` (plus optional `*** Move to:`) and hunks addressed either by content (`@@` or `@@ <context>` with ` `/`-`/`+` lines) or by served anchors (`@@<a>` / `@@<a>-<b>` with `+` lines only). `*** Copy From:` / `*** Cut From:` copy or move a served span from another file. Every target validates before anything is written; a rejection changes nothing and returns the current rows with anchors. On success the result lists the changed files and the new anchors for the changed lines, so a follow-up call needs no `read`. `write` stays for whole-file replacement.
1
+ Apply one Codex-format patch to one or more files in a single transaction: `{ input: string }`. `input` is the complete patch text: `*** Begin Patch` ... `*** End Patch`, with `*** Add File:`, `*** Delete File:`, `*** Update File:` (plus optional `*** Move to:`) and hunks addressed either by content (`@@` or `@@ <context>` with ` `/`-`/`+` lines) or by served anchors (`@@<a>` / `@@<a>-<b>` with `+` lines only). `*** Copy From:` / `*** Cut From:` copy or move a served span from another file. Every target validates before anything is written; a rejection changes nothing and returns the current rows with anchors. On success the result lists the changed files and the new anchors for the changed lines, so a follow-up call needs no `read`. To replace a whole file, use `*** Update File:` with one identity hunk spanning every served line and `+` lines for the new content.