dsh-instruction-memory 1.0.18 → 1.2.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/CHANGELOG.md CHANGED
@@ -7,6 +7,145 @@
7
7
  > 1.0.13 起由 CI 经 OIDC Trusted Publishing 发布(首个带 provenance 的版本)。
8
8
  > 这些历史 tag **不应回填**——npm 上的版本号已被占用。
9
9
 
10
+ ## [1.2.0] — 2026-10-15
11
+
12
+ P2 进阶增强:多情境配置集、版本历史与回滚、手改友好化(JSON Schema)、源码模式、
13
+ 按需指令的本地预演、自定义备份位置。
14
+
15
+ ### 新增
16
+
17
+ - **多情境配置集(F13)**:一份存储文件内可维护多个命名集合(「工作」「个人」「某客户」…),
18
+ 设置页顶部列出全部集合并可**一键切换**,注入**只渲染当前激活的那一套**。
19
+ (字段名用 `sets` 而非需求里的 "Profiles":`profiles` 已被 1.1.0 的「用户形象」占用,
20
+ 两者含义完全不同。)
21
+ - 存储格式升到**版本 2**:新增 `activeSetId`、`sets`(每项仅 `id` + 名称),条目与用户形象
22
+ 通过 `setId` 声明归属。切换只是改一个字段,**不移动任何用户文本**,因此不存在"切到一半"
23
+ 的中间态;新建与复制**不会**自动激活。
24
+ - 版本 1 文件**平滑迁移**:条目与形象全部归入合成的「默认」集合,载入本身不写盘,
25
+ 下次保存才写为版本 2,并明确提示。反过来,旧版插件打开版本 2 文件会**拒绝载入并暂停修改**
26
+ (原文件保留、数据完好),这是刻意设计——旧版看不到 `sets`,按 v1 读进去再保存就会抹掉配置集。
27
+ - **红线:绝不按进程 cwd 隐式建集或切换。** `activeSetId` 只由用户显式命令写入;这条纪律
28
+ 来自早期"cwd 漂移导致记忆像消失"的真实事故(见 README)。
29
+ - 新路由:`save-set`(新建/重命名)、`delete-set`(拒绝删除激活集合;删除其他集合会连同其中
30
+ 条目与形象一并删除并报数)、`set-active-set`(切换,写盘并推进 rev)、`duplicate-set`
31
+ (以全新 id 复制集合内容,用于模板派生)。上限:20 个集合;指令/形象仍按**全集合总量**计
32
+ (200 / 50)。
33
+ - 隔离是硬性的:注入计划、设置页快照、条目/形象的单条与批量增删改**全部**限定在激活集合。
34
+ 特别地,提交一个属于**其他集合**的 id 时不再就地覆盖,而是改用新 id 在当前集合新建
35
+ (否则一次"保存"就能把别的集合的条目搬过来并改写内容)。
36
+ - 导出为**整份存储**(所有集合);导入时若文件带 `sets` 会按需创建对应集合并把条目放回原集合,
37
+ 去重按**集合内**计算(同一句话可以同时存在于两个集合,且重复导入仍是幂等 no-op)。
38
+
39
+ - **版本历史与回滚(F14)**:每次成功保存后把该版本原样写入 `<备份位置>/history/` 下的
40
+ 时间戳快照(文件名 `20261015T152355123Z-r12-laptop.json`:UTC 时间 + rev + 写入设备,
41
+ 固定宽度所以按名排序即按时间排序)。
42
+ - 每台设备各保留最近 20 份,逐出最旧;**不改名、不碰**任何不符合命名的文件。放在
43
+ `history/` 子目录是为了避开监听 `memory.json` 的 `fs.watch`(子目录不在非递归监听范围内,
44
+ 文件名过滤再兜底一层)。快照写失败不会让保存失败,只在面板说明。
45
+ - 设置页「版本历史」可展开(时间、rev、条目/形象数、体积、是否来自其他设备),一键回滚或删除。
46
+ 回滚把快照内容作为**新的一版**写回(rev 只增不减),并且回滚自身也留快照——可以再回滚回来。
47
+ - 新增**「从备份恢复…」**入口:导入只能合并、无法还原整份,这个入口按确认后**整体替换**当前
48
+ 记忆,替换前的内容仍在 `memory.json.bak` 与版本历史里。
49
+ - 新路由:`history-list`、`history-restore`、`history-delete`、`restore-data`。
50
+
51
+ - **手改友好化(F15)**:随包发布 `memory.schema.json`(draft-07),描述 v1/v2 两种形状与全部
52
+ 长度上限;写出的文件带 `"$schema": "./memory.schema.json"`,宿主同时把该 Schema 的副本放到
53
+ `memory.json` 旁边,因此任何支持 JSON Schema 的编辑器打开存储文件即可获得校验与补全,
54
+ 无需知道包的位置。`verify.mjs` 逐项断言 Schema 与代码中的上限一致,防止漂移。
55
+ 另外**首次需要落盘前会先重读一次磁盘**,若文件在此期间出现(手放/别的工具恢复)就采用它,
56
+ 不再用空存储覆盖。
57
+ - **CLI 管理命令不在承诺范围内**(依赖宿主是否开放插件命令能力,未经验证)——`tui`/`headless`
58
+ 仍只能手改 JSON,这是当前官方路径。
59
+
60
+ - **设置页源码模式(F16)**:在文本框里直接编辑整份存储 JSON。输入时实时做语法校验(防抖 250ms)
61
+ 并摘要条数;保存走与手改文件**完全相同**的路径(归一化 + 披露 + `rev` 冲突保护),披露包括
62
+ 未能载入的条目、被修正的选项值、被截断的字段、指向不存在集合的 `setId`、未来格式版本拒绝。
63
+ 一次拒绝**不会**丢失草稿;提交携带的是**打开源码时**读到的 rev,因此编辑期间的他人修改会被
64
+ 如实判为冲突。与卡片编辑互斥(有草稿在编时按钮禁用)。新路由:`read-source`、`write-source`。
65
+
66
+ - **按需指令的本地预演(F17)**:设置页输入一句任务描述,前端**纯字面关键词**匹配各条「按需」
67
+ 指令的适用场景/标题/内容(场景权重最高),列出重合度较高的条目并给高/中/低分档,同时在列表
68
+ 里打标。中文用 2/3 字滑窗切词(无分词依赖),术语上限 40,纯本地计算、不发请求。
69
+ 界面明确标注**「本地预演 ≠ 模型的真实判断」**,`verify.mjs` 断言该免责声明在两种语言中都存在;
70
+ 它不参与注入,也不代表模型是否采纳。
71
+
72
+ - **自定义备份位置(F18,不含网络同步)**:可把备份指向用户指定目录(如本地云盘)。
73
+ 影响 `history/` 快照目录、「导出到备份目录」的去向,以及**额外**一份 `memory.json.bak`;
74
+ **存储文件本身与它的跨进程锁不会移动**(崩溃恢复用的 `.bak` 必须留在存储目录)。
75
+ 设置时当场校验(绝对路径、目录存在、探针写入成功),不满足即拒绝并说明原因;它是**本机设置**,
76
+ 整体替换与回滚**不会**改写它。README 给出多设备指引(只同步导出文件与历史快照,
77
+ **不要**同步存储目录本身)。新路由:`export-to-dir`。
78
+
79
+ ### 变更
80
+
81
+ - 存储格式版本 **1 → 2**(见上);导出信封 `version` 同步为 `2`。
82
+ - 版本拒绝文案改为列出**受支持**的版本(`1 / 2`)而不是"只支持版本 1"。
83
+ - 设置页新增「配置集」「按需指令本地预演」「版本历史」「备份位置」四块区域;「从备份恢复…」
84
+ 与「源码模式」两个入口;`zh`/`en` 文案同步。
85
+ - `.gitignore` 的临时 harness 规则放宽为 `.test-dsh-home*/`,覆盖各套件各自的家目录变体
86
+ (清理由重命名/沙箱阻止时不再留下未跟踪噪声)。
87
+
88
+ ### 测试
89
+
90
+ - `smoke-test.mjs` 新增:作用域解析(无集合=旧形状、未知 `activeSetId` 回退首个、无归属条目
91
+ 跟随激活集合)、**只渲染激活集合**的隔离断言、`sanitizeSet`、`setId` 保留规则、
92
+ 快照命名与解析往返、设备标签清洗、`LIMITS` 冻结与请求体上限下界。
93
+ - `contract-test.mjs` 新增独立宿主块(约 60 条断言):v1 → v2 迁移与披露、切换/新建/重命名/
94
+ 复制/删除的全部分支与拒绝路径、跨集合删除与批量的 no-op、跨集合 id 不得覆盖、
95
+ 快照目录与命名、回滚推进 rev、20 份保留上限、源码模式的采纳与全部拒绝分支(语法/空对象/
96
+ 非对象/未来版本/过期 rev)、整体替换(含"本机设置不被采用")、导入的集合创建与回退计数、
97
+ 备份位置校验与两个 `.bak` 副本、Schema 副本落盘。原「未来版本」场景的种子升到 `version: 3`。
98
+ - `verify.mjs` 新增:Schema 与 `LIMITS` 的逐项一致性(含 v1 仍可校验)、请求体上限断言改用
99
+ 宿主发布的真实常量、`__relevanceTerms`/`__relevancePreview`/`__sourceParseVerdict` 的行为断言、
100
+ F13–F18 的双语文案键集与免责声明断言,以及**每个 `t('…')` 调用点都能在两种语言里解析到文案**
101
+ (键集相等不足以保证这一点:漏声明的键会在界面上直接显示成键名)**且没有从未使用的死文案**。
102
+ - **新增 `ui-test.mjs`**:用约 60 行的 React 垫片(保持 hook 状态、按依赖运行 effect、展开函数
103
+ 组件)真实渲染设置页并**点按钮、填输入框**,路由直连真实宿主。覆盖:v1 迁移后的界面、
104
+ 新建集合→切换→**注入内容随之清空**、新条目落在激活集合、预演命中、历史列表与回滚、
105
+ 源码模式的实时校验与保存、备份位置应用与导出。同时断言**每次渲染的 hook 顺序稳定**
106
+ (能抓出条件式 hook)。已并入 `npm test`。面板语言在测试内**固定为 zh**:Node ≥ 21 的全局
107
+ `navigator` 会把 CI 运行器的英文语言带进面板,与断言中的中文文案冲突(本机 zh 通过、
108
+ CI 英文失败——已在 CI 上实测到并修复)。
109
+ - `docs/demo.html` 演示宿主补齐 F13–F18 的路由与快照字段(演示数据现在是两个集合,
110
+ 切换能立刻看到注入内容变化)。
111
+
112
+ ### 兼容性
113
+
114
+ - 宿主支持范围**不变**(`>=0.1.3-alpha.2` 起的各段,见 `host-range-test.mjs`)。
115
+ - 存储文件:v1 可读可迁移;v2 只能由本版本及以后读写(降级会被拒绝并暂停修改,数据无损)。
116
+
117
+ ## [1.1.0] — 2026-10-13
118
+
119
+ ### 新增
120
+
121
+ - **用户形象(关于用户本人的画像)**:设置页新增「用户形象」分区,可维护若干关于**用户本人**的信息
122
+ (身份背景、喜好、性格、长期追求等),每张卡片可单独勾选「使用该形象」——支持不使用、使用一个或
123
+ 同时使用多个(多个作为同一画像的不同侧面综合)。这与"要求 AI 怎么做"的指令条目是两类数据:
124
+ - Host 新增可选 `profiles: [{ id, name, content, enabled, updatedAt }]`(与 `entries` 平级,存储格式
125
+ 仍为 v1,旧记忆无该字段即按空数组加载,无需迁移);名称单行 ≤40 字符、内容 ≤4000 字符、最多 50 个,
126
+ 名称与内容同时为空或超限时载入会剔除并明确披露,名称缺失时取内容前 20 字符。
127
+ - 注入时形象段以「## 关于用户(用户形象)」抬头输出在指令块**之前**,抬头自带优先级护栏:仅供 AI
128
+ 在保证回答正确、完整、客观且扣题的前提下调整详略/举例/建议侧重,不得为迎合而改变事实、省略风险
129
+ 提示或偏离问题,无关时忽略;形象不是让 AI 扮演的角色。
130
+ - 形象段**不参与指令字数预算裁剪**,始终完整注入;快照的 `injection` 新增 `profiles` / `profileChars`
131
+ / `entryChars`(指令用量条只计量指令部分,不被形象体积顶高)。
132
+ - 新增 `save-profile` / `delete-profile` 路由(复用 rev 乐观锁、原子写盘、未来版本拒绝、写盘失败
133
+ 回滚与 SSE 多窗口同步);导入/导出支持 `profiles`,导入仍为只增合并(同 id 或同名+同内容去重),
134
+ 指令与形象在同一事务内落盘;空形象拒绝保存;请求体上限推导纳入形象容量。
135
+ - 设置页新增形象卡片列表(勾选开关、展开/收起、编辑、删除确认)、独立编辑器与已启用形象合计字符
136
+ 提示;旧宿主不发布形象字段时客户端自动按无形象处理,两侧均向前/向后兼容。
137
+
138
+ ### 测试
139
+
140
+ - `smoke-test.mjs` 新增形象清洗与注入语义断言(0 个时与旧块逐字节一致、1/多个抬头与顺序、停用/空内容
141
+ 不注入、仅有形象时仍注入、总开关同时关闭两段、形象不受预算裁剪、确定性)。
142
+ - `contract-test.mjs` 新增独立形象验证块:经真实路由断言保存/更新/删除(rev 恰好 +1、no-op 不写盘)、
143
+ 空形象拒绝、stale rev 冲突、快照与 preview 字段、勾选/取消勾选的注入变化、50 个上限、混合导入一次
144
+ 事务与去重、仅含形象的导入文件有效。
145
+ - `verify.mjs` 新增形象双语文案键断言,以及预算用量条在形象宿主上只取 `entryChars` 的接缝断言。
146
+ - `docs/demo.html` 演示数据加入 3 个形象(含停用示例),演示宿主支持 `save-profile`/`delete-profile`
147
+ 并实时计算形象注入段。
148
+
10
149
  ## [1.0.18] — 2026-10-13
