dsh-continual-evolve 0.12.0 → 0.13.0

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
@@ -7,9 +7,9 @@ English | [中文](README.zh.md)
7
7
  [![CI](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml/badge.svg)](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
8
8
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
9
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-339933)](package.json)
10
- [![Tests](https://img.shields.io/badge/tests-32%20passing-brightgreen)]()
10
+ [![Tests](https://img.shields.io/badge/tests-62%20passing-brightgreen)]()
11
11
  [![Coverage · statements](https://img.shields.io/badge/coverage_statements-98%25-brightgreen)]()
12
- [![Coverage · branches](https://img.shields.io/badge/coverage_branches-98%25-green)]()
12
+ [![Coverage · branches](https://img.shields.io/badge/coverage_branches-95%25-green)]()
13
13
  [![Coverage · functions](https://img.shields.io/badge/coverage_functions-100%25-brightgreen)]()
14
14
 
15
15
  A workspace-memory plugin for the [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (`dsh`): one plain-markdown memory store per workspace (`<workspace>/.evolve/memory/`), the index injected at session start, used by the model with its **native file tools** — and nothing else.
@@ -23,6 +23,7 @@ Reusable experience gathered in one session (user preferences, pitfalls, project
23
23
  1. **Session-start injection** — the plugin registers a single system-prompt section: the `MEMORY.md` index content (whole-line truncated over budget, with a read-the-directory hint), the absolute store path, and the when_to_save guide. The section is computed once per session and reused byte-for-byte, keeping the system prompt stable and the prompt cache warm; an empty store costs zero tokens.
24
24
  2. **Native read/write** — DSH fully permits file access inside the workspace (reads are never fenced; the write fence only covers paths outside the workspace, see [`docs/FAQ.md`](docs/FAQ.md) #6). The model reads, writes, and edits memory files and maintains the index directly; the store is bootstrapped on first use.
25
25
  3. **Governance is the file** — no versions, no snapshots, no approvals, no background extraction: a bad memory is a visible file in the workspace, and deleting it is the retirement path. Memories are personal context — on bootstrap in a git workspace the store appends `.evolve/` to the workspace `.gitignore` (non-git workspaces are left untouched).
26
+ 4. **Read-only card** — the plugin mounts a read-only memory card on its own page in the official plugin manager (the `plugins.bundle.config` slot; hosts without it silently degrade to no card): recently served workspaces, per-workspace entry and index counts, and index-drift warnings (unindexed files, index rows whose file is gone). The card follows the market's look — host theme tokens, the `Button` primitive for refresh, and zh/en copy that tracks the DSH language (plain themed elements on old hosts). The card is a request-time projection of the files on disk — the single source of truth remains `.evolve/memory/`; the card has no edit path, edit the files directly.
26
27
 
27
28
  Memory file format (same as ZCode): frontmatter with `name` / `description` (the hook that decides whether a future session recalls it) / `metadata.type` (`user | feedback | project | reference`); `feedback` bodies must carry **Why:** and **How to apply:** lines.
28
29
 
@@ -40,7 +41,7 @@ After installing or updating, restart the DSH profile you actually use (`dsh web
40
41
 
41
42
  ## Usage
42
43
 
43
- No commands, no tools. On the first session after restart, `.evolve/memory/` is created and the index is injected automatically. Just tell the model "remember …" / "forget …" — the memory files plus their index lines are the entire persistent state. Memories written mid-session become visible in the next session (the section is frozen to protect the prompt cache); for an immediate look the model simply reads the directory.
44
+ No commands, no tools. On the first session after restart, `.evolve/memory/` is created and the index is injected automatically. Just tell the model "remember …" / "forget …" — the memory files plus their index lines are the entire persistent state. Memories written mid-session become visible in the next session (the section is frozen to protect the prompt cache); for an immediate look the model simply reads the directory. To see the whole store, open the plugin's page in the official plugin manager: the read-only card shows each workspace's entries and index drift.
44
45
 
45
46
  ## Configuration
46
47
 
@@ -50,6 +51,7 @@ No commands, no tools. On the first session after restart, `.evolve/memory/` is
50
51
  | `memoryIndex.guide` | `true` | Inject the when_to_save guide (zero tokens when off and the index is empty) |
51
52
  | `memoryIndex.order` | `400` | Section order (upstream named slots start at `PLAN_POLICY=500`) |
52
53
  | `memoryIndex.maxChars` | `6000` | Hard character budget for the injected index |
54
+ | `memoryCard.enabled` | `true` | Read-only memory card in the official plugin manager (degrades to no card when the host lacks `webServer`/`plugins.bundle.config`) |
53
55
 
54
56
  Profile patch example:
55
57
 
@@ -64,7 +66,7 @@ Profile patch example:
64
66
 
65
67
  ```bash
66
68
  pnpm install && pnpm build # deps + tsc -> lib/
67
- pnpm test # vitest (32 tests)
69
+ pnpm test # vitest (66 tests)
68
70
  pnpm test:coverage # v8 coverage, CI-enforced thresholds
69
71
  pnpm lint # oxlint src test
70
72
  ```
@@ -73,10 +75,15 @@ Layout:
73
75
 
74
76
  ```
75
77
  ├── src/
76
- │ ├── index.ts # registration: the one section + config
78
+ │ ├── index.ts # registration: the one section + config + card route wiring
77
79
  │ ├── memory-section.ts # injection: read index / bootstrap / truncate / session freeze
78
- │ └── memory-guide.ts # guide: when_to_save and upkeep (adapted from ZCode)
79
- ├── test/ # vitest suite (3 files)
80
+ │ ├── memory-guide.ts # guide: when_to_save and upkeep (adapted from ZCode)
81
+ │ ├── memory-snapshot.ts # card: read-only store projection (entries / drift / error degrade)
82
+ │ ├── known-workspaces.ts# card: in-process LRU workspace allowlist (the API read fence)
83
+ │ └── card-routes.ts # card: GET-only read API on the host webServer
84
+ ├── client/
85
+ │ └── client.js # hand-written client bundle: read-only card in the plugin manager
86
+ ├── test/ # vitest suite (7 files)
80
87
  ├── lib/ # build output (tsc)
81
88
  ├── docs/ # design.md (design) · FAQ.md (real-world pitfalls)
82
89
  └── .agents/ # AI collaboration layer (AGENTS.md, skills, ADR notes)
package/README.zh.md CHANGED
@@ -7,9 +7,9 @@
7
7
  [![CI](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml/badge.svg)](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
8
8
  [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
9
9
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-339933)](package.json)
10
- [![Tests](https://img.shields.io/badge/tests-32%20passing-brightgreen)]()
10
+ [![Tests](https://img.shields.io/badge/tests-62%20passing-brightgreen)]()
11
11
  [![Coverage · statements](https://img.shields.io/badge/coverage_statements-98%25-brightgreen)]()
12
- [![Coverage · branches](https://img.shields.io/badge/coverage_branches-98%25-green)]()
12
+ [![Coverage · branches](https://img.shields.io/badge/coverage_branches-95%25-green)]()
13
13
  [![Coverage · functions](https://img.shields.io/badge/coverage_functions-100%25-brightgreen)]()
14
14
 
15
15
  [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(`dsh`)的工作区记忆插件:每个工作区一个纯 markdown 记忆库(`<workspace>/.evolve/memory/`),会话开局注入索引,模型用**原生文件读写工具**直接使用——仅此而已。
@@ -23,6 +23,7 @@ Agent 每个会话积累的可复用经验(用户偏好、踩坑教训、项
23
23
  1. **开局注入** —— 插件只注册一个 system-prompt section:注入 `MEMORY.md` 索引正文(超预算按整行截断并提示读目录)、记忆目录绝对路径、when_to_save 指南。该 section 每会话只算一次并逐字节复用,system prompt 稳定、prompt cache 持续命中;空 store 零 token。
24
24
  2. **原生读写** —— DSH 对工作区内文件读写全放行(读路径从不设防,写围栏只围工作区外,见 [`docs/FAQ.md`](docs/FAQ.md) #6)。模型直接 Read/Write/Edit 记忆文件并维护索引;store 不存在时自动 bootstrap。
25
25
  3. **治理即文件** —— 无版本、无快照、无审批、无后台提取:一条坏记忆就是工作区里一个可见的文件,删掉它就是退役。记忆是个人上下文——bootstrap 时若工作区是 git 仓库会自动把 `.evolve/` 追加进 `.gitignore`(非 git 工作区不碰任何文件)。
26
+ 4. **只读卡片** —— 插件在官方插件管理的本 bundle 页面挂一张只读记忆卡片(`plugins.bundle.config` slot,老宿主自动降级为无卡片):最近服务过的工作区、各自记忆库的条目数/索引行数、索引漂移警告(未索引文件与索引失联文件)。卡片与市场同款——宿主主题 token、`Button` 原语刷新按钮、中英文案跟随 DSH 语言(老宿主降级为同主题裸元素)。卡片是磁盘文件的请求时投影——单一事实源永远是 `.evolve/memory/` 里的文件,卡片没有编辑路径,编辑请直接改文件。
26
27
 
27
28
  记忆文件格式(与 ZCode 一致):frontmatter 带 `name` / `description`(决定未来会话会不会想起它)/ `metadata.type`(`user | feedback | project | reference`);`feedback` 正文必带 **Why:** 与 **How to apply:**。
28
29
 
@@ -40,7 +41,7 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
40
41
 
41
42
  ## 使用
42
43
 
43
- 无需命令、无需工具。重启后第一场会话,`.evolve/memory/` 会自动生成,索引自动注入开场;对模型直接说"记住……"/"忘掉……"即可,写入的记忆文件 + 索引行就是全部持久状态。会话中途写入的记忆下个会话生效(会话内冻结守 prompt cache);需要立即查看时,模型直接读目录。
44
+ 无需命令、无需工具。重启后第一场会话,`.evolve/memory/` 会自动生成,索引自动注入开场;对模型直接说"记住……"/"忘掉……"即可,写入的记忆文件 + 索引行就是全部持久状态。会话中途写入的记忆下个会话生效(会话内冻结守 prompt cache);需要立即查看时,模型直接读目录。想看记忆库全貌,打开官方插件管理里本插件的页面:只读卡片展示各工作区的记忆条目与索引漂移。
44
45
 
45
46
  ## 配置
46
47
 
@@ -50,6 +51,7 @@ dsh plugin add ZK-Andy/dsh-continual-evolve
50
51
  | `memoryIndex.guide` | `true` | 是否注入 when_to_save 指南(关闭且索引为空时该 section 零 token) |
51
52
  | `memoryIndex.order` | `400` | section 顺序(上游命名槽位自 `PLAN_POLICY=500` 起) |
52
53
  | `memoryIndex.maxChars` | `6000` | 注入索引的硬字符预算 |
54
+ | `memoryCard.enabled` | `true` | 官方插件管理内的只读记忆卡片(宿主缺 `webServer`/`plugins.bundle.config` 时自动降级为无卡片) |
53
55
 
54
56
  profile patch 示例:
55
57
 
@@ -64,7 +66,7 @@ profile patch 示例:
64
66
 
65
67
  ```bash
66
68
  pnpm install && pnpm build # 依赖 + tsc -> lib/
67
- pnpm test # vitest(32 例)
69
+ pnpm test # vitest(66 例)
68
70
  pnpm test:coverage # v8 覆盖率,CI 强制阈值
69
71
  pnpm lint # oxlint src test
70
72
  ```
@@ -73,10 +75,15 @@ pnpm lint # oxlint src test
73
75
 
74
76
  ```
75
77
  ├── src/
76
- │ ├── index.ts # 注册:唯一 section + 配置
78
+ │ ├── index.ts # 注册:唯一 section + 配置 + 卡片路由接线
77
79
  │ ├── memory-section.ts # 注入:读索引 / bootstrap / 截断 / 会话冻结
78
- │ └── memory-guide.ts # 指南:when_to_save 与维护纪律(改编自 ZCode)
79
- ├── test/ # vitest 测试(3 个文件)
80
+ │ ├── memory-guide.ts # 指南:when_to_save 与维护纪律(改编自 ZCode)
81
+ │ ├── memory-snapshot.ts # 卡片:记忆库只读投影(条目 / 漂移 / 错误降级)
82
+ │ ├── known-workspaces.ts# 卡片:进程内 LRU 工作区白名单(API 读围栏)
83
+ │ └── card-routes.ts # 卡片:webServer 上的 GET-only 只读 API
84
+ ├── client/
85
+ │ └── client.js # 手写 client bundle:官方插件管理内只读卡片
86
+ ├── test/ # vitest 测试(7 个文件)
80
87
  ├── lib/ # 构建产物(tsc)
81
88
  ├── docs/ # design.md(设计)· FAQ.md(踩坑)
82
89
  └── .agents/ # AI 协作层(AGENTS.md、技能、ADR 笔记)
@@ -0,0 +1,425 @@
1
+ /**
2
+ * dsh-continual-evolve — client bundle for the official plugin manager card.
3
+ *
4
+ * Hand-written (no bundler: docs/coding-standard.md forbids a second one) in
5
+ * the host loader's factory shape, verified against dshmarket's built
6
+ * bundle: `window.__ModuleLoader__.load({ id, factory })`, where the factory
7
+ * resolves host-provided modules through the passed `require` (here: react
8
+ * plus, optionally, the UI primitives) and returns its `module.exports`.
9
+ * The host mounts the bundle via package.json `dsh.client` + the `./client`
10
+ * export.
11
+ *
12
+ * The card registers one slot, `plugins.bundle.config` — the seat the 0.1.7+
13
+ * plugin manager declares for a bundle's own page. Slot detection is the
14
+ * compatibility contract: a host that never declares the slot never runs the
15
+ * registration, so the card is silently absent while the memory section is
16
+ * unaffected. The render function answers the host's `view === "summary"`
17
+ * call with null (the bundle list row shows the package description; the
18
+ * card is the page).
19
+ *
20
+ * Market parity (v0.13): locale/theme/primitives wiring copied from
21
+ * dshmarket's client — `ctx.locale.register(NS, { zh, en })` + `bind`,
22
+ * `var(--dsw-alias-*)` theme tokens, `Button` primitive for the refresh
23
+ * control. Missing primitives/locale degrade to themed plain elements, so an
24
+ * old host never blanks the settings dialog. The card stays a read-only
25
+ * projection: fetched at render time from this plugin's host routes
26
+ * (same-origin). No editor, no write path, no cached state.
27
+ */
28
+ window.__ModuleLoader__.load({
29
+ id: "dsh-continual-evolve",
30
+ factory: function (require) {
31
+ var module = { exports: {} };
32
+ var exports = module.exports;
33
+ var react = undefined;
34
+ try {
35
+ react = require("react");
36
+ } catch (error) {
37
+ // Browser side: this runs before any cordis logger exporter exists,
38
+ // so console is the only outlet (same escape hatch dshmarket uses).
39
+ console.warn(
40
+ "[dsh-continual-evolve] host react module unavailable — card disabled: " +
41
+ (error instanceof Error ? error.message : String(error)),
42
+ );
43
+ return module.exports;
44
+ }
45
+ if (react === undefined || typeof react.createElement !== "function") {
46
+ // A module table that resolves react to nothing is the same downgrade:
47
+ // render would throw and blank the whole settings dialog (dshmarket #671 shape).
48
+ console.warn("[dsh-continual-evolve] host react module has no createElement — card disabled");
49
+ return module.exports;
50
+ }
51
+
52
+ var primitives = undefined;
53
+ try {
54
+ primitives = require("@deepseek-ai/dsh-client-ui-primitives");
55
+ } catch (error) {
56
+ // Old host without the primitives package: themed plain elements below.
57
+ void error;
58
+ primitives = undefined;
59
+ }
60
+
61
+ var createElement = react.createElement;
62
+ var useState = react.useState;
63
+ var useEffect = react.useEffect;
64
+
65
+ var NS = "dsh-continual-evolve";
66
+ var SLOT = "plugins.bundle.config";
67
+ var API_ROOT = "/dsh-continual-evolve/api/v1";
68
+
69
+ var zh = {
70
+ title: "工作区记忆(只读投影)",
71
+ refresh: "刷新",
72
+ hint: "事实源是各工作区 .evolve/memory/ 下的 markdown 文件——模型原生读写;本卡片只展示,编辑请直接改文件。",
73
+ loading: "读取中…",
74
+ loadFailed: "卡片数据加载失败:",
75
+ empty: "本进程还没有服务过任何工作区会话——在某个工作区开一次会话后回来刷新。",
76
+ absent: "该工作区尚无记忆库(开一次会话自动创建)。",
77
+ readFailed: "记忆库读取失败:",
78
+ stats: "{files} 条记忆 · 索引 {index} 行",
79
+ missing: "索引失联(索引引用但文件已删):",
80
+ unindexed: "未入索引(文件存在但索引没有行):",
81
+ readError: "读取失败:",
82
+ };
83
+ var en = {
84
+ title: "Workspace memory (read-only)",
85
+ refresh: "Refresh",
86
+ hint: "The source of truth is the markdown files under each workspace's .evolve/memory/ — read and written by the model; this card only displays them, edit the files directly.",
87
+ loading: "Loading…",
88
+ loadFailed: "Failed to load card data: ",
89
+ empty: "This process has not served any workspace session yet — open a session in a workspace, then come back and refresh.",
90
+ absent: "No memory store in this workspace yet (one is created when you open a session).",
91
+ readFailed: "Failed to read the memory store: ",
92
+ stats: "{files} memories · {index} index rows",
93
+ missing: "Dangling index (indexed but deleted): ",
94
+ unindexed: "Unindexed (on disk, missing from the index): ",
95
+ readError: "Read failed: ",
96
+ };
97
+
98
+ function format(template, params) {
99
+ return String(template).replace(/\{(\w+)\}/g, function (match, key) {
100
+ var value = params[key];
101
+ return value === undefined || value === null ? match : String(value);
102
+ });
103
+ }
104
+
105
+ /**
106
+ * Assemble the card's view model: the workspace allowlist, then one
107
+ * snapshot per workspace. Network or API failure per workspace degrades
108
+ * to an `error` string on that workspace's entry — one bad store never
109
+ * blanks the whole card.
110
+ *
111
+ * @param {typeof fetch} fetchImpl - injectable for tests.
112
+ * @returns {Promise<{workspaces: Array<object>}>}
113
+ */
114
+ async function loadCardModel(fetchImpl) {
115
+ var listResponse = await fetchImpl(API_ROOT + "/workspaces");
116
+ if (!listResponse.ok) {
117
+ throw new Error("HTTP " + listResponse.status);
118
+ }
119
+ var listBody = await listResponse.json();
120
+ var workspaces = Array.isArray(listBody.workspaces) ? listBody.workspaces : [];
121
+ var entries = await Promise.all(
122
+ workspaces.map(async function (workspace) {
123
+ var root = typeof workspace.root === "string" ? workspace.root : "";
124
+ var lastSeen = typeof workspace.lastSeen === "string" ? workspace.lastSeen : "";
125
+ try {
126
+ var snapshotResponse = await fetchImpl(API_ROOT + "/memory?root=" + encodeURIComponent(root));
127
+ if (!snapshotResponse.ok) {
128
+ return { root: root, lastSeen: lastSeen, snapshot: null, error: "HTTP " + snapshotResponse.status };
129
+ }
130
+ return { root: root, lastSeen: lastSeen, snapshot: await snapshotResponse.json(), error: null };
131
+ } catch (error) {
132
+ return {
133
+ root: root,
134
+ lastSeen: lastSeen,
135
+ snapshot: null,
136
+ error: error instanceof Error ? error.message : String(error),
137
+ };
138
+ }
139
+ }),
140
+ );
141
+ return { workspaces: entries };
142
+ }
143
+
144
+ var CARD_STYLE = {
145
+ background: "var(--dsw-alias-bg-layer-1,#fff)",
146
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
147
+ borderRadius: "12px",
148
+ padding: "12px 14px",
149
+ display: "flex",
150
+ flexDirection: "column",
151
+ gap: "12px",
152
+ };
153
+ var TITLE_STYLE = {
154
+ color: "var(--dsw-alias-label-primary,#1f2328)",
155
+ fontSize: "15px",
156
+ fontWeight: 600,
157
+ lineHeight: "22px",
158
+ };
159
+ var HINT_STYLE = {
160
+ color: "var(--dsw-alias-label-tertiary,#8b93a1)",
161
+ fontSize: "12px",
162
+ lineHeight: "18px",
163
+ };
164
+ var SECTION_STYLE = {
165
+ borderTop: "1px solid var(--dsw-alias-border-l2,#f0f1f3)",
166
+ paddingTop: "8px",
167
+ display: "flex",
168
+ flexDirection: "column",
169
+ gap: "6px",
170
+ };
171
+ var ROOT_STYLE = {
172
+ fontFamily: "ui-monospace,Menlo,monospace",
173
+ fontSize: "11px",
174
+ color: "var(--dsw-alias-label-secondary,#6b7280)",
175
+ wordBreak: "break-all",
176
+ };
177
+ var STATS_STYLE = {
178
+ fontSize: "12px",
179
+ lineHeight: "18px",
180
+ color: "var(--dsw-alias-label-secondary,#6b7280)",
181
+ };
182
+ var WARN_STYLE = {
183
+ fontSize: "12px",
184
+ lineHeight: "18px",
185
+ color: "var(--dsw-alias-state-warn-primary,#b45309)",
186
+ };
187
+ var ERROR_STYLE = {
188
+ fontSize: "12px",
189
+ lineHeight: "18px",
190
+ color: "var(--dsw-alias-state-error-primary,#dc2626)",
191
+ };
192
+ var LIST_STYLE = { margin: "4px 0 0", paddingLeft: "18px", fontSize: "12px", lineHeight: "18px" };
193
+ var FALLBACK_BUTTON_STYLE = {
194
+ background: "var(--dsw-alias-bg-layer-2,#f3f4f6)",
195
+ color: "var(--dsw-alias-label-primary,#1f2328)",
196
+ border: "1px solid var(--dsw-alias-border-l2,#e5e7eb)",
197
+ borderRadius: "6px",
198
+ padding: "3px 10px",
199
+ fontSize: "12px",
200
+ lineHeight: "18px",
201
+ cursor: "pointer",
202
+ };
203
+
204
+ function RefreshButton(props) {
205
+ var UI = props.UI;
206
+ var t = props.t;
207
+ var onClick = props.onClick;
208
+ if (UI !== null && UI !== undefined && typeof UI.Button !== "undefined" && UI.Button !== null) {
209
+ return createElement(
210
+ UI.Button,
211
+ { variant: "outline", size: "sm", onClick: onClick },
212
+ t("refresh"),
213
+ );
214
+ }
215
+ return createElement("button", { type: "button", style: FALLBACK_BUTTON_STYLE, onClick: onClick }, t("refresh"));
216
+ }
217
+
218
+ /** One workspace block: root, stats, drift warnings, per-file lines. */
219
+ function WorkspaceBlock(props) {
220
+ var entry = props.entry;
221
+ var t = props.t;
222
+ if (entry.error !== null && entry.error !== undefined) {
223
+ return createElement(
224
+ "div",
225
+ { style: SECTION_STYLE },
226
+ createElement("div", { style: ROOT_STYLE }, entry.root),
227
+ createElement("div", { style: ERROR_STYLE }, t("readError") + entry.error),
228
+ );
229
+ }
230
+ var snapshot = entry.snapshot;
231
+ if (snapshot === null || snapshot === undefined || !snapshot.exists) {
232
+ return createElement(
233
+ "div",
234
+ { style: SECTION_STYLE },
235
+ createElement("div", { style: ROOT_STYLE }, entry.root),
236
+ createElement("div", { style: HINT_STYLE }, t("absent")),
237
+ );
238
+ }
239
+ if (snapshot.readError !== null && snapshot.readError !== undefined) {
240
+ return createElement(
241
+ "div",
242
+ { style: SECTION_STYLE },
243
+ createElement("div", { style: ROOT_STYLE }, entry.root),
244
+ createElement("div", { style: ERROR_STYLE }, t("readFailed") + snapshot.readError),
245
+ );
246
+ }
247
+ var children = [
248
+ createElement("div", { key: "root", style: ROOT_STYLE }, entry.root),
249
+ createElement(
250
+ "div",
251
+ { key: "stats", style: STATS_STYLE },
252
+ t("stats", { files: snapshot.fileCount, index: snapshot.indexEntryCount }),
253
+ ),
254
+ ];
255
+ if (snapshot.missingFiles.length > 0) {
256
+ children.push(
257
+ createElement("div", { key: "missing", style: WARN_STYLE }, t("missing") + snapshot.missingFiles.join("、")),
258
+ );
259
+ }
260
+ if (snapshot.unindexedFiles.length > 0) {
261
+ children.push(
262
+ createElement(
263
+ "div",
264
+ { key: "unindexed", style: WARN_STYLE },
265
+ t("unindexed") + snapshot.unindexedFiles.join("、"),
266
+ ),
267
+ );
268
+ }
269
+ children.push(
270
+ createElement(
271
+ "ul",
272
+ { key: "files", style: LIST_STYLE },
273
+ snapshot.files.map(function (file) {
274
+ return createElement(
275
+ "li",
276
+ { key: file.file },
277
+ createElement("strong", null, file.file),
278
+ file.description !== "" ? " — " + file.description : "",
279
+ );
280
+ }),
281
+ ),
282
+ );
283
+ return createElement("div", { style: SECTION_STYLE }, children);
284
+ }
285
+
286
+ /** The card page: refresh control + one block per known workspace. */
287
+ function MemoryCard(props) {
288
+ var t = props.t;
289
+ var UI = props.UI;
290
+ var state = useState({ status: "loading", model: null, message: "" });
291
+ var model = state[0];
292
+ var setModel = state[1];
293
+ var reloadState = useState(0);
294
+ var reload = reloadState[1];
295
+ useEffect(
296
+ function () {
297
+ var alive = true;
298
+ loadCardModel(fetch).then(
299
+ function (data) {
300
+ if (alive) setModel({ status: "ready", model: data, message: "" });
301
+ },
302
+ function (error) {
303
+ if (alive) {
304
+ setModel({ status: "error", model: null, message: error instanceof Error ? error.message : String(error) });
305
+ }
306
+ },
307
+ );
308
+ return function () {
309
+ alive = false;
310
+ };
311
+ },
312
+ [reloadState[0]],
313
+ );
314
+ var body = null;
315
+ if (model.status === "loading") {
316
+ body = createElement("div", { key: "body", style: HINT_STYLE }, t("loading"));
317
+ } else if (model.status === "error") {
318
+ body = createElement("div", { key: "body", style: ERROR_STYLE }, t("loadFailed") + model.message);
319
+ } else if (model.model.workspaces.length === 0) {
320
+ body = createElement("div", { key: "body", style: HINT_STYLE }, t("empty"));
321
+ } else {
322
+ body = createElement(
323
+ "div",
324
+ { key: "body" },
325
+ model.model.workspaces.map(function (entry) {
326
+ return createElement(WorkspaceBlock, { key: entry.root, entry: entry, t: t, UI: UI });
327
+ }),
328
+ );
329
+ }
330
+ return createElement(
331
+ "div",
332
+ { style: CARD_STYLE },
333
+ createElement(
334
+ "div",
335
+ { style: { display: "flex", alignItems: "center", gap: "12px" } },
336
+ createElement("div", { style: TITLE_STYLE }, t("title")),
337
+ createElement("span", { style: { flex: 1 } }),
338
+ createElement(RefreshButton, {
339
+ UI: UI,
340
+ t: t,
341
+ onClick: function () {
342
+ setModel({ status: "loading", model: null, message: "" });
343
+ reload(function (n) {
344
+ return n + 1;
345
+ });
346
+ },
347
+ }),
348
+ ),
349
+ createElement("div", { style: HINT_STYLE }, t("hint")),
350
+ body,
351
+ );
352
+ }
353
+
354
+ exports.name = NS;
355
+ exports.inject = ["slots", "locale", "theme"];
356
+ exports.loadCardModel = loadCardModel;
357
+ exports.apply = function apply(ctx) {
358
+ var tRaw = null;
359
+ try {
360
+ if (ctx.locale !== undefined && ctx.locale !== null) {
361
+ var register = function () {
362
+ return ctx.locale.register(NS, { zh: zh, en: en });
363
+ };
364
+ if (typeof ctx.effect === "function") {
365
+ ctx.effect(register, "dsh-continual-evolve: dictionaries");
366
+ } else if (typeof ctx.locale.register === "function") {
367
+ register();
368
+ }
369
+ if (typeof ctx.locale.bind === "function") {
370
+ tRaw = ctx.locale.bind(NS);
371
+ }
372
+ }
373
+ } catch (error) {
374
+ void error;
375
+ tRaw = null;
376
+ }
377
+ var fallbackT = function (key, params) {
378
+ var template = Object.prototype.hasOwnProperty.call(zh, key) ? zh[key] : key;
379
+ if (params !== undefined) {
380
+ return format(template, params);
381
+ }
382
+ return template;
383
+ };
384
+ var t = fallbackT;
385
+ if (typeof tRaw === "function") {
386
+ t = function (key, params) {
387
+ var rendered = null;
388
+ try {
389
+ rendered = tRaw(key);
390
+ } catch (error) {
391
+ void error;
392
+ rendered = null;
393
+ }
394
+ if (typeof rendered !== "string") {
395
+ return fallbackT(key, params);
396
+ }
397
+ if (params !== undefined) {
398
+ return format(rendered, params);
399
+ }
400
+ return rendered;
401
+ };
402
+ }
403
+ var UI = null;
404
+ if (primitives !== undefined && primitives !== null && typeof primitives.Button !== "undefined") {
405
+ UI = { Button: primitives.Button };
406
+ }
407
+ ctx.slots.inject(SLOT, function () {
408
+ return ctx.slots.register(
409
+ {
410
+ name: SLOT,
411
+ key: NS,
412
+ locale: NS,
413
+ inject: function () {
414
+ return { t: typeof tRaw === "function" ? tRaw : fallbackT };
415
+ },
416
+ },
417
+ function (ownerProps) {
418
+ return ownerProps && ownerProps.view === "summary" ? null : createElement(MemoryCard, { t: t, UI: UI });
419
+ },
420
+ );
421
+ });
422
+ };
423
+ return module.exports;
424
+ },
425
+ });
@@ -0,0 +1,32 @@
1
+ import type { KnownWorkspaces } from "./known-workspaces.js";
2
+ /** URL prefix of the card API (kind-exact routes registered under it). */
3
+ export declare const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
4
+ export declare const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
5
+ /** Minimal request shape the handlers touch (Node IncomingMessage subset). */
6
+ export interface CardRequest {
7
+ method?: string;
8
+ url?: string;
9
+ }
10
+ /** Minimal response shape the handlers touch (Node ServerResponse subset). */
11
+ export interface CardResponse {
12
+ writeHead(status: number, headers?: Record<string, string>): unknown;
13
+ end(body?: string): unknown;
14
+ }
15
+ /** One `kind: "exact"` route registration on the host web server. */
16
+ export interface CardRoute {
17
+ kind: "exact";
18
+ path: string;
19
+ handler(request: CardRequest, response: CardResponse): void;
20
+ }
21
+ /** Minimal webServer surface the card mounts on. */
22
+ export interface CardWebServer {
23
+ register(route: CardRoute): unknown;
24
+ }
25
+ /**
26
+ * Register the two card routes and return a disposer that removes them.
27
+ * Registration return values are passed through untouched: whatever disposer
28
+ * shape the host hands back (or none, as on some host versions), the
29
+ * disposer calls only the functions among them.
30
+ */
31
+ export declare function mountCardRoutes(webServer: CardWebServer, workspaces: KnownWorkspaces): () => void;
32
+ //# sourceMappingURL=card-routes.d.ts.map
@@ -0,0 +1,92 @@
1
+ /**
2
+ * The read-only HTTP surface the plugin management card fetches from.
3
+ *
4
+ * Mounted through the host's `webServer` service (same registration shape
5
+ * dshmarket uses: `kind: "exact"` routes on the profile's web server), the
6
+ * two endpoints are GET-only projections: the workspace allowlist and, for a
7
+ * known root, the memory snapshot. The allowlist IS the security fence — the
8
+ * memory endpoint resolves its `root` parameter only against roots the
9
+ * running process has actually served, so the API cannot read arbitrary
10
+ * paths. Handlers are synchronous and never throw past the response.
11
+ */
12
+ import { resolve } from "node:path";
13
+ import { memorySnapshot } from "./memory-snapshot.js";
14
+ /** URL prefix of the card API (kind-exact routes registered under it). */
15
+ export const CARD_API_WORKSPACES_PATH = "/dsh-continual-evolve/api/v1/workspaces";
16
+ export const CARD_API_MEMORY_PATH = "/dsh-continual-evolve/api/v1/memory";
17
+ function sendJson(response, status, body) {
18
+ response.writeHead(status, { "content-type": "application/json; charset=utf-8" });
19
+ response.end(JSON.stringify(body));
20
+ }
21
+ function isGet(request) {
22
+ return (request.method ?? "GET") === "GET";
23
+ }
24
+ /**
25
+ * Register the two card routes and return a disposer that removes them.
26
+ * Registration return values are passed through untouched: whatever disposer
27
+ * shape the host hands back (or none, as on some host versions), the
28
+ * disposer calls only the functions among them.
29
+ */
30
+ export function mountCardRoutes(webServer, workspaces) {
31
+ const disposers = [];
32
+ disposers.push(webServer.register({
33
+ kind: "exact",
34
+ path: CARD_API_WORKSPACES_PATH,
35
+ handler(request, response) {
36
+ if (!isGet(request)) {
37
+ response.writeHead(405, { allow: "GET" });
38
+ response.end();
39
+ return;
40
+ }
41
+ sendJson(response, 200, { workspaces: workspaces.list() });
42
+ },
43
+ }));
44
+ disposers.push(webServer.register({
45
+ kind: "exact",
46
+ path: CARD_API_MEMORY_PATH,
47
+ handler(request, response) {
48
+ if (!isGet(request)) {
49
+ response.writeHead(405, { allow: "GET" });
50
+ response.end();
51
+ return;
52
+ }
53
+ const root = rootParamOf(request);
54
+ if (root === null) {
55
+ sendJson(response, 400, { error: "root query parameter is required and must be an absolute path" });
56
+ return;
57
+ }
58
+ const known = workspaces.list().find((workspace) => workspace.root === resolve(root));
59
+ if (known === undefined) {
60
+ // The allowlist answers 404 without distinguishing "never
61
+ // served" from "wrong path": no filesystem oracle here.
62
+ sendJson(response, 404, { error: "unknown workspace root" });
63
+ return;
64
+ }
65
+ sendJson(response, 200, memorySnapshot(known.root));
66
+ },
67
+ }));
68
+ return () => {
69
+ for (const disposer of disposers) {
70
+ if (typeof disposer === "function") {
71
+ disposer();
72
+ }
73
+ }
74
+ };
75
+ }
76
+ /** The decoded `root` query parameter, or null when absent/relative. */
77
+ function rootParamOf(request) {
78
+ try {
79
+ const url = new URL(request.url ?? "/", "http://dsh-card.invalid");
80
+ const root = url.searchParams.get("root");
81
+ if (root === null || root.trim().length === 0) {
82
+ return null;
83
+ }
84
+ return root;
85
+ }
86
+ catch (error) {
87
+ // A malformed URL is a bad request, not a crash: null maps to 400.
88
+ void error;
89
+ return null;
90
+ }
91
+ }
92
+ //# sourceMappingURL=card-routes.js.map
package/lib/index.d.ts CHANGED
@@ -9,6 +9,13 @@
9
9
  * background extraction, and no governance. See
10
10
  * .agents/notes/implemented/architecture/2026-10-04-zcode-alignment-teardown.md.
11
11
  *
12
+ * The 2026-10-04 plugin-management card adds a read-only surface: the
13
+ * workspace roots the section has served feed an in-memory allowlist, and a
14
+ * `webServer` route pair projects any known workspace's store for the card
15
+ * in the official plugin manager (`plugins.bundle.config`, client bundle in
16
+ * client/client.js). The card is a viewer — see
17
+ * .agents/notes/implemented/feature/2026-10-04-plugin-management-card.md.
18
+ *
12
19
  * The host API surface is declared locally (the minimal shape this plugin
13
20
  * touches) instead of importing the DSH type packages — the section call is
14
21
  * the same one every prior release used, verified in production.
@@ -52,6 +59,17 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
52
59
  order: z<number, number, "plain">;
53
60
  maxChars: z<number, number, "plain">;
54
61
  }>>, "plain">;
62
+ /**
63
+ * The read-only memory card in the official plugin manager
64
+ * (`plugins.bundle.config`). Defaults to on; hosts without the
65
+ * `webServer` service or the `plugins.bundle.config` slot degrade to no
66
+ * card, with the memory section unaffected.
67
+ */
68
+ memoryCard: z<Schemastery.ObjectS<NoInfer<{
69
+ enabled: z<boolean, boolean, "plain">;
70
+ }>>, Schemastery.ObjectT<NoInfer<{
71
+ enabled: z<boolean, boolean, "plain">;
72
+ }>>, "plain">;
55
73
  }>>, Schemastery.ObjectT<NoInfer<{
56
74
  /**
57
75
  * The memory section: injects the workspace memory index at session
@@ -71,6 +89,17 @@ export declare const Config: z<Schemastery.ObjectS<NoInfer<{
71
89
  order: z<number, number, "plain">;
72
90
  maxChars: z<number, number, "plain">;
73
91
  }>>, "plain">;
92
+ /**
93
+ * The read-only memory card in the official plugin manager
94
+ * (`plugins.bundle.config`). Defaults to on; hosts without the
95
+ * `webServer` service or the `plugins.bundle.config` slot degrade to no
96
+ * card, with the memory section unaffected.
97
+ */
98
+ memoryCard: z<Schemastery.ObjectS<NoInfer<{
99
+ enabled: z<boolean, boolean, "plain">;
100
+ }>>, Schemastery.ObjectT<NoInfer<{
101
+ enabled: z<boolean, boolean, "plain">;
102
+ }>>, "plain">;
74
103
  }>>, "plain">;
75
104
  /**
76
105
  * Structurally typed resolved config (loader passes the validated object).
package/lib/index.js CHANGED
@@ -9,12 +9,21 @@
9
9
  * background extraction, and no governance. See
10
10
  * .agents/notes/implemented/architecture/2026-10-04-zcode-alignment-teardown.md.
11
11
  *
12
+ * The 2026-10-04 plugin-management card adds a read-only surface: the
13
+ * workspace roots the section has served feed an in-memory allowlist, and a
14
+ * `webServer` route pair projects any known workspace's store for the card
15
+ * in the official plugin manager (`plugins.bundle.config`, client bundle in
16
+ * client/client.js). The card is a viewer — see
17
+ * .agents/notes/implemented/feature/2026-10-04-plugin-management-card.md.
18
+ *
12
19
  * The host API surface is declared locally (the minimal shape this plugin
13
20
  * touches) instead of importing the DSH type packages — the section call is
14
21
  * the same one every prior release used, verified in production.
15
22
  */
16
23
  import z from "@deepseek-ai/schemastery";
17
- import { createFrozenMemorySection, DEFAULT_MAX_CHARS, DEFAULT_MEMORY_SECTION_ORDER, MEMORY_SECTION_NAME, memorySectionText, } from "./memory-section.js";
24
+ import { mountCardRoutes } from "./card-routes.js";
25
+ import { createKnownWorkspaces } from "./known-workspaces.js";
26
+ import { createFrozenMemorySection, cwdOf, DEFAULT_MAX_CHARS, DEFAULT_MEMORY_SECTION_ORDER, MEMORY_SECTION_NAME, memorySectionText, } from "./memory-section.js";
18
27
  export const name = "continual-evolve";
19
28
  export const inject = ["systemPrompt"];
20
29
  export const Config = z.object({
@@ -31,20 +40,60 @@ export const Config = z.object({
31
40
  order: z.natural(),
32
41
  maxChars: z.natural(),
33
42
  }),
43
+ /**
44
+ * The read-only memory card in the official plugin manager
45
+ * (`plugins.bundle.config`). Defaults to on; hosts without the
46
+ * `webServer` service or the `plugins.bundle.config` slot degrade to no
47
+ * card, with the memory section unaffected.
48
+ */
49
+ memoryCard: z.object({
50
+ enabled: z.boolean(),
51
+ }),
34
52
  });
53
+ /**
54
+ * Mount the card routes on the host web server through a nested inject, so
55
+ * the plugin stays mountable on hosts without that service. Called only when
56
+ * the card is enabled.
57
+ */
58
+ function mountCardWhenAvailable(host, workspaces) {
59
+ const nested = host;
60
+ if (typeof nested.inject !== "function") {
61
+ host.logger("continual-evolve").warn("host context exposes no inject — memory card routes not mounted (memory section unaffected)");
62
+ return;
63
+ }
64
+ nested.inject(["webServer"], (scoped) => {
65
+ const webServer = scoped.webServer;
66
+ if (webServer === undefined) {
67
+ host.logger("continual-evolve").warn("webServer service present but empty — memory card routes not mounted (memory section unaffected)");
68
+ return;
69
+ }
70
+ mountCardRoutes(webServer, workspaces);
71
+ });
72
+ }
35
73
  export function apply(host, config) {
36
74
  if (config.memoryIndex?.enabled === false) {
37
75
  return;
38
76
  }
77
+ const workspaces = createKnownWorkspaces();
39
78
  const frozen = createFrozenMemorySection();
40
79
  host.systemPrompt.section({
41
80
  name: MEMORY_SECTION_NAME,
42
81
  order: config.memoryIndex?.order ?? DEFAULT_MEMORY_SECTION_ORDER,
43
- text: (context) => frozen.textFor(context.agent, (agent) => memorySectionText(agent, {
44
- maxChars: config.memoryIndex?.maxChars ?? DEFAULT_MAX_CHARS,
45
- guide: config.memoryIndex?.guide ?? true,
46
- })),
82
+ text: (context) => {
83
+ const cwd = cwdOf(context.agent);
84
+ if (cwd !== undefined) {
85
+ workspaces.remember(cwd);
86
+ }
87
+ return frozen.textFor(context.agent, (agent) => memorySectionText(agent, {
88
+ maxChars: config.memoryIndex?.maxChars ?? DEFAULT_MAX_CHARS,
89
+ guide: config.memoryIndex?.guide ?? true,
90
+ }));
91
+ },
47
92
  });
48
93
  host.logger("continual-evolve").info("continual-evolve memory section registered (workspace .evolve/memory, native file read/write)");
94
+ if (config.memoryCard?.enabled === false) {
95
+ return;
96
+ }
97
+ mountCardWhenAvailable(host, workspaces);
49
98
  }
50
99
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,22 @@
1
+ /** One known workspace: the resolved root and when it was last served. */
2
+ export interface KnownWorkspace {
3
+ root: string;
4
+ /** ISO timestamp of the most recent section assembly seen for this root. */
5
+ lastSeen: string;
6
+ }
7
+ /** The allowlist the card routes read through. */
8
+ export interface KnownWorkspaces {
9
+ /**
10
+ * Record (or refresh) a workspace root. A relative or blank cwd is a
11
+ * silent no-op — the caller is the injection path, where a bad cwd
12
+ * already renders an empty section and must not turn into a throw.
13
+ */
14
+ remember(cwd: string): void;
15
+ /** Known roots, most recently served first. */
16
+ list(): KnownWorkspace[];
17
+ }
18
+ /** How many recent workspaces the card tracks. */
19
+ export declare const DEFAULT_KNOWN_WORKSPACE_LIMIT = 8;
20
+ /** Create the in-memory allowlist; `limit` is the LRU bound. */
21
+ export declare function createKnownWorkspaces(limit?: number): KnownWorkspaces;
22
+ //# sourceMappingURL=known-workspaces.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The workspace roots the card may look at, in memory only.
3
+ *
4
+ * Populated as a side effect of section assembly (the plugin already knows
5
+ * each session's cwd there), this list is the card's read allowlist: the
6
+ * memory API refuses any root the running process has not served a session
7
+ * in, so the endpoint cannot be used to probe the filesystem. Bounded LRU,
8
+ * never persisted — a restart starts empty and one session in a workspace
9
+ * puts it back. No second store on disk.
10
+ */
11
+ import { isAbsolute, resolve } from "node:path";
12
+ /** How many recent workspaces the card tracks. */
13
+ export const DEFAULT_KNOWN_WORKSPACE_LIMIT = 8;
14
+ /** Create the in-memory allowlist; `limit` is the LRU bound. */
15
+ export function createKnownWorkspaces(limit = DEFAULT_KNOWN_WORKSPACE_LIMIT) {
16
+ const max = Math.max(1, limit);
17
+ // Insertion order encodes recency: the most recently served root is last.
18
+ const recent = new Map();
19
+ return {
20
+ remember(cwd) {
21
+ const trimmed = cwd.trim();
22
+ if (trimmed.length === 0 || !isAbsolute(trimmed)) {
23
+ return;
24
+ }
25
+ recent.delete(resolve(trimmed));
26
+ recent.set(resolve(trimmed), new Date().toISOString());
27
+ while (recent.size > max) {
28
+ const oldest = recent.keys().next();
29
+ if (oldest.done === true) {
30
+ break;
31
+ }
32
+ recent.delete(oldest.value);
33
+ }
34
+ },
35
+ list() {
36
+ return [...recent.entries()]
37
+ .reverse()
38
+ .map(([root, lastSeen]) => ({ root, lastSeen }));
39
+ },
40
+ };
41
+ }
42
+ //# sourceMappingURL=known-workspaces.js.map
@@ -14,5 +14,5 @@
14
14
  /** Heading and framing before the `<memories>` block. Takes the absolute memory dir. */
15
15
  export declare function memoryGuideIntro(memoryDir: string): string;
16
16
  /** The file format, when_to_save / how_to_use rules, and maintenance discipline. */
17
- export declare const MEMORY_GUIDE_RULES = "## \u8BB0\u5FC6\u6587\u4EF6\u7684\u683C\u5F0F\n\n---\nname: <\u77ED\u6A2A\u7EBF\u5C0F\u5199\u6807\u8BC6>\ndescription: <\u4E00\u53E5\u8BDD\u76F8\u5173\u6027\u94A9\u5B50\u2014\u2014\u51B3\u5B9A\u672A\u6765\u4F1A\u8BDD\u4F1A\u4E0D\u4F1A\u60F3\u8D77\u8FD9\u6761\u8BB0\u5FC6>\nmetadata:\n type: user | feedback | project | reference\n---\n\n<\u6B63\u6587>\n\n## \u4F55\u65F6\u5199\u5165\u8BB0\u5FC6\n\n\u7528\u6237\u660E\u786E\u8981\u6C42\u8BB0\u4F4F\u65F6\uFF0C\u7ACB\u5373\u5B58\u4E3A\u6700\u5408\u9002\u7684\u7C7B\u578B\uFF1B\u8981\u6C42\u5FD8\u8BB0\u65F6\uFF0C\u627E\u5230\u5E76\u5220\u9664\u76F8\u5173\u6587\u4EF6\u4E0E\u7D22\u5F15\u884C\u3002\n\u9664\u6B64\u4E4B\u5916\uFF0C\u5728\u5BF9\u8BDD\u4E2D\u81EA\u7136\u9047\u5230\u4EE5\u4E0B\u4FE1\u53F7\u65F6\u4E3B\u52A8\u5199\u5165\u3002\u5B58\u4E4B\u524D\u5148\u8FC7\u4E00\u9053\u7B5B\u5B50\uFF1A\n**\u80FD\u4ECE\u4EE3\u7801\u3001git \u5386\u53F2\u3001\u4ED3\u5E93\u6587\u4EF6\u91CC\u91CD\u65B0\u63A8\u5BFC\u51FA\u6765\u7684\u4E8B\u5B9E\u4E0D\u5B58**\u2014\u2014\u53EA\u5B58\u91CD\u63A8\u5BFC\u4E0D\u51FA\u6765\u7684\n\u80CC\u666F\u3001\u51B3\u7B56\u548C\u504F\u597D\u3002\u76F8\u5BF9\u65E5\u671F\u4E00\u5F8B\u8F6C\u6210\u7EDD\u5BF9\u65E5\u671F\uFF08\"\u5468\u56DB\"\u2192\"2026-10-08\"\uFF09\uFF0C\u5426\u5219\u672A\u6765\n\u7684\u4F1A\u8BDD\u65E0\u6CD5\u89E3\u8BFB\u3002\u4E00\u6761\u4E8B\u5B9E\u4E00\u4E2A\u6587\u4EF6\uFF1B\u4E0D\u786E\u5B9A\u503C\u4E0D\u503C\u5F97\u5B58\u65F6\uFF0C\u95EE\u81EA\u5DF1\"\u8FD9\u6761\u7684\u4FE1\u606F\u91CF\n\u662F\u5426\u5728'\u4E3A\u4EC0\u4E48'\u91CC\uFF0C\u800C\u4E0D\u5728'\u662F\u4EC0\u4E48'\u91CC\"\u3002\n\n<type name=\"user\">\n <when_to_save>\u5F97\u77E5\u7528\u6237\u7684\u89D2\u8272\u3001\u6280\u672F\u6808\u3001\u7ECF\u9A8C\u6C34\u5E73\u3001\u504F\u597D\u6216\u77E5\u8BC6\u80CC\u666F\u7684\u4EFB\u4F55\u7EC6\u8282\u65F6\u3002\n \u4F8B\u5982\u7528\u6237\u81EA\u8FF0\"\u6211\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\u5728\u67E5\u65E5\u5FD7\u7CFB\u7EDF\"\u2014\u2014\u5B58\uFF1A\u7528\u6237\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\n \u5F53\u524D\u5173\u6CE8\u53EF\u89C2\u6D4B\u6027/\u65E5\u5FD7\u3002</when_to_save>\n <how_to_use>\u540E\u7EED\u89E3\u91CA\u548C\u5EFA\u8BAE\u6309\u7528\u6237\u7684\u80CC\u666F\u5B9A\u5236\u6DF1\u6D45\u4E0E\u7C7B\u6BD4\uFF1B\u5BF9\u8D44\u6DF1\u5DE5\u7A0B\u5E08\n \u4E0D\u89E3\u91CA\u57FA\u7840\u6982\u5FF5\uFF0C\u5BF9\u67D0\u9886\u57DF\u7684\u65B0\u624B\u7528\u5176\u719F\u6089\u9886\u57DF\u7684\u7C7B\u6BD4\u5207\u5165\u3002</how_to_use>\n</type>\n\n<type name=\"feedback\">\n <when_to_save>\u4E24\u6761\u4FE1\u53F7\uFF0C\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n \uFF081\uFF09\u7EA0\u6B63\u2014\u2014\u7528\u6237\u8BF4\"\u4E0D\u662F\u8FD9\u6837\"\"\u522B\"\"\u505C\u4E0B\"\u65F6\uFF0C\u5B58\u88AB\u7EA0\u6B63\u7684\u505A\u6CD5\u4E0E\u6B63\u786E\u505A\u6CD5\uFF1B\n \uFF082\uFF09\u786E\u8BA4\u2014\u2014\u8FD9\u662F\u66F4\u5B89\u9759\u3001\u66F4\u5BB9\u6613\u6F0F\u7684\u4FE1\u53F7\uFF1A\u7528\u6237\u63A5\u53D7\u4E86\u4F60\u4E0D\u5BFB\u5E38\u7684\u9009\u62E9\u800C\u6CA1\u6709\u5F02\u8BAE\uFF0C\n \u6216\u660E\u786E\u8BF4\"\u5BF9\uFF0C\u5C31\u8FD9\u6837\"\"\u4FDD\u6301\u8FD9\u6837\"\u3002\u786E\u8BA4\u8FC7\u7684\u5224\u65AD\u662F\u5DF2\u9A8C\u8BC1\u7684\u65B9\u6CD5\u8BBA\uFF0C\u53EA\u5B58\u7EA0\u9519\n \u4F1A\u8BA9\u4F60\u7684\u884C\u4E3A\u8D8A\u6765\u8D8A\u4FDD\u5B88\u3001\u504F\u79BB\u7528\u6237\u5DF2\u8BA4\u53EF\u7684\u8DEF\u3002\n \u4F8B\uFF1A\"\u6D4B\u8BD5\u522B mock \u6570\u636E\u5E93\uFF0C\u4E0A\u6B21 mock \u8FC7\u4E86\u7EBF\u4E0A\u8FC1\u79FB\u6302\u4E86\"\u2014\u2014\u5B58\u89C4\u5219\u5E76\u5E26\u4E0A\u4E8B\u6545\u539F\u56E0\u3002\n \u4F8B\uFF1A\"\u7ED3\u5C3E\u4E0D\u7528\u603B\u7ED3\uFF0Cdiff \u6211\u81EA\u5DF1\u4F1A\u770B\"\u2014\u2014\u5B58\uFF1A\u8BE5\u7528\u6237\u4E0D\u8981\u5C3E\u968F\u6027\u603B\u7ED3\u3002</when_to_save>\n <how_to_use>\u8BA9\u7528\u6237\u4E0D\u5FC5\u628A\u540C\u4E00\u6761\u6307\u5BFC\u8BF4\u7B2C\u4E8C\u904D\u3002</how_to_use>\n <body_structure>\u89C4\u5219\u5148\u884C\uFF0C\u968F\u540E\u5FC5\u5E26\u4E24\u884C\uFF1A**Why:**\uFF08\u7528\u6237\u7ED9\u7684\u7406\u7531\uFF0C\u5E38\u662F\u67D0\u6B21\n \u4E8B\u6545\u6216\u5F3A\u504F\u597D\u2014\u2014\u77E5\u9053\u4E3A\u4EC0\u4E48\u624D\u80FD\u5224\u65AD\u8FB9\u7F18\u60C5\u51B5\u8BE5\u4E0D\u8BE5\u7834\u4F8B\uFF09\u548C **How to apply:**\n \uFF08\u4F55\u65F6\u4F55\u5730\u751F\u6548\uFF09\u3002feedback \u7C7B\u578B\u7F3A\u8FD9\u4E24\u884C\u89C6\u4E3A\u4E0D\u5B8C\u6574\u3002</body_structure>\n</type>\n\n<type name=\"project\">\n <when_to_save>\u5F97\u77E5\u8FDB\u884C\u4E2D\u7684\u5DE5\u4F5C\u3001\u76EE\u6807\u3001\u51B3\u7B56\u3001\u622A\u6B62\u671F\u6216\u4E8B\u6545\u7684\u80CC\u666F\u52A8\u673A\u65F6\u2014\u2014\n \u51E1\u662F\"\u8C01\u5728\u505A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u3001\u5230\u4EC0\u4E48\u65F6\u5019\"\u4E14\u65E0\u6CD5\u4ECE\u4EE3\u7801\u6216 git \u63A8\u5BFC\u7684\u4FE1\u606F\u3002\n \u8FD9\u7C7B\u8BB0\u5FC6\u8870\u51CF\u5FEB\uFF0C\u72B6\u6001\u53D8\u5316\u65F6\u66F4\u65B0\u5B83\uFF0C\u522B\u5806\u53E0\u65B0\u6587\u4EF6\u3002</when_to_save>\n <how_to_use>\u7528\u5B83\u8865\u5168\u8BF7\u6C42\u80CC\u540E\u7684\u4E0A\u4E0B\u6587\u4E0E\u52A8\u673A\uFF0C\u8BA9\u5EFA\u8BAE\u8D34\u4F4F\u771F\u5B9E\u7EA6\u675F\u3002\n \u4F8B\uFF1A\"\u5468\u56DB\u8D77\u51BB\u7ED3\u5408\u5E76\"\u2014\u2014\u5B58\uFF1A2026-03-05 \u8D77\u51BB\u7ED3\u5408\u5E76\uFF0C\u975E\u5173\u952E PR \u63D0\u524D\u6807\u8BB0\u3002</how_to_use>\n <body_structure>\u4E8B\u5B9E/\u51B3\u7B56\u5148\u884C + **Why:**\uFF08\u52A8\u673A\uFF1A\u7EA6\u675F\u3001deadline\u3001\u5E72\u7CFB\u4EBA\u8981\u6C42\uFF09\n + **How to apply:**\uFF08\u8FD9\u6761\u5982\u4F55\u5F71\u54CD\u540E\u7EED\u5EFA\u8BAE\uFF09\u3002</body_structure>\n</type>\n\n<type name=\"reference\">\n <when_to_save>\u7528\u6237\u63D0\u5230\u672A\u6765\u4F1A\u7528\u5230\u7684\u5916\u90E8\u8D44\u6E90\u6307\u9488\uFF1AURL\u3001\u4EEA\u8868\u76D8\u3001\u5DE5\u5355\u3001\n \u6587\u6863\u4F4D\u7F6E\u3002</when_to_save>\n <how_to_use>\u9700\u8981\u65F6\u76F4\u63A5\u53D6\u7528\uFF0C\u4E0D\u91CD\u65B0\u68C0\u7D22\u3002</how_to_use>\n</type>\n\n## \u5982\u4F55\u7EF4\u62A4\n\n- \u5199\u5B8C\u8BB0\u5FC6\u6587\u4EF6\u540E\u7ACB\u5373\u5728 MEMORY.md \u8FFD\u52A0\u6216\u66F4\u65B0\u4E00\u884C\u7D22\u5F15\uFF1A`- [\u6807\u9898](\u6587\u4EF6\u540D.md) \u2014 \u94A9\u5B50`\uFF1B\u72B6\u6001\u53D8\u5316\u65F6\u539F\u6587\u4E0E\u7D22\u5F15\u884C\u4E00\u8D77\u6539\uFF0C\u522B\u8BA9\u7D22\u5F15\u8BF4\u8C0E\u3002\n- \u66F4\u65B0\u4F18\u5148\u4E8E\u65B0\u5EFA\uFF1A\u72B6\u6001\u53D8\u4E86\u6539\u539F\u6587\u4EF6\uFF0C\u4E0D\u53E6\u8D77\u4E00\u6761\uFF08\u65B0\u65E7\u5E76\u5B58\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\uFF09\u3002\n- [[\u540D\u5B57]] \u4E92\u76F8\u5F15\u7528\u76F8\u5173\u8BB0\u5FC6\uFF1B\u5F15\u7528\u8FD8\u4E0D\u5B58\u5728\u7684\u540D\u5B57\u662F\u5408\u6CD5\u7684\uFF0C\u5B83\u6807\u8BB0\"\u503C\u5F97\u5C06\u6765\u8865\u5199\"\u3002";
17
+ export declare const MEMORY_GUIDE_RULES = "## \u8BB0\u5FC6\u6587\u4EF6\u7684\u683C\u5F0F\n\n---\nname: <\u77ED\u6A2A\u7EBF\u5C0F\u5199\u6807\u8BC6>\ndescription: <\u4E00\u53E5\u8BDD\u76F8\u5173\u6027\u94A9\u5B50\u2014\u2014\u51B3\u5B9A\u672A\u6765\u4F1A\u8BDD\u4F1A\u4E0D\u4F1A\u60F3\u8D77\u8FD9\u6761\u8BB0\u5FC6>\nmetadata:\n type: user | feedback | project | reference\n---\n\n<\u6B63\u6587>\n\n## \u4F55\u65F6\u5199\u5165\u8BB0\u5FC6\n\n\u7528\u6237\u660E\u786E\u8981\u6C42\u8BB0\u4F4F\u65F6\uFF0C\u7ACB\u5373\u5B58\u4E3A\u6700\u5408\u9002\u7684\u7C7B\u578B\uFF1B\u8981\u6C42\u5FD8\u8BB0\u65F6\uFF0C\u627E\u5230\u5E76\u5220\u9664\u76F8\u5173\u6587\u4EF6\u4E0E\u7D22\u5F15\u884C\u3002\n\u9664\u6B64\u4E4B\u5916\uFF0C\u5728\u5BF9\u8BDD\u4E2D\u81EA\u7136\u9047\u5230\u4EE5\u4E0B\u4FE1\u53F7\u65F6\u4E3B\u52A8\u5199\u5165\u3002\u5B58\u4E4B\u524D\u5148\u8FC7\u4E00\u9053\u7B5B\u5B50\uFF1A\n**\u80FD\u4ECE\u4EE3\u7801\u3001git \u5386\u53F2\u3001\u4ED3\u5E93\u6587\u4EF6\u91CC\u91CD\u65B0\u63A8\u5BFC\u51FA\u6765\u7684\u4E8B\u5B9E\u4E0D\u5B58**\u2014\u2014\u53EA\u5B58\u91CD\u63A8\u5BFC\u4E0D\u51FA\u6765\u7684\n\u80CC\u666F\u3001\u51B3\u7B56\u548C\u504F\u597D\u3002\u76F8\u5BF9\u65E5\u671F\u4E00\u5F8B\u8F6C\u6210\u7EDD\u5BF9\u65E5\u671F\uFF08\"\u5468\u56DB\"\u2192\"2026-10-08\"\uFF09\uFF0C\u5426\u5219\u672A\u6765\n\u7684\u4F1A\u8BDD\u65E0\u6CD5\u89E3\u8BFB\u3002\u4E00\u6761\u4E8B\u5B9E\u4E00\u4E2A\u6587\u4EF6\uFF1B\u4E0D\u786E\u5B9A\u503C\u4E0D\u503C\u5F97\u5B58\u65F6\uFF0C\u95EE\u81EA\u5DF1\"\u8FD9\u6761\u7684\u4FE1\u606F\u91CF\n\u662F\u5426\u5728'\u4E3A\u4EC0\u4E48'\u91CC\uFF0C\u800C\u4E0D\u5728'\u662F\u4EC0\u4E48'\u91CC\"\u3002\n\n<type name=\"user\">\n <when_to_save>\u5F97\u77E5\u7528\u6237\u7684\u89D2\u8272\u3001\u6280\u672F\u6808\u3001\u7ECF\u9A8C\u6C34\u5E73\u3001\u504F\u597D\u6216\u77E5\u8BC6\u80CC\u666F\u7684\u4EFB\u4F55\u7EC6\u8282\u65F6\u3002\n \u4F8B\u5982\u7528\u6237\u81EA\u8FF0\"\u6211\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\u5728\u67E5\u65E5\u5FD7\u7CFB\u7EDF\"\u2014\u2014\u5B58\uFF1A\u7528\u6237\u662F\u6570\u636E\u79D1\u5B66\u5BB6\uFF0C\n \u5F53\u524D\u5173\u6CE8\u53EF\u89C2\u6D4B\u6027/\u65E5\u5FD7\u3002</when_to_save>\n <how_to_use>\u540E\u7EED\u89E3\u91CA\u548C\u5EFA\u8BAE\u6309\u7528\u6237\u7684\u80CC\u666F\u5B9A\u5236\u6DF1\u6D45\u4E0E\u7C7B\u6BD4\uFF1B\u5BF9\u8D44\u6DF1\u5DE5\u7A0B\u5E08\n \u4E0D\u89E3\u91CA\u57FA\u7840\u6982\u5FF5\uFF0C\u5BF9\u67D0\u9886\u57DF\u7684\u65B0\u624B\u7528\u5176\u719F\u6089\u9886\u57DF\u7684\u7C7B\u6BD4\u5207\u5165\u3002</how_to_use>\n</type>\n\n<type name=\"feedback\">\n <when_to_save>\u4E24\u6761\u4FE1\u53F7\uFF0C\u7F3A\u4E00\u4E0D\u53EF\uFF1A\n \uFF081\uFF09\u7EA0\u6B63\u2014\u2014\u7528\u6237\u8BF4\"\u4E0D\u662F\u8FD9\u6837\"\"\u522B\"\"\u505C\u4E0B\"\u65F6\uFF0C\u5B58\u88AB\u7EA0\u6B63\u7684\u505A\u6CD5\u4E0E\u6B63\u786E\u505A\u6CD5\uFF1B\n \uFF082\uFF09\u786E\u8BA4\u2014\u2014\u8FD9\u662F\u66F4\u5B89\u9759\u3001\u66F4\u5BB9\u6613\u6F0F\u7684\u4FE1\u53F7\uFF1A\u7528\u6237\u63A5\u53D7\u4E86\u4F60\u4E0D\u5BFB\u5E38\u7684\u9009\u62E9\u800C\u6CA1\u6709\u5F02\u8BAE\uFF0C\n \u6216\u660E\u786E\u8BF4\"\u5BF9\uFF0C\u5C31\u8FD9\u6837\"\"\u4FDD\u6301\u8FD9\u6837\"\u3002\u786E\u8BA4\u8FC7\u7684\u5224\u65AD\u662F\u5DF2\u9A8C\u8BC1\u7684\u65B9\u6CD5\u8BBA\uFF0C\u53EA\u5B58\u7EA0\u9519\n \u4F1A\u8BA9\u4F60\u7684\u884C\u4E3A\u8D8A\u6765\u8D8A\u4FDD\u5B88\u3001\u504F\u79BB\u7528\u6237\u5DF2\u8BA4\u53EF\u7684\u8DEF\u3002\n \u4F8B\uFF1A\"\u6D4B\u8BD5\u522B mock \u6570\u636E\u5E93\uFF0C\u4E0A\u6B21 mock \u8FC7\u4E86\u7EBF\u4E0A\u8FC1\u79FB\u6302\u4E86\"\u2014\u2014\u5B58\u89C4\u5219\u5E76\u5E26\u4E0A\u4E8B\u6545\u539F\u56E0\u3002\n \u4F8B\uFF1A\"\u7ED3\u5C3E\u4E0D\u7528\u603B\u7ED3\uFF0Cdiff \u6211\u81EA\u5DF1\u4F1A\u770B\"\u2014\u2014\u5B58\uFF1A\u8BE5\u7528\u6237\u4E0D\u8981\u5C3E\u968F\u6027\u603B\u7ED3\u3002</when_to_save>\n <how_to_use>\u8BA9\u7528\u6237\u4E0D\u5FC5\u628A\u540C\u4E00\u6761\u6307\u5BFC\u8BF4\u7B2C\u4E8C\u904D\u3002</how_to_use>\n <body_structure>\u89C4\u5219\u5148\u884C\uFF0C\u968F\u540E\u5FC5\u5E26\u4E24\u884C\uFF1A**Why:**\uFF08\u7528\u6237\u7ED9\u7684\u7406\u7531\uFF0C\u5E38\u662F\u67D0\u6B21\n \u4E8B\u6545\u6216\u5F3A\u504F\u597D\u2014\u2014\u77E5\u9053\u4E3A\u4EC0\u4E48\u624D\u80FD\u5224\u65AD\u8FB9\u7F18\u60C5\u51B5\u8BE5\u4E0D\u8BE5\u7834\u4F8B\uFF09\u548C **How to apply:**\n \uFF08\u4F55\u65F6\u4F55\u5730\u751F\u6548\uFF09\u3002feedback \u7C7B\u578B\u7F3A\u8FD9\u4E24\u884C\u89C6\u4E3A\u4E0D\u5B8C\u6574\u3002</body_structure>\n</type>\n\n<type name=\"project\">\n <when_to_save>\u5F97\u77E5\u8FDB\u884C\u4E2D\u7684\u5DE5\u4F5C\u3001\u76EE\u6807\u3001\u51B3\u7B56\u3001\u622A\u6B62\u671F\u6216\u4E8B\u6545\u7684\u80CC\u666F\u52A8\u673A\u65F6\u2014\u2014\n \u51E1\u662F\"\u8C01\u5728\u505A\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u3001\u5230\u4EC0\u4E48\u65F6\u5019\"\u4E14\u65E0\u6CD5\u4ECE\u4EE3\u7801\u6216 git \u63A8\u5BFC\u7684\u4FE1\u606F\u3002\n \u8FD9\u7C7B\u8BB0\u5FC6\u8870\u51CF\u5FEB\uFF0C\u72B6\u6001\u53D8\u5316\u65F6\u66F4\u65B0\u5B83\uFF0C\u522B\u5806\u53E0\u65B0\u6587\u4EF6\u3002</when_to_save>\n <how_to_use>\u7528\u5B83\u8865\u5168\u8BF7\u6C42\u80CC\u540E\u7684\u4E0A\u4E0B\u6587\u4E0E\u52A8\u673A\uFF0C\u8BA9\u5EFA\u8BAE\u8D34\u4F4F\u771F\u5B9E\u7EA6\u675F\u3002\n \u4F8B\uFF1A\"\u5468\u56DB\u8D77\u51BB\u7ED3\u5408\u5E76\"\u2014\u2014\u5B58\uFF1A2026-03-05 \u8D77\u51BB\u7ED3\u5408\u5E76\uFF0C\u975E\u5173\u952E PR \u63D0\u524D\u6807\u8BB0\u3002</how_to_use>\n <body_structure>\u4E8B\u5B9E/\u51B3\u7B56\u5148\u884C + **Why:**\uFF08\u52A8\u673A\uFF1A\u7EA6\u675F\u3001deadline\u3001\u5E72\u7CFB\u4EBA\u8981\u6C42\uFF09\n + **How to apply:**\uFF08\u8FD9\u6761\u5982\u4F55\u5F71\u54CD\u540E\u7EED\u5EFA\u8BAE\uFF09\u3002</body_structure>\n</type>\n\n<type name=\"reference\">\n <when_to_save>\u7528\u6237\u63D0\u5230\u672A\u6765\u4F1A\u7528\u5230\u7684\u5916\u90E8\u8D44\u6E90\u6307\u9488\uFF1AURL\u3001\u4EEA\u8868\u76D8\u3001\u5DE5\u5355\u3001\n \u6587\u6863\u4F4D\u7F6E\u3002</when_to_save>\n <how_to_use>\u9700\u8981\u65F6\u76F4\u63A5\u53D6\u7528\uFF0C\u4E0D\u91CD\u65B0\u68C0\u7D22\u3002</how_to_use>\n</type>\n\n## \u5982\u4F55\u7EF4\u62A4\n\n- \u5199\u5B8C\u8BB0\u5FC6\u6587\u4EF6\u540E\u7ACB\u5373\u5728 MEMORY.md \u8FFD\u52A0\u6216\u66F4\u65B0\u4E00\u884C\u7D22\u5F15\uFF1A`- [\u6807\u9898](\u6587\u4EF6\u540D.md) \u2014 \u94A9\u5B50`\uFF1B\u72B6\u6001\u53D8\u5316\u65F6\u539F\u6587\u4E0E\u7D22\u5F15\u884C\u4E00\u8D77\u6539\uFF0C\u522B\u8BA9\u7D22\u5F15\u8BF4\u8C0E\u3002\n- \u66F4\u65B0\u4F18\u5148\u4E8E\u65B0\u5EFA\uFF1A\u72B6\u6001\u53D8\u4E86\u6539\u539F\u6587\u4EF6\uFF0C\u4E0D\u53E6\u8D77\u4E00\u6761\uFF08\u65B0\u65E7\u5E76\u5B58\u6BD4\u6CA1\u6709\u8BB0\u5FC6\u66F4\u7CDF\uFF09\u3002\n- \u5F15\u7528\u8BB0\u5FC6\u524D\u82E5\u53D1\u73B0\u4E0E\u73B0\u5B9E\u4E0D\u7B26\uFF0C\u5F53\u573A\u4FEE\u6B63\u6216\u5220\u9664\u8BE5\u6587\u4EF6\u4E0E\u7D22\u5F15\u884C\u2014\u2014\u574F\u8BB0\u5FC6\u7684\u5371\u5BB3\u968F\u5F15\u7528\u6269\u6563\uFF0C\n \u9000\u5F79\u4E0D\u7559\u7ED9\u7528\u6237\u3002\n- [[\u540D\u5B57]] \u4E92\u76F8\u5F15\u7528\u76F8\u5173\u8BB0\u5FC6\uFF1B\u5F15\u7528\u8FD8\u4E0D\u5B58\u5728\u7684\u540D\u5B57\u662F\u5408\u6CD5\u7684\uFF0C\u5B83\u6807\u8BB0\"\u503C\u5F97\u5C06\u6765\u8865\u5199\"\u3002";
18
18
  //# sourceMappingURL=memory-guide.d.ts.map
@@ -80,5 +80,7 @@ metadata:
80
80
 
81
81
  - 写完记忆文件后立即在 MEMORY.md 追加或更新一行索引:\`- [标题](文件名.md) — 钩子\`;状态变化时原文与索引行一起改,别让索引说谎。
82
82
  - 更新优先于新建:状态变了改原文件,不另起一条(新旧并存比没有记忆更糟)。
83
+ - 引用记忆前若发现与现实不符,当场修正或删除该文件与索引行——坏记忆的危害随引用扩散,
84
+ 退役不留给用户。
83
85
  - [[名字]] 互相引用相关记忆;引用还不存在的名字是合法的,它标记"值得将来补写"。`;
84
86
  //# sourceMappingURL=memory-guide.js.map
@@ -0,0 +1,54 @@
1
+ /** The index file name (same store contract as the injection path). */
2
+ export declare const MEMORY_SNAPSHOT_INDEX_FILE = "MEMORY.md";
3
+ /** One memory file with its frontmatter fields ("" when absent). */
4
+ export interface MemoryFileInfo {
5
+ file: string;
6
+ name: string;
7
+ description: string;
8
+ type: string;
9
+ }
10
+ /** One parsed index row: `- [title](file.md) — hook`. */
11
+ export interface MemoryIndexRow {
12
+ title: string;
13
+ file: string;
14
+ }
15
+ /** The full read-only snapshot the card renders for one workspace root. */
16
+ export interface MemorySnapshot {
17
+ root: string;
18
+ memoryDir: string;
19
+ exists: boolean;
20
+ fileCount: number;
21
+ indexEntryCount: number;
22
+ files: MemoryFileInfo[];
23
+ /** Index rows whose referenced file no longer exists on disk. */
24
+ missingFiles: string[];
25
+ /** .md files on disk that no index row references. */
26
+ unindexedFiles: string[];
27
+ /** Set when the store exists but could not be read; null otherwise. */
28
+ readError: string | null;
29
+ }
30
+ /** Minimal frontmatter fields the card shows. */
31
+ export interface MemoryFrontmatter {
32
+ name: string;
33
+ description: string;
34
+ type: string;
35
+ }
36
+ /**
37
+ * Parse `- [title](file.md) …` rows out of an index text. Anything else
38
+ * (headings, the bootstrap hint comment, blank lines) is ignored.
39
+ */
40
+ export declare function parseMemoryIndexRows(indexText: string): MemoryIndexRow[];
41
+ /**
42
+ * Read the card-relevant frontmatter fields (`name`, `description`, `type`)
43
+ * from a leading `---` block. A missing or malformed block yields "" fields —
44
+ * the body text remains the source of truth the user opens in the editor.
45
+ */
46
+ export declare function parseMemoryFrontmatter(text: string): MemoryFrontmatter;
47
+ /**
48
+ * The read-only memory snapshot for one workspace root. A missing store
49
+ * reports `exists: false` (the card shows the empty state); a store that
50
+ * exists but fails to read reports `readError` instead of throwing — the
51
+ * card must observe, never break.
52
+ */
53
+ export declare function memorySnapshot(root: string): MemorySnapshot;
54
+ //# sourceMappingURL=memory-snapshot.d.ts.map
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Read-only projection of one workspace memory store for the plugin
3
+ * management card. Everything here is computed from disk at call time — the
4
+ * card is a viewer, never a second store: no caching, no writes, no bootstrap
5
+ * (unlike the injection path, a missing store is reported, not created).
6
+ */
7
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
8
+ import { join, resolve } from "node:path";
9
+ import { memoryDirFor } from "./memory-section.js";
10
+ /** The index file name (same store contract as the injection path). */
11
+ export const MEMORY_SNAPSHOT_INDEX_FILE = "MEMORY.md";
12
+ /**
13
+ * Parse `- [title](file.md) …` rows out of an index text. Anything else
14
+ * (headings, the bootstrap hint comment, blank lines) is ignored.
15
+ */
16
+ export function parseMemoryIndexRows(indexText) {
17
+ const rows = [];
18
+ for (const line of indexText.split("\n")) {
19
+ const match = /^-\s+\[(.+?)\]\(([^()\s]+\.md)\)/.exec(line.trim());
20
+ if (match?.[1] !== undefined && match?.[2] !== undefined) {
21
+ rows.push({ title: match[1], file: match[2] });
22
+ }
23
+ }
24
+ return rows;
25
+ }
26
+ /**
27
+ * Read the card-relevant frontmatter fields (`name`, `description`, `type`)
28
+ * from a leading `---` block. A missing or malformed block yields "" fields —
29
+ * the body text remains the source of truth the user opens in the editor.
30
+ */
31
+ export function parseMemoryFrontmatter(text) {
32
+ const fields = { name: "", description: "", type: "" };
33
+ if (!text.startsWith("---")) {
34
+ return fields;
35
+ }
36
+ const blockEnd = text.indexOf("\n---", 3);
37
+ if (blockEnd < 0) {
38
+ return fields;
39
+ }
40
+ for (const line of text.slice(3, blockEnd).split("\n")) {
41
+ const match = /^(name|description|type):\s*(.*)$/.exec(line.trim());
42
+ if (match?.[1] === undefined || match?.[2] === undefined) {
43
+ continue;
44
+ }
45
+ const value = match[2].trim().replace(/^["']|["']$/g, "");
46
+ if (match[1] === "name") {
47
+ fields.name = value;
48
+ }
49
+ else if (match[1] === "description") {
50
+ fields.description = value;
51
+ }
52
+ else {
53
+ fields.type = value;
54
+ }
55
+ }
56
+ return fields;
57
+ }
58
+ /** Store files the card inspects: every .md except the index, sorted. */
59
+ function listStoreFiles(memoryDir) {
60
+ return readdirSync(memoryDir)
61
+ .filter((file) => file.endsWith(".md") && file !== MEMORY_SNAPSHOT_INDEX_FILE)
62
+ .sort();
63
+ }
64
+ /** Single read pass over the store; throws on any filesystem failure. */
65
+ function readStoreState(memoryDir) {
66
+ const indexText = readFileSync(join(memoryDir, MEMORY_SNAPSHOT_INDEX_FILE), "utf8");
67
+ const diskFiles = listStoreFiles(memoryDir);
68
+ const files = diskFiles.map((file) => {
69
+ const frontmatter = parseMemoryFrontmatter(readFileSync(join(memoryDir, file), "utf8"));
70
+ return { file, name: frontmatter.name, description: frontmatter.description, type: frontmatter.type };
71
+ });
72
+ return { indexRows: parseMemoryIndexRows(indexText), diskFiles, files };
73
+ }
74
+ /**
75
+ * The read-only memory snapshot for one workspace root. A missing store
76
+ * reports `exists: false` (the card shows the empty state); a store that
77
+ * exists but fails to read reports `readError` instead of throwing — the
78
+ * card must observe, never break.
79
+ */
80
+ export function memorySnapshot(root) {
81
+ const workspaceRoot = resolve(root);
82
+ const memoryDir = memoryDirFor(workspaceRoot);
83
+ const absent = {
84
+ root: workspaceRoot,
85
+ memoryDir,
86
+ exists: false,
87
+ fileCount: 0,
88
+ indexEntryCount: 0,
89
+ files: [],
90
+ missingFiles: [],
91
+ unindexedFiles: [],
92
+ readError: null,
93
+ };
94
+ if (!existsSync(memoryDir)) {
95
+ return absent;
96
+ }
97
+ let state;
98
+ try {
99
+ state = readStoreState(memoryDir);
100
+ }
101
+ catch (error) {
102
+ // Filesystem read failure degrades to an error field: the card shows
103
+ // what went wrong instead of taking the host down with it.
104
+ return {
105
+ ...absent,
106
+ exists: true,
107
+ readError: error instanceof Error ? error.message : String(error),
108
+ };
109
+ }
110
+ const referenced = new Set(state.indexRows.map((row) => row.file));
111
+ return {
112
+ root: workspaceRoot,
113
+ memoryDir,
114
+ exists: true,
115
+ fileCount: state.diskFiles.length,
116
+ indexEntryCount: state.indexRows.length,
117
+ files: state.files,
118
+ missingFiles: state.indexRows
119
+ .map((row) => row.file)
120
+ .filter((file) => !state.diskFiles.includes(file)),
121
+ unindexedFiles: state.diskFiles.filter((file) => !referenced.has(file)),
122
+ readError: null,
123
+ };
124
+ }
125
+ //# sourceMappingURL=memory-snapshot.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-continual-evolve",
3
- "version": "0.12.0",
4
- "description": "ZCode-style workspace memory for DeepSeek Harness: a per-workspace markdown store (<workspace>/.evolve/memory/) with session-start index injection — the model reads and writes the files with its native tools.",
3
+ "version": "0.13.0",
4
+ "description": "DSH 工作区记忆:每个工作区一个 markdown 记忆库,会话开局注入索引,模型原生读写 / ZCode-style workspace memory for DeepSeek Harness: a per-workspace markdown store with session-start index injection.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -19,11 +19,13 @@
19
19
  "types": "./lib/index.d.ts",
20
20
  "default": "./lib/index.js"
21
21
  },
22
+ "./client": "./client/client.js",
22
23
  "./package.json": "./package.json"
23
24
  },
24
25
  "files": [
25
26
  "lib/**/*.js",
26
27
  "lib/**/*.d.ts",
28
+ "client/client.js",
27
29
  "cordis.patch.yml",
28
30
  "README.md",
29
31
  "LICENSE"
@@ -31,6 +33,14 @@
31
33
  "dsh": {
32
34
  "bundle": {
33
35
  "patch": "./cordis.patch.yml"
36
+ },
37
+ "client": {
38
+ "inject": [
39
+ "@deepseek-ai/dsh-client-locale",
40
+ "@deepseek-ai/dsh-client-ui-settings",
41
+ "@deepseek-ai/dsh-client-ui-theme"
42
+ ],
43
+ "platform": "web"
34
44
  }
35
45
  },
36
46
  "engines": {