dsh-engramory 0.2.6 → 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/README.md CHANGED
@@ -24,10 +24,12 @@ because the index loads every session and everything past the cap silently stops
24
24
  recalled. On most hosts that limit is rules plus a checker the agent has to remember to
25
25
  run. dsh exposes `ctx.tools.guard()` — a synchronous, **monotonic** refusal: once a guard
26
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.6 or later**: 0.2.0 shipped before profile installs
27
+ enforced, not requested. Use **0.2.7 or later**: 0.2.0 shipped before profile installs
28
28
  worked upstream and carried an `inject` declaration this Cordis does not accept — it
29
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 (see Known limits). Outside Claude Code, dsh is the first host with
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
31
33
  the shim actually written (a few others expose equivalent pre-write seams — see
32
34
  PORTING.md — but have no shim yet).
33
35
 
@@ -67,10 +69,10 @@ supplies the protocol; it does not create the store.
67
69
 
68
70
  | Field | Default | Meaning |
69
71
  |---|---|---|
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. Nothing else is inspected. |
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. |
71
73
  | `maxLines` | `200` | Hard line cap. Non-positive or non-finite values fall back to the default. |
72
74
  | `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), and a path that does not exist yet still has its first write guarded. |
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. |
74
76
  | `registerSkill` | `true` | Set `false` to keep the cap but skip the runtime skill. |
75
77
  | `skill` | built-in | Replace the skill body with your own markdown. |
76
78
 
@@ -81,13 +83,15 @@ supplies the protocol; it does not create the store.
81
83
  | `write` (or `str_replace_editor` `create`) ending within caps | allow |
82
84
  | `write`/`create` that GROWS the index past a cap | **deny**, naming the numbers and what to compact |
83
85
  | `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 |
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 |
85
87
  | Unsimulable partial (e.g. `insert`) on an over-cap index | **deny**, telling the agent a shrinking whole-file write passes |
86
88
  | Unsimulable partial on a healthy index | allow |
87
89
  | `read`, `view`, or any unknown tool | allow — recall must never be blocked, even over cap |
88
90
  | Any write to any other file | allow — not the guard's business |
89
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 |
90
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 |
91
95
  | Malformed execution (no arguments, non-string path/content) | allow — not recognisably a write |
92
96
 
93
97
  The deny rule mirrors `hooks/engramory_index_guard.py` exactly: refuse only a result
@@ -114,7 +118,7 @@ newline, so an index sitting exactly at the cap stays writable.
114
118
  failure byte for byte; 0.2.1 boots and serves) and against the vendored
115
119
  `@deepseek-ai/cordis` resolver (activation with, without, and with a late-mounted
116
120
  skill registry). The test mock now mirrors the reflective-context access rules, and
117
- the guard's decision table stays covered by `node --test` (37 cases, run in
121
+ the guard's decision table stays covered by `node --test` (47 cases, run in
118
122
  Engramory's CI).
119
123
  - **A relative path is measured where dsh writes it.** dsh 0.2 gives every session its
120
124
  own working directory (moved by its `working_directory` tool) and resolves a
package/README.zh-CN.md CHANGED
@@ -21,10 +21,11 @@
21
21
  都要加载,超出的部分会**悄无声息地不再被召回**。在多数宿主上,这个限制只是"规则 + 一个
22
22
  要靠 agent 自己记得去跑的校验器"。而 dsh 暴露了 `ctx.tools.guard()` —— 一个同步的、
23
23
  **单调**的否决:守卫一旦返回理由,后面任何监听器都无法把它翻回放行。所以在这里,上限是被
24
- **强制执行**的,不是被请求的。请用 **0.2.6 或更高版本**:0.2.0 发布时上游的 profile 安装还
24
+ **强制执行**的,不是被请求的。请用 **0.2.7 或更高版本**:0.2.0 发布时上游的 profile 安装还
25
25
  没打通,而且它的 `inject` 声明是这版 Cordis 不接受的写法 —— 装得上,但**永远激活不了**