11
150
 
12
151
  ### 新增
package/README.md CHANGED
@@ -38,12 +38,14 @@ dsh plugin --profile web remove dsh-instruction-memory
38
38
  | `lib/client.js` | Client 半:在 `settings.section` 注册「指令记忆」设置页(手写模块,无需构建) |
39
39
  | `cordis.patch.yml` | bundle patch:把 Host 行插入宿主组合 |
40
40
  | `install.mjs` | 开发安装脚本:从本仓库检出安装时,把包链接进 web profile 的 `node_modules`(junction)并注册 `dsh.profile.bundles`;`--remove` 完整回滚,改 profile 前自动备份 |
41
- | `contract-test.mjs` | 契约测试:驱动真实路由,验证两半之间的响应格式 |
42
- | `verify.mjs` | 装载验证:按 DSH 的方式加载两半 |
43
- | `smoke-test.mjs` | 注入渲染逻辑的单元测试 |
41
+ | `contract-test.mjs` | 契约测试:驱动真实路由,验证两半之间的响应格式(含配置集、版本历史、源码模式、备份位置) |
42
+ | `ui-test.mjs` | 设置页行为测试:用一个 ~60 行的 React 垫片真实渲染面板、点按钮/填输入框,再断言注入内容随之变化(不出图、不依赖浏览器) |
43
+ | `verify.mjs` | 装载验证:按 DSH 的方式加载两半,并逐项断言 `memory.schema.json` 与代码中的上限一致 |
44
+ | `smoke-test.mjs` | 注入渲染逻辑的单元测试(含「只渲染激活配置集」的隔离断言与 cwd 红线) |
44
45
  | `host-range-test.mjs` | 宿主版本范围契约测试:固定矩阵 × 两种 semver 判定模式,并把 `peerDependencies`(**被执行的那个**)、`dsh.engines.dsh`、`dsh.compatibility.dsh` 三处声明锁到同一个生成值 |
