dsh-engramory 0.2.6 → 0.2.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -73
- package/README.zh-CN.md +54 -54
- package/index.js +884 -530
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
|
|
6
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
7
|
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,
|
|
11
|
-
|
|
8
|
+
plain markdown notes you can open with anything,
|
|
9
|
+
[one writer and many readers](../../reader/README.md) across hosts, and a pre-write
|
|
10
|
+
index guard for matched file tools. (If the store
|
|
12
11
|
lives inside a project's git repo it must be git-ignored — memories carry machine-local
|
|
13
12
|
detail; see SKILL.md §1. A *dedicated private* repo for the store itself is fine.)
|
|
14
13
|
|
|
@@ -16,20 +15,24 @@ Part of [Engramory](https://github.com/tinqiao-oss/engramory).
|
|
|
16
15
|
|
|
17
16
|
## Why a plugin, when an `AGENTS.md` block already works
|
|
18
17
|
|
|
19
|
-
The block
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
18
|
+
The block carries the discipline. Three things it cannot do:
|
|
19
|
+
|
|
20
|
+
**The index is already in context — recall needs no read.** On every step the plugin
|
|
21
|
+
puts the memory index into dsh's runtime context: the project store
|
|
22
|
+
(`<project>/.engramory-memory/MEMORY.md`, the nearest one between the session's
|
|
23
|
+
directory and its git root — the way dsh finds a project's `AGENTS.md`) and the global one
|
|
24
|
+
(`$DSH_HOME/.engramory-memory/MEMORY.md`, `~/.dsh` when `$DSH_HOME` is unset) —
|
|
25
|
+
where `tools/engramory_init.py dsh` puts them — or, when set, `indexPath` alone. It is
|
|
26
|
+
re-read whenever it changes, so a write shows on the next request, and cut at the cap:
|
|
27
|
+
lines past it are left out, and the model is told to compact. Turn it off with
|
|
28
|
+
`injectIndex: false`.
|
|
29
|
+
|
|
30
|
+
**The cap becomes deterministic for matched tools.** `ctx.tools.guard()` is a
|
|
31
|
+
synchronous, **monotonic** refusal: once a guard returns a reason, later listeners
|
|
32
|
+
cannot allow the call. This keeps the index within its configured growth limits
|
|
33
|
+
without relying on the agent to run a checker. Use **`dsh-engramory >= 0.2.7`**;
|
|
34
|
+
[PORTING.md](../../../PORTING.md) records which hosts have shipped guards, and
|
|
35
|
+
[CHANGELOG.md](../../../CHANGELOG.md) holds release history.
|
|
33
36
|
|
|
34
37
|
**The protocol arrives with the plugin.** The skill is registered at runtime through
|
|
35
38
|
dsh's skill registry — via a reactive `ctx.inject(['skills'], …)` child, so a profile
|
|
@@ -39,11 +42,18 @@ roots dsh scans, a path that is easy to get subtly wrong and fails silently when
|
|
|
39
42
|
|
|
40
43
|
## Install
|
|
41
44
|
|
|
45
|
+
Zero-dependency ESM with no build step; requires Node.js 18+ and `pnpm` on PATH
|
|
46
|
+
for dsh's plugin command.
|
|
47
|
+
|
|
42
48
|
```sh
|
|
43
49
|
dsh plugin --profile <name> add dsh-engramory
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
|
|
52
|
+
Check `dsh.compatibility.dshReleases` in [`package.json`](package.json) for the
|
|
53
|
+
exact compatible releases, backed by [`results.json`](../../../tests/dsh_e2e/results.json).
|
|
54
|
+
Use the [E2E guide](../../../tests/dsh_e2e/README.md) to verify another release.
|
|
55
|
+
|
|
56
|
+
The package ships a `dsh.bundle` manifest pointing at its own
|
|
47
57
|
`cordis.patch.yml`, so the row is inserted into the profile's plugin tree for you — no
|
|
48
58
|
hand-editing. To change a default, patch the EXISTING row by id in your profile's own
|
|
49
59
|
patch layer — do **not** use a second `- insert:` (insert always appends, so you would
|
|
@@ -63,15 +73,22 @@ For the store itself and the always-on block, use the installer in the Engramory
|
|
|
63
73
|
(`python tools/engramory_init.py dsh --install-skill`). This plugin enforces the cap and
|
|
64
74
|
supplies the protocol; it does not create the store.
|
|
65
75
|
|
|
76
|
+
To uninstall:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
dsh plugin --profile <name> remove dsh-engramory
|
|
80
|
+
```
|
|
81
|
+
|
|
66
82
|
## Configuration
|
|
67
83
|
|
|
68
84
|
| Field | Default | Meaning |
|
|
69
85
|
|---|---|---|
|
|
70
|
-
| `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.
|
|
86
|
+
| `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. |
|
|
71
87
|
| `maxLines` | `200` | Hard line cap. Non-positive or non-finite values fall back to the default. |
|
|
72
88
|
| `maxBytes` | `25600` | Hard UTF-8 byte cap (25 KB). |
|
|
73
|
-
| `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)
|
|
89
|
+
| `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. |
|
|
74
90
|
| `registerSkill` | `true` | Set `false` to keep the cap but skip the runtime skill. |
|
|
91
|
+
| `injectIndex` | `true` | Put the memory index into dsh's runtime context on every step (see above). Set `false` to recall by reading the file instead. |
|
|
75
92
|
| `skill` | built-in | Replace the skill body with your own markdown. |
|
|
76
93
|
|
|
77
94
|
## What the guard actually does
|
|
@@ -81,69 +98,48 @@ supplies the protocol; it does not create the store.
|
|
|
81
98
|
| `write` (or `str_replace_editor` `create`) ending within caps | allow |
|
|
82
99
|
| `write`/`create` that GROWS the index past a cap | **deny**, naming the numbers and what to compact |
|
|
83
100
|
| `write`/`create` that SHRINKS or keeps an over-cap index | allow — incremental compaction (210 → 205 → 198) must stay possible |
|
|
84
|
-
| Edit carrying `old_str`/`new_str`: result simulated | judged by the RESULT, same grow/shrink rule as a write |
|
|
101
|
+
| 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 judged on lower bounds of its bytes and lines |
|
|
85
102
|
| Unsimulable partial (e.g. `insert`) on an over-cap index | **deny**, telling the agent a shrinking whole-file write passes |
|
|
86
103
|
| Unsimulable partial on a healthy index | allow |
|
|
87
104
|
| `read`, `view`, or any unknown tool | allow — recall must never be blocked, even over cap |
|
|
88
105
|
| Any write to any other file | allow — not the guard's business |
|
|
89
106
|
| 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 |
|
|
90
107
|
| Missing/unreadable index on a partial edit | allow — a guard must never block work over a path it cannot read |
|
|
108
|
+
| 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) |
|
|
109
|
+
| An index past the read limit (`min(16 MiB, max(1 MiB, 4 × maxBytes))`) | 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 |
|
|
91
110
|
| Malformed execution (no arguments, non-string path/content) | allow — not recognisably a write |
|
|
92
111
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
For measured results, deny only a dimension that is over its cap AND grew past
|
|
113
|
+
the current file. The line count ignores a trailing newline, so an index exactly
|
|
114
|
+
at the cap stays writable. This decision table is pinned by `node --test` in CI.
|
|
115
|
+
|
|
116
|
+
Relative paths use the session's `workingDirectory` service when available,
|
|
117
|
+
falling back to the process cwd. Edits match LF-normalised text and preserve the
|
|
118
|
+
file's line endings when sizing the result, including CRLF bytes.
|
|
96
119
|
|
|
97
120
|
## Known limits
|
|
98
121
|
|
|
99
|
-
- **
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- **
|
|
104
|
-
`
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
own working directory (moved by its `working_directory` tool) and resolves a
|
|
121
|
-
relative `file_path` against that. Up to 0.2.5 the guard resolved against the
|
|
122
|
-
process's cwd instead, so after a cd an edit of a bare `MEMORY.md` read no current
|
|
123
|
-
file and went through unmeasured — on dsh 0.2.1-rc.2 it grew the index to 253
|
|
124
|
-
lines. 0.2.6 reads the session's directory from dsh's `workingDirectory` service
|
|
125
|
-
when the profile has one, and falls back to the process's cwd when it does not
|
|
126
|
-
(every 0.1.x release).
|
|
127
|
-
- **A CRLF index is edited the way dsh edits it.** dsh matches an edit on
|
|
128
|
-
LF-normalised text and writes the result back in the file's own line endings. Up
|
|
129
|
-
to 0.2.5 the guard matched the raw bytes, so on a CRLF `MEMORY.md` — the usual
|
|
130
|
-
shape on Windows — an LF `old_string` looked absent, the edit was predicted to
|
|
131
|
-
change nothing, and dsh applied it. 0.2.6 normalises the same way and sizes a CRLF
|
|
132
|
-
index in CRLF bytes.
|
|
133
|
-
- **Checked against the dsh you are likely running, not only the one it was written
|
|
134
|
-
on.** `tests/dsh_e2e/run.py` in the Engramory repo installs a dsh release into a
|
|
135
|
-
throwaway prefix, adds this plugin with `dsh plugin add`, and drives one headless
|
|
136
|
-
task against a scripted stand-in for the DeepSeek API: a 260-line write and an edit
|
|
137
|
-
past the cap must come back refused by this guard with nothing reaching the disk, a
|
|
138
|
-
small write must land byte for byte, the skill must be advertised, every tool dsh
|
|
139
|
-
offers must already be classified (a new one could write past the cap), and removal
|
|
140
|
-
must leave a profile that still boots. The releases it passed on are listed in
|
|
141
|
-
`package.json` under `dsh.compatibility.dshReleases`; a unit test rejects a
|
|
142
|
-
`compatible` there without a recorded run of the same plugin version and code.
|
|
143
|
-
- dsh is a developer preview and its plugin API can change. This plugin deliberately
|
|
144
|
-
touches only `ctx.tools.guard()` and two reactive `ctx.inject()` children — one
|
|
145
|
-
registers the skill, one reads each session's working directory — so it stays cheap
|
|
146
|
-
to fix.
|
|
122
|
+
- **Unsimulable partial edits can cross the cap from below.** On a readable,
|
|
123
|
+
already-over-cap index they are denied; use a shrinking whole-file write to
|
|
124
|
+
compact. Missing/unreadable files and the read-limit exceptions are listed above.
|
|
125
|
+
Run `engramory_check.py` after index edits and `engramory_doctor.py` periodically.
|
|
126
|
+
- **Coverage is limited to known file tools:** `write`/`edit` with `file_path`,
|
|
127
|
+
`str_replace`/`insert`, and mutating `str_replace_editor` commands with `path`.
|
|
128
|
+
Unknown tools pass. **Shell tools, MCP file tools, and external editors bypass
|
|
129
|
+
the guard.**
|
|
130
|
+
- **Injection finds the stores where the installer puts them.** A store elsewhere
|
|
131
|
+
(`--memory-root`) needs `indexPath`. The project root is found by dsh's default `.git`
|
|
132
|
+
marker only (a profile's custom `projectRootMarkers` are not visible to a plugin), and
|
|
133
|
+
a project store created mid-session can take up to ten seconds to appear. A project
|
|
134
|
+
store whose real path leaves the project — a symlink out of the checkout — is not
|
|
135
|
+
read; point `indexPath` at it if that is deliberate. The global store's location comes
|
|
136
|
+
from `$DSH_HOME`, resolved the way dsh resolves it. What is injected is framed as notes,
|
|
137
|
+
not instructions, that may be stale; a host without runtime context
|
|
138
|
+
(no `ctx.systemPrompt.context`) goes without it.
|
|
139
|
+
- dsh's plugin API can change. The plugin uses `ctx.tools.guard()` and three reactive
|
|
140
|
+
`ctx.inject()` children for the skill registry, the session working directory, and
|
|
141
|
+
the prompt registry; a compatible-release declaration needs recorded host evidence,
|
|
142
|
+
not just unit tests.
|
|
147
143
|
|
|
148
144
|
## License
|
|
149
145
|
|
package/README.zh-CN.md
CHANGED
|
@@ -4,28 +4,29 @@
|
|
|
4
4
|
|
|
5
5
|
[](https://unstone.github.io/dsh-xray/registry.html#tinqiao-oss__engramory)
|
|
6
6
|
|
|
7
|
-
<sub>`C2` 是 `manifest.bundle.patch` 给每个可挂载 dsh 插件定的档位(被扫描生态里 74.6% 都在这一档);它是这张卡片上**唯一**的标记 —— 没有 `exec`、没有 `eval`、没有安装脚本、没有外连域名、不读环境变量。</sub>
|
|
8
|
-
|
|
9
7
|
给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 用的**策展式、文件式**长期记忆 ——
|
|
10
|
-
纯 markdown
|
|
11
|
-
|
|
8
|
+
纯 markdown 笔记,随便什么工具都能打开;跨宿主共享遵循
|
|
9
|
+
[一个写入者、多个只读者](../../reader/README.md),索引守卫会在匹配的文件工具写入前检查上限。(记忆库若位于某个项目的 git 仓库内,必须 git-ignore ——
|
|
12
10
|
记忆里带着本机独有的细节,见 SKILL.md §1。给记忆库单独开一个*私有仓*则完全可以。)
|
|
13
11
|
|
|
14
12
|
隶属于 [Engramory](https://github.com/tinqiao-oss/engramory)。
|
|
15
13
|
|
|
16
14
|
## 已经有 `AGENTS.md` 常驻块了,为什么还要插件
|
|
17
15
|
|
|
18
|
-
|
|
16
|
+
那个常驻块承载了纪律。但有三件事它做不到:
|
|
17
|
+
|
|
18
|
+
**索引已经在上下文里 —— 召回不用再读文件。** 插件在每一步都把记忆索引放进 dsh 的运行时
|
|
19
|
+
上下文:项目库(`<项目>/.engramory-memory/MEMORY.md`,取会话目录到 git 根目录之间离会话最近的那个,和 dsh 找项目 `AGENTS.md` 的方式一致)和全局库
|
|
20
|
+
(`$DSH_HOME/.engramory-memory/MEMORY.md`,未设 `$DSH_HOME` 时为 `~/.dsh`)—— 正是
|
|
21
|
+
`tools/engramory_init.py dsh` 建库的位置;设了 `indexPath` 时则只注入它。内容一变就重新读取,
|
|
22
|
+
所以写入后下一轮请求就能看到;超过上限的行会被截掉,并提示模型去压缩。用
|
|
23
|
+
`injectIndex: false` 关闭。
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
没打通,而且它的 `inject` 声明是这版 Cordis 不接受的写法 —— 装得上,但**永远激活不了**
|
|
26
|
-
(issue #8);0.2.5 及以前,在 CRLF 换行的索引上、或在 dsh 0.2 上 cd 之后用相对路径,edit
|
|
27
|
-
能绕过上限(见下文「已知限制」)。在 Claude Code 之外,dsh 是第一个真正把这层垫片写出来的宿主(另有几个宿主
|
|
28
|
-
同样暴露了等价的写前接缝,见 PORTING.md,但还没有垫片实现)。
|
|
25
|
+
**匹配工具的上限由守卫确定性执行。** `ctx.tools.guard()` 提供同步、**单调**的否决:
|
|
26
|
+
守卫一旦返回理由,后续监听器就不能再放行。索引的增长受配置上限约束,无需依赖 agent
|
|
27
|
+
记得运行校验器。请用 **`dsh-engramory >= 0.2.7`**;
|
|
28
|
+
[PORTING.md](../../../PORTING.md) 记录各宿主是否已有守卫实现,
|
|
29
|
+
[CHANGELOG.md](../../../CHANGELOG.md) 保存版本历史。
|
|
29
30
|
|
|
30
31
|
**协议随插件一起到达。** skill 是在运行时通过 dsh 的 skill 注册表注册的 —— 走一个响应式的
|
|
31
32
|
`ctx.inject(['skills'], …)` 子上下文,所以没有注册表的 profile 照样拿到上限,而注册表晚一步
|
|
@@ -34,11 +35,17 @@
|
|
|
34
35
|
|
|
35
36
|
## 安装
|
|
36
37
|
|
|
38
|
+
零依赖 ESM,不需要构建;需要 Node.js 18+,dsh 的插件命令还要求 PATH 中有 `pnpm`。
|
|
39
|
+
|
|
37
40
|
```sh
|
|
38
41
|
dsh plugin --profile <名字> add dsh-engramory
|
|
39
42
|
```
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
确切的兼容版本以 [`package.json`](package.json) 的 `dsh.compatibility.dshReleases` 为准,
|
|
45
|
+
由 [`results.json`](../../../tests/dsh_e2e/results.json) 的记录支持。
|
|
46
|
+
验证其他版本请看[端到端验证指南](../../../tests/dsh_e2e/README.md)。
|
|
47
|
+
|
|
48
|
+
这个包自带 `dsh.bundle` manifest 指向它自己的 `cordis.patch.yml`,所以那一行
|
|
42
49
|
会被自动插进 profile 的插件树里 —— 不需要手改配置。要改默认值,请在你自己 profile 的 patch
|
|
43
50
|
层里**按 id 修改那一行已存在的配置**,**不要**再写一个 `- insert:`(insert 永远是追加,你会得到
|
|
44
51
|
两行 engramory,而原来那行的上限依旧在生效):
|
|
@@ -57,15 +64,22 @@ dsh plugin --profile <名字> add dsh-engramory
|
|
|
57
64
|
(`python tools/engramory_init.py dsh --install-skill`)。本插件负责强制上限、提供协议;
|
|
58
65
|
**它不创建记忆库**。
|
|
59
66
|
|
|
67
|
+
卸载:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
dsh plugin --profile <名字> remove dsh-engramory
|
|
71
|
+
```
|
|
72
|
+
|
|
60
73
|
## 配置
|
|
61
74
|
|
|
62
75
|
| 字段 | 默认值 | 含义 |
|
|
63
76
|
|---|---|---|
|
|
64
|
-
| `indexName` | `MEMORY.md` | 被当作记忆索引的文件名(不区分大小写;`file_path` 与 `str_replace_editor` 的 `path` 都会检查)
|
|
77
|
+
| `indexName` | `MEMORY.md` | 被当作记忆索引的文件名(不区分大小写;`file_path` 与 `str_replace_editor` 的 `path` 都会检查)—— 看写入时用的文件名,或写入实际落到的那个文件的文件名,所以换了名字的 symlink 也会被拦。空值或非字符串会回退到默认值。 |
|
|
65
78
|
| `maxLines` | `200` | 硬性行数上限。非正数或非有限值回退到默认值。 |
|
|
66
79
|
| `maxBytes` | `25600` | 硬性 UTF-8 字节上限(25 KB)。 |
|
|
67
|
-
| `indexPath` | 未设置 | 要守护的**那一个**索引的绝对路径。不设它时,守卫只按文件名匹配,于是**别的项目里一个无关的 `MEMORY.md` 也会被拦**;设了它就只有这个确切的文件会被守。比较走的是身份而非拼写(symlink 与 `..` 都会解析;仅在 Windows 上折叠大小写),而且即使文件尚不存在,它的第一次写入照样受守护。 |
|
|
80
|
+
| `indexPath` | 未设置 | 要守护的**那一个**索引的绝对路径。不设它时,守卫只按文件名匹配,于是**别的项目里一个无关的 `MEMORY.md` 也会被拦**;设了它就只有这个确切的文件会被守。比较走的是身份而非拼写 —— 看写入实际落到的那个文件,不管它叫什么名字(symlink 与 `..` 都会解析;仅在 Windows 上折叠大小写),而且即使文件尚不存在,它的第一次写入照样受守护。 |
|
|
68
81
|
| `registerSkill` | `true` | 设为 `false` 则保留上限、但跳过运行时 skill。 |
|
|
82
|
+
| `injectIndex` | `true` | 每一步都把记忆索引放进 dsh 的运行时上下文(见上文)。设为 `false` 则改回由 agent 读文件召回。 |
|
|
69
83
|
| `skill` | 内置 | 用你自己的 markdown 替换 skill 正文。 |
|
|
70
84
|
|
|
71
85
|
## 守卫到底做了什么
|
|
@@ -75,54 +89,40 @@ dsh plugin --profile <名字> add dsh-engramory
|
|
|
75
89
|
| `write`(或 `str_replace_editor` 的 `create`)且结果在上限内 | 放行 |
|
|
76
90
|
| `write`/`create` 会让索引**增长**并越过上限 | **拒绝**,并报出具体数字和该压缩什么 |
|
|
77
91
|
| `write`/`create` 让一个已超限的索引**缩小或持平** | 放行 —— 增量压缩(210 → 205 → 198)必须走得通 |
|
|
78
|
-
| 带 `old_str`/`new_str` 的 edit:结果可模拟 | 按**结果**判定,增长/缩小规则与整文件写入相同 |
|
|
92
|
+
| 带 `old_str`/`new_str` 的 edit:结果可模拟 | 按**结果**判定,增长/缩小规则与整文件写入相同 —— 先算大小再构建,结果长到没法构建的 `replace_all` 按字节与行数的下界判定 |
|
|
79
93
|
| 无法模拟的部分写入(如 `insert`)落在已超限的索引上 | **拒绝**,并告诉 agent:缩小的整文件写入是放行的 |
|
|
80
94
|
| 无法模拟的部分写入落在健康的索引上 | 放行 |
|
|
81
95
|
| `read`、`view`,以及任何未知工具 | 放行 —— 哪怕已超限,召回也绝不能被挡住 |
|
|
82
96
|
| 对任何其他文件的写入 | 放行 —— 不关守卫的事 |
|
|
83
97
|
| 整文件写入时当前索引缺失/读不到 | 视为**空**(与 Python 版守卫一致)—— 上限内的写入放行,而首次就超限的写入被拒 |
|
|
84
98
|
| 部分编辑时索引缺失/读不到 | 放行 —— 守卫绝不能因为一个自己读不了的路径而挡住工作 |
|
|
99
|
+
| 用索引名字的 FIFO、设备或目录 | 一律不读,按读不到处理(在那里读会把宿主卡死或撑爆内存,而宿主在主线程上跑守卫) |
|
|
100
|
+
| 超过读取上限的索引(`min(16 MiB, max(1 MiB, 4 × maxBytes))`) | 不整份读入:整文件写入按文件系统报告的大小比较字节(缩小一律放行;行数未知,这时不卡行数),edit 无法测量,按读不到的索引处理,放行 |
|
|
85
101
|
| 畸形的执行(没有 arguments、路径/内容不是字符串) | 放行 —— 它压根不像一次写入 |
|
|
86
102
|
|
|
87
|
-
|
|
88
|
-
|
|
103
|
+
对可测量的结果,只拒绝某个维度既超出上限、又比当前文件增长的写入。行数统计忽略结尾换行,
|
|
104
|
+
所以正好卡在上限的索引仍然可写。此判决表由 CI 中的 `node --test` 固定验证。
|
|
105
|
+
|
|
106
|
+
相对路径优先通过会话的 `workingDirectory` 服务解析,服务不存在时退回进程 cwd。
|
|
107
|
+
edit 在统一为 LF 的文本上匹配,计算结果大小时保留文件原有换行,包括 CRLF 的字节数。
|
|
89
108
|
|
|
90
109
|
## 已知限制
|
|
91
110
|
|
|
92
|
-
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
`
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
`
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
- **相对路径按 dsh 实际写入的位置来量。** dsh 0.2 给每个会话一个自己的工作目录(由它的
|
|
108
|
-
`working_directory` 工具切换),并按这个目录解析相对的 `file_path`。0.2.5 及以前的守卫却按
|
|
109
|
-
进程的 cwd 解析,于是 cd 之后对裸 `MEMORY.md` 的 edit 读不到当前文件、不经测量就放行 ——
|
|
110
|
-
在 dsh 0.2.1-rc.2 上它把索引撑到了 253 行。0.2.6 在 profile 提供 `workingDirectory` 服务时
|
|
111
|
-
读取会话目录,没有时(所有 0.1.x 版本)退回进程 cwd。
|
|
112
|
-
- **CRLF 索引按 dsh 的方式来模拟 edit。** dsh 先把文本统一成 LF 再匹配 edit,写回时恢复文件
|
|
113
|
-
原来的换行。0.2.5 及以前的守卫按原始字节匹配,于是在 CRLF 的 `MEMORY.md` 上(Windows 上很
|
|
114
|
-
常见),LF 的 `old_string` 看起来不存在,edit 被预测成"什么都不改"而放行,dsh 却照常改了。
|
|
115
|
-
0.2.6 用同样的方式归一化,并按 CRLF 字节数计算 CRLF 索引的大小。
|
|
116
|
-
- **验的是你大概率正在用的 dsh,不只是写它时的那一版。** Engramory 仓库里的
|
|
117
|
-
`tests/dsh_e2e/run.py` 会把指定的 dsh 版本装进临时目录,用 `dsh plugin add` 装上本插件,
|
|
118
|
-
再对着一个脚本化的 DeepSeek API 替身跑一次 headless 任务:260 行的整文件写入、把索引撑过
|
|
119
|
-
上限的 edit,都必须被本守卫拒绝,而且磁盘上什么都没变;小写入必须逐字节落盘;skill 必须出现
|
|
120
|
-
在发给模型的技能目录里;dsh 提供的每个工具都必须已经归过类(没归类的新工具可能绕过上限写文件);卸载后
|
|
121
|
-
profile 必须还能启动。通过的版本逐个写在 `package.json` 的 `dsh.compatibility.dshReleases`
|
|
122
|
-
里;没有同一插件版本、同一份代码的通过记录,单元测试就不接受把它标成 `compatible`。
|
|
123
|
-
- dsh 目前是开发者预览版,插件 API 可能变化。本插件刻意只碰 `ctx.tools.guard()` 和两个
|
|
124
|
-
响应式 `ctx.inject()` 子上下文 —— 一个注册 skill,一个读取各会话的工作目录 —— 所以要跟着改
|
|
125
|
-
也很便宜。
|
|
111
|
+
- **无法模拟的部分编辑可能从上限之下直接越界。** 若索引可读且已经超限,则拒绝这种编辑;
|
|
112
|
+
可以用缩小的整文件写入来压缩。文件缺失、不可读和超过读取上限的例外见上表。
|
|
113
|
+
编辑索引后运行 `engramory_check.py`,定期运行 `engramory_doctor.py`。
|
|
114
|
+
- **只覆盖已知文件工具:**使用 `file_path` 的 `write`/`edit`、`str_replace`/`insert`,
|
|
115
|
+
以及使用 `path` 的 `str_replace_editor` 修改命令。未知工具放行。
|
|
116
|
+
**Shell 工具、MCP 文件工具和外部编辑器都绕过守卫。**
|
|
117
|
+
- **注入只在安装器建库的位置找记忆库。** 库放在别处(`--memory-root`)时要设 `indexPath`。
|
|
118
|
+
项目根只按 dsh 默认的 `.git` 标记判断(profile 里自定义的 `projectRootMarkers` 插件看不到);
|
|
119
|
+
会话中途新建的项目库最多要十秒才会出现。真实路径跑出项目之外的项目库(比如指向仓库外的符号
|
|
120
|
+
链接)不会被读取;如果是有意为之,请把 `indexPath` 指向它。全局库的位置来自 `$DSH_HOME`,
|
|
121
|
+
解析方式与 dsh 自己一致。注入的内容被标注为"笔记而非指令、可能过时";没有运行时上下文
|
|
122
|
+
(`ctx.systemPrompt.context`)的宿主则不注入。
|
|
123
|
+
- dsh 的插件 API 可能变化。本插件使用 `ctx.tools.guard()` 和三个响应式 `ctx.inject()`
|
|
124
|
+
子上下文,分别接入 skill 注册表、会话工作目录和提示词注册表;兼容声明需要真实宿主的
|
|
125
|
+
运行记录,不能只凭单元测试。
|
|
126
126
|
|
|
127
127
|
## 许可
|
|
128
128
|
|