26
26
  (issue #8);0.2.5 及以前,在 CRLF 换行的索引上、或在 dsh 0.2 上 cd 之后用相对路径,edit
27
- 能绕过上限(见下文「已知限制」)。在 Claude Code 之外,dsh 是第一个真正把这层垫片写出来的宿主(另有几个宿主
27
+ 能绕过上限;0.2.6 及以前,换了名字的 symlink 也能绕过、用索引名字的 FIFO 能把宿主卡死
28
+ (#20、#21;见下文「已知限制」)。在 Claude Code 之外,dsh 是第一个真正把这层垫片写出来的宿主(另有几个宿主
28
29
  同样暴露了等价的写前接缝,见 PORTING.md,但还没有垫片实现)。
29
30
 
30
31
  **协议随插件一起到达。** skill 是在运行时通过 dsh 的 skill 注册表注册的 —— 走一个响应式的
@@ -61,10 +62,10 @@ dsh plugin --profile <名字> add dsh-engramory
61
62
 
62
63
  | 字段 | 默认值 | 含义 |
63
64
  |---|---|---|
64
- | `indexName` | `MEMORY.md` | 被当作记忆索引的文件名(不区分大小写;`file_path` 与 `str_replace_editor` 的 `path` 都会检查)。空值或非字符串会回退到默认值。除此之外不检查任何东西。 |
65
+ | `indexName` | `MEMORY.md` | 被当作记忆索引的文件名(不区分大小写;`file_path` 与 `str_replace_editor` 的 `path` 都会检查)—— 看写入时用的文件名,或写入实际落到的那个文件的文件名,所以换了名字的 symlink 也会被拦。空值或非字符串会回退到默认值。 |
65
66
  | `maxLines` | `200` | 硬性行数上限。非正数或非有限值回退到默认值。 |
66
67
  | `maxBytes` | `25600` | 硬性 UTF-8 字节上限(25 KB)。 |
67
- | `indexPath` | 未设置 | 要守护的**那一个**索引的绝对路径。不设它时,守卫只按文件名匹配,于是**别的项目里一个无关的 `MEMORY.md` 也会被拦**;设了它就只有这个确切的文件会被守。比较走的是身份而非拼写(symlink 与 `..` 都会解析;仅在 Windows 上折叠大小写),而且即使文件尚不存在,它的第一次写入照样受守护。 |
68
+ | `indexPath` | 未设置 | 要守护的**那一个**索引的绝对路径。不设它时,守卫只按文件名匹配,于是**别的项目里一个无关的 `MEMORY.md` 也会被拦**;设了它就只有这个确切的文件会被守。比较走的是身份而非拼写 —— 看写入实际落到的那个文件,不管它叫什么名字(symlink 与 `..` 都会解析;仅在 Windows 上折叠大小写),而且即使文件尚不存在,它的第一次写入照样受守护。 |
68
69
  | `registerSkill` | `true` | 设为 `false` 则保留上限、但跳过运行时 skill。 |
69
70
  | `skill` | 内置 | 用你自己的 markdown 替换 skill 正文。 |
70
71
 
@@ -75,13 +76,15 @@ dsh plugin --profile <名字> add dsh-engramory
75
76
  | `write`(或 `str_replace_editor` 的 `create`)且结果在上限内 | 放行 |
76
77
  | `write`/`create` 会让索引**增长**并越过上限 | **拒绝**,并报出具体数字和该压缩什么 |
77
78
  | `write`/`create` 让一个已超限的索引**缩小或持平** | 放行 —— 增量压缩(210 → 205 → 198)必须走得通 |
78
- | 带 `old_str`/`new_str` 的 edit:结果可模拟 | 按**结果**判定,增长/缩小规则与整文件写入相同 |
79
+ | 带 `old_str`/`new_str` 的 edit:结果可模拟 | 按**结果**判定,增长/缩小规则与整文件写入相同 —— 先算大小再构建,结果长到没法构建的 `replace_all` 直接按大小拒绝 |
79
80
  | 无法模拟的部分写入(如 `insert`)落在已超限的索引上 | **拒绝**,并告诉 agent:缩小的整文件写入是放行的 |
80
81
  | 无法模拟的部分写入落在健康的索引上 | 放行 |
81
82
  | `read`、`view`,以及任何未知工具 | 放行 —— 哪怕已超限,召回也绝不能被挡住 |
82
83
  | 对任何其他文件的写入 | 放行 —— 不关守卫的事 |
83
84
  | 整文件写入时当前索引缺失/读不到 | 视为**空**(与 Python 版守卫一致)—— 上限内的写入放行,而首次就超限的写入被拒 |
84
85
  | 部分编辑时索引缺失/读不到 | 放行 —— 守卫绝不能因为一个自己读不了的路径而挡住工作 |
86
+ | 用索引名字的 FIFO、设备或目录 | 一律不读,按读不到处理(在那里读会把宿主卡死或撑爆内存,而宿主在主线程上跑守卫) |
87
+ | 超过读取上限的索引(1 MiB,或 `maxBytes` 的 4 倍,最多 16 MiB) | 不整份读入:整文件写入按文件系统报告的大小比较字节(缩小一律放行;行数未知,这时不卡行数),edit 无法测量,按读不到的索引处理,放行 |
85
88
  | 畸形的执行(没有 arguments、路径/内容不是字符串) | 放行 —— 它压根不像一次写入 |
86
89
 
87
90
  拒绝规则与 `hooks/engramory_index_guard.py` **完全一致**:只拒绝那种结果既超过上限、
@@ -103,7 +106,7 @@ dsh plugin --profile <名字> add dsh-engramory
103
106
  profile 上端到端验证过(0.2.0 逐字节复现启动失败;0.2.1 能启动并提供服务),也对着 dsh
104
107
  内置的 `@deepseek-ai/cordis` 解析器验过(注册表存在、不存在、以及晚挂载三种情况下的激活)。
105
108
  测试用的 mock 现在会照着反射式上下文的访问规则来,守卫的判决表则由 `node --test`
106
- 持续覆盖(37 个用例,在 Engramory 的 CI 里跑)。
109
+ 持续覆盖(47 个用例,在 Engramory 的 CI 里跑)。
107
110
  - **相对路径按 dsh 实际写入的位置来量。** dsh 0.2 给每个会话一个自己的工作目录(由它的
108
111
  `working_directory` 工具切换),并按这个目录解析相对的 `file_path`。0.2.5 及以前的守卫却按
109
112
  进程的 cwd 解析,于是 cd 之后对裸 `MEMORY.md` 的 edit 读不到当前文件、不经测量就放行 ——