45
46
  | `lint.mjs` | 语法门禁:`node --check` 解析所有随包发布与本仓库运行的 JS 文件,已并入 `npm test` |
46
47
  | `docs/demo.html` | 离线演示页:桩掉 DSH 外壳、加载真实 Client 半渲染设置页(不随 npm 包发布) |
48
+ | `memory.schema.json` | 手改存储文件用的 JSON Schema(随包发布,并被宿主复制到 `memory.json` 旁边) |
47
49
 
48
50
  ## 生效方式
49
51
 
@@ -67,15 +69,26 @@ dsh plugin --profile web remove dsh-instruction-memory
67
69
  多选与上方的筛选/搜索叠加(「全选」只作用于当前可见条目)。整批修改在宿主侧是**单次写入**
68
70
  (只产生一次落盘、一次修订号递增与一次同步推送),全部条目已是目标状态时不写盘;批量删除需确认。
69
71
  批量的启停/优先级/标签调整不重盖更新时间,因此不会改变同优先级下的注入先后。
72
+ - **用户形象**:除了"要求 AI 怎么做"的指令,你还可以记录**关于你本人**的信息(身份背景、喜好、性格、
73
+ 长期追求等),每张形象卡片可单独勾选「使用该形象」——可以一个都不用、只用一个、或同时勾选多个
74
+ (多个会作为同一画像的不同侧面综合)。启用后,形象段会以「## 关于用户(用户形象)」为抬头注入在
75
+ 指令块**之前**,抬头明确把它限定为"仅供调整回答侧重":AI 应在**保证回答正确、完整、客观且扣题**的
76
+ 前提下据此调整解释深浅、详略、举例与建议方向,**不得为了迎合而改变事实、省略风险提示或偏离问题本身**,
77
+ 无关时直接忽略。形象不是让 AI 扮演的角色。形象段独立于指令字数上限,始终完整注入、不做预算裁剪;
78
+ 页面会显示已启用形象的合计字符数,便于你权衡提示词体积。总开关关闭时形象与指令一起停用。
70
79
 
