@yolk_vat-y/dsh-project-memory 0.5.7 → 0.5.9
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 +188 -0
- package/README.md +139 -193
- package/README.zh-CN.md +136 -192
- package/client/client.js +458 -107
- package/client/client.js.map +1 -1
- package/cordis.patch.yml +7 -1
- package/package.json +4 -6
- package/scripts/bench-synthetic.mjs +3 -3
- package/scripts/bench.mjs +1 -1
- package/src/audit.js +63 -0
- package/src/auto-inject.js +177 -34
- package/src/client/MemoryView.tsx +12 -4
- package/src/client/TaskCommandNode.tsx +1 -1
- package/src/client/TaskComponents.tsx +3 -2
- package/src/client/TaskPanel.tsx +14 -14
- package/src/client/client.ts +93 -21
- package/src/client/css-modules.d.ts +12 -0
- package/src/client/icons.ts +63 -0
- package/src/client/locales.ts +11 -0
- package/src/client/session-id.js +88 -0
- package/src/client/slash.ts +184 -0
- package/src/commands/insight-actions.js +15 -3
- package/src/commands/task-actions.js +19 -14
- package/src/commands/tasks.js +4 -22
- package/src/commands/workflow.js +54 -0
- package/src/index.js +51 -9
- package/src/insight-store.js +39 -0
- package/src/lazy.js +22 -80
- package/src/reflection-pipeline.js +2 -1
- package/src/setup/taskbridge.js +19 -12
- package/src/tools/forget.js +2 -2
- package/src/tools/index-doc.js +20 -5
- package/src/tools/index-repo.js +50 -17
- package/src/tools/lesson-tools.js +3 -6
- package/src/tools/query-memory.js +15 -12
- package/src/tools/remember.js +2 -2
- package/src/tools/stats.js +2 -2
- package/src/tools/task-tools.js +6 -20
- package/src/tools/watch-repo.js +17 -10
- package/src/util/fs.js +351 -16
- package/src/util/task-view.js +66 -0
- package/src/util/text.js +14 -0
- package/src/watch.js +46 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,193 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.9 (2026-09-24)
|
|
4
|
+
|
|
5
|
+
### 修复:issue #5 —— 在家目录 / `/opt/homebrew` 自动建索引把 DSH 撑到 OOM
|
|
6
|
+
|
|
7
|
+
三个根因叠加,缺一条都复现不出报告者的现象:
|
|
8
|
+
|
|
9
|
+
- **`findProjectRoot` 会把任意目录升格成项目根。** 旧实现有两条路:`looksLikeProjectRoot`
|
|
10
|
+
只要目录里有 `lib`+`include` 之类的"源码目录名"就认(`/opt/homebrew` 正好命中),以及找不到
|
|
11
|
+
任何标记时兜底返回 `path.dirname(filePath)`(读到家目录下的散文件就把家目录当根)。另外
|
|
12
|
+
ARM Mac 的 Homebrew 是 `git clone` 到 `/opt/homebrew` 的——**那里有 `.git`**,所以单靠
|
|
13
|
+
"只认项目标记"仍然会把整个 Homebrew 判成项目根,危险前缀必须显式列名单。
|
|
14
|
+
- **`.dsh-project-memory` 自己算项目标记,形成自我固化环。** `shadowLog` 默认开、每步写盘前
|
|
15
|
+
`mkdirSync`,所以只要在家目录里启动过一次 dsh,该目录就被建出来;此后读家目录下任意文件都会
|
|
16
|
+
把根解析回家目录,家目录被**永久**锁成"项目"(并写进 `watch.json`)。
|
|
17
|
+
- **扫描/索引全链路没有上限。** `walkDir` 同步把整棵树攒成一个数组;`index_repo` 攒完全部
|
|
18
|
+
updates 才 commit 一次(峰值 = 整棵树的条目);`DEFAULT_IGNORE` 不含 `Library`/`Documents`/
|
|
19
|
+
`Cellar`;watch 每 15 秒把这一切重跑一遍。
|
|
20
|
+
|
|
21
|
+
修复:
|
|
22
|
+
|
|
23
|
+
- 新增 `isUnsafeRoot` / `assertSafeRoot` / `resolveSafeIndexRoot`:文件系统根、家目录及其**所有
|
|
24
|
+
祖先**、共享临时目录、系统与包管理器前缀一律拒绝,且**按平台分组**(POSIX:`/opt/homebrew`、
|
|
25
|
+
`/opt`、`/usr`、`/usr/local`、`/home/linuxbrew`、`/nix` …;Windows:`%SystemRoot%`、
|
|
26
|
+
`%ProgramFiles%`、`%ProgramData%` …)——`C:\opt`/`C:\usr` 是 Windows 上的正常用户目录,
|
|
27
|
+
套用 POSIX 名单既误伤又自相矛盾。判定是
|
|
28
|
+
**精确匹配**:只拒绝这些目录本身,子目录照常可用(`~/Library/Mobile Documents/…/proj` 仍是合法项目根)。
|
|
29
|
+
- 所有会写盘/扫描的入口统一过这道关:`index_repo`、`watch_repo`、`index_doc`、`remember`/
|
|
30
|
+
`forget`/`lesson`/`query_memory`/`memory_stats`/任务工具、watch 管理器、`autoIndexOnFirstUse`。
|
|
31
|
+
- **项目根推导收敛成 `util/fs.js` 里的唯一策略**,懒索引 / 会话审计 / TaskBridge / 所有工具
|
|
32
|
+
用同一套顺序:显式 `root` → 显式登记的根(`watch_repo`)→ 最近的 VCS/清单标记 →
|
|
33
|
+
**会话工作目录本身(只要它是安全目录)** → `null`。旧实现是"标记优先,找不到就兜底到文件
|
|
34
|
+
所在目录",且审计/工具侧直接用原始 cwd、懒索引用标记根,同一个会话会分裂成两个 store。
|
|
35
|
+
现在"有没有标记"不再决定"能不能用"——只决定"这个根是声明的还是推定的"。没有标记的散目录
|
|
36
|
+
仍然**不会**被升格:只有会话 cwd(人明确选择的工作位置)才有这个资格。
|
|
37
|
+
- **推定根会通告模型**:根来自无标记的会话工作目录时,首次注入前追加一行"记忆根 = X
|
|
38
|
+
(无项目标记,按会话工作目录推定);若要改,给 index_repo / watch_repo / remember /
|
|
39
|
+
query_memory 传 `root: <dir>`,或在项目目录里重启 dsh"。每个会话一次,与常驻任务卡同类
|
|
40
|
+
(状态声明,不占条目额度),`autoContext.rootNotice: false` 可关。
|
|
41
|
+
- 危险根里**自动路径零副作用**:懒索引不索引、auto-inject 不读 store 也不写审计(不再把
|
|
42
|
+
`.dsh-project-memory` `mkdirSync` 进家目录)、TaskBridge 不建档不记文件;即使被显式调用的工具
|
|
43
|
+
也会收到可执行的拒绝说明,只有新配置 `allowUnsafeRoots: true` 才放行显式调用。
|
|
44
|
+
- 有界化:`walkDir` 返回 `{files, truncated}` 并支持 `maxScanFiles`(默认 20000)/`maxScanDepth`
|
|
45
|
+
(默认 12);`index_repo` 每 200 个文件分批提交;**截断时不执行"删除本轮未见到条目"的清理**
|
|
46
|
+
(没扫到 ≠ 被删除,否则一份被上限截断的树每轮都会自我清空一半);`DEFAULT_IGNORE` 补入
|
|
47
|
+
`Library`/`Applications`/`Cellar`/`Caskroom`/`Frameworks`/`DerivedData`/`Pods` 等。
|
|
48
|
+
- 自愈:`WatchManager.restorePersisted()` 用新判据剔除历史遗留的污染 watch root(家目录、
|
|
49
|
+
`/opt/homebrew` 等),用户不需要手删 `watch.json`。
|
|
50
|
+
- 无项目的会话不再静默:`/tasks` 等命令给出明确说明,auto-inject 在 stderr 提示一次。
|
|
51
|
+
|
|
52
|
+
验证:新增 `test/root-guards.test.mjs`(69 项:判定与例外、**用 `path.win32` 在任意平台上
|
|
53
|
+
模拟验证 Windows 分支**、每个入口的拒绝、历史污染自愈、安全无标记 cwd 照常可用、推定根通告、
|
|
54
|
+
审计与懒索引用同一个根、截断不误删、auto-inject 静默);
|
|
55
|
+
`test/run-test.mjs` 里依赖旧语义(启发式、兜底、temp ceiling)的断言改为新契约;
|
|
56
|
+
`injection-audit` / `injection-budget` 的夹具根补上项目标记,避免根通告混进它们的计数。
|
|
57
|
+
全量测试与 `npm run typecheck` 通过。
|
|
58
|
+
|
|
59
|
+
### 开发工具:typecheck 从「一直不可用」修到可跑,并接入 CI
|
|
60
|
+
|
|
61
|
+
- 根 `tsconfig.json` 的 `include: ["src/**/*"]` 在纯 JS 源码且未开 `allowJs` 时**匹配不到任何输入**,
|
|
62
|
+
`tsc` 只会报 `TS18003`;`tsconfig.client.json` 也从未通过过(`.ts/.tsx` 后缀导入 `TS5097` ×13、
|
|
63
|
+
CSS Modules 无声明 `TS2307`、`session-id.js` 无类型 `TS7016`、`catch (err)` 取 `.message` `TS2339`)。
|
|
64
|
+
现在:根项目开 `allowJs`(`checkJs: false`,只做解析级校验),客户端项目开
|
|
65
|
+
`allowImportingTsExtensions` + `allowJs`,并补 `src/client/css-modules.d.ts`;新增 `npm run typecheck`,
|
|
66
|
+
CI 在 `npm test` 之前执行。**没有**重建 `client/client.js`:那处收窄是纯类型改动,产物逐字不变。
|
|
67
|
+
|
|
68
|
+
### 重构:步骤读取收敛为单一纯函数模块(无行为变更,含两处刻意的语义对齐)
|
|
69
|
+
|
|
70
|
+
- **同一套读取逻辑原先抄了 8 份,并且已经漂移。** `commands/tasks.js`、`commands/task-actions.js`、
|
|
71
|
+
`tools/query-memory.js`、`tools/task-tools.js`、`tools/lesson-tools.js`、`setup/taskbridge.js`、
|
|
72
|
+
`auto-inject.js`、`reflection-pipeline.js` 各自实现「取步骤文本 / 归一状态 / 数完成度」:取文本分裂成
|
|
73
|
+
`content ?? text` 与 `content || text` 两种,状态归一分裂成三种(无校验 / `Set` 白名单 / 数组白名单);
|
|
74
|
+
此外还有 `timeAgo` ×2、`sessionIdOf` ×2、`textOf` ×2、`done/total` 内联 ×2。现在统一到
|
|
75
|
+
`src/util/task-view.js`(`stepContent` / `stepStatus` / `stepProgress` / `timeAgo` / `sessionIdOf`),
|
|
76
|
+
消息文本抽取并入 `src/util/text.js` 的 `textOf`。
|
|
77
|
+
- 两处刻意的语义决定(**不是笔误**,由 `test/task-view.test.mjs` 钉住):
|
|
78
|
+
1. `content` 为空串时**不再**回退 `text` —— 显式清空的步骤不该顶出旧文本;
|
|
79
|
+
2. `/tasks todos` 收到字符串步骤时不再被静默过滤成「0 条」而清空步骤,改为按 `pending` 接受。
|
|
80
|
+
非法 `status` 一律归一为 `pending`(宿主 `todo/write` 只认那三个合法值)。
|
|
81
|
+
- 验证:真实 store(28 任务 / 150 步骤)的状态取值本就只有三个合法值,所以归一化对现存数据零影响;
|
|
82
|
+
`git stash` 前后各跑一次真实 `/tasks` 快照,输出逐字相同;全量 182 项测试与 `typecheck` 均通过。
|
|
83
|
+
|
|
84
|
+
## 0.5.8 (2026-09-22) — 准入的可复现性 + dsh 0.1.7 兼容
|
|
85
|
+
|
|
86
|
+
### 兼容性修复(dsh 0.1.7 必崩)
|
|
87
|
+
|
|
88
|
+
- **`message.source.kind: 'plugin'` 会被 0.1.7 宿主拒绝,导致整轮运行失败。** 会话格式 v4 起
|
|
89
|
+
`MessageSourceMap` 是"每个生产者声明自己的 kind"的可合并联合类型,**没有** `plugin` 兜底;
|
|
90
|
+
宿主在编码落盘那一刻抛 `format v4 message requires a producer-owned source kind`
|
|
91
|
+
(`session-format-v3-to-v4/src/message-sources.ts`)。现在写 `plugin:dsh-project-memory`
|
|
92
|
+
——这正是宿主自己的 v3→v4 迁移对本插件历史消息的改写结果,新写的与迁移后的旧消息是**同一个
|
|
93
|
+
生产者身份**(已用宿主真实编码器验证:新 kind 通过 encode + native restore,旧 kind 被 encode /
|
|
94
|
+
`assertV4MessageSources` / `assertV4SourceRowAdmission` 三处一致拒绝,v3 日志迁移后正好落到新 kind)。
|
|
95
|
+
- 自激闸门(`lastUserText`)从"看 `source.plugin` 字段"改为 `isOwnInjection()`,同时认三种历史形状
|
|
96
|
+
(`plugin:dsh-project-memory` / `project-memory` / `plugin:'dsh-project-memory'`)。迁移会**丢弃**
|
|
97
|
+
`plugin` 字段,所以旧判据在迁移后的历史上一律失效——那会让上一步的注入正文变成这一步的检索查询。
|
|
98
|
+
|
|
99
|
+
### 兼容性修复(dsh 0.1.7 面板拿不到会话)
|
|
100
|
+
|
|
101
|
+
- **会话列表快照结构变了,面板因此永远拿不到会话。** 0.1.7 的 `SessionListState` 是
|
|
102
|
+
`{ ids, byId, phase, projectionsBySession }`,而插件读的是 `snap.current` 与 `snap.items`
|
|
103
|
+
——**两个字段都已不存在**(见 `session-controller/.../sessions/service.ts`)。于是
|
|
104
|
+
`useSessionId` 永远返回 null:面板顶部显示「还没有会话」,项目/全局记忆视图报
|
|
105
|
+
`同步失败: no session / commands service`。而**任务视图渲染的是 task-data-store 里的缓存
|
|
106
|
+
快照,看起来正常**,所以这个故障极易被误读成"数据格式 / 旧版本兼容"问题。
|
|
107
|
+
解析抽成 `src/client/session-id.js`(纯函数 + 单测),两种形状都读。这是本版第三个 dsh 0.1.7
|
|
108
|
+
破坏性变更(前两个:`message.source.kind`、ui-primitives 图标名)。
|
|
109
|
+
- **面板跟随会话切换。** 光"解析出一个非 null 的 id"还不够:兜底取"列表里第一个非 blank",
|
|
110
|
+
它**不随用户切换对话变化**,面板会一直停在同一个会话上(从而显示另一个项目的任务)。
|
|
111
|
+
0.1.7 判断"当前会话"的正式依据是 `SessionSummary.retainedBy.mainView > 0`:主视图正在 retain
|
|
112
|
+
的那个就是用户正在看的那个(第一方同款判据,见 `ui-layout/DocumentTitle.tsx` 与 `ui-session`)。
|
|
113
|
+
它就在**列表快照的 `byId` 行上**,而 retain 计数变化会 `list.set(...)` 重新发布快照
|
|
114
|
+
(`session-controller/.../service.ts` 的 `publishRetention`),所以订阅 `ctx.sessions.list` 的
|
|
115
|
+
面板会在切换时自动重渲染——这条必须走快照内字段,**不能另开 `retainInfo()` 订阅**,否则切换
|
|
116
|
+
不会触发重渲染。优先级:`current`(旧形状) → `mainView > 0` → 第一个非 blank → 第一个。
|
|
117
|
+
- 记忆视图在没有活跃会话时**不再报「同步失败」**:面板顶部已经显示「还没有会话」,
|
|
118
|
+
重复报错会让人以为记忆库坏了。
|
|
119
|
+
|
|
120
|
+
### `/` 菜单:命令有了图标和分组,且不再重复
|
|
121
|
+
|
|
122
|
+
- **三条宿主命令合并成一条 `/tasks`**(`src/commands/workflow.js`)。`/task` 与 `/insight` 的动作
|
|
123
|
+
成为 `/tasks` 的子动词(`/tasks switch <id>`、`/tasks insight list project` …),由卡片按钮经
|
|
124
|
+
`remote.commands.execute` 驱动。原因:**宿主命令只要注册就会出现在 `/` 菜单的「指令」小节里,
|
|
125
|
+
插件无法隐藏**(`commands.list()` 与 `commands.execute()` 读同一个视图,`CommandDefinition`
|
|
126
|
+
也没有 hidden 字段),三条命令就是三行去不掉的原始行,与自建分组形成重复。
|
|
127
|
+
合并后菜单里的重复降到一行 —— 而那一行是插件执行宿主侧工作的唯一通道(插件没有自己的
|
|
128
|
+
client→host RPC,`api/remotes` 的远程命名空间是宿主装配期写死的白名单)。
|
|
129
|
+
- `/tasks` **刻意不声明 `input`**:一旦声明就是 leadingInput,`ui-commands.matchEnter` 对带 input
|
|
130
|
+
的命令一律返回 claim,手敲 `/tasks` 回车会被回填并要求再按一次回车。不声明则裸 `/tasks` 一次回车
|
|
131
|
+
即执行,与合并前一致。代价:`/tasks switch x` 这类手敲带参行不再被认作命令(会作为普通消息发给
|
|
132
|
+
模型)——这些动作的入口本来就是卡片按钮。
|
|
133
|
+
- 新增自建 `/` 触发器源(`src/client/slash.ts`):三个视图入口(任务 / 项目记忆 / 全局记忆,复用
|
|
134
|
+
面板已有的 `view.*` 文案)出现在带图标的「工作流」组里,标题按语言切换中英文。
|
|
135
|
+
为什么必须自建源:`/` 菜单的「分组」就是触发器源,而宿主 `ui-commands` 只给第一方
|
|
136
|
+
`definitionId` 白名单配图标和中文标题(`presentation.ts` 的 `HOST_FACES` / `SECTION_ROWS`),
|
|
137
|
+
宿主 `CommandDescriptor` 也只有 `name/description/input`——第三方宿主命令改配置也变不出图标。
|
|
138
|
+
- 分组标题走**候选的 `section`**,不走 `slash.menu` 词典:该 namespace 由 ui-input-trigger 独占,
|
|
139
|
+
`register('slash.menu','zh')` 会抛 `already has locale`,未知 key 则原样回显源名;
|
|
140
|
+
MenuView 在「组内任一行带 section」时不渲染组标题行,只渲染 section 标题——标题文案因此回到
|
|
141
|
+
插件手里,还能双语。
|
|
142
|
+
- 排序取 `order: 1`:排在宿主内置源(默认 0)之后,不抢主位置;没有能同时满足"在宿主之后"与
|
|
143
|
+
"在所有其他插件之前"的取值。
|
|
144
|
+
- 兼容性:`inputTriggers` 是**软依赖**(不进顶层 `inject`,否则没有 slash 服务的宿主根本不会加载
|
|
145
|
+
本插件,任务面板会一起消失);整段注册两层 `try/catch`,注册失败只降级、不抛穿。本源不实现
|
|
146
|
+
`matchSpace`/`matchEnter`,手敲命令与面板调用的行为完全不变。
|
|
147
|
+
|
|
148
|
+
### 注入精度
|
|
149
|
+
|
|
150
|
+
- `autoContext.hintMinCoverage` 出厂值 `0.30` → `0.45`。真实 store(43 条同源洞察)上,对照组场景
|
|
151
|
+
「改 pptx 时间戳」以 cov 0.32~0.35 注入了 3 条无关提示:同源语料共享词多、IDF 分辨力被拉平,
|
|
152
|
+
"矮子里拔将军"能过 0.30。0.45 落在实测分布的空隙(假阳性 ≤0.35、下一个真命中 ≥0.49)。
|
|
153
|
+
8 个真实会话的注入字符从 33,143 降到约 30,367(−8%),对照组归零。
|
|
154
|
+
- `readiness-eval` 新增第 7 项棘轮,把 `hintMinCoverage ≥ 0.45` 钉住(合成标注集对 0.30~0.60
|
|
155
|
+
整段不敏感,保不住这个值,反例只在真实 store 上)。
|
|
156
|
+
|
|
157
|
+
### 使用记账(修一个会吃掉有用记忆的缺陷)
|
|
158
|
+
|
|
159
|
+
- `applyDecay` / `pruneItems` 判活跃度只看 `lastHitAt || updatedAt || createdAt`,而 `lastHitAt`
|
|
160
|
+
此前**只**由 merge/reinforce 写(= 模型又写了一条相近知识)。于是"天天被注入、但没人重写它"的
|
|
161
|
+
条目在 `decayDays`(90)后被自动归档——用得最多的反而等于没人用过。README 里"decay/capacity
|
|
162
|
+
prune archived entries only"的说法与代码不符,一并更正。
|
|
163
|
+
- 新增 `recordHit()`,并把两个使用入口接上:`agent/pre-step` 注入成功后写 `hitCount`/`lastHitAt`
|
|
164
|
+
**并立即落盘**(注入路径不走其它 `save()`,只标脏就会在进程退出时丢);`query_memory` 命中
|
|
165
|
+
insight 时记账(热路径只改内存 + 标脏,不强制落盘)。
|
|
166
|
+
- 语义是"曝光次数",不是"被采纳次数";记账不参与任何注入判据,失败静默。
|
|
167
|
+
|
|
168
|
+
### 影子记录(让阈值问题可以离线回答)
|
|
169
|
+
|
|
170
|
+
- 新增 `admission-shadow.jsonl`:**每步**一行(含零注入的静默步与对照组),带本步全部被评分的
|
|
171
|
+
候选 + 判据特征(`rel` / `coverage` / `matched` / `support` / `terms` / `decision`)与场景
|
|
172
|
+
(`query` / `ops` / `writes`)。主审计只在真的注入时写,静默步零痕迹 → "换个阈值会怎样"
|
|
173
|
+
永远无法离线回答,也攒不出训练样本。`decision` 直接指出每条候选卡在哪一关。
|
|
174
|
+
- `scoreHints` 只做加法:影子候选单独一条路径,`hints` / `dropped` 的行为一字未改。
|
|
175
|
+
- 配置:`autoContext.shadowLog`(默认 true)/ `shadowMaxBytes`(默认 2 MB,超限轮转 `.1`)。
|
|
176
|
+
只写盘、不进 prompt、不花 token;任何 IO 失败静默。
|
|
177
|
+
|
|
178
|
+
### 可复现性 / 工具
|
|
179
|
+
|
|
180
|
+
- `test/injection-scenarios.test.mjs`:`--selfcheck` 原先排在 `--store` 分支之前并
|
|
181
|
+
`process.exit(0)`,导致 `npm run selfcheck:triggers` **恒定**打印合成池——README 承诺的
|
|
182
|
+
"看你自己哪些条目推不动"从未真的读到过用户自己的条目。现在 `--store` 优先,未给时自动探测
|
|
183
|
+
`./.dsh-project-memory/insights.json` 与 `~/.config/dsh-project-memory/global.json`。
|
|
184
|
+
- `--store` 模式新增**对照组硬闸门**:expect 为空的场景必须零注入,否则退出码 1;新增
|
|
185
|
+
`--hint-cov <n>` 用于换一条底线重放(选阈值的扫描口)。
|
|
186
|
+
- **补声明准入旋钮**:`gateCooldownSteps` / `maxItemsPerSession` / `maxItemCharsPerSession` /
|
|
187
|
+
`hintMinCoverage` / `hintMinMatched` / `hintMinSupport` / `legacyScope` / `auditLog` /
|
|
188
|
+
`auditMaxBytes` 自 S2/S4 起就在 `cfgEngine` 生效、README 也一直写着,但从未进过 `Schema`——
|
|
189
|
+
经 `cordis.patch.yml` 配置它们会被宿主按「not a declared property」拒掉,等于文档里的旋钮是假的。
|
|
190
|
+
|
|
3
191
|
## 0.5.7 (2026-09-20) — bug-fix release
|
|
4
192
|
|
|
5
193
|
发布前审计在 313 项全绿下发现并修复以下缺陷,新增 18 项回归测试(共 331)。
|