@yolk_vat-y/dsh-project-memory 0.5.6 → 0.5.8

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.
Files changed (47) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/README.md +37 -34
  3. package/README.zh-CN.md +35 -34
  4. package/client/client.js +458 -107
  5. package/client/client.js.map +1 -1
  6. package/package.json +3 -6
  7. package/src/audit.js +63 -0
  8. package/src/auto-inject.js +447 -286
  9. package/src/chunker.js +14 -6
  10. package/src/client/MemoryView.tsx +12 -4
  11. package/src/client/TaskCommandNode.tsx +1 -1
  12. package/src/client/TaskComponents.tsx +3 -2
  13. package/src/client/TaskPanel.tsx +13 -13
  14. package/src/client/client.ts +93 -21
  15. package/src/client/icons.ts +63 -0
  16. package/src/client/locales.ts +11 -0
  17. package/src/client/session-id.js +88 -0
  18. package/src/client/slash.ts +184 -0
  19. package/src/commands/insight-actions.js +31 -16
  20. package/src/commands/invocation.js +36 -0
  21. package/src/commands/task-actions.js +36 -40
  22. package/src/commands/tasks.js +18 -13
  23. package/src/commands/workflow.js +54 -0
  24. package/src/enhancer.js +11 -2
  25. package/src/index-pipeline.js +119 -0
  26. package/src/index.js +24 -8
  27. package/src/insight-store.js +96 -28
  28. package/src/lazy.js +15 -46
  29. package/src/link.js +10 -1
  30. package/src/parsers/pdfjs-parser.js +2 -4
  31. package/src/project-profile.js +12 -5
  32. package/src/readiness.js +27 -3
  33. package/src/recall.js +28 -8
  34. package/src/setup/taskbridge.js +11 -9
  35. package/src/store.js +56 -17
  36. package/src/symbols.js +58 -19
  37. package/src/tools/forget.js +2 -1
  38. package/src/tools/index-doc.js +4 -1
  39. package/src/tools/index-repo.js +33 -86
  40. package/src/tools/lesson-tools.js +2 -1
  41. package/src/tools/query-memory.js +29 -4
  42. package/src/tools/remember.js +3 -1
  43. package/src/tools/task-tools.js +4 -1
  44. package/src/tools/watch-repo.js +9 -5
  45. package/src/util/fs.js +14 -0
  46. package/src/util/session-cache.js +72 -0
  47. package/src/watch.js +39 -81
