dsh-engramory 0.2.5 → 0.2.7

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 CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Beijing Tinqiao Technology Co., Ltd.
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Beijing Tinqiao Technology Co., Ltd.
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,134 +1,154 @@
1
- [English](README.md) | [简体中文](README.zh-CN.md)
2
-
3
- # dsh-engramory
4
-
5
- [![dsh-xray](https://img.shields.io/endpoint?url=https%3A%2F%2Funstone.github.io%2Fdsh-xray%2Fbadge%2Ftinqiao-oss__engramory.json)](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
6
-
7
- <sub>`C2` is the level `manifest.bundle.patch` puts every mountable dsh plugin at (74.6% of the scanned ecosystem); it is the only flag on this card — no `exec`, `eval`, install script, outbound domain, or environment read.</sub>
8
-
9
- Curated, file-based long-term memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) —
10
- plain markdown notes you can open with anything, one store shared across every host you
11
- use, and an index cap that is a **real refusal** rather than a request. (If the store
12
- lives inside a project's git repo it must be git-ignored — memories carry machine-local
13
- detail; see SKILL.md §1. A *dedicated private* repo for the store itself is fine.)
14
-
15
- Part of [Engramory](https://github.com/tinqiao-oss/engramory).
16
-
17
- ## Why a plugin, when an `AGENTS.md` block already works
18
-
19
- The block does carry the discipline, and for recall that is enough. Two things it cannot
20
- do:
21
-
22
- **The cap becomes deterministic.** Engramory keeps its index under 200 lines / 25 KB
23
- because the index loads every session and everything past the cap silently stops being
24
- recalled. On most hosts that limit is rules plus a checker the agent has to remember to
25
- run. dsh exposes `ctx.tools.guard()` — a synchronous, **monotonic** refusal: once a guard
26
- returns a reason, no later listener can turn it back into an allow. So here the limit is
27
- enforced, not requested. Use **0.2.1 or later**: 0.2.0 shipped before profile installs
28
- worked upstream and carried an `inject` declaration this Cordis does not accept — it
29
- installed but never activated (issue #8). Outside Claude Code, dsh is the first host with
30
- the shim actually written (a few others expose equivalent pre-write seams — see
31
- PORTING.md — but have no shim yet).
32
-
33
- **The protocol arrives with the plugin.** The skill is registered at runtime through
34
- dsh's skill registry — via a reactive `ctx.inject(['skills'], …)` child, so a profile
35
- without a registry still gets the cap and one whose registry activates late still gets
36
- the skill — which means it does not depend on landing files in one of the five skill
37
- roots dsh scans, a path that is easy to get subtly wrong and fails silently when you do.
38
-
39
- ## Install
40
-
41
- ```sh
42
- dsh plugin --profile <name> add dsh-engramory
43
- ```
44
-
45
- That is the whole install. The package ships a `dsh.bundle` manifest pointing at its own
46
- `cordis.patch.yml`, so the row is inserted into the profile's plugin tree for you — no
47
- hand-editing. To change a default, patch the EXISTING row by id in your profile's own
48
- patch layer — do **not** use a second `- insert:` (insert always appends, so you would
49
- end up with two engramory rows and the original caps still enforced):
50
-
51
- ```yaml
52
- - id: engramory
53
- config:
54
- indexName: MEMORY.md
55
- maxLines: 200
56
- maxBytes: 25600
57
- ```
58
-
59
- A patch replaces the targeted row's whole `config`, so list every key you mean to keep.
60
-
61
- For the store itself and the always-on block, use the installer in the Engramory repo
62
- (`python tools/engramory_init.py dsh --install-skill`). This plugin enforces the cap and
63
- supplies the protocol; it does not create the store.
64
-
65
- ## Configuration
66
-
67
- | Field | Default | Meaning |
68
- |---|---|---|
69
- | `indexName` | `MEMORY.md` | Basename treated as the memory index (case-insensitive; both `file_path` and `str_replace_editor`'s `path` are checked). An empty or non-string value falls back to the default. Nothing else is inspected. |
70
- | `maxLines` | `200` | Hard line cap. Non-positive or non-finite values fall back to the default. |
71
- | `maxBytes` | `25600` | Hard UTF-8 byte cap (25 KB). |
72
- | `indexPath` | unset | Absolute path of the ONE index to guard. Without it the guard matches on basename alone, so an unrelated `MEMORY.md` in another project is gated too; set this and only that exact file is. Compared by identity (symlinks and `..` resolved; case-folded on Windows only), and a path that does not exist yet still has its first write guarded. |
73
- | `registerSkill` | `true` | Set `false` to keep the cap but skip the runtime skill. |
74
- | `skill` | built-in | Replace the skill body with your own markdown. |
75
-
76
- ## What the guard actually does
77
-
78
- | Call | Decision |
79
- |---|---|
80
- | `write` (or `str_replace_editor` `create`) ending within caps | allow |
81
- | `write`/`create` that GROWS the index past a cap | **deny**, naming the numbers and what to compact |
82
- | `write`/`create` that SHRINKS or keeps an over-cap index | allow — incremental compaction (210 → 205 → 198) must stay possible |
83
- | Edit carrying `old_str`/`new_str`: result simulated | judged by the RESULT, same grow/shrink rule as a write |
84
- | Unsimulable partial (e.g. `insert`) on an over-cap index | **deny**, telling the agent a shrinking whole-file write passes |
85
- | Unsimulable partial on a healthy index | allow |
86
- | `read`, `view`, or any unknown tool | allow — recall must never be blocked, even over cap |
87
- | Any write to any other file | allow — not the guard's business |
88
- | Missing/unreadable current index on a whole-file write | treated as EMPTY (mirrors the Python guard) — a within-caps write passes, an over-cap first write is refused |
89
- | Missing/unreadable index on a partial edit | allow — a guard must never block work over a path it cannot read |
90
- | Malformed execution (no arguments, non-string path/content) | allow — not recognisably a write |
91
-
92
- The deny rule mirrors `hooks/engramory_index_guard.py` exactly: refuse only a result
93
- that is over a cap AND grew past the current file. The line count ignores a trailing
94
- newline, so an index sitting exactly at the cap stays writable.
95
-
96
- ## Known limits
97
-
98
- - **An unsimulable partial that crosses the cap from under it is not caught.** `insert`
99
- and friends do not carry the resulting text. The breach is caught by the next
100
- whole-file write, or by `engramory_check.py`. What is guaranteed: an already-over
101
- index cannot be grown further, and a shrinking write always passes.
102
- - **The tool roster follows dsh's documented tool-fs contract** (`write`/`edit` with
103
- `file_path`, `str_replace_editor` with `path`) and is deliberately conservative:
104
- unknown tools pass.
105
- - **0.2.0 never activated; 0.2.1 is the first version that runs.** While the rc.6
106
- preview bug blocked all third-party profile installs upstream, the only coverage a
107
- plugin could have was mocks — and 0.2.0's mock hid two activation bugs: an
108
- older-Cordis `{ required, optional }` inject shape (read as waiting for services
109
- literally named `required`/`optional`, pending forever) and a bare `ctx.skills` read
110
- of an undeclared service (throws under Cordis' reflective context). The first field
111
- install caught both the moment installs became possible (issue #8). 0.2.1 fixes them,
112
- verified end to end on a live dsh 0.1.0-rc.7 web profile (0.2.0 reproduces the boot
113
- failure byte for byte; 0.2.1 boots and serves) and against the vendored
114
- `@deepseek-ai/cordis` resolver (activation with, without, and with a late-mounted
115
- skill registry). The test mock now mirrors the reflective-context access rules, and
116
- the guard's decision table stays covered by `node --test` (28 cases, run in
117
- Engramory's CI).
118
- - **Checked against the dsh you are likely running, not only the one it was written
119
- on.** `tests/dsh_e2e/run.py` in the Engramory repo installs a dsh release into a
120
- throwaway prefix, adds this plugin with `dsh plugin add`, and drives one headless
121
- task against a scripted stand-in for the DeepSeek API: a 260-line write and an edit
122
- past the cap must come back refused by this guard with nothing reaching the disk, a
123
- small write must land byte for byte, the skill must be advertised, every tool dsh
124
- offers must already be classified (a new one could write past the cap), and removal
125
- must leave a profile that still boots. The releases it passed on are listed in
126
- `package.json` under `dsh.compatibility.dshReleases`; a unit test rejects a
127
- `compatible` there without a recorded run of the same plugin version and code.
128
- - dsh is a developer preview and its plugin API can change. This plugin deliberately
129
- touches only `ctx.tools.guard()` and a reactive `ctx.inject(['skills'], …)` child
130
- that registers the skill, so it stays cheap to fix.
131
-
132
- ## License
133
-
134
- MIT — see the [Engramory repository](https://github.com/tinqiao-oss/engramory).
1
+ [English](README.md) | [简体中文](README.zh-CN.md)
2
+
3
+ # dsh-engramory
4
+
5
+ [![dsh-xray](https://img.shields.io/endpoint?url=https%3A%2F%2Funstone.github.io%2Fdsh-xray%2Fbadge%2Ftinqiao-oss__engramory.json)](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
6
+
7
+ <sub>`C2` is the level `manifest.bundle.patch` puts every mountable dsh plugin at (74.6% of the scanned ecosystem); it is the only flag on this card — no `exec`, `eval`, install script, outbound domain, or environment read.</sub>
8
+
9
+ Curated, file-based long-term memory for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) —
10
+ plain markdown notes you can open with anything, one store shared across every host you
11
+ use, and an index cap that is a **real refusal** rather than a request. (If the store
12
+ lives inside a project's git repo it must be git-ignored — memories carry machine-local
13
+ detail; see SKILL.md §1. A *dedicated private* repo for the store itself is fine.)
14
+
15
+ Part of [Engramory](https://github.com/tinqiao-oss/engramory).
16
+
17
+ ## Why a plugin, when an `AGENTS.md` block already works
18
+
19
+ The block does carry the discipline, and for recall that is enough. Two things it cannot
20
+ do:
21
+
22
+ **The cap becomes deterministic.** Engramory keeps its index under 200 lines / 25 KB
23
+ because the index loads every session and everything past the cap silently stops being
24
+ recalled. On most hosts that limit is rules plus a checker the agent has to remember to
25
+ run. dsh exposes `ctx.tools.guard()` — a synchronous, **monotonic** refusal: once a guard
26
+ returns a reason, no later listener can turn it back into an allow. So here the limit is
27
+ enforced, not requested. Use **0.2.7 or later**: 0.2.0 shipped before profile installs
28
+ worked upstream and carried an `inject` declaration this Cordis does not accept — it
29
+ installed but never activated (issue #8) — and up to 0.2.5 an edit could slip past the
30
+ cap on a CRLF index, or on dsh 0.2 through a relative path after a cd, and up to 0.2.6
31
+ through a symlink under another name — or a FIFO named like the index could hang the host
32
+ (#20, #21; see Known limits). Outside Claude Code, dsh is the first host with
33
+ the shim actually written (a few others expose equivalent pre-write seams — see
34
+ PORTING.md — but have no shim yet).
35
+
36
+ **The protocol arrives with the plugin.** The skill is registered at runtime through
37
+ dsh's skill registry — via a reactive `ctx.inject(['skills'], …)` child, so a profile
38
+ without a registry still gets the cap and one whose registry activates late still gets
39
+ the skill — which means it does not depend on landing files in one of the five skill
40
+ roots dsh scans, a path that is easy to get subtly wrong and fails silently when you do.
41
+
42
+ ## Install
43
+
44
+ ```sh
45
+ dsh plugin --profile <name> add dsh-engramory
46
+ ```
47
+
48
+ That is the whole install. The package ships a `dsh.bundle` manifest pointing at its own
49
+ `cordis.patch.yml`, so the row is inserted into the profile's plugin tree for you — no
50
+ hand-editing. To change a default, patch the EXISTING row by id in your profile's own
51
+ patch layer — do **not** use a second `- insert:` (insert always appends, so you would
52
+ end up with two engramory rows and the original caps still enforced):
53
+
54
+ ```yaml
55
+ - id: engramory
56
+ config:
57
+ indexName: MEMORY.md
58
+ maxLines: 200
59
+ maxBytes: 25600
60
+ ```
61
+
62
+ A patch replaces the targeted row's whole `config`, so list every key you mean to keep.
63
+
64
+ For the store itself and the always-on block, use the installer in the Engramory repo
65
+ (`python tools/engramory_init.py dsh --install-skill`). This plugin enforces the cap and
66
+ supplies the protocol; it does not create the store.
67
+
68
+ ## Configuration
69
+
70
+ | Field | Default | Meaning |
71
+ |---|---|---|
72
+ | `indexName` | `MEMORY.md` | Basename treated as the memory index (case-insensitive; both `file_path` and `str_replace_editor`'s `path` are checked) — the name a write is called by, or the name of the file it lands on, so a symlink under another name is caught too. An empty or non-string value falls back to the default. |
73
+ | `maxLines` | `200` | Hard line cap. Non-positive or non-finite values fall back to the default. |
74
+ | `maxBytes` | `25600` | Hard UTF-8 byte cap (25 KB). |
75
+ | `indexPath` | unset | Absolute path of the ONE index to guard. Without it the guard matches on basename alone, so an unrelated `MEMORY.md` in another project is gated too; set this and only that exact file is. Compared by identity — the file the write lands on, whatever it is called (symlinks and `..` resolved; case-folded on Windows only) — and a path that does not exist yet still has its first write guarded. |
76
+ | `registerSkill` | `true` | Set `false` to keep the cap but skip the runtime skill. |
77
+ | `skill` | built-in | Replace the skill body with your own markdown. |
78
+
79
+ ## What the guard actually does
80
+
81
+ | Call | Decision |
82
+ |---|---|
83
+ | `write` (or `str_replace_editor` `create`) ending within caps | allow |
84
+ | `write`/`create` that GROWS the index past a cap | **deny**, naming the numbers and what to compact |
85
+ | `write`/`create` that SHRINKS or keeps an over-cap index | allow — incremental compaction (210 → 205 → 198) must stay possible |
86
+ | Edit carrying `old_str`/`new_str`: result simulated | judged by the RESULT, same grow/shrink rule as a write — sized before it is built, so a `replace_all` whose result would be too long to build is refused by its size alone |
87
+ | Unsimulable partial (e.g. `insert`) on an over-cap index | **deny**, telling the agent a shrinking whole-file write passes |
88
+ | Unsimulable partial on a healthy index | allow |
89
+ | `read`, `view`, or any unknown tool | allow — recall must never be blocked, even over cap |
90
+ | Any write to any other file | allow — not the guard's business |
91
+ | Missing/unreadable current index on a whole-file write | treated as EMPTY (mirrors the Python guard) — a within-caps write passes, an over-cap first write is refused |
92
+ | Missing/unreadable index on a partial edit | allow — a guard must never block work over a path it cannot read |
93
+ | A FIFO, device, or directory under the index's name | never read — counts as unreadable (a read there would hang or flood the host, which runs guards on its main thread) |
94
+ | An index past the read limit (1 MiB, or 4× `maxBytes`, at most 16 MiB) | not read whole: a whole-file write is judged on bytes against the filesystem's size (every shrink passes; its line count is unknown, so lines are not capped there), and an edit passes unmeasured, as on an unreadable index |
95
+ | Malformed execution (no arguments, non-string path/content) | allow — not recognisably a write |
96
+
97
+ The deny rule mirrors `hooks/engramory_index_guard.py` exactly: refuse only a result
98
+ that is over a cap AND grew past the current file. The line count ignores a trailing
99
+ newline, so an index sitting exactly at the cap stays writable.
100
+
101
+ ## Known limits
102
+
103
+ - **An unsimulable partial that crosses the cap from under it is not caught.** `insert`
104
+ and friends do not carry the resulting text. The breach is caught by the next
105
+ whole-file write, or by `engramory_check.py`. What is guaranteed: an already-over
106
+ index cannot be grown further, and a shrinking write always passes.
107
+ - **The tool roster follows dsh's documented tool-fs contract** (`write`/`edit` with
108
+ `file_path`, `str_replace_editor` with `path`) and is deliberately conservative:
109
+ unknown tools pass.
110
+ - **0.2.0 never activated; 0.2.1 is the first version that runs.** While the rc.6
111
+ preview bug blocked all third-party profile installs upstream, the only coverage a
112
+ plugin could have was mocks — and 0.2.0's mock hid two activation bugs: an
113
+ older-Cordis `{ required, optional }` inject shape (read as waiting for services
114
+ literally named `required`/`optional`, pending forever) and a bare `ctx.skills` read
115
+ of an undeclared service (throws under Cordis' reflective context). The first field
116
+ install caught both the moment installs became possible (issue #8). 0.2.1 fixes them,
117
+ verified end to end on a live dsh 0.1.0-rc.7 web profile (0.2.0 reproduces the boot
118
+ failure byte for byte; 0.2.1 boots and serves) and against the vendored
119
+ `@deepseek-ai/cordis` resolver (activation with, without, and with a late-mounted
120
+ skill registry). The test mock now mirrors the reflective-context access rules, and
121
+ the guard's decision table stays covered by `node --test` (47 cases, run in
122
+ Engramory's CI).
123
+ - **A relative path is measured where dsh writes it.** dsh 0.2 gives every session its
124
+ own working directory (moved by its `working_directory` tool) and resolves a
125
+ relative `file_path` against that. Up to 0.2.5 the guard resolved against the
126
+ process's cwd instead, so after a cd an edit of a bare `MEMORY.md` read no current
127
+ file and went through unmeasured — on dsh 0.2.1-rc.2 it grew the index to 253
128
+ lines. 0.2.6 reads the session's directory from dsh's `workingDirectory` service
129
+ when the profile has one, and falls back to the process's cwd when it does not
130
+ (every 0.1.x release).
131
+ - **A CRLF index is edited the way dsh edits it.** dsh matches an edit on
132
+ LF-normalised text and writes the result back in the file's own line endings. Up
133
+ to 0.2.5 the guard matched the raw bytes, so on a CRLF `MEMORY.md` — the usual
134
+ shape on Windows — an LF `old_string` looked absent, the edit was predicted to
135
+ change nothing, and dsh applied it. 0.2.6 normalises the same way and sizes a CRLF
136
+ index in CRLF bytes.
137
+ - **Checked against the dsh you are likely running, not only the one it was written
138
+ on.** `tests/dsh_e2e/run.py` in the Engramory repo installs a dsh release into a
139
+ throwaway prefix, adds this plugin with `dsh plugin add`, and drives one headless
140
+ task against a scripted stand-in for the DeepSeek API: a 260-line write and an edit
141
+ past the cap must come back refused by this guard with nothing reaching the disk, a
142
+ small write must land byte for byte, the skill must be advertised, every tool dsh
143
+ offers must already be classified (a new one could write past the cap), and removal
144
+ must leave a profile that still boots. The releases it passed on are listed in
145
+ `package.json` under `dsh.compatibility.dshReleases`; a unit test rejects a
146
+ `compatible` there without a recorded run of the same plugin version and code.
147
+ - dsh is a developer preview and its plugin API can change. This plugin deliberately
148
+ touches only `ctx.tools.guard()` and two reactive `ctx.inject()` children — one
149
+ registers the skill, one reads each session's working directory — so it stays cheap
150
+ to fix.
151
+
152
+ ## License
153
+
154
+ MIT — see the [Engramory repository](https://github.com/tinqiao-oss/engramory).