dsh-continual-evolve 0.12.0 → 0.12.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 +14 -7
- package/README.zh.md +14 -7
- package/client/client.js +253 -0
- package/lib/card-routes.d.ts +32 -0
- package/lib/card-routes.js +92 -0
- package/lib/index.d.ts +29 -0
- package/lib/index.js +54 -5
- package/lib/known-workspaces.d.ts +22 -0
- package/lib/known-workspaces.js +42 -0
- package/lib/memory-guide.d.ts +1 -1
- package/lib/memory-guide.js +2 -0
- package/lib/memory-snapshot.d.ts +54 -0
- package/lib/memory-snapshot.js +125 -0
- package/package.json +7 -1
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@ English | [中文](README.zh.md)
|
|
|
7
7
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
|
-
[]()
|
|
13
13
|
[]()
|
|
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 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 (
|
|
69
|
+
pnpm test # vitest (62 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
|
-
│
|
|
79
|
-
├──
|
|
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
|
[](https://github.com/ZK-Andy/dsh-continual-evolve/actions/workflows/ci.yml)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](package.json)
|
|
10
|
-
[]()
|
|
11
11
|
[]()
|
|
12
|
-
[]()
|
|
13
13
|
[]()
|
|
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,老宿主自动降级为无卡片):最近服务过的工作区、各自记忆库的条目数/索引行数、索引漂移警告(未索引文件与索引失联文件)。卡片是磁盘文件的请求时投影——单一事实源永远是 `.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(
|
|
69
|
+
pnpm test # vitest(62 例)
|
|
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
|
-
│
|
|
79
|
-
├──
|
|
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 笔记)
|
package/client/client.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
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: only
|
|
8
|
+
* react) and returns its `module.exports`. The host mounts the bundle via
|
|
9
|
+
* package.json `dsh.client` + the `./client` export.
|
|
10
|
+
*
|
|
11
|
+
* The card registers one slot, `plugins.bundle.config` — the seat the 0.1.7+
|
|
12
|
+
* plugin manager declares for a bundle's own page. Slot detection is the
|
|
13
|
+
* compatibility contract: a host that never declares the slot never runs the
|
|
14
|
+
* registration, so the card is silently absent while the memory section is
|
|
15
|
+
* unaffected. The render function answers the host's `view === "summary"`
|
|
16
|
+
* call with null (the bundle list row shows the package description; the
|
|
17
|
+
* card is the page).
|
|
18
|
+
*
|
|
19
|
+
* The card is a read-only projection of the workspace memory store, fetched
|
|
20
|
+
* at render time from this plugin's host routes (same-origin). No editor, no
|
|
21
|
+
* write path, no cached state — refresh refetches everything.
|
|
22
|
+
*/
|
|
23
|
+
window.__ModuleLoader__.load({
|
|
24
|
+
id: "dsh-continual-evolve",
|
|
25
|
+
factory: function (require) {
|
|
26
|
+
var module = { exports: {} };
|
|
27
|
+
var exports = module.exports;
|
|
28
|
+
var react = undefined;
|
|
29
|
+
try {
|
|
30
|
+
react = require("react");
|
|
31
|
+
} catch (error) {
|
|
32
|
+
// Browser side: this runs before any cordis logger exporter exists,
|
|
33
|
+
// so console is the only outlet (same escape hatch dshmarket uses).
|
|
34
|
+
console.warn(
|
|
35
|
+
"[dsh-continual-evolve] host react module unavailable — card disabled: " +
|
|
36
|
+
(error instanceof Error ? error.message : String(error)),
|
|
37
|
+
);
|
|
38
|
+
return module.exports;
|
|
39
|
+
}
|
|
40
|
+
if (react === undefined || typeof react.createElement !== "function") {
|
|
41
|
+
// A module table that resolves react to nothing is the same downgrade:
|
|
42
|
+
// render would throw and blank the whole settings dialog (dshmarket #671 shape).
|
|
43
|
+
console.warn("[dsh-continual-evolve] host react module has no createElement — card disabled");
|
|
44
|
+
return module.exports;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
var createElement = react.createElement;
|
|
48
|
+
var useState = react.useState;
|
|
49
|
+
var useEffect = react.useEffect;
|
|
50
|
+
|
|
51
|
+
var NS = "dsh-continual-evolve";
|
|
52
|
+
var SLOT = "plugins.bundle.config";
|
|
53
|
+
var API_ROOT = "/dsh-continual-evolve/api/v1";
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Assemble the card's view model: the workspace allowlist, then one
|
|
57
|
+
* snapshot per workspace. Network or API failure per workspace degrades
|
|
58
|
+
* to an `error` string on that workspace's entry — one bad store never
|
|
59
|
+
* blanks the whole card.
|
|
60
|
+
*
|
|
61
|
+
* @param {typeof fetch} fetchImpl - injectable for tests.
|
|
62
|
+
* @returns {Promise<{workspaces: Array<object>}>}
|
|
63
|
+
*/
|
|
64
|
+
async function loadCardModel(fetchImpl) {
|
|
65
|
+
var listResponse = await fetchImpl(API_ROOT + "/workspaces");
|
|
66
|
+
if (!listResponse.ok) {
|
|
67
|
+
throw new Error("workspaces API answered HTTP " + listResponse.status);
|
|
68
|
+
}
|
|
69
|
+
var listBody = await listResponse.json();
|
|
70
|
+
var workspaces = Array.isArray(listBody.workspaces) ? listBody.workspaces : [];
|
|
71
|
+
var entries = await Promise.all(
|
|
72
|
+
workspaces.map(async function (workspace) {
|
|
73
|
+
var root = typeof workspace.root === "string" ? workspace.root : "";
|
|
74
|
+
var lastSeen = typeof workspace.lastSeen === "string" ? workspace.lastSeen : "";
|
|
75
|
+
try {
|
|
76
|
+
var snapshotResponse = await fetchImpl(API_ROOT + "/memory?root=" + encodeURIComponent(root));
|
|
77
|
+
if (!snapshotResponse.ok) {
|
|
78
|
+
return { root: root, lastSeen: lastSeen, snapshot: null, error: "HTTP " + snapshotResponse.status };
|
|
79
|
+
}
|
|
80
|
+
return { root: root, lastSeen: lastSeen, snapshot: await snapshotResponse.json(), error: null };
|
|
81
|
+
} catch (error) {
|
|
82
|
+
return {
|
|
83
|
+
root: root,
|
|
84
|
+
lastSeen: lastSeen,
|
|
85
|
+
snapshot: null,
|
|
86
|
+
error: error instanceof Error ? error.message : String(error),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
91
|
+
return { workspaces: entries };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** One workspace block: root, stats, drift warnings, per-file lines. */
|
|
95
|
+
function WorkspaceBlock(props) {
|
|
96
|
+
var entry = props.entry;
|
|
97
|
+
var snapshot = entry.snapshot;
|
|
98
|
+
var children = [
|
|
99
|
+
createElement("div", { key: "root", style: { fontFamily: "monospace", wordBreak: "break-all" } }, entry.root),
|
|
100
|
+
];
|
|
101
|
+
if (entry.error !== null) {
|
|
102
|
+
children.push(createElement("div", { key: "error" }, "读取失败:" + entry.error));
|
|
103
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
104
|
+
}
|
|
105
|
+
if (snapshot === null || !snapshot.exists) {
|
|
106
|
+
children.push(createElement("div", { key: "absent" }, "该工作区尚无记忆库(开一次会话自动创建)。"));
|
|
107
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
108
|
+
}
|
|
109
|
+
if (snapshot.readError !== null) {
|
|
110
|
+
children.push(createElement("div", { key: "readError" }, "记忆库读取失败:" + snapshot.readError));
|
|
111
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
112
|
+
}
|
|
113
|
+
children.push(
|
|
114
|
+
createElement(
|
|
115
|
+
"div",
|
|
116
|
+
{ key: "stats" },
|
|
117
|
+
snapshot.fileCount + " 条记忆 · 索引 " + snapshot.indexEntryCount + " 行",
|
|
118
|
+
),
|
|
119
|
+
);
|
|
120
|
+
if (snapshot.missingFiles.length > 0) {
|
|
121
|
+
children.push(
|
|
122
|
+
createElement(
|
|
123
|
+
"div",
|
|
124
|
+
{ key: "missing" },
|
|
125
|
+
"索引失联(索引引用但文件已删):" + snapshot.missingFiles.join("、"),
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
if (snapshot.unindexedFiles.length > 0) {
|
|
130
|
+
children.push(
|
|
131
|
+
createElement(
|
|
132
|
+
"div",
|
|
133
|
+
{ key: "unindexed" },
|
|
134
|
+
"未入索引(文件存在但索引没有行):" + snapshot.unindexedFiles.join("、"),
|
|
135
|
+
),
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
children.push(
|
|
139
|
+
createElement(
|
|
140
|
+
"ul",
|
|
141
|
+
{ key: "files", style: { margin: "4px 0 0", paddingLeft: "18px" } },
|
|
142
|
+
snapshot.files.map(function (file) {
|
|
143
|
+
return createElement(
|
|
144
|
+
"li",
|
|
145
|
+
{ key: file.file },
|
|
146
|
+
createElement("strong", null, file.file) +
|
|
147
|
+
(file.description !== "" ? " — " + file.description : ""),
|
|
148
|
+
);
|
|
149
|
+
}),
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
return createElement("div", { style: { marginBottom: "12px" } }, children);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The card page: refresh control + one block per known workspace. */
|
|
156
|
+
function MemoryCard() {
|
|
157
|
+
var state = useState({ status: "loading", model: null, message: "" });
|
|
158
|
+
var model = state[0];
|
|
159
|
+
var setModel = state[1];
|
|
160
|
+
var reloadState = useState(0);
|
|
161
|
+
var reload = reloadState[1];
|
|
162
|
+
useEffect(
|
|
163
|
+
function () {
|
|
164
|
+
var alive = true;
|
|
165
|
+
loadCardModel(fetch).then(
|
|
166
|
+
function (data) {
|
|
167
|
+
if (alive) setModel({ status: "ready", model: data, message: "" });
|
|
168
|
+
},
|
|
169
|
+
function (error) {
|
|
170
|
+
if (alive) {
|
|
171
|
+
setModel({ status: "error", model: null, message: error instanceof Error ? error.message : String(error) });
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
);
|
|
175
|
+
return function () {
|
|
176
|
+
alive = false;
|
|
177
|
+
};
|
|
178
|
+
},
|
|
179
|
+
[reloadState[0]],
|
|
180
|
+
);
|
|
181
|
+
var children = [
|
|
182
|
+
createElement(
|
|
183
|
+
"div",
|
|
184
|
+
{ key: "head", style: { display: "flex", alignItems: "baseline", gap: "12px" } },
|
|
185
|
+
createElement("strong", null, "工作区记忆(只读投影)"),
|
|
186
|
+
createElement(
|
|
187
|
+
"button",
|
|
188
|
+
{
|
|
189
|
+
type: "button",
|
|
190
|
+
onClick: function () {
|
|
191
|
+
setModel({ status: "loading", model: null, message: "" });
|
|
192
|
+
reload(function (n) {
|
|
193
|
+
return n + 1;
|
|
194
|
+
});
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
"刷新",
|
|
198
|
+
),
|
|
199
|
+
),
|
|
200
|
+
createElement(
|
|
201
|
+
"div",
|
|
202
|
+
{ key: "hint" },
|
|
203
|
+
"事实源是各工作区 .evolve/memory/ 下的 markdown 文件——模型原生读写;本卡片只展示,编辑请直接改文件。",
|
|
204
|
+
),
|
|
205
|
+
];
|
|
206
|
+
if (model.status === "loading") {
|
|
207
|
+
children.push(createElement("div", { key: "body" }, "读取中…"));
|
|
208
|
+
} else if (model.status === "error") {
|
|
209
|
+
children.push(createElement("div", { key: "body" }, "卡片数据加载失败:" + model.message));
|
|
210
|
+
} else if (model.model.workspaces.length === 0) {
|
|
211
|
+
children.push(
|
|
212
|
+
createElement(
|
|
213
|
+
"div",
|
|
214
|
+
{ key: "body" },
|
|
215
|
+
"本进程还没有服务过任何工作区会话——在某个工作区开一次会话后回来刷新。",
|
|
216
|
+
),
|
|
217
|
+
);
|
|
218
|
+
} else {
|
|
219
|
+
children.push(
|
|
220
|
+
createElement(
|
|
221
|
+
"div",
|
|
222
|
+
{ key: "body" },
|
|
223
|
+
model.model.workspaces.map(function (entry) {
|
|
224
|
+
return createElement(WorkspaceBlock, { key: entry.root, entry: entry });
|
|
225
|
+
}),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
return createElement("div", null, children);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
exports.name = NS;
|
|
233
|
+
exports.inject = ["slots"];
|
|
234
|
+
exports.loadCardModel = loadCardModel;
|
|
235
|
+
exports.apply = function apply(ctx) {
|
|
236
|
+
ctx.slots.inject(SLOT, function () {
|
|
237
|
+
return ctx.slots.register(
|
|
238
|
+
{
|
|
239
|
+
name: SLOT,
|
|
240
|
+
key: NS,
|
|
241
|
+
inject: function () {
|
|
242
|
+
return {};
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
function (ownerProps) {
|
|
246
|
+
return ownerProps && ownerProps.view === "summary" ? null : createElement(MemoryCard);
|
|
247
|
+
},
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
};
|
|
251
|
+
return module.exports;
|
|
252
|
+
},
|
|
253
|
+
});
|
|
@@ -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 {
|
|
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) =>
|
|
44
|
-
|
|
45
|
-
|
|
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
|
package/lib/memory-guide.d.ts
CHANGED
|
@@ -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
|
package/lib/memory-guide.js
CHANGED
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-continual-evolve",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
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.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -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,10 @@
|
|
|
31
33
|
"dsh": {
|
|
32
34
|
"bundle": {
|
|
33
35
|
"patch": "./cordis.patch.yml"
|
|
36
|
+
},
|
|
37
|
+
"client": {
|
|
38
|
+
"inject": [],
|
|
39
|
+
"platform": "web"
|
|
34
40
|
}
|
|
35
41
|
},
|
|
36
42
|
"engines": {
|