package/CHANGELOG.md CHANGED
@@ -1,5 +1,158 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.8 (2026-09-22) — 准入的可复现性 + dsh 0.1.7 兼容
4
+
5
+ ### 兼容性修复(dsh 0.1.7 必崩)
6
+
7
+ - **`message.source.kind: 'plugin'` 会被 0.1.7 宿主拒绝,导致整轮运行失败。** 会话格式 v4 起
8
+ `MessageSourceMap` 是"每个生产者声明自己的 kind"的可合并联合类型,**没有** `plugin` 兜底;
9
+ 宿主在编码落盘那一刻抛 `format v4 message requires a producer-owned source kind`
10
+ (`session-format-v3-to-v4/src/message-sources.ts`)。现在写 `plugin:dsh-project-memory`
11
+ ——这正是宿主自己的 v3→v4 迁移对本插件历史消息的改写结果,新写的与迁移后的旧消息是**同一个
12
+ 生产者身份**(已用宿主真实编码器验证:新 kind 通过 encode + native restore,旧 kind 被 encode /
13
+ `assertV4MessageSources` / `assertV4SourceRowAdmission` 三处一致拒绝,v3 日志迁移后正好落到新 kind)。
14
+ - 自激闸门(`lastUserText`)从"看 `source.plugin` 字段"改为 `isOwnInjection()`,同时认三种历史形状
15
+ (`plugin:dsh-project-memory` / `project-memory` / `plugin:'dsh-project-memory'`)。迁移会**丢弃**
16
+ `plugin` 字段,所以旧判据在迁移后的历史上一律失效——那会让上一步的注入正文变成这一步的检索查询。
17
+
18
+ ### 兼容性修复(dsh 0.1.7 面板拿不到会话)
19
+
20
+ - **会话列表快照结构变了,面板因此永远拿不到会话。** 0.1.7 的 `SessionListState` 是
21
+ `{ ids, byId, phase, projectionsBySession }`,而插件读的是 `snap.current` 与 `snap.items`
22
+ ——**两个字段都已不存在**(见 `session-controller/.../sessions/service.ts`)。于是
23
+ `useSessionId` 永远返回 null:面板顶部显示「还没有会话」,项目/全局记忆视图报
24
+ `同步失败: no session / commands service`。而**任务视图渲染的是 task-data-store 里的缓存
25
+ 快照,看起来正常**,所以这个故障极易被误读成"数据格式 / 旧版本兼容"问题。
26
+ 解析抽成 `src/client/session-id.js`(纯函数 + 单测),两种形状都读。这是本版第三个 dsh 0.1.7
27
+ 破坏性变更(前两个:`message.source.kind`、ui-primitives 图标名)。
28
+ - **面板跟随会话切换。** 光"解析出一个非 null 的 id"还不够:兜底取"列表里第一个非 blank",
29
+ 它**不随用户切换对话变化**,面板会一直停在同一个会话上(从而显示另一个项目的任务)。
30
+ 0.1.7 判断"当前会话"的正式依据是 `SessionSummary.retainedBy.mainView > 0`:主视图正在 retain
31
+ 的那个就是用户正在看的那个(第一方同款判据,见 `ui-layout/DocumentTitle.tsx` 与 `ui-session`)。
32
+ 它就在**列表快照的 `byId` 行上**,而 retain 计数变化会 `list.set(...)` 重新发布快照
33
+ (`session-controller/.../service.ts` 的 `publishRetention`),所以订阅 `ctx.sessions.list` 的
34
+ 面板会在切换时自动重渲染——这条必须走快照内字段,**不能另开 `retainInfo()` 订阅**,否则切换
35
+ 不会触发重渲染。优先级:`current`(旧形状) → `mainView > 0` → 第一个非 blank → 第一个。
36
+ - 记忆视图在没有活跃会话时**不再报「同步失败」**:面板顶部已经显示「还没有会话」,
37
+ 重复报错会让人以为记忆库坏了。
38
+
39
+ ### `/` 菜单:命令有了图标和分组,且不再重复
40
+
41
+ - **三条宿主命令合并成一条 `/tasks`**(`src/commands/workflow.js`)。`/task` 与 `/insight` 的动作
42
+ 成为 `/tasks` 的子动词(`/tasks switch <id>`、`/tasks insight list project` …),由卡片按钮经
43
+ `remote.commands.execute` 驱动。原因:**宿主命令只要注册就会出现在 `/` 菜单的「指令」小节里,
44
+ 插件无法隐藏**(`commands.list()` 与 `commands.execute()` 读同一个视图,`CommandDefinition`
45
+ 也没有 hidden 字段),三条命令就是三行去不掉的原始行,与自建分组形成重复。
46
+ 合并后菜单里的重复降到一行 —— 而那一行是插件执行宿主侧工作的唯一通道(插件没有自己的
47
+ client→host RPC,`api/remotes` 的远程命名空间是宿主装配期写死的白名单)。
48
+ - `/tasks` **刻意不声明 `input`**:一旦声明就是 leadingInput,`ui-commands.matchEnter` 对带 input
49
+ 的命令一律返回 claim,手敲 `/tasks` 回车会被回填并要求再按一次回车。不声明则裸 `/tasks` 一次回车
50
+ 即执行,与合并前一致。代价:`/tasks switch x` 这类手敲带参行不再被认作命令(会作为普通消息发给
51
+ 模型)——这些动作的入口本来就是卡片按钮。
52
+ - 新增自建 `/` 触发器源(`src/client/slash.ts`):三个视图入口(任务 / 项目记忆 / 全局记忆,复用
53
+ 面板已有的 `view.*` 文案)出现在带图标的「工作流」组里,标题按语言切换中英文。
54
+ 为什么必须自建源:`/` 菜单的「分组」就是触发器源,而宿主 `ui-commands` 只给第一方
55
+ `definitionId` 白名单配图标和中文标题(`presentation.ts` 的 `HOST_FACES` / `SECTION_ROWS`),
56
+ 宿主 `CommandDescriptor` 也只有 `name/description/input`——第三方宿主命令改配置也变不出图标。
57
+ - 分组标题走**候选的 `section`**,不走 `slash.menu` 词典:该 namespace 由 ui-input-trigger 独占,
58
+ `register('slash.menu','zh')` 会抛 `already has locale`,未知 key 则原样回显源名;
59
+ MenuView 在「组内任一行带 section」时不渲染组标题行,只渲染 section 标题——标题文案因此回到
60
+ 插件手里,还能双语。
61
+ - 排序取 `order: 1`:排在宿主内置源(默认 0)之后,不抢主位置;没有能同时满足"在宿主之后"与
62
+ "在所有其他插件之前"的取值。
63
+ - 兼容性:`inputTriggers` 是**软依赖**(不进顶层 `inject`,否则没有 slash 服务的宿主根本不会加载
64
+ 本插件,任务面板会一起消失);整段注册两层 `try/catch`,注册失败只降级、不抛穿。本源不实现
65
+ `matchSpace`/`matchEnter`,手敲命令与面板调用的行为完全不变。
66
+
67
+ ### 注入精度
68
+
69
+ - `autoContext.hintMinCoverage` 出厂值 `0.30` → `0.45`。真实 store(43 条同源洞察)上,对照组场景
70
+ 「改 pptx 时间戳」以 cov 0.32~0.35 注入了 3 条无关提示:同源语料共享词多、IDF 分辨力被拉平,
71
+ "矮子里拔将军"能过 0.30。0.45 落在实测分布的空隙(假阳性 ≤0.35、下一个真命中 ≥0.49)。
72
+ 8 个真实会话的注入字符从 33,143 降到约 30,367(−8%),对照组归零。
73
+ - `readiness-eval` 新增第 7 项棘轮,把 `hintMinCoverage ≥ 0.45` 钉住(合成标注集对 0.30~0.60
74
+ 整段不敏感,保不住这个值,反例只在真实 store 上)。
75
+
76
+ ### 使用记账(修一个会吃掉有用记忆的缺陷)
77
+
78
+ - `applyDecay` / `pruneItems` 判活跃度只看 `lastHitAt || updatedAt || createdAt`,而 `lastHitAt`
79
+ 此前**只**由 merge/reinforce 写(= 模型又写了一条相近知识)。于是"天天被注入、但没人重写它"的
80
+ 条目在 `decayDays`(90)后被自动归档——用得最多的反而等于没人用过。README 里"decay/capacity
81
+ prune archived entries only"的说法与代码不符,一并更正。
82
+ - 新增 `recordHit()`,并把两个使用入口接上:`agent/pre-step` 注入成功后写 `hitCount`/`lastHitAt`
83
+ **并立即落盘**(注入路径不走其它 `save()`,只标脏就会在进程退出时丢);`query_memory` 命中
84
+ insight 时记账(热路径只改内存 + 标脏,不强制落盘)。
85
+ - 语义是"曝光次数",不是"被采纳次数";记账不参与任何注入判据,失败静默。
86
+
87
+ ### 影子记录(让阈值问题可以离线回答)
88
+
89
+ - 新增 `admission-shadow.jsonl`:**每步**一行(含零注入的静默步与对照组),带本步全部被评分的
90
+ 候选 + 判据特征(`rel` / `coverage` / `matched` / `support` / `terms` / `decision`)与场景
91
+ (`query` / `ops` / `writes`)。主审计只在真的注入时写,静默步零痕迹 → "换个阈值会怎样"
92
+ 永远无法离线回答,也攒不出训练样本。`decision` 直接指出每条候选卡在哪一关。
93
+ - `scoreHints` 只做加法:影子候选单独一条路径,`hints` / `dropped` 的行为一字未改。
94
+ - 配置:`autoContext.shadowLog`(默认 true)/ `shadowMaxBytes`(默认 2 MB,超限轮转 `.1`)。
95
+ 只写盘、不进 prompt、不花 token;任何 IO 失败静默。
96
+
97
+ ### 可复现性 / 工具
98
+
99
+ - `test/injection-scenarios.test.mjs`:`--selfcheck` 原先排在 `--store` 分支之前并
100
+ `process.exit(0)`,导致 `npm run selfcheck:triggers` **恒定**打印合成池——README 承诺的
101
+ "看你自己哪些条目推不动"从未真的读到过用户自己的条目。现在 `--store` 优先,未给时自动探测
102
+ `./.dsh-project-memory/insights.json` 与 `~/.config/dsh-project-memory/global.json`。
103
+ - `--store` 模式新增**对照组硬闸门**:expect 为空的场景必须零注入,否则退出码 1;新增
104
+ `--hint-cov <n>` 用于换一条底线重放(选阈值的扫描口)。
105
+ - **补声明准入旋钮**:`gateCooldownSteps` / `maxItemsPerSession` / `maxItemCharsPerSession` /
106
+ `hintMinCoverage` / `hintMinMatched` / `hintMinSupport` / `legacyScope` / `auditLog` /
107
+ `auditMaxBytes` 自 S2/S4 起就在 `cfgEngine` 生效、README 也一直写着,但从未进过 `Schema`——
108
+ 经 `cordis.patch.yml` 配置它们会被宿主按「not a declared property」拒掉,等于文档里的旋钮是假的。
109
+
110
+ ## 0.5.7 (2026-09-20) — bug-fix release
111
+
112
+ 发布前审计在 313 项全绿下发现并修复以下缺陷,新增 18 项回归测试(共 331)。
113
+
114
+ ### 数据完整性
115
+
116
+ - 旧库迁移:`index.json` 损坏时会连带删除完好的 `entries.json`(静默清空整个 store)
117
+ - `store.load()` 不幂等,重复调用丢掉未落盘的变更
118
+ - 符号后到时 doc↔symbol 链接不落盘;畸形 shard/insight 条目会让所有读取抛错
119
+
120
+ ### 召回与注入
121
+
122
+ - 提示通道覆盖率语料与 BM25 排序不一致:只匹配 `fix` 的查询会让整条提示通道沉默
123
+ - `recallItems` 改为每层各自 top-k(文档不再挤掉符号层);任务级 draft 不再泄漏
124
+ - 未加引号的 CJK 文件名不再触发 `when.intents`
125
+ - `when.writes` 纳入人类消息里的路径(动手前可命中;读取路径不算)
126
+ - 会话条目字符额度不再截断常驻任务卡;`entryOn:false` 与显式 `0` 生效;`fitBody` 边界
127
+
128
+ ### insight
129
+
130
+ - `applyDecay` 判据不可达,`decayDays` 完全失效
131
+ - 提升/降级同样收口 `maxProject` / `maxGlobalProcedures`
132
+ - 跨层移动保留 `hitCount`/`createdAt`/`triggerDerived`;global 也回填派生 trigger
133
+
134
+ ### 索引
135
+
136
+ - watch 不再每轮重读重哈希所有未变文件;`index_doc` 补 `terms` 回填
137
+ - `readTextFile` 先 stat 再读;chunker `sourceLine` 不再漂移
138
+ - `export`/多行 interface 与 type 别名可被 L1 扫到;扩展名大小写不敏感
139
+ - 损坏 PDF 销毁 loading task;TS 增强器 type 别名与关闭开关
140
+ - scoped 依赖 tag 修正;畸形依赖不再清空全部 tags
141
+
142
+ ### 工具与命令
143
+
144
+ - `query_memory(type:'task')` 读错步骤字段;空查询被拒绝
145
+ - 任务文件超限淘汰最冷文件;任务 id 补随机后缀
146
+ - `/insight edit` 合并 trigger 而非重建;`/task rename|todos` 保留内部空白
147
+ - 写类工具校验 root;`invocationContext` 补 `agent.session` 降级
148
+
149
+ ### 重构(已在 main 上,无行为变更)
150
+
151
+ - 索引逻辑收敛为 `src/index-pipeline.js`;auto-inject 会话状态收敛为 `InjectionSessions`
152
+ - 命令处理器共用 `invocationContext` / `fencedJson`
153
+
154
+ 未修的低优先级发现见 `RELEASE-NOTES-0.5.7.md` 的 backlog。
155
+
3
156
  ## 0.5.6 (2026-09-16) — injection admission (lessons/decisions/procedures stop arriving by coincidence)
4
157
 
5
158
  ### Changed (only what you are about to *do* can trigger an injection)
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](README.md) | [简体中文](README.zh-CN.md)
6
6
 
