@momenttttt/pi-zcode-memory 0.1.0 → 0.1.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
@@ -1,51 +1,51 @@
1
1
  # @momenttttt/pi-zcode-memory
2
2
 
3
- 让 pi 与 ZCode 共用同一份项目记忆。
3
+ [中文文档](https://github.com/Momenttttt/pi-extension-suite/blob/main/packages/pi-zcode-memory/README.zh-CN.md) · [pi extension suite](https://github.com/Momenttttt/pi-extension-suite)
4
4
 
5
- - 三个工具:`memory_list`、`memory_read`、`memory_write`
6
- - 会话开始时只把 `MEMORY.md` 索引注入一次;正文永不自动注入,按需读
7
- - 目录算法、索引格式、事实文件 frontmatter 逐条对齐 ZCode,两边读写同一批文件
8
- - `/memory` 查看状态与索引体检,`/memory reload` 强制下一次运行重新注入
5
+ One project memory shared between pi and ZCode.
9
6
 
10
- ## 安装
7
+ - Three tools: `memory_list`, `memory_read`, `memory_write`
8
+ - Only the `MEMORY.md` index is injected, once, at session start; bodies are never injected automatically and are read on demand
9
+ - The directory algorithm, the index format and the fact-file frontmatter all follow ZCode item by item, so both sides read and write the same files
10
+ - `/memory` shows the status and checks the index; `/memory reload` forces the next run to inject again
11
+
12
+ ## Install
11
13
 
12
14
  ```bash
13
15
  pi install npm:@momenttttt/pi-zcode-memory
14
16
  ```
15
17
 
16
- 本地开发:
18
+ To try this package without installing it:
17
19
 
18
20
  ```bash
19
21
  pi -e ./packages/pi-zcode-memory/extensions/zcode-memory.ts
20
22
  ```
21
23
 
22
- ## 记忆目录怎么定位
24
+ ## How the memory directory is located
23
25
 
24
- 按共享记忆目录的路径算法计算:
26
+ Computed with the shared memory directory's path algorithm:
25
27
 
26
28
  ```
27
- ~/.zcode -> 追加 cli(basename 已是 cli 则不追加)
29
+ ~/.zcode -> append cli (not appended when the basename already is cli)
28
30
  <storageRoot>/memories/projects/<slug>-<hash16>/memory/
29
31
  hash16 = sha256(win32 ? resolve(cwd).toLowerCase() : resolve(cwd)).slice(0, 16)
30
- slug = 小写、非 [a-z0-9._-] 换 -、去首尾 -、截 48、空则 project
32
+ slug = lowercase, non [a-z0-9._-] to -, trim leading/trailing -, cut to 48, "project" when empty
31
33
  ```
32
34
 
33
- 实际使用时有三级回退:
35
+ At runtime there are three tiers of fallback:
34
36
 
35
- 1. 配置里的 `dir`(最高优先级;路径算法对不上时用它,例如工作区标识或路径写法差异)
36
- 2. 算法算出的路径存在 → 用它
37
- 3. 不存在时,`memories/projects/` 下恰好一个 `<slug>-*` 目录 → 用它并警告;
38
- 否则视为"该项目尚无 ZCode 记忆"(不创建目录;`memory_write` 时才按计算路径创建)
37
+ 1. `dir` from the configuration (highest priority; for when the algorithm does not match, e.g. a workspace identity or a different spelling of the path)
38
+ 2. The path the algorithm computes, when it exists
39
+ 3. When it does not exist: exactly one `<slug>-*` directory under `memories/projects/` is used, with a warning; otherwise the project is treated as having no ZCode memory yet (no directory is created; `memory_write` creates it under the computed path when it is actually needed)
39
40
 
40
- 检测到 `project-` 前缀目录时会提示:ZCode 可能用了 workspace identity(slug 固定为 `project`,
41
- hash 输入不是路径)。独立 CLI 与普通桌面项目不会出现这种情况。
41
+ A directory carrying the `project-` prefix triggers a note: ZCode may have used a workspace identity (slug fixed to `project`, hash input not a path). Standalone CLI sessions and ordinary desktop projects do not look like that.
42
42
 
43
- ## 配置
43
+ ## Configuration
44
44
 
45
- 两个位置,深合并,项目覆盖全局:
45
+ Two locations, deep-merged, project over global:
46
46
 
47
- - 全局:`<agent-dir>/extensions/zcode-memory.json`(默认 `~/.pi/agent/extensions/zcode-memory.json`)
48
- - 项目:`<cwd>/.pi/zcode-memory.json`
47
+ - Global: `<agent-dir>/extensions/zcode-memory.json` (default `~/.pi/agent/extensions/zcode-memory.json`)
48
+ - Project: `<cwd>/.pi/zcode-memory.json`
49
49
 
50
50
  ```json
51
51
  {
@@ -55,98 +55,76 @@ hash 输入不是路径)。独立 CLI 与普通桌面项目不会出现这种
55
55
  }
56
56
  ```
57
57
 
58
- | 项 | 默认 | 说明 |
58
+ | Option | Default | Meaning |
59
59
  | --- | --- | --- |
60
- | `enabled` | `true` | 关掉后工具仍注册,但不注入索引(工具本身仍可用) |
61
- | `dir` | 无 | 显式记忆目录。任意异常场景的兜底 |
62
- | `injectIndex` | `true` | 是否做索引注入 |
60
+ | `enabled` | `true` | when off, the tools stay registered but the index is not injected (the tools themselves still work) |
61
+ | `dir` | none | explicit memory directory. The fallback for any case the algorithm cannot handle |
62
+ | `injectIndex` | `true` | whether to inject the index |
63
63
 
64
- 配置解析失败(文件坏了、JSON 不合法)不会让插件崩,退回默认值。
64
+ A config that fails to parse (corrupt file, invalid JSON) does not crash the extension; it falls back to the defaults.
65
65
 
66
- ## 工具
66
+ ## Tools
67
67
 
68
- | 工具 | 行为 |
68
+ | Tool | Behavior |
69
69
  | --- | --- |
70
- | `memory_list` | 递归扫记忆目录(排除 `MEMORY.md`、不跟随目录符号链接),返回文件名 / name / description / type / mtime。坏 frontmatter 降级成"无描述条目",不抛错 |
71
- | `memory_read` | 按 name 或文件名定位;返回 frontmatter 摘要 + 正文。正文按 ZCode 召回上限截断(前 200 行 / 4096 字节)并提示去哪里读全文 |
72
- | `memory_write` | `name` / `description` / `type` / `body` / `mode: create\|upsert`。原子写事实文件 + 维护 `MEMORY.md` 索引行 |
70
+ | `memory_list` | walks the memory directory recursively (skipping `MEMORY.md`, not following directory symlinks) and returns file name / name / description / type / mtime. Broken frontmatter degrades to an entry with no description instead of throwing |
71
+ | `memory_read` | locates a memory by name or file name and returns the frontmatter summary plus the body. The body is truncated at ZCode's recall limit (first 200 lines / 4096 bytes) with a pointer to where the full text can be read |
72
+ | `memory_write` | takes `name` / `description` / `type` / `body` / `mode: create\|upsert`. Writes the fact file atomically and maintains its line in the `MEMORY.md` index |
73
73
 
74
- 写入的 frontmatter 只有四个键:
74
+ A written frontmatter carries four keys and no more:
75
75
 
76
76
  ```yaml
77
77
  ---
78
78
  name: <kebab-case-slug>
79
- description: <一行描述>
79
+ description: <one-line description>
80
80
  metadata:
81
81
  node_type: memory
82
82
  type: user | feedback | project | reference
83
83
  ---
84
84
  ```
85
85
 
86
- **不写** `originSessionId`(那是读文件的一方在缺失时自己补的惰性字段,我们不伪造不属于自己的
87
- 会话 id),也不在 `metadata` 里加自定义键。
88
-
89
- ## 设计取舍
90
-
91
- **为什么索引只注入一次,而不是每轮重注?**
92
- 只有 `MEMORY.md` 进上下文,正文按需读。注入形态是 `before_agent_start` 返回的
93
- 一条 `display: false` custom 消息(不是塞进 system prompt)。之后用
94
- `pi.appendEntry("zcode-memory/injected", { indexHash })` 记标记;`session_start`
95
- (new / resume / fork)按当前会话投影重建标记。重新注入只有三种情况:索引内容 hash 变化、
96
- 注入消息被压缩摘要掉(不在保留尾部里)、`/memory reload`。`messageType === "custom"`
97
- 的运行(`pi.sendMessage(..., { triggerTurn: true })` 起的)直接跳过。
98
-
99
- **为什么 `memory_write` 走 `withFileMutationQueue` + revision 重试,而不是做跨进程锁?**
100
- ZCode 的前台写本来就没有跨进程锁(后台整理进程除外),它靠 revision
101
- (`mtime:<截断毫秒>:size:<字节>`)发现并发。我们走同一条自愈路径:进程内用 pi 的写队列
102
- 串行;`MEMORY.md` 写前比对 revision,不一致就重读重试一次,再失败就报错让模型重试——
103
- **不静默覆盖**。代价是 ZCode 下一次写会撞上 revision 冲突,它重读索引后即可继续。
104
-
105
- **为什么 `memory_read` 后要发读态事件?**
106
- 用 `memory_read` 读过的记忆,紧接着用内置 `edit` 修改时不应该再被
107
- [pi-read-guard](https://github.com/Momenttttt/pi-extension-suite) 要求"先读"。两个包通过
108
- `pi.events` 共享总线互通(频道 `read-guard/file-read`):发布方只 `emit`,接收方只 `on`,
109
- 任一方缺席时静默降级,没有 import 依赖。相比"配置声明的读态提供方",少一层配置形状与
110
- 解析规则,收益相同。截断读发布 `kind: "truncated"` 且不带内容 hash —— 截断读不能成为
111
- 写水位,和内置 `read` 的 `details.truncation` 是同一条规则。
112
-
113
- **为什么正文读取要有截断上限?**
114
- `memory_read` 返回的就是"会话里实际可用的量":一条记忆被召回时最多取前 200 行 /
115
- 4096 字节,完整内容始终可以用内置 `read` 读。这样模型不会误以为工具给了全文。
116
-
117
- **为什么目录算法不做 `~` 展开、不做 realpath?**
118
- hash 的输入就是 `resolve(cwd)` 这个路径字符串。realpath 会把符号链接/短路径折成另一个
119
- 字符串,反而算错目录。win32 折叠大小写也是同一个目的:让同一目录的不同写法落到同一个 hash。
120
-
121
- ## 边界与已知限制
122
-
123
- - **与 ZCode 是最终一致,不是实时同步。** ZCode 的 `MEMORY.md` **整个运行期只读一次**,
124
- 刷新点只有首次上下文初始化、冷恢复、`/clear`。**压缩不是刷新点**——陈旧索引会活过压缩。
125
- 所以 pi 写完记忆后,ZCode 那个正在跑的会话看不到,要到新会话/冷恢复才可见;事实文件正文
126
- 不被缓存,比索引新鲜。
127
- - **ZCode 的 `Write` 是整体覆盖。** 它拿着旧快照写 `MEMORY.md` 时,我们的外部改动会被
128
- 整份盖掉(它不会合并)。缓解只有"每次写前读一次"和"不要在 ZCode 正主动写记忆时批量写"。
129
- - **索引可能被模型写坏。** `memory_list` 走文件系统兜底;`/memory` 给出孤儿索引行与
130
- 未入索引文件的体检,但**不自动修正**索引。
131
- - **frontmatter 只在前 30 行内解析。** 闭合行更靠后的文件,读它的一方同样会当成
132
- "无描述条目"——所以写文件时 frontmatter 必须短。
133
- - **写入拒绝跟随符号链接。** 目标是符号链接时报错,要求传真实路径。
134
- - **`memory_read` 截断读不会成为写水位**:接着用内置 `edit` 改同一文件仍会被
135
- pi-read-guard 要求重读(先读后写约束的预期行为)。
136
- - **没有删除工具、没有自动抽取。** v1 需要"忘记"时用内置 `bash rm`;提炼记忆走显式
137
- 工具调用,不在后台跑模型。
138
- - **索引是 `custom_message`**,不在 pi-context-guard 微压缩的可清理白名单里,不会被清理。
139
-
140
- ## 开发
86
+ `originSessionId` is **not** written: that is a lazy field the reading side fills in itself when it is missing, and borrowing another tool's session id would be a fabrication. No custom keys are added to `metadata` either.
87
+
88
+ ## Design tradeoffs
89
+
90
+ **Why is the index injected once instead of on every turn?**
91
+ Only `MEMORY.md` enters the context; bodies are read on demand. The injection is a `display: false` custom message returned from `before_agent_start` (not stuffed into the system prompt). A marker is then recorded with `pi.appendEntry("zcode-memory/injected", { indexHash })`, and `session_start` (new / resume / fork) rebuilds the marker from the current session projection. Re-injection happens in exactly three cases: the index content hash changed, the injected message was summarized away (no longer in the kept tail), or `/memory reload` was run. Runs with `messageType === "custom"` (started by `pi.sendMessage(..., { triggerTurn: true })`) are skipped outright.
92
+
93
+ **Why does `memory_write` use `withFileMutationQueue` plus a revision retry instead of a cross-process lock?**
94
+ ZCode's foreground writes have no cross-process lock either (only its background organizing process does); it detects concurrency through a revision (`mtime:<truncated ms>:size:<bytes>`). This package takes the same self-healing path: writes are serialized in-process through pi's queue, and before writing `MEMORY.md` the revision is compared; on a mismatch it re-reads and retries once, and on a second failure it returns an error for the model to retry. It **never** overwrites silently. The cost is that ZCode's next write hits a revision conflict, which it resolves by re-reading the index.
95
+
96
+ **Why must memory files go through these three tools?**
97
+ `memory_write` is the only path that keeps a fact file and its `MEMORY.md` index line in sync. A fact created with the built-in `write`, or one whose description was changed with the built-in `edit`, has no correct index line, and the injected index is what a later session actually sees, so the memory goes unnoticed. ZCode has the model maintain the index by hand because it has no memory tool, and hand-editing is safe there because of guarantees in its own filesystem adapter (atomic writes, a stale-write check) that pi's built-in `edit` does not have. The boundary is prompt-level only: the read-state bridge below stays, as a fallback for a model that ignores the descriptions and for hand edits the user asks for.
98
+
99
+ **Why does `memory_read` emit a read-state event?**
100
+ A memory read through `memory_read` and then modified with the built-in `edit` should not be rejected by [pi-read-guard](https://github.com/Momenttttt/pi-extension-suite/blob/main/packages/pi-read-guard/README.md) for not having been read. The two packages talk over `pi.events` (channel `read-guard/file-read`): the publisher only `emit`s, the subscriber only `on`s, and either side's absence degrades silently. There is no import dependency between them. Compared with a "configure your read-state provider" approach, this drops a layer of configuration shape and parsing rules for the same benefit. A truncated read publishes `kind: "truncated"` with no content hash: a truncated read cannot become a write watermark, the same rule the built-in `read` follows through `details.truncation`.
101
+
102
+ **Why cap what `memory_read` returns?**
103
+ What `memory_read` returns is what is actually available in the session: a recalled memory carries at most its first 200 lines / 4096 bytes, and the full text is always reachable with the built-in `read`. That way the model does not mistake the tool output for the whole file.
104
+
105
+ **Why does the directory algorithm skip `~` expansion and realpath?**
106
+ The hash input is the path string `resolve(cwd)` and nothing else. realpath would fold symlinks and short paths into a different string and compute the wrong directory. Folding case on win32 serves the same purpose: different spellings of one directory land on the same hash.
107
+
108
+ ## Boundaries and known limits
109
+
110
+ - **Eventual consistency with ZCode, not live sync.** ZCode reads `MEMORY.md` **once for the entire run**; it refreshes only at first context initialization, cold resume and `/clear`. **Compaction is not a refresh point**, so a stale index survives it. After pi writes a memory, a ZCode session that is already running cannot see it until a new session or a cold resume; fact-file bodies are not cached and are fresher than the index.
111
+ - **ZCode's `Write` is a full overwrite.** When it writes `MEMORY.md` from a stale snapshot, outside changes are wiped wholesale (it does not merge). The only mitigations are reading before every write, and not batch-writing while ZCode is actively writing memory.
112
+ - **The index can be corrupted by the model.** `memory_list` falls back to the filesystem; `/memory` reports orphan index lines and files missing from the index, but does not repair the index automatically.
113
+ - **The tool boundary is prompt-level.** The descriptions steer the model to `memory_read` / `memory_write`; mechanically, pi-read-guard still lets a built-in `edit` through after a `memory_read`, and nothing blocks a hand edit.
114
+ - **Frontmatter is only parsed within the first 30 lines.** A file whose closing `---` sits lower is treated as having no description by whichever side reads it, so a written frontmatter has to stay short.
115
+ - **Writes refuse to follow symlinks.** A symlink target is an error, and the real path has to be passed instead.
116
+ - **A truncated `memory_read` does not become a write watermark**: editing the same file with the built-in `edit` afterwards still triggers a re-read demand from pi-read-guard. That is the expected behavior of the read-before-write rule.
117
+ - **No delete tool, no automatic extraction.** In v1, forgetting something means the built-in `bash rm`; distilling memory is an explicit tool call, with no background model run.
118
+ - **The index is a `custom_message`**, outside pi-context-guard's prunable whitelist, so it is never pruned.
119
+
120
+ ## Verify before installing
141
121
 
142
122
  ```bash
143
123
  npm install
144
- npm run check # typecheck + test + 文档交叉引用
145
- git config core.hooksPath .githooks # 提交身份与提交信息格式钩子
124
+ npm run check # typecheck + tests
146
125
  ```
147
126
 
148
- 测试分两层:纯函数单测(目录算法、frontmatter、解析/截断、索引行、注入决策),以及
149
- "进程内真实会话 + 桩 provider"的端到端测试(工具输出、索引注入、与 read-guard 的互通)。
127
+ The checks run offline: they cover the directory algorithm, frontmatter parsing, index injection, and the hand-off with pi-read-guard.
150
128
 
151
129
  ## License
152
130
 
@@ -0,0 +1,163 @@
1
+ # @momenttttt/pi-zcode-memory
2
+
3
+ [English](README.md) · [pi 扩展四件套](https://github.com/Momenttttt/pi-extension-suite)
4
+
5
+ 让 pi 与 ZCode 共用同一份项目记忆。
6
+
7
+ - 三个工具:`memory_list`、`memory_read`、`memory_write`
8
+ - 会话开始时只把 `MEMORY.md` 索引注入一次;正文永不自动注入,按需读
9
+ - 目录算法、索引格式、事实文件 frontmatter 逐条对齐 ZCode,两边读写同一批文件
10
+ - `/memory` 查看状态与索引体检,`/memory reload` 强制下一次运行重新注入
11
+
12
+ ## 安装
13
+
14
+ ```bash
15
+ pi install npm:@momenttttt/pi-zcode-memory
16
+ ```
17
+
18
+ 想不安装先试试这个包的效果:
19
+
20
+ ```bash
21
+ pi -e ./packages/pi-zcode-memory/extensions/zcode-memory.ts
22
+ ```
23
+
24
+ ## 记忆目录怎么定位
25
+
26
+ 按共享记忆目录的路径算法计算:
27
+
28
+ ```
29
+ ~/.zcode -> 追加 cli(basename 已是 cli 则不追加)
30
+ <storageRoot>/memories/projects/<slug>-<hash16>/memory/
31
+ hash16 = sha256(win32 ? resolve(cwd).toLowerCase() : resolve(cwd)).slice(0, 16)
32
+ slug = 小写、非 [a-z0-9._-] 换 -、去首尾 -、截 48、空则 project
33
+ ```
34
+
35
+ 实际使用时有三级回退:
36
+
37
+ 1. 配置里的 `dir`(最高优先级;路径算法对不上时用它,例如工作区标识或路径写法差异)
38
+ 2. 算法算出的路径存在 → 用它
39
+ 3. 不存在时,`memories/projects/` 下恰好一个 `<slug>-*` 目录 → 用它并警告;
40
+ 否则视为“该项目尚无 ZCode 记忆”(不创建目录;`memory_write` 时才按计算路径创建)
41
+
42
+ 检测到 `project-` 前缀目录时会提示:ZCode 可能用了 workspace identity(slug 固定为 `project`,
43
+ hash 输入不是路径)。独立 CLI 与普通桌面项目不会出现这种情况。
44
+
45
+ ## 配置
46
+
47
+ 两个位置,深合并,项目覆盖全局:
48
+
49
+ - 全局:`<agent-dir>/extensions/zcode-memory.json`(默认 `~/.pi/agent/extensions/zcode-memory.json`)
50
+ - 项目:`<cwd>/.pi/zcode-memory.json`
51
+
52
+ ```json
53
+ {
54
+ "enabled": true,
55
+ "dir": "D:/zcode/cli/memories/projects/chat-ace14747b286680c/memory",
56
+ "injectIndex": true
57
+ }
58
+ ```
59
+
60
+ | 项 | 默认 | 说明 |
61
+ | --- | --- | --- |
62
+ | `enabled` | `true` | 关掉后工具仍注册,但不注入索引(工具本身仍可用) |
63
+ | `dir` | 无 | 显式记忆目录。任意异常场景的兜底 |
64
+ | `injectIndex` | `true` | 是否做索引注入 |
65
+
66
+ 配置解析失败(文件坏了、JSON 不合法)不会让插件崩,退回默认值。
67
+
68
+ ## 工具
69
+
70
+ | 工具 | 行为 |
71
+ | --- | --- |
72
+ | `memory_list` | 递归扫记忆目录(排除 `MEMORY.md`、不跟随目录符号链接),返回文件名 / name / description / type / mtime。坏 frontmatter 降级成“无描述条目”,不抛错 |
73
+ | `memory_read` | 按 name 或文件名定位;返回 frontmatter 摘要 + 正文。正文按 ZCode 召回上限截断(前 200 行 / 4096 字节)并提示去哪里读全文 |
74
+ | `memory_write` | `name` / `description` / `type` / `body` / `mode: create\|upsert`。原子写事实文件 + 维护 `MEMORY.md` 索引行 |
75
+
76
+ 写入的 frontmatter 只有四个键:
77
+
78
+ ```yaml
79
+ ---
80
+ name: <kebab-case-slug>
81
+ description: <一行描述>
82
+ metadata:
83
+ node_type: memory
84
+ type: user | feedback | project | reference
85
+ ---
86
+ ```
87
+
88
+ **不写** `originSessionId`(那是读文件的一方在缺失时自己补的惰性字段,我们不伪造不属于自己的
89
+ 会话 id),也不在 `metadata` 里加自定义键。
90
+
91
+ ## 设计取舍
92
+
93
+ **为什么索引只注入一次,而不是每轮重注?**
94
+ 只有 `MEMORY.md` 进上下文,正文按需读。注入形态是 `before_agent_start` 返回的
95
+ 一条 `display: false` custom 消息(不是塞进 system prompt)。之后用
96
+ `pi.appendEntry("zcode-memory/injected", { indexHash })` 记标记;`session_start`
97
+ (new / resume / fork)按当前会话投影重建标记。重新注入只有三种情况:索引内容 hash 变化、
98
+ 注入消息被压缩摘要掉(不在保留尾部里)、`/memory reload`。`messageType === "custom"`
99
+ 的运行(`pi.sendMessage(..., { triggerTurn: true })` 起的)直接跳过。
100
+
101
+ **为什么 `memory_write` 走 `withFileMutationQueue` + revision 重试,而不是做跨进程锁?**
102
+ ZCode 的前台写本来就没有跨进程锁(后台整理进程除外),它靠 revision
103
+ (`mtime:<截断毫秒>:size:<字节>`)发现并发。我们走同一条自愈路径:进程内用 pi 的写队列
104
+ 串行;`MEMORY.md` 写前比对 revision,不一致就重读重试一次,再失败就报错让模型重试,
105
+ **不静默覆盖**。代价是 ZCode 下一次写会撞上 revision 冲突,它重读索引后即可继续。
106
+
107
+ **为什么记忆文件只能经这三个工具操作?**
108
+ 只有 `memory_write` 能让事实文件与它的 `MEMORY.md` 索引行保持同步。用内置 `write` 新建
109
+ 一条事实,或者用内置 `edit` 改掉一条事实的 description,索引行就跟不上了;而之后每个会话
110
+ 实际看到的是注入的那份索引,于是这条记忆等于不存在。ZCode 让模型手工维护索引,是因为它
111
+ 没有记忆工具;它那边手改之所以安全,靠的是自己 fs 适配器提供的保证(原子写、陈旧写检测),
112
+ pi 的内置 `edit` 没有这些。这条边界只到提示词这一层:下面的读态互通照旧保留,用来兜住
113
+ 不理这两句描述的模型,以及用户点名的手改。
114
+
115
+ **为什么 `memory_read` 后要发读态事件?**
116
+ 用 `memory_read` 读过的记忆,紧接着用内置 `edit` 修改时不应该再被
117
+ [pi-read-guard](https://github.com/Momenttttt/pi-extension-suite/blob/main/packages/pi-read-guard/README.md) 要求“先读”。两个包通过
118
+ `pi.events` 共享总线互通(频道 `read-guard/file-read`):发布方只 `emit`,接收方只 `on`,
119
+ 任一方缺席时静默降级,没有 import 依赖。相比“配置声明的读态提供方”,少一层配置形状与
120
+ 解析规则,收益相同。截断读发布 `kind: "truncated"` 且不带内容 hash:截断读不能成为
121
+ 写水位,和内置 `read` 的 `details.truncation` 是同一条规则。
122
+
123
+ **为什么正文读取要有截断上限?**
124
+ `memory_read` 返回的就是“会话里实际可用的量”:一条记忆被召回时最多取前 200 行 /
125
+ 4096 字节,完整内容始终可以用内置 `read` 读。这样模型不会误以为工具给了全文。
126
+
127
+ **为什么目录算法不做 `~` 展开、不做 realpath?**
128
+ hash 的输入就是 `resolve(cwd)` 这个路径字符串。realpath 会把符号链接/短路径折成另一个
129
+ 字符串,反而算错目录。win32 折叠大小写也是同一个目的:让同一目录的不同写法落到同一个 hash。
130
+
131
+ ## 边界与已知限制
132
+
133
+ - **与 ZCode 是最终一致,不是实时同步。** ZCode 的 `MEMORY.md` **整个运行期只读一次**,
134
+ 刷新点只有首次上下文初始化、冷恢复、`/clear`。**压缩不是刷新点**,陈旧索引会活过压缩。
135
+ 所以 pi 写完记忆后,ZCode 那个正在跑的会话看不到,要到新会话/冷恢复才可见;事实文件正文
136
+ 不被缓存,比索引新鲜。
137
+ - **ZCode 的 `Write` 是整体覆盖。** 它拿着旧快照写 `MEMORY.md` 时,我们的外部改动会被
138
+ 整份盖掉(它不会合并)。缓解只有“每次写前读一次”和“不要在 ZCode 正主动写记忆时批量写”。
139
+ - **索引可能被模型写坏。** `memory_list` 走文件系统兜底;`/memory` 给出孤儿索引行与
140
+ 未入索引文件的体检,但**不自动修正**索引。
141
+ - **工具边界只到提示词这一层。** 描述把模型引向 `memory_read` / `memory_write`;机械上
142
+ pi-read-guard 仍放行 `memory_read` 之后的内置 `edit`,手改也没有任何东西拦着。
143
+ - **frontmatter 只在前 30 行内解析。** 闭合行更靠后的文件,读它的一方同样会当成
144
+ “无描述条目”,所以写文件时 frontmatter 必须短。
145
+ - **写入拒绝跟随符号链接。** 目标是符号链接时报错,要求传真实路径。
146
+ - **`memory_read` 截断读不会成为写水位**:接着用内置 `edit` 改同一文件仍会被
147
+ pi-read-guard 要求重读(先读后写约束的预期行为)。
148
+ - **没有删除工具、没有自动抽取。** v1 需要“忘记”时用内置 `bash rm`;提炼记忆走显式
149
+ 工具调用,不在后台跑模型。
150
+ - **索引是 `custom_message`**,不在 pi-context-guard 微压缩的可清理白名单里,不会被清理。
151
+
152
+ ## 先验证再安装
153
+
154
+ ```bash
155
+ npm install
156
+ npm run check # typecheck + 测试
157
+ ```
158
+
159
+ 检查在本地跑,不需要网络:覆盖目录算法、frontmatter 解析、索引注入,以及与 pi-read-guard 的互通。
160
+
161
+ ## License
162
+
163
+ MIT
@@ -124,7 +124,9 @@ const zcodeMemory: ExtensionFactory = (pi: ExtensionAPI): void => {
124
124
  description:
125
125
  "Read one project memory's frontmatter summary and body. The parameter is the memory's name or filename. " +
126
126
  "A body longer than 200 lines or 4096 bytes is truncated at the same limit ZCode uses when it recalls a memory, " +
127
- "and the result says where to read the full text — so what you see here is what is actually usable in a session.",
127
+ "and the result says where to read the full text — so what you see here is what is actually usable in a session. " +
128
+ "Memory files are managed through these tools only: read a memory with memory_read, never with the built-in read tool. " +
129
+ "The only exception is a truncated body: the result then says where to read the rest.",
128
130
  promptSnippet: "Read the body of one project memory",
129
131
  parameters: memoryReadParams,
130
132
  exposure: "direct",
@@ -205,7 +207,9 @@ const zcodeMemory: ExtensionFactory = (pi: ExtensionAPI): void => {
205
207
  "Write one long-term fact into the project memory directory shared with ZCode, and maintain its MEMORY.md index line. " +
206
208
  "Memories are long-term facts (user preferences, working-style feedback the user gave, project constraints, external resources), **not a to-do list**, " +
207
209
  "and not temporary state that ends with the conversation; do not write down information that the repository code, git history, or this task already answers. " +
208
- "Before writing, work out whether an existing memory covers the same thing: if so, upsert it rather than creating a duplicate entry.",
210
+ "Before writing, work out whether an existing memory covers the same thing: if so, upsert it rather than creating a duplicate entry. " +
211
+ "Memory files are managed through these tools only: create or change a memory with memory_write, never with the built-in write or edit. " +
212
+ "Only this tool keeps the fact file and its MEMORY.md index line in sync; a fact written by hand has no index line, and later sessions cannot see it.",
209
213
  promptSnippet: "Write a long-term fact into the project memory shared with ZCode (not a to-do)",
210
214
  parameters: memoryWriteParams,
211
215
  exposure: "direct",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@momenttttt/pi-zcode-memory",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Share ZCode's per-project memory directory from pi (memory_read / memory_write / memory_list)",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "extensions",
26
26
  "src",
27
27
  "README.md",
28
+ "README.zh-CN.md",
28
29
  "LICENSE"
29
30
  ],
30
31
  "pi": {