71
- 条目全部停用或总开关关闭时,该提示词段落**整个注销**,系统提示词与未安装本插件时逐字节一致。
80
+ 总开关关闭时,该提示词段落(指令与用户形象)**整个注销**,系统提示词与未安装本插件时逐字节一致;
81
+ 总开关开启但没有任何生效指令、也没有启用形象时,段落同样不出现。
72
82
 
73
83
  ## 存储
74
84
 
75
85
  记忆保存在 **DSH 数据根目录**下(设置页会显示完整绝对路径):
76
86
 
77
87
  ```
78
- <DSH_HOME>/instruction-memory/memory.json # DSH_HOME 默认为 ~/.dsh
88
+ <DSH_HOME>/instruction-memory/memory.json # DSH_HOME 默认为 ~/.dsh
89
+ <DSH_HOME>/instruction-memory/memory.json.bak # 上一版(崩溃恢复用)
90
+ <DSH_HOME>/instruction-memory/memory.schema.json # 手改用的 JSON Schema 副本
91
+ <DSH_HOME>/instruction-memory/history/ # 版本历史快照(见下文 F14)
79
92
  ```
80
93
 
81
94
  路径优先级与官方 `@deepseek-ai/dsh-home-paths` 一致:显式 `$DSH_HOME` → `~/.dsh`。