7
- [![ci](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@yolk_vat-y/dsh-project-memory)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
7
+ [![ci](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@yolk_vat-y/dsh-project-memory)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![npm downloads](https://img.shields.io/npm/dm/%40yolk_vat-y%2Fdsh-project-memory?style=flat-square&color=orange)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
8
8
 
9
9
 
10
10
  A persistent **project development memory** for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (dsh) agents. Built specifically for project development, natively integrated with dsh's task system: task lists and files read during a session are automatically persisted as cross-session task records, with tasks ↔ files linked — workflows can be switched and resumed, no need to re-scope the whole project, solving context loss. Documents (PDF/Markdown/txt) and code symbols are stored separately per workspace; documents are automatically cross-linked to the code symbols they mention. Experience notes (problem → solution) are automatically deduplicated, preventing repeated mistakes. All data is stored per project on disk, survives session compaction and handover; recalls include `path:line` citations for source verification. Only one dependency, no vector DB, no native builds.
@@ -16,31 +16,23 @@ The workflow panel is collapsible, automatically adapts to dsh and theme plugin
16
16
  ![alt text](docs/images/image-4.png)
17
17
  ## Features
18
18
 
19
- - **TaskBridge: cross-session development tasks** — the plugin watches each session's live todo list (`todo_write` events) and file reads (`tool/call`): progress snapshots (`steps`) and touched files sync into durable per-project task entities. An unbound session that writes a todo auto-creates a task. Associated files are kept in **recency-weighted order (written/edited first; a read never outranks a written file)** so a resumed session sees at a glance where to look. New sessions continue by `list_tasks` → `select_task` (bind / rename / unarchive); `query_memory` gains `type: 'task'` and appends a task-count hint to `type: 'all'` results. The user-side `/tasks` command shows the task stack, step progress, involved files, and the current session binding. A task named by the model via `select_task(title=…)` keeps that title; one **auto-created** by the first `todo_write` is titled from its **first list entry** (≤48 chars), falling back to the first human message (the part after the last colon), then `Untitled Task`. Sessions spawned as **subagents** are excluded from auto-creation (`origin: 'subagent'` / `delegationDepth > 0`); merging delegated work back into a task is deliberately unbuilt — see §11 in Design tradeoffs. Capacity is project-size adaptive (`fileCount/20`, clamped 5–100). Storage: `.dsh-project-memory/tasks.json` + `binding.json`. Auto-sync requires a dsh build with session events + `todo_write` (verified on 0.1.2-alpha.x, re-verified against the 0.1.5-rc.1 host surface); on older hosts the task tools still work as a plain record list.
20
- - **Task Panel (v0.4.2+): Floating task panel in dsh web** — built on the real dsh web 0.1.5-rc.1 client plugin contract (cordis inject + apply, registered into host `shell.overlay` slot). Draggable cards show steps/files (click to copy path); collapse to a draggable mini-bar; hide completely (summon with `/task` / `/tasks`). Render errors have error boundaries — panel crash no longer takes down the host.
21
- - **Task Panel Behavior** —
22
- - **Default hidden**: panel does not show on dsh web startup
23
- - **Explicit summon**: type `/tasks` or `/task` (list form) to open; model calls `show_task_panel` tool to open
24
- - **Session switch**: only syncs data in background, **does not** auto-open panel
25
- - **Page refresh**: panel stays hidden (UI state `closed` not persisted)
26
- - **Manual close**: click × to fully hide (no mini-bar); reopen requires explicit summon
27
- - **Collapse to mini-bar**: click ↓ to keep draggable top bar; click bar to expand
28
- - **Hide hints**: click ? to suppress every hover tooltip in the panel (drag handle, style/view/minimize/close, rename, step status, copy path, mini-bar, memory view); the preference is stored in localStorage and survives a refresh; the button dims while hints are off — click again to restore
29
- - **Bidirectional task-list sync (host ↔ plugin tasks, v0.4.2+)** — `select_task` or `/task switch` pushes task steps to host `todo/write` so dsh's rendered task list mirrors the plugin's task entity. Config `tasklist.syncHostOnAdopt` (default on) to toggle. Empty `todo/write` means "clear": unbound session clears list without creating junk tasks; bound session clears that task's steps (task retained). Panel edits (step text/status) = write back bound task + push host list, sharing one code path with model `todo_write`. `/task` subcommands: `switch`, `archive`, `unbind`, `rename`, `todos` (invoked by panel buttons/clicks, not the model); `unbind` also clears the host task list above the input.
30
- - **Panel editing & themes (v0.4.2+)** — bound cards: double-click title/step for inline edit (input auto-grows); click step status icon to cycle todo→in-progress→done. Non-bound cards read-only. **Four visual themes** (click folder icon left of title, persisted locally): Native / Glassmorphism / Brutalist / Terminal monospace — only material, geometry, typeface, density change; colors always use dsw alias tokens, follow host light/dark and theme plugins.
31
- - **Document memorization** — PDF, Markdown, and plain text files are chunked and summarized **without any model call**: each entry keeps a ≤300-character `summary` for injection, a bounded (≤160) deterministic, stop-word-filtered `terms` set that covers the **entire chunk** (search-only, so recall is not limited to the opening lines), and a `path:line` citation back to the source. The legacy `blindSpots` field is always empty now that indexing never calls a model; it is kept only so stores written by older versions still load.
32
- - **Code symbol memory (L1 regex)** — a dependency-free scanner extracts functions, classes and methods with full signatures (generics, parameter/return types, overloads) plus interfaces and type aliases across 8 languages, producing one-line identity signatures `fn(a: A, b: B): R — file.ts:42`. It masks strings/comments, joins multi-line signatures, is indentation-aware for Python and carries class-method context — with zero LLM tokens.
33
- - **Optional TypeScript semantic enhancement (L2/L3)** — when `typescript` is installed in the user project (`npm i -D typescript`), the plugin automatically activates a second layer (L2) that uses the TS Compiler API to infer return types, resolve generics, extract interfaces and type aliases, and enrich arrow functions — all asynchronously in a priority queue (P0 on `fs/observed`, P1 on `watch`, P2 on `index_repo`). Results are cached on disk keyed by file content hash (L3) for instant cold-start reuse. Zero config: just install TS (5.x or 6.x) and restart dsh. Fully optional; if TS is absent or disabled via `enableTypeScript: false`, the plugin falls back to L1 regex-only extraction.
34
- - **Automatic refresh** — a background poll (`watch_repo`) detects new or changed files by content hash and re-memorizes only those.
35
- - **Read-time memorization** — files are memorized the moment the model actually reads them (`fs/observed`), so the memory is a byproduct of normal work, not a separate upfront scan. Files that are never read are never indexed. The project root is detected by markers (`.git`, `package.json`, …), a README plus source directories, or the file's own directory as a last resort.
36
- - **Doc ↔ code cross-linking** — when a document mentions a symbol, the match is recorded as a `reference`; querying a symbol also surfaces the documents that describe it.
37
- - **BM25 memory recall** — ranked search over documents, symbols, and experience notes, with optional LLM query expansion to handle vocabulary mismatch. **CJK-optimized**: precise phrase boost (3+ char phrases ×1.5 score on title/keywords match), synonym table (e.g. 数据库连接池 ↔ 连接池 ↔ DB pool), and CJK-aware word boundaries for doc↔symbol linking.
38
- - **Experience notes** — problems → solutions; similar problems supersede instead of duplicating, and notes are returned only when a search matches. The note store is bounded: capacity scales with project size (clamped to 100–2000), and the oldest notes are pruned when the limit is exceeded. **Supersede tightened to bidirectional 0.7 overlap** (was 0.6); **experience `problem` field now participates in CJK phrase boost** for long-tail query recall.
39
- - **v0.5 tiered insight memory (lessons / decisions / procedures)** — one `insight` entity across three scopes: `task` (private drafts in `tasks.json`), `project` (`.dsh-project-memory/insights.json`), `global` (`~/.config/dsh-project-memory/global.json`). `save_lesson` writes any scope; dedupe is bidirectional token overlap ≥ 0.7 (merge) with a 0.65–0.7 reinforce band; **promotion is a scope change, not a copy** — 2 tasks hitting the same insight promote it to project, 3+ to global. Archive is soft (`archived`), decay/capacity prune archived entries only; writes are filtered for secret/token-shaped content. LLM **reflection is off by default** and only ever writes task-level drafts (`source: reflect`) on task switch-away/archive. Panel gains a Task / Project / Global memory view with approve, promote/demote, archive/restore, delete, edit and a create form (procedures can carry an “as Skill” trigger). Old `experience.json` notes are imported into `insights.json` once, non-destructively. Every kind can carry an authored `trigger`: **only `when` can trigger**, `guard` can only narrow, and `prevents` states what breaks without the entry. `when.ops` are normalized action ids resolved from the tool call itself (`file-write` / `file-delete` / `git-commit` / `release` / `npm-publish` / `render-doc` / `run-bench` / …), `when.writes` are the files this step is about to **write**, `when.intents` are intent words from the human message **after stripping quoted/path references and filenames**. A hit injects the entry deterministically **before the action**. Legacy `keywords` / `symbols` / `actions` / `paths` / `scope` are auto-migrated in memory (actions → `ops`, concrete paths → `writes`, keywords → `intents`, extension/name globs and dead action ids dropped) — an entry left with **no** triggerable member is no longer pushed; run `npm run selfcheck:triggers` to see which ones those are.
40
- - **Streaming TF + IDF caching** — query path caches IDF (term inverse frequency) per store version; on cache hit, single-pass streaming scores 20k entries (5k files) in p50 2.6 ms / p95 5.4 ms — and 4k entries (1k files) in p50 0.6 ms / p95 1.6 ms — with zero intermediate objects. Only a **dirty** write bumps the version and drops the cache — a no-op `save()` returns before touching the disk, so the 15 s watch poll can never clear the cache a query just built.
41
- - **Lock-free sync transactions** — all writes (index / watch / remember / forget / watch_repo) go through synchronous transactions `store.commit(fn)`; fn succeeds then atomic write; the JS single-threaded event loop guarantees no interleaving (**in-process only** — see Consistency); `remember`/`forget` are never blocked by watch re-indexing.
42
- - **Minimal dependencies** — pure JavaScript; the only runtime dependency is `pdfjs-dist` (PDF text extraction), no native builds required.
43
- - **Negligible overhead** — pure in-process operation; a 5k-file store loads in 40 ms, and a cached query over 20k entries is p50 2.6 ms / p95 5.4 ms (4k entries: p50 0.6 ms / p95 1.6 ms); the bottleneck is PDF extraction and disk I/O, not the plugin's scoring.
19
+ - **TaskBridge: cross-session development tasks** — the session's todo list and the files it touches are persisted as durable per-project task entities, so a workflow can be switched and resumed without re-scoping the project. Associated files are kept in recency-weighted order (a read never outranks a written file), so a resumed session sees where to look first. New sessions continue through `list_tasks` → `select_task`. Work delegated to subagents does not create tasks (see Design tradeoffs). Auto-sync needs a dsh build with session events; on older hosts the task tools still work as a plain record list.
20
+ - **Task panel in dsh web (v0.4.2+)** — draggable cards show a task's steps and files, collapse to a mini-bar, or hide entirely. The panel stays hidden until summoned, syncs in the background on session switch, and does not reopen itself after a page refresh. Render errors are contained, so a panel failure cannot take down the host.
21
+ - **Panel editing and themes (v0.4.2+)** — bound cards allow inline editing of title and steps and status cycling; unbound cards are read-only. Four visual themes change material, geometry, typeface and density only; colours follow the host.
22
+ - **Bidirectional task-list sync (v0.4.2+)** — binding a task pushes its steps to the host task list, and panel edits write back through the same code path as model updates. Set `tasklist.syncHostOnAdopt` to false to opt out.
23
+ - **Document memory** — PDF, Markdown and plain text are chunked and summarized without a model call. Each entry keeps a short summary for injection, a bounded search-only term set built from the whole chunk so recall is not limited to the opening lines, and a citation back to the source.
24
+ - **Code symbol memory** — a dependency-free scanner extracts functions, classes, methods, interfaces and type aliases with full signatures across 8 languages, one line per declaration. Where `typescript` is installed, an optional second layer infers return types, resolves generics and extracts interfaces; it runs asynchronously, is cached by content hash, and never blocks indexing.
25
+ - **Automatic refresh** — a background poll detects new and changed files by content hash and re-memorizes only those.
26
+ - **Read-time memorization** — a file is memorized the moment the model reads it, so memory is a byproduct of normal work rather than a separate upfront scan. Files that are never read are never indexed.
27
+ - **Doc ↔ code cross-linking** — a document that mentions a symbol is surfaced when that symbol is queried.
28
+ - **BM25 recall** — ranked search over documents, symbols, experience notes and insights, with optional LLM query expansion. Tuned for CJK: phrase boost, a synonym table and CJK-aware boundaries for doc↔symbol linking.
29
+ - **Experience notes** — problems → solutions, deduplicated by overlap rather than repeated, bounded by project size, and returned only when a search matches.
30
+ - **Tiered insight memory (lessons / decisions / procedures, v0.5)** — one entity across task, project and global scope. Near-duplicates merge or reinforce; promotion moves an entry between scopes rather than copying it. Use is recorded, so decay and capacity rank by activity rather than by age alone. LLM reflection is off by default and writes task-level drafts only. The panel exposes a per-scope memory view for reviewing and editing entries.
31
+ - **Triggered injection** — an insight may carry an authored trigger: only `when` triggers, `guard` narrows it, and `prevents` records what breaks without the entry. Corpus text can never trigger an injection; the statistical channel is gated separately.
32
+ - **Streaming TF + IDF caching** — the query path caches term weights per store version, scoring 20k entries in p50 2.6 ms / p95 5.4 ms and 4k entries in p50 0.6 ms / p95 1.6 ms. Only a real write drops the cache, so the watch poll never clears one a query just built.
33
+ - **Lock-free sync transactions** — all writes go through a synchronous transaction, so `remember` and `forget` never queue behind re-indexing. The lock is in-process: avoid pointing two dsh instances at the same store.
34
+ - **Minimal dependencies** — pure JavaScript; one runtime dependency for PDF text extraction, no native builds.
35
+ - **Negligible overhead** — memory work is in-process; the bottleneck is document extraction and disk I/O, not scoring.
44
36
 
45
37
  ## Performance
46
38
 
@@ -141,13 +133,19 @@ The tools below are **invoked by the agent**, not typed by the user. In the chat
141
133
  | `select_task` | Bind the session to a task so its todo list and file reads sync into it. Exact `taskId`, or exact `title` (multiple matches return candidates; no match creates a new task). Pass `title` with `taskId` to rename. Auto-unarchives. |
142
134
  | `archive_task` | Archive a task (hide from default views, exclude from capacity, stop syncing). `select_task` restores it. |
143
135
  | `show_task_panel` | Show the task panel in the UI. Call when the user asks to see the task list or when you want to display the panel. |
144
- | `/tasks` (typed by the user, not the model) | Shows the task stack: title, step progress, involved files, and which task the current session is bound to. |
145
- | `/task` (typed by the user, not the model) | Task panel subcommands: `switch` / `archive` / `unbind` / `rename` / `todos`. Invoked by panel buttons/clicks; does not go through the model. |
146
- | `/insight` (typed by the user, not the model) | v0.5 memory view actions (panel buttons): `list [task|project|global]`, `confirm` / `promote` / `demote` / `archive` / `restore` / `delete` `<scope> <id>`, `save <scope> <json>`, `edit <scope> <id> <json>`. |
136
+ | `/tasks` (typed by the user, not the model) | The only user command: shows the task stack (title, step progress, involved files, current session binding) and drives the workflow card. Every other action is a **sub-verb** invoked by card buttons, never typed: `/tasks switch` / `archive` / `unbind` / `rename` / `todos …` (task actions), `/tasks insight list` / `confirm` / `promote` / `demote` / `archive` / `restore` / `delete` / `save` / `edit …` (memory actions). |
147
137
  | `remember problem solution` | Save an experience note. Similar problems supersede instead of duplicating. |
148
138
  | `forget id_or_query` | Delete stale experience notes. |
149
139
  | `save_lesson` (agent tool) | Save a lesson/decision/procedure at task/project/global scope (single insight entity). Near-duplicates merge (≥ 0.7 overlap) or reinforce (0.65–0.7); 2+ tasks hitting the same insight auto-promote task → project, 3+ → global. Params: `title`, `kind`, `scope`, `pattern`/`fix` or `choice`/`reason` or `steps`, `trigger` (`when` = `ops`/`writes`/`intents`, the only trigger surface; `guard` = `paths`/`not_paths`/`hosts`/`tags`, narrowing only; `prevents` = what breaks without it; legacy `keywords`/`symbols`/`actions`/`paths`/`scope` still accepted and auto-migrated), `task_id`, `files`, `symbols`, `confidence`, `root`. |
150
140
 
141
+ `/tasks` appears in the web `/` menu inside an icon-bearing **Workflow** group, offering three view
142
+ entries — Tasks / Project Memory / Global Memory (labelled in the UI language). A host command always
143
+ shows up in the built-in **Commands** section and a plugin cannot hide it (`commands.list()` and
144
+ `commands.execute()` read the same view, and `CommandDefinition` has no hidden flag), so the plugin
145
+ registers **only `/tasks`** and every other action rides it as a sub-verb driven by card buttons: the
146
+ menu duplication is one row. Typing `/tasks` + Enter still executes immediately; typing an argued line
147
+ such as `/tasks switch x` is no longer recognised as a command — use the card buttons.
148
+
151
149
  ## Design
152
150
 
153
151
  ```
@@ -160,6 +158,8 @@ The tools below are **invoked by the agent**, not typed by the user. In the chat
160
158
  tasks.json TaskBridge task entities (cross-session)
161
159
  binding.json current session ↔ task binding
162
160
  insights.json v0.5 project-scope insights (lessons/decisions/procedures); v0.4 experience notes imported once, non-destructively
161
+ injection-audit.jsonl one line per real injection (what / why / dropped / budget)
162
+ admission-shadow.jsonl one line **per step**, all scored candidates + features (offline replay, labels)
163
163
  ```
164
164
 
165
165
  Stores created before v0.2.0 (single `entries.json` / `index.json`) migrate automatically and idempotently on first load. Within one dsh process, all tool calls share a single in-memory store per project, so hot-path indexing writes only the shard that changed.
@@ -299,11 +299,13 @@ These are deliberate scope choices.
299
299
  | `autoContext.gateCooldownSteps` | 2 | **admission knobs.** Minimum number of pre-steps between two *item* injections (the resident task card is exempt — it is a state snapshot and should update when it changes). This is the main "don't inject often" dial |
300
300
  | `autoContext.maxItemsPerSession` | 12 | hard per-session cap on injected items; the budget is a ceiling, not a target — once exhausted the item channel stays silent |
301
301
  | `autoContext.maxItemCharsPerSession` | 4000 | same, in characters |
302
- | `autoContext.hintMinCoverage` | 0.3 | **absolute** floor for the statistical (hint) channel: IDF-weighted share of the query's information mass the entry covers. A ratio-only threshold cannot tell signal from "best of a bad lot" (`relative:1.00` on an unrelated entry) |
302
+ | `autoContext.hintMinCoverage` | 0.45 | **absolute** floor for the statistical (hint) channel: IDF-weighted share of the query's information mass the entry covers. A ratio-only threshold cannot tell signal from "best of a bad lot" (`relative:1.00` on an unrelated entry). Raised from 0.30 in 0.5.8: on a real 43-entry store the control scenario injected 3 unrelated hints at cov 0.32–0.35, because a same-corpus store flattens IDF |
303
303
  | `autoContext.hintMinMatched` | 2 | a hint must share at least this many terms with the query — one generic word ("plugin") is not evidence |
304
304
  | `autoContext.hintMinSupport` | 0.15 | channel-level silence: if less than this share of the query's terms exist anywhere in the corpus, the hint channel says nothing this round — a long sentence that happens to share one word otherwise reports `cov:1.00` |
305
305
  | `autoContext.legacyScope` | `filter` | how to treat a legacy `trigger.scope`: `filter` keeps the old semantics, `ignore` drops it. `npm run selfcheck:triggers` reports entries whose scope values cannot intersect the project tag space |
306
306
  | `autoContext.auditLog` | true | append one JSONL line per **actual** injection to `<root>/.dsh-project-memory/injection-audit.jsonl` (what was injected, why it matched, what was dropped, session budget snapshot); rotates to `.1` past `auditMaxBytes` (`262144`). Silent on any I/O error — never affects the host request |
307
+ | `autoContext.shadowLog` | true | append one JSONL line **per step** (including steps that injected nothing) to `admission-shadow.jsonl`: every scored candidate with its judgement features (`rel` / `coverage` / `matched` / `support` / `terms` / `decision`) plus the step's `query` / `ops` / `writes`. This is what makes a threshold change answerable offline on real history (`decision` shows which gate rejected each candidate). Rotates past `shadowMaxBytes` (`2097152`). Disk only — never enters the prompt, costs no tokens |
308
+ | `autoContext.shadowMaxBytes` | 2097152 | rotation cap for `admission-shadow.jsonl` |
307
309
 
308
310
  ### Injection admission (why it stays quiet)
309
311
 
@@ -314,7 +316,7 @@ Automatic injection used to be a *retrieval* problem ("which entry is most relat
314
316
  - **Ratio *plus* an absolute floor.** The hint channel needs the relative score *and* an IDF-weighted coverage floor *and* at least two shared terms — `relative:1.00` also happens on entries that share nothing with the step.
315
317
  - **Frequency is bounded.** At most one item injection every `gateCooldownSteps`, capped per session by count and characters. The resident task card is exempt (it is a snapshot that should update); the budget is a ceiling, not a target.
316
318
  - **Prefix-cache discipline.** Injections are appended as a user message at the tail of the history, so the cached prefix is never rewritten. What they add is resident *cache-read* tokens, not cache misses; nothing is ever edited in place.
317
- - **It is auditable.** Every real injection appends one line to `injection-audit.jsonl` (reason, dropped candidates, session budget), and `npm run eval:injection` scores 8 labelled scenarios — currently precision 1.00 / recall 1.00 with a clean control group.
319
+ - **It is auditable.** Every real injection appends one line to `injection-audit.jsonl` (reason, dropped candidates, session budget), and `admission-shadow.jsonl` adds one line **per step** — including the steps that correctly injected nothing — with every candidate's features and the gate that rejected it. That second file is what makes a threshold question answerable offline instead of by re-running the agent. `npm run eval:injection` scores 8 labelled scenarios on a **synthetic** pool — currently precision 1.00 / recall 1.00 with a clean control group. That pool is the CI baseline, not evidence about your data: point the same harness at your own store and the control group becomes a **hard gate** (`--store`, exits non-zero on violation). That is how the 0.45 floor was chosen, and how you can re-choose it (`--hint-cov <n>` replays at another floor).
318
320
 
319
321
  ### Toggling features
320
322
 
@@ -352,9 +354,10 @@ These commands are for **maintaining the plugin code** — regular users do not
352
354
 
353
355
  ```bash
354
356
  npm install
355
- npm test # 313 tests (184 core + 16 TaskBridge + 11 insight-store + 9 insight-actions + 8 doc-index + 7 auto-inject + 9 host-contract + 5 reflection + 4 llm-route + 2 client-hints + 8 recall + 14 readiness + 7 insight-derive + 6 readiness-eval + 6 ops + 6 injection-audit + 5 injection-budget + 6 injection-scenarios)
356
- npm run eval:injection # scenario P/R: 14/14 hits, 0 false positives, control group clean
357
- npm run selfcheck:triggers # which entries can still push, which declarations are dead
357
+ npm test # 360 tests (184 core + 16 TaskBridge + 12 insight-store + 9 insight-actions + 8 doc-index + 7 auto-inject + 9 host-contract + 5 reflection + 4 llm-route + 2 client-hints + 8 recall + 14 readiness + 7 insight-derive + 7 readiness-eval + 6 ops + 8 injection-audit + 5 injection-budget + 6 injection-scenarios + 18 bugfix-0.5.7 + 3 client-icons + 10 client-slash + 5 workflow-command + 7 client-session-id)
358
+ npm run eval:injection # scenario P/R on the synthetic pool: 14/14 hits, 0 false positives, control group clean
359
+ npm run eval:injection -- --store .dsh-project-memory/insights.json # replay on YOUR store; control group is a hard gate
360
+ npm run selfcheck:triggers # which entries can still push, which declarations are dead (reads your local store)
358
361
  npm run bench -- /path/to/project # index/query performance on any project — no dsh needed
359
362
  ```
360
363
 
package/README.zh-CN.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [English](README.md) | [简体中文](README.zh-CN.md)
6
6
 
7
- [![ci](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@yolk_vat-y/dsh-project-memory)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
7
+ [![ci](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml/badge.svg)](https://github.com/00080000/dsh-project-memory/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![npm](https://img.shields.io/npm/v/@yolk_vat-y/dsh-project-memory)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![npm downloads](https://img.shields.io/npm/dm/%40yolk_vat-y%2Fdsh-project-memory?style=flat-square&color=orange)](https://www.npmjs.com/package/@yolk_vat-y/dsh-project-memory) [![Listed on dsh-plugin.org](https://dsh-plugin.org/badges/listed.svg)](https://dsh-plugin.org/plugins/00080000/dsh-project-memory) [![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)
8
8
 
9
9
  为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(dsh)agent 提供持久化的 **项目开发记忆**。专门针对项目开发,原生融合 dsh 任务系统,会话内任务清单与读过的文件自动沉淀为跨会话任务记录,任务↔文件自动关联——开发工作流可切换、可续接,无需重复梳理整个项目,解决上下文失效;文档(PDF/Markdown/txt)与代码符号写入工作区独立存储,文档自动交叉链接至所提及的代码符号;经验笔记(问题 → 方案)自动去重,避免重复踩坑。所有数据按项目落盘,跨会话压缩与交接保留,召回附带 `路径:行号` 可回源核实。单依赖,无向量数据库,无原生构建。
10
10
 
@@ -17,31 +17,23 @@
17
17
  ![alt text](docs/images/image-4.png)
18
18
  ## 特性
19
19
 
20
- - **TaskBridge:跨会话开发任务** — 监听会话内宿主 `todo_write` 维护的任务清单与 `tool/call` 读文件:进度快照(steps)与触碰文件自动同步进跨会话的任务实体。未绑定会话写 todo 时自动建档。关联文件按**最近活跃排序(写过/编辑的排最前,任何读取不越过写过文件)**,续接时一眼看到该看哪些文件。新会话通过 `list_tasks` → `select_task`(绑定/改名/解归档)续接;`query_memory` 新增 `type:'task'`,`type:'all'` 结果尾部附任务计数提示。用户侧 `/tasks` 命令展示任务栈、步骤进度、涉及文件与当前会话绑定。由模型经 `select_task(title=…)` 命名的任务沿用该标题;由首次 `todo_write` **自动建档**的任务取**清单首条**(≤48 字符)为标题,回退到首条真人消息(取最后一个「:」后的任务段),再回退 `Untitled Task`。以**子代理**身份启动的会话被排除在自动建档之外(`origin: 'subagent'` / `delegationDepth > 0`);把委派出去的工作并回任务这件事**有意没做**——见「设计取舍」第 11 条。容量随项目体积自适应(fileCount/20,clamp 5–100)。存储:`.dsh-project-memory/tasks.json` + `binding.json`。自动同步需含会话事件与 `todo_write` 的 dsh(0.1.2-alpha.x 实测,并已针对 0.1.5-rc.1 宿主面复核);旧宿主下降级为纯记录。
21
- - **Task Panel(v0.4.2+):dsh web 浮动任务面板** — 按 dsh web 0.1.5-rc.1 真实 client 插件契约落地(cordis inject + apply,注册进宿主 `shell.overlay` 槽)。卡片可拖拽、展开查看步骤/文件(点击复制路径);折叠为可拖拽顶部迷你条;可彻底隐藏(输入 `/task` / `/tasks` 唤起)。渲染错误有边界兜底,面板崩溃不再拖垮宿主。
22
- - **任务面板行为** —
23
- - **默认隐藏**:dsh web 启动时面板不显示
24
- - **显式唤起**:输入 `/tasks` 或 `/task`(列表形式)打开;模型调用 `show_task_panel` 工具打开
25
- - **会话切换**:仅后台同步数据,**不**自动打开面板
26
- - **刷新页面**:面板保持隐藏(UI 状态 `closed` 不持久化)
27
- - **手动关闭**:点击 × 彻底隐藏(无迷你条);重新打开需显式唤起
28
- - **折叠迷你条**:点击 ↓ 仅保留顶部可拖拽迷你条;点击迷你条展开
29
- - **隐藏提示信息**:点击 ? 关闭面板内所有悬停提示气泡(含拖拽把手、风格/视图/收起/关闭、双击改名、步骤状态、复制路径、迷你条与记忆视图),偏好写入 localStorage,刷新后保持;关闭态按钮变暗,再点恢复
30
- - **任务清单双向同步(宿主 ↔ 插件任务,v0.4.2+)** — `select_task` 或 `/task switch` 绑定任务时,将任务 steps 推给宿主 `todo/write`,dsh 渲染的任务清单跟随我们维护的任务实体。配置 `tasklist.syncHostOnAdopt`(默认开)可关。空 `todo/write` 语义定为「清空」:未绑定会话清空清单不再误建垃圾任务;已绑定则清空该任务 steps(任务保留)。面板编辑(改步骤文本/状态)= 写回绑定任务并推宿主清单,与模型 `todo_write` 共用一套逻辑,无第二套同步。`/task` 新增 `switch` / `archive` / `unbind` / `rename` / `todos`(均由面板按钮/双击调用,不经模型);`unbind` 同时清掉输入框上方的宿主任务清单。
31
- - **面板编辑与风格(v0.4.2+)** — 绑定卡片:双击标题/步骤行内编辑(输入框随内容自动增高),点步骤状态图标循环 待办→进行中→已完成;非绑定卡片只读。**四档外观风格**(点标题左侧文件夹图标切换,本地记忆):原生 / 玻璃拟态 / 粗野主义 / 终端等宽——只改材质、几何、字型与密度,颜色始终取自 dsw 别名令牌,跟随宿主明暗与主题插件。
32
- - **文档记忆** — PDF、Markdown、纯文本按块切分并生成摘要,**索引期不调用任何模型**:每条记忆保留 ≤300 字符的 `summary` 供注入、一个有界(≤160)且确定性、去停用词的 `terms` 集合覆盖**整个 chunk**(仅用于检索,因此召回不受开头几行限制),并携带 `路径:行号` 引用回源文件。历史字段 `blindSpots` 在索引期不调用模型后恒为空,仅为兼容早期版本写下的存储而保留。
33
- - **代码符号记忆(L1 正则)** — 零依赖扫描器提取函数、类与方法及其完整签名(泛型、参数/返回类型、重载),并覆盖接口与类型别名,支持 8 种语言,产出单行身份签名 `fn(a: A, b: B): R — file.ts:42`;含字符串/注释掩码、多行签名续行、Python 缩进感知与类方法上下文,不使用 LLM token。
34
- - **可选 TypeScript 语义增强 (L2/L3)** — 当用户项目安装了 `typescript`(`npm i -D typescript@5` 或 `npm i -D typescript@6`),插件自动激活第二层(L2),利用 TS Compiler API 推导返回类型、实例化泛型、提取接口与类型别名、丰富箭头函数签名 —— 全部在优先级队列中异步后台处理(P0:`fs/observed` 读文件瞬间、P1:`watch` 变更后、P2:`index_repo` 批量索引)。结果按文件内容哈希缓存到磁盘(L3),冷启动毫秒级复用。零配置:装 TS 再重启 dsh 即可。完全可选;若无 TS 或设置 `enableTypeScript: false`,回退至 L1 正则提取。
35
- - **自动刷新** — `watch_repo` 后台轮询,按内容哈希识别新增或变更文件,仅重记这些文件。
36
- - **读到即记忆** — 文件在模型**实际读取的瞬间**被记忆(监听 `fs/observed`),记忆是正常工作的副产品,而非额外的一次全量扫描。从未读过的文件不会被记忆。项目根通过标记(`.git`、`package.json` 等)、README 加源码目录、或兜底到文件所在目录逐级识别。
37
- - **文档 ↔ 代码交叉链接** — 文档提及某符号时记录为 `reference`;查询符号时同时带出描述该符号的文档。
38
- - **BM25 记忆召回** — 对文档、符号与经验笔记进行排序召回,可选 LLM 查询扩展以应对表述不一致。**CJK 增强**:精确短语乘法加分(3+ 字短语在标题/关键词命中 ×1.5)、同义词表(如 数据库连接池 ↔ 连接池 ↔ DB pool)、CJK 感知的文档↔符号链接边界。
39
- - **经验笔记** — 记录问题 → 方案;相似问题覆盖而非重复;笔记仅在检索命中时返回。笔记数量有界:容量随项目规模伸缩(钳制在 100–2000),超限时淘汰最旧的笔记。**覆盖阈值收紧为双向 0.7 重叠**(原 0.6);**经验 `problem` 字段现参与 CJK 短语加分**,提升长尾问句召回。
40
- - **v0.5 分层 insight 记忆(教训 / 决策 / 流程)** — 一个 `insight` 实体贯穿三级:`task`(任务私有草稿,存 `tasks.json`)、`project`(`.dsh-project-memory/insights.json`)、`global`(`~/.config/dsh-project-memory/global.json`)。`save_lesson` 三级可写;去重采用双向 token overlap ≥ 0.7(合并)外加 0.65–0.7 近重复强化带;**提升 = scope 字段变更而非复制**——同一 insight 被 2 个任务命中升 project、3+ 升 global。归档为软删(`archived`),容量/衰减只清归档区;写盘前过滤密钥/token 形态内容。LLM **反思默认关闭**,且只产任务级草稿(`source: reflect`,触发于任务切走/归档时)。面板新增 Task / Project / Global 记忆视图:审核、提升/降级、归档/恢复、删除、编辑与新建表单(procedure 可带"作为 Skill"触发关键词)。旧 `experience.json` 笔记**非破坏**导入 `insights.json` 一次。所有 kind 都可带 authored `trigger`:**只有 `when` 能触发**,`guard` 只能收窄,`prevents` 说明不知道这条会做错什么。`when.ops` 是从工具调用本身解析出的归一动作 id(`file-write` / `file-delete` / `git-commit` / `release` / `npm-publish` / `render-doc` / `run-bench` …),`when.writes` 是这一步**要写**的文件,`when.intents` 是**剥离引号/路径/文件名之后**的人类意图词。命中即**在动手前**确定性注入。旧字段 `keywords`/`symbols`/`actions`/`paths`/`scope` 会在内存里自动迁移(actions→ops、具体路径→writes、keywords→intents,扩展名与泛名 glob、死 action 值一律丢弃);迁移后**没有任何可触发成员**的条目不再被推送,用 `npm run selfcheck:triggers` 看是哪些。
41
- - **流式 TF + IDF 缓存** — 查询路径按存储版本缓存 IDF(词逆频率);命中时单次流式遍历 20k 条目(5k 文件)为 p50 2.6 ms / p95 5.4 ms,4k 条目(1k 文件)为 p50 0.6 ms / p95 1.6 ms,零中间对象。只有**真正脏了**的写入才递增版本号并清空缓存——无变更时 `save()` 在碰盘前直接返回,因此 15 秒一轮的 watch 轮询不会把查询刚建好的 IDF 缓存清掉。
42
- - **无锁同步事务** — 不采用锁:所有写入(index / watch / remember / forget / watch_repo)统一走同步事务 `store.commit(fn)`,fn 成功后才一次落盘;JS 单线程事件循环保证事务间不交错,`remember`/`forget` 不会被 watch 重索引阻塞排队。全部写入在**进程内**串行;CAS 幂等更新保证同一文件的重复写入不会写坏。但这里**没有跨进程文件锁**——请勿让多个 dsh 实例同时写同一项目存储(见「设计」的一致性一节)。
43
- - **依赖极简** — 纯 JavaScript;唯一运行时依赖是 `pdfjs-dist`(PDF 文本提取),无需原生构建。
44
- - **开销可忽略** — 纯进程内操作;5k 文件的 store 冷加载 40 ms,20k 条目的缓存查询 p50 2.6 ms / p95 5.4 ms(4k 条目:p50 0.6 ms / p95 1.6 ms);瓶颈在 PDF 解析与磁盘 I/O,插件本身的打分开销不阻塞。
20
+ - **TaskBridge:跨会话开发任务** — 会话内的任务清单与触碰过的文件持久化为跨会话的项目任务实体,工作流可以随时切走再续接,不必重新界定项目范围。关联文件按最近活跃排序(读取永远不越过写过的文件),续接时一眼看到该先看哪些。新会话通过 `list_tasks` → `select_task` 续接。委派给子代理的工作不会建档(见「设计取舍」)。自动同步需要含会话事件的 dsh 构建;旧宿主下降级为纯记录。
21
+ - **dsh web 浮动任务面板(v0.4.2+)** — 卡片可拖拽,展示任务步骤与涉及文件,可折叠为迷你条或彻底隐藏。面板默认隐藏,仅在显式唤起后出现;会话切换只后台同步,刷新页面不会自行打开。渲染错误有边界兜底,面板崩溃不会拖垮宿主。
22
+ - **面板编辑与外观(v0.4.2+)** — 绑定卡片支持标题与步骤的行内编辑、步骤状态循环切换;非绑定卡片只读。四档外观风格只改材质、几何、字型与密度,颜色跟随宿主。
23
+ - **任务清单双向同步(v0.4.2+)** — 绑定任务时把步骤推给宿主任务清单,面板编辑与模型更新写回同一套逻辑。配置 `tasklist.syncHostOnAdopt` 可关闭。
24
+ - **文档记忆** — PDF、Markdown 与纯文本按块切分并生成摘要,索引期不调用模型。每条保留一段简短摘要供注入、一个覆盖整个 block 的有界检索词集合(召回不受开头几行限制),以及回源引用。
25
+ - **代码符号记忆** — 零依赖扫描器提取函数、类、方法、接口与类型别名及其完整签名,覆盖 8 种语言,一行一条。项目装了 `typescript` 时,可选第二层补推导返回类型、泛型与接口,异步执行、按内容哈希缓存、不阻塞索引。
26
+ - **自动刷新** — 后台轮询按内容哈希识别新增与变更文件,只重记这些。
27
+ - **读到即记忆** — 文件在模型实际读取的瞬间被记忆,记忆是正常工作的副产品,而不是额外的一次全量扫描。从未读过的文件不会被记忆。
28
+ - **文档 ↔ 代码交叉链接** — 文档提及的符号,在查询该符号时一并带出。
29
+ - **BM25 记忆召回** — 对文档、符号、经验笔记与 insight 排序召回,可选 LLM 查询扩展。针对 CJK 增强:短语加分、同义词表,以及文档↔符号链接的词边界处理。
30
+ - **经验笔记** — 记录问题 → 方案;相似问题覆盖而不重复,数量随项目规模有界,仅在检索命中时返回。
31
+ - **分层 insight 记忆(教训 / 决策 / 流程,v0.5)** — 同一实体贯穿任务、项目、全局三级。近重复合并或强化;提升是更换归属而非复制。被使用会记账,因此衰减与容量按活跃度排序,而不是只按时间。LLM 反思默认关闭,只写任务级草稿。面板提供分级的记忆视图用于审核与编辑。
32
+ - **触发式注入** — insight 可携带 authored trigger:只有 `when` 能触发,`guard` 只能收窄,`prevents` 记下没有它会坏在哪。语料正文永远不能触发注入;统计通道另有独立门槛。
33
+ - **流式 TF + IDF 缓存** — 查询路径按存储版本缓存词权重,20k 条目 p50 2.6 ms / p95 5.4 ms,4k 条目 p50 0.6 ms / p95 1.6 ms。只有真实写入才清空缓存,watch 轮询不会把查询刚建好的缓存清掉。
34
+ - **无锁同步事务** — 所有写入走同步事务,`remember` / `forget` 不会排在重索引之后。锁在进程内:不要让两个 dsh 实例写同一个 store。
35
+ - **依赖极简** — 纯 JavaScript;运行时只有一个用于 PDF 文本提取的依赖,无需原生构建。
36
+ - **开销可忽略** — 记忆操作在进程内完成;瓶颈是文档提取与磁盘 I/O,而不是打分。
45
37
 
46
38
  ## 性能
47
39
 
@@ -142,13 +134,17 @@ dsh plugin --profile web add /path/to/dsh-project-memory.tgz
142
134
  | `select_task` | 将会话绑定到某任务(此后 todo 清单与读文件同步进该任务)。按 `taskId` 精确绑定,或按 `title` 完全匹配(多个同名返回候选;无则新建)。带 title 可改名;自动解归档。 |
143
135
  | `archive_task` | 归档任务(隐藏默认视图、不占容量、停止同步)。`select_task` 可恢复。 |
144
136
  | `show_task_panel` | 在 UI 中打开任务面板。用户要求查看任务列表或你想展示面板时调用。 |
145
- | `/tasks`(用户输入,不经模型) | 展示任务栈:标题、步骤进度、涉及文件、当前会话绑定哪套任务。 |
146
- | `/task`(用户输入,不经模型) | 任务面板子命令:`switch` / `archive` / `unbind` / `rename` / `todos`(面板按钮/点击触发,不经模型)。 |
147
- | `/insight`(用户输入,不经模型) | v0.5 记忆视图动作(面板按钮触发):`list [task|project|global]`、`confirm` / `promote` / `demote` / `archive` / `restore` / `delete` `<scope> <id>`、`save <scope> <json>`、`edit <scope> <id> <json>`。 |
137
+ | `/tasks`(用户输入,不经模型) | 唯一的用户命令:展示任务栈(标题、步骤进度、涉及文件、当前会话绑定)并驱动工作流卡片。其余动作是它的**子动词**,由卡片按钮调用、无需手敲 —— `/tasks switch` / `archive` / `unbind` / `rename` / `todos …`(任务动作)、`/tasks insight list` / `confirm` / `promote` / `demote` / `archive` / `restore` / `delete` / `save` / `edit …`(记忆动作)。 |
148
138
  | `remember problem solution` | 保存经验笔记。相似问题覆盖而非重复。 |
149
139
  | `forget id_or_query` | 删除过期经验笔记。 |
150
140
  | `save_lesson`(模型工具) | 在 task/project/global 任一作用域保存教训/决策/流程(单一 insight 实体)。近重复按双向 overlap ≥ 0.7 合并、0.65–0.7 强化;同一 insight 被 2+ 任务命中自动 task→project、3+ → global。参数:`title`、`kind`、`scope`、`pattern`/`fix` 或 `choice`/`reason` 或 `steps`、`trigger`(`when` = `ops`/`writes`/`intents`,唯一触发面;`guard` = `paths`/`not_paths`/`hosts`/`tags`,只能收窄;`prevents` = 准入条件;旧 `keywords`/`symbols`/`actions`/`paths`/`scope` 仍接受并自动迁移)、`task_id`、`files`、`symbols`、`confidence`、`root`。 |
151
141
 
142
+ `/tasks` 在 web 的 `/` 菜单里以带图标的**「工作流」**组呈现 —— 三个视图入口:任务 / 项目记忆 / 全局记忆
143
+ (标题随界面语言切换)。宿主命令只要注册就会出现在一贯的**「指令」**小节里,且插件无法隐藏它
144
+ (`commands.list()` 与 `commands.execute()` 读同一个视图,`CommandDefinition` 没有 hidden 字段),
145
+ 所以插件的宿主命令**只保留 `/tasks` 一条**,其余动作全部作为它的子动词由卡片按钮驱动:菜单里的重复因此只有一行。
146
+ 手敲 `/tasks` 回车即执行(与合并前一致);手敲 `/tasks switch x` 这类带参数的行不再被识别为命令,请用卡片按钮。
147
+
152
148
  ## 设计
153
149
 
154
150
  ```
@@ -161,6 +157,8 @@ dsh plugin --profile web add /path/to/dsh-project-memory.tgz
161
157
  tasks.json TaskBridge 任务实体(跨会话)
162
158
  binding.json 当前会话 ↔ 任务绑定
163
159
  insights.json v0.5 项目级 insights(教训/决策/流程);v0.4 经验笔记非破坏导入一次
160
+ injection-audit.jsonl 每次真实注入一行(注入了什么 / 为什么 / 丢了什么 / 额度)
161
+ admission-shadow.jsonl **每步**一行,全部被评分的候选 + 特征(离线重放、训练样本)
164
162
  ```
165
163
 
166
164
  v0.2.0 之前创建的库(单文件 `entries.json` / `index.json`)在首次加载时自动幂等迁移。同一个 dsh 进程内,所有工具调用共享每个项目的单一内存 store 实例,热路径索引只写发生变化的那一个分片。
@@ -298,11 +296,13 @@ TaskPanel (Container)
298
296
  | `autoContext.gateCooldownSteps` | 2 | **准入旋钮**:两次*条目*注入之间至少隔几步(常驻任务卡不受限——它是状态快照,内容变了就该更新)。这是"别频繁注入"的主旋钮 |
299
297
  | `autoContext.maxItemsPerSession` | 12 | 每会话条目注入条数硬上限;预算是上限不是目标,用尽后条目通道持续沉默 |
300
298
  | `autoContext.maxItemCharsPerSession` | 4000 | 同上,按字符计 |
301
- | `autoContext.hintMinCoverage` | 0.3 | 提示通道的**绝对**下限:条目覆盖了查询多少 IDF 加权信息量。只用相对阈值分不出"有信号"和"矮子里拔将军"(实测无关条目也拿 `relative:1.00`) |
299
+ | `autoContext.hintMinCoverage` | 0.45 | 提示通道的**绝对**下限:条目覆盖了查询多少 IDF 加权信息量。只用相对阈值分不出"有信号"和"矮子里拔将军"(实测无关条目也拿 `relative:1.00`)。0.5.8 从 0.30 上调:真实 43 条 store 上对照组以 cov 0.32~0.35 注入了 3 条无关提示——同源语料会把 IDF 分辨力拉平 |
302
300
  | `autoContext.hintMinMatched` | 2 | 提示还必须至少共享这么多个词:单个通用词("插件")不构成证据 |
303
301
  | `autoContext.hintMinSupport` | 0.15 | 通道级沉默:查询里能在语料中找到对应的词占比低于此值时,提示通道本轮整体不出声——否则一句只碰巧共享一个词的长句子会报出 `cov:1.00` |
304
302
  | `autoContext.legacyScope` | `filter` | 旧 `trigger.scope` 的处理:`filter` 保留旧语义,`ignore` 丢弃。`npm run selfcheck:triggers` 会列出 scope 值与项目画像 tag 空间不可能相交的条目 |
305
303
  | `autoContext.auditLog` | true | 每次**真实**注入往 `<root>/.dsh-project-memory/injection-audit.jsonl` 追加一行(注入了什么、为什么命中、丢了什么、会话额度快照);超过 `auditMaxBytes`(`262144`)轮转 `.1`。任何 IO 失败都静默,绝不影响宿主请求 |
304
+ | `autoContext.shadowLog` | true | **每步**(含什么都没注入的步)往 `admission-shadow.jsonl` 追加一行:本步全部被评分的候选 + 判据特征(`rel` / `coverage` / `matched` / `support` / `terms` / `decision`)+ 场景(`query` / `ops` / `writes`)。它让"换个阈值会怎样"可以在真实历史上离线回答(`decision` 直接指出每条候选卡在哪一关)。超过 `shadowMaxBytes`(`2097152`)轮转。只写盘,不进 prompt、不花 token |
305
+ | `autoContext.shadowMaxBytes` | 2097152 | `admission-shadow.jsonl` 的轮转上限 |
306
306
 
307
307
  ### 注入的准入化(为什么它保持安静)
308
308
 
@@ -313,7 +313,7 @@ TaskPanel (Container)
313
313
  - **相对分 + 绝对下限**。提示通道要同时满足相对分、IDF 加权覆盖率下限、以及至少两个共同词——`relative:1.00` 也会出现在和这一步毫无关系的条目上。
314
314
  - **频率有上限**。每 `gateCooldownSteps` 步最多一次条目注入,每会话还有条数与字符上限;常驻任务卡不受限(它是快照),预算是上限不是目标。
315
315
  - **前缀缓存纪律**。注入以 user 消息追加在历史尾部,缓存前缀永不被改写;它带来的是常驻的 cache-read token,不是缓存失效;没有任何内容被原地改写。
316
- - **可审计**。每次真实注入往 `injection-audit.jsonl` 落一行(原因、被丢弃的候选、会话额度),`npm run eval:injection` 跑 8 个标注场景——当前精确率 1.00 / 召回率 1.00,对照组零注入。
316
+ - **可审计**。每次真实注入往 `injection-audit.jsonl` 落一行(原因、被丢弃的候选、会话额度);`admission-shadow.jsonl` 再**每步**落一行(包括"正确地什么都没注入"的步),带全部候选的特征与卡在哪一关。后一个文件才是"阈值问题可以离线回答、而不是重跑 agent"的前提。`npm run eval:injection` 在**合成**池上跑 8 个标注场景——当前精确率 1.00 / 召回率 1.00,对照组零注入;那个池子是 CI 基线,不是你数据的证据。把同一套 harness 指向你自己的 store(`--store`),对照组就变成**硬闸门**(失守则退出码非 0)——0.45 这条底线就是这么选出来的,你也可以用 `--hint-cov <n>` 换一条底线重放。
317
317
 
318
318
  ### 功能开关
319
319
 
@@ -351,9 +351,10 @@ dsh web --patch ./config.yml
351
351
 
352
352
  ```bash
353
353
  npm install
354
- npm test # 313 项测试(核心 184 + TaskBridge 16 + insight-store 11 + insight-actions 9 + doc-index 8 + auto-inject 7 + host-contract 9 + reflection 5 + llm-route 4 + client-hints 2 + recall 8 + readiness 14 + insight-derive 7 + readiness-eval 6 + ops 6 + injection-audit 6 + injection-budget 5 + injection-scenarios 6)
355
- npm run eval:injection # 场景 P/R:命中 14/14、假阳性 0、对照组零注入
356
- npm run selfcheck:triggers # 哪些条目还推得动、哪些声明是死的
354
+ npm test # 360 项测试(核心 184 + TaskBridge 16 + insight-store 12 + insight-actions 9 + doc-index 8 + auto-inject 7 + host-contract 9 + reflection 5 + llm-route 4 + client-hints 2 + recall 8 + readiness 14 + insight-derive 7 + readiness-eval 7 + ops 6 + injection-audit 8 + injection-budget 5 + injection-scenarios 6 + bugfix-0.5.7 18 + client-icons 3 + client-slash 10 + workflow-command 5 + client-session-id 7)
355
+ npm run eval:injection # 合成池上的场景 P/R:命中 14/14、假阳性 0、对照组零注入
356
+ npm run eval:injection -- --store .dsh-project-memory/insights.json # 用你自己的 store 重放;对照组是硬闸门
357
+ npm run selfcheck:triggers # 哪些条目还推得动、哪些声明是死的(读你本地的 store)
357
358
  npm run bench -- /你的/项目路径 # 对任意项目量索引/查询性能,不需要 dsh
358
359
  ```
359
360