@@ -85,6 +98,13 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
85
98
  > 1.0.0 之前的构建曾把数据写在宿主进程 cwd 下(`.dsh-instruction-memory.json`)。
86
99
  > 首次启动新版本时会自动迁移到上述位置,并把原文件改名为 `*.migrated` 保留。
87
100
 
101
+ **当前存储格式为版本 2**(1.1.0 及更早写的是版本 1,见下文「多情境配置集」)。
102
+ 两者都能载入:版本 1 的文件会**平滑迁移**——其中的条目与用户形象全部归入一个名为
103
+ 「默认」的配置集,文件在**下次保存时**写为版本 2(载入本身不写盘),并明确提示这一点。
104
+ 反过来,**用 1.1.0 及更早的版本打开版本 2 的文件会被拒绝载入并暂停修改**(原文件保留、
105
+ 数据完好),这是刻意设计:旧版看不到 `sets` 字段,若按版本 1 读进去,下一次保存就会把
106
+ 配置集信息抹掉。升级回新版即可继续。
107
+
88
108
  该文件可手工编辑,保存后已打开的设置页会**自动刷新**(见下文「外部改动自动刷新」);
89
109
  流式更新不可用时「重新载入」按钮仍可手动读回。每次保存先写临时文件再原子替换,
90
110
  进程崩溃不会留下损坏的半截 `memory.json`;保存前把上一版**复制**为 `memory.json.bak`,
@@ -143,11 +163,17 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
143
163
  **暂停一切修改**(此时保存会用 v1 格式覆盖文件,可能损坏数据),直到你升级插件或把
144
164
  文件改回版本 1 后点「重新载入」。
145
165
 
146
- 设置页支持**导出 / 导入**:导出为自带说明的 JSON 文件;导入采用**合并语义**——按 id 与
147
- 「标题+内容」去重,只新增、绝不覆盖或清空现有条目。请求体上限由条目上限推导
148
- (`MAX_ENTRIES × (MAX_CONTENT + MAX_TITLE + MAX_WHEN + 200) × 2 + 100000`),保证**本插件
149
- 导出的文件一定能被自己导回**——早先写死的 1,000,000 小于满配导出(200 × 6000 = 1.2 M 字符),
150
- 导致满配记忆导出后无法导入。
166
+ 设置页支持**导出 / 导入**:导出为自带说明的 JSON 文件,导出的是**整份存储**(所有配置集,
167
+ 不是只看当前激活的那一套)——备份只带走一半就没有意义了;导入采用**合并语义**——在**同一个
168
+ 配置集内**按 id 与「标题+内容」去重,只新增、绝不覆盖或清空现有条目。导入文件里带
169
+ `sets` 时会**按需创建对应配置集**并把条目放回原集合(本插件的导出天然带上);指向不存在集合
170
+ 的条目会归入当前激活集合,并在结果里说明有几项如此处理。要"整体还原"请用上文
171
+ 「从备份恢复…」,导入不做覆盖。
172
+
173
+ 请求体上限由各项上限推导(`(MAX_ENTRIES × (MAX_CONTENT + MAX_TITLE + MAX_WHEN + 8×24 + 200)
174
+ + MAX_PROFILES × (MAX_PROFILE_CONTENT + MAX_PROFILE_NAME + 200) + MAX_SETS × (MAX_SET_NAME + 200))
175
+ × 2 + 100000`,另加形象与配置集的开销),保证**本插件导出的文件一定能被自己导回**——早先
176
+ 写死的 1,000,000 小于满配导出(200 × 6000 = 1.2 M 字符),导致满配记忆导出后无法导入。
151
177
 
152
178
  ### 首次运行不写文件(惰性创建)
153
179
 
@@ -165,6 +191,121 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
165
191
  **不受影响**:从 `.bak` 恢复、以及从旧位置迁移——这两条分支只有在**磁盘上已有真实数据**时才会走,
166
192
  它们仍然会立即写回。
167
193
 
194
+ ## 多情境配置集(配置集 / sets)
195
+
196
+ 一份记忆装不下"工作 / 个人 / 某客户"三套互不相干的规则,所以设置页新增了**配置集**:
197
+ 多个命名集合共存于**同一个 `memory.json`**,用户显式一键切换,**注入只渲染当前激活的那一套**。
198
+
199
+ > **命名**:存储字段叫 `sets`(配置集),不叫 `profiles`——`profiles` 已经被 1.1.0 的
200
+ > 「用户形象」占用了,两者是完全不同的东西:配置集是"记忆的分区",用户形象是"关于你本人的
201
+ > 画像"。文档里的 "Profiles" 指的就是配置集。
202
+
203
+ - 配置集本身只有 `id` 与名称;条目与用户形象通过各自的 `setId` 指向它。因此"切换"只是
204
+ 改一个字段——**不会移动、复制或删除任何一条用户文本**,也不可能出现"切到一半"的中间态。
205
+ - 设置页顶部列出全部配置集(含各自的条目/形象数量),点击「切换」立即生效并写盘(重启后
206
+ 保持);「新建」「重命名」「复制」「删除」齐备。**新建与复制都不会自动激活**——激活是
207
+ 唯一会改变注入内容的动作,它始终是一次单独的、看得见的点击。
208
+ - **删除当前激活的配置集会被拒绝**(请先切换);删除其他配置集会连同其中的条目与形象一起
209
+ 删除,确认框里写明数量。文件里至少要保留一个配置集。
210
+ - 「复制」用于从模板派生:条目与形象会以全新 id 复制到新集合,`客户A → 客户B` 就是这么来的。
211
+ - 全部配置集**合计**最多 200 条指令、50 个用户形象、20 个配置集。总开关与注入字数上限是
212
+ **全局**的,不分集合。
213
+ - 提示词里**不会**出现配置集名称或任何"当前属于哪套"的标记——注入的就是激活集本身,和只有
214
+ 一套记忆时的文本逐字节一致。切换集合会改变提示词前缀,这是预期的(缓存会随之失效一次)。
215
+
216
+ > **红线:绝不按进程 cwd 隐式建集或切换。** 这条规则来自一次真实事故:早期构建把数据文件
217
+ > 解析到宿主进程的工作目录下,用户换个目录启动 DSH 就得到第二份空记忆,看起来像"记忆凭空
218
+ > 消失"。配置集沿用同一条纪律:`activeSetId` 只能由用户显式命令写入,载入时它只从文件里读,
219
+ > 任何"根据当前项目目录推断集合"的想法都不要加进来。
220
+
221
+ ## 版本历史与回滚(F14)
222
+
223
+ 现在只有一份 `.bak`,误改或批量误删之后回不到更早的状态。每次**成功保存**后,插件会把这一版
224
+ 原样写入 `<备份位置>/history/` 下的一个快照文件:
225
+
226
+ ```
227
+ 20261015T152355123Z-r12-laptop.json # UTC 时间戳 - rev - 写入设备
228
+ ```
229
+
230
+ - 命名是固定宽度的:按文件名排序就是按时间排序,列出历史只需读目录,不必逐个打开文件。
231
+ - 每台设备**各保留最近 20 份**(按文件名里的设备标签分组),逐出最旧的。两台机器共用一个
232
+ 备份目录时,谁也不会把对方的历史剪掉。不符合该命名的文件(手工放的、别的工具写的、
233
+ 云盘的冲突副本)**一律不碰**。
234
+ - **为什么放在 `history/` 子目录**:`memory.json` 所在目录被 `fs.watch` 监听以做外部改动
235
+ 自动刷新,快照绝不能混进去触发重载;子目录不在非递归监听的范围内,文件名过滤也会兜底。
236
+ - 设置页「版本历史」里可展开列表(时间、rev、条目/形象数、体积、是否来自其他设备),
237
+ 一键**回滚**或删除某一份快照。回滚是把快照内容作为**新的一版**写回(rev 只增不减),并且
238
+ 它自己也会留下一份快照——所以回滚本身也能再回滚回来;被替换掉的内容仍然躺在
239
+ `memory.json.bak` 里。
240
+ - 快照写失败**不会**让这次保存失败(存储已经安全落盘),只会在面板上说明"历史快照未写入"。
241
+
242
+ 另外,导入(见下文)是**只增合并**,无法还原整份记忆;因此另有一个**「从备份恢复…」**入口:
243
+ 选择本插件导出的文件,确认后**整体替换**当前记忆。同样是"可反悔"的——替换前的内容已经在
244
+ `.bak` 与版本历史里。
245
+
246
+ ## 手改与 JSON Schema(F15)
247
+
248
+ 随包发布一份 `memory.schema.json`(JSON Schema draft-07),描述版本 1/2 两种文件形状与全部
249
+ 长度上限。宿主还会把它的**副本写到 `memory.json` 旁边**,而 `memory.json` 里写死了:
250
+
251
+ ```json
252
+ { "$schema": "./memory.schema.json", "version": 2, … }
253
+ ```
254
+
255
+ 于是把 `memory.json` 拖进任何支持 JSON Schema 的编辑器(VS Code 等),就能直接得到字段校验、
256
+ 补全与悬停说明,**不需要知道这个包装在哪里**。Schema 与代码的一致性由 `verify.mjs` 逐项断言
257
+ (上限、枚举、最大条数),因此它不会和实现悄悄漂移。
258
+
259
+ > **CLI 管理命令不在本次承诺范围内。** 它依赖宿主是否向插件开放命令能力,目前未经验证;
260
+ > `tui` / `headless` 环境下设置页不可用这一点没有改变,手改 JSON + 这份 Schema 是当前
261
+ > 官方支持的路径。
262
+
263
+ ## 源码模式(F16)
264
+
265
+ 设置页的「源码模式」把**整份存储文件**(与 `memory.json` 内容一致)放进一个文本框直接编辑:
266
+
267
+ - 输入时**实时做 JSON 语法校验**(防抖 250ms),并摘要显示"几条指令 / 几个形象 / 几个配置集";
268
+ 语法不通过时保存按钮直接禁用。
269
+ - 保存走的是**与手改文件完全相同的一条路**:解析 → 归一化 → 披露(未能载入的条目、被修正的
270
+ 选项值、被截断的字段、指向不存在配置集的 `setId`、未来格式版本拒绝),再按 `rev` 提交。
271
+ 因此它比裸编辑器安全,也**不会绕过**任何既有校验。
272
+ - **草稿不会因为一次拒绝而丢失**:冲突(其他窗口/进程在你编辑期间提交过)、版本拒绝、
273
+ 语法错误都只显示原因,文本框里的内容原样保留;点「重新载入最新内容」才会放弃本地修改。
274
+ - 提交时带的 `rev` 是**打开源码时**读到的那个,而不是当前面板上的最新值——这样"你编辑期间
275
+ 别人改过"会被如实判为冲突,而不是把你的整份文本盖到别人的修改上。
276
+ - 与卡片编辑互斥:正在编辑某条指令/形象时不能进入源码模式(按钮禁用),避免两份草稿互相覆盖。
277
+
278
+ ## 自定义备份位置(F18,不含网络同步)
279
+
280
+ 默认一切都在存储目录里。设置页可以把**备份位置**指向别处(例如本地云盘文件夹):
281
+
282
+ - 影响三件事:`history/` 快照目录、「导出到备份目录」的去向、以及**额外**一份
283
+ `memory.json.bak` 副本。**存储文件本身与它的跨进程锁不会移动**——崩溃恢复用的
284
+ `memory.json.bak` 始终留在存储目录,因为那是"下次启动必须能找到"的东西。
285
+ - 设置时会**当场校验**:必须是绝对路径、目录存在、且实际写入一个探针文件成功;不满足就直接
286
+ 拒绝并说明原因(例如"目录不存在或不可访问"),而不是等到某次保存悄悄失败。
287
+ - **它是本机设置**:从备份文件"整体替换"或回滚历史时**不会**被改写——否则一台机器上的路径
288
+ 会被带到另一台机器上,把备份悄悄写到一个不存在的目录里。
289
+ - 多设备建议:把备份位置指向本地云盘目录,**只让导出文件与 `history/` 快照同步**;
290
+ **不要同步存储目录本身**(`memory.json`)——两台机器同时写入会互相覆盖,插件那套
291
+ rev 冲突检测与写锁都只在同一台机器内有效。换机器时用「导出 → 另一台导入/恢复」。
292
+
293
+ ## 按需指令的本地预演(F17)
294
+
295
+ 「按需」条目到底会不会被采纳,本质上是模型的判断,插件看不到也猜不准。设置页提供一个
296
+ **本地预演**:输入一句当前任务描述,前端**纯字面关键词**匹配各条按需指令的
297
+ 「适用场景 / 标题 / 内容」(场景权重最高、正文最低),列出字面重合度较高的条目并给出
298
+ 高/中/低分档,同时在列表里给这些条目打上「预演·高/中/低」标记。
299
+
300
+ 它的用途只有一个:**帮你把「适用场景」写清楚、预判取舍**(例如"没写场景的条目只能靠正文
301
+ 碰运气"、"这条优先级低、一旦超出字数上限会先被丢下")。因此:
302
+
303
+ - 措辞是克制的:界面上明确写着**「本地预演 ≠ 模型的真实判断」**,它不参与注入,也不代表模型
304
+ 实际会不会采纳;`verify.mjs` 断言了这句免责声明在两种语言里都存在。
305
+ - 只有「按需」条目参与;「始终」条目无条件生效,与预演无关。
306
+ - 匹配是启发式的:中文按 2/3 字滑窗切词(无分词依赖),英文/数字按词切分,术语上限 40 个,
307
+ 纯前端计算、不发任何请求。
308
+
168
309
  ## 无 Web 服务器时依然生效
169
310
 
170
311
  `systemPrompt` 是本插件唯一的硬依赖。**Web 服务器不是前置条件**:设置页那部分路由通过
@@ -183,7 +324,7 @@ DSH 就会得到第二份空记忆,看起来像"记忆消失"。
183
324
  `info`,过程细节写 `debug`(走宿主的 `ctx.logger`):
184
325
 
185
326
  ```
186
- [instruction-memory] mounted: store=<DSH_HOME>\instruction-memory\memory.json from=memory.json onDisk=yes entries=2 enabled=true section=437 chars (pending assembly) pulls=0
327
+ [instruction-memory] mounted: store=<DSH_HOME>\instruction-memory\memory.json from=memory.json onDisk=yes entries=2 sets=2 activeSet=st-default enabled=true section=437 chars (pending assembly) pulls=0
187
328
  ```
188
329
 
189
330
  挂载行含**数据来源**(`memory.json` / `memory.json.bak (main file missing)` / `memory.json.bak (main file corrupt)`
@@ -430,15 +571,25 @@ Package → Settings → Trusted publishing → Add trusted publisher
430
571
 
431
572
  ## 数据格式
432
573
 
574
+ 版本 2(当前写入的格式;版本 1 的文件同样能载入,见上文「存储」):
575
+
433
576
  ```json
434
577
  {
435
- "version": 1,
578
+ "$schema": "./memory.schema.json",
579
+ "version": 2,
436
580
  "rev": 3,
437
581
  "enabled": true,
438
582
  "budgetChars": 4000,
583
+ "backupDir": "",
584
+ "activeSetId": "st-default",
585
+ "sets": [
586
+ { "id": "st-default", "name": "默认" },
587
+ { "id": "st-work", "name": "工作" }
588
+ ],
439
589
  "entries": [
440
590
  {
441
591
  "id": "im-xxxx",
592
+ "setId": "st-default",
442
593
  "title": "回答语言",
443
594
  "content": "所有回答使用简体中文。",
444
595
  "mode": "always",
@@ -448,6 +599,16 @@ Package → Settings → Trusted publishing → Add trusted publisher
448
599
  "tags": ["沟通"],
449
600
  "updatedAt": 1700000000000
450
601
  }
602
+ ],
603
+ "profiles": [
604
+ {
605
+ "id": "pf-xxxx",
606
+ "setId": "st-default",
607
+ "name": "职业背景",
608
+ "content": "有 8 年后端开发经验,技术问题可跳过入门概念。",
609
+ "enabled": true,
610
+ "updatedAt": 1700000000000
611
+ }
451
612
  ]
452
613
  }
453
614
  ```
@@ -455,4 +616,21 @@ Package → Settings → Trusted publishing → Add trusted publisher
455
616
  `mode` 为 `always` | `auto`;`priority` 为 `2` 高 / `1` 普通 / `0` 低。`rev` 是存储修订号,
456
617
  由插件在每次成功写入时递增,**无需手填**(缺失视为 0)。`tags` 为可选的短标签字符串数组
457
618
  (最多 8 个、单个不超过 24 字符,仅用于设置页筛选,不参与注入);缺失或非数组一律按无标签处理,
458
- 非法项会被剔除、超长会被截断、重复项会去重,格式版本号仍为 `1`。
619
+ 非法项会被剔除、超长会被截断、重复项会去重。
620
+
621
+ `sets` 为**配置集**列表(每项只有 `id` 与 `name`,单行不超过 30 字符;最多 20 个),
622
+ `activeSetId` 指向当前激活的那一个——**注入只渲染它**。每条 `entries` / `profiles` 用
623
+ `setId` 声明归属:指向不存在的集合时归入当前激活集合(并在设置页披露);留空视为"跟随当前
624
+ 激活集合"。缺失 `sets` 字段即版本 1 文件,载入时会合成一个名为「默认」的集合。
625
+
626
+ `backupDir` 为**本机**的自定义备份位置(留空 = 与存储文件同目录,最多 512 字符);它不是
627
+ 可移植内容,从备份恢复时不会被改写。
628
+
629
+ `profiles` 为可选的**用户形象**数组(缺失按空数组处理):`name` 单行不超过 40 字符,
630
+ `content` 不超过 4000 字符,最多 50 个;`enabled` 控制是否使用该形象。名称与内容同时为空的形象会在
631
+ 载入时剔除并明确披露;名称为空时自动取内容前 20 字符。
632
+
633
+ **上限一览**(`memory.schema.json` 与 `verify.mjs` 保证它和代码一致):
634
+ 指令最多 200 条(全部配置集合计)/ 标题 120 字符 / 内容 6000 字符 / 适用场景 200 字符 /
635
+ 每条最多 8 个标签、单个 24 字符;用户形象最多 50 个 / 名称 40 字符 / 内容 4000 字符;
636
+ 配置集最多 20 个 / 名称 30 字符;注入字数上限 800–40000 字符。