Repository navigation
Releases: bonerush/dsh-obsidian-mem
Release list
dsh-obsidian-mem v2.0.2
- 面板 UI 两处修复:刷新图标改用 lucide rotate-cw;热力图末尾月份标签不再被裁掉。
lib/client.js的ICONS.refresh是手绘弧线(9 单位半径 + 6 单位箭头),与相邻的
lucide rotate-ccw 不一致;现换成同表 rotate-cw(顺时针孪生,同 5 单位箭头)。
lib/memory-panel.js新增HEAT_MONTH_TAIL = 24:月份行.mem-heat-months原先
宽度恰为gridWidth且overflow:hidden,当月份从最后一列开始时(窗口在月初结束,
例如 2026-11-03)标签 left =gridWidth − 10,只剩 10px,而中文 "11月" / 英文
"Nov" 约 22px,尾部被裁成 "11";现在 canvas 与月份行各预留 24px,网格列与 tooltip
锚点几何不变(tooltip 的居中偏移由同一个canvasWidth推导)。- 证据:先红后绿——回退
lib/client.js、lib/memory-panel.js后
node --test test/panel-ui.test.js test/panel-client.test.js5 项失败(新增回归
与 4 项宽度断言);应用修复后同一命令 122/122 通过,六个 panel 测试文件
192/192 通过。新增断言覆盖:refresh svg 的两段 path 等于 rotate-cw、末列月份
标签left = gridWidth − 10、月份行宽gridWidth + 24、每个标签在行内至少留
24px,以及以 2025-11-02(周日)开始、止于 2026-11-07 的 371 天 fixture 让
"11月" 落在最后一列。
- 证据:先红后绿——回退
dsh-obsidian-mem v2.0.1
-
embedding warm 现在可中断、可恢复,并按请求耗时自适应批大小。
lib/embedding-store.js的sync每完成 8 个顶层批或距上次写盘 30s 就增量
checkpoint,只序列化已有向量的笔记;失败时先把已嵌入的进度写盘再抛错,重跑按内容
hash 复用,因此一次中断不再丢掉整次 warm。rank()每次读snapshot(),所以 warm
进行中即可用上部分向量。新增导出纯函数nextBatchSize(current, elapsedMs)
(收缩到目标 2800ms、增长 +1,钳制 4–16);任何provider-timeout都会先把下一批
减半再二分,避免继续硬碰 5s 上限。1404 条真实 vault 的 warm 实测超时率
17.8%(24/135)→5.7%(18/318)、请求放大 1.22×→1.06×。- 证据:
node --test test/embedding-store.test.js test/architecture.test.js test/hybrid-recall.test.js test/retrieval-provider.test.js48/48;
npm run checkexit 0(pack 契约 92 entries / 82 lib modules)。
test/architecture.test.js的 embedding-store 预算从 370 提到 500(实测 434 行 +
30 行注释空间)。测试覆盖超时收缩与恢复增长、checkpoint 后重跑跳过已嵌入项、首个
checkpoint 之前中断也能保存进度、显式batchSize不漂移。 - 未验证:warm 总墙钟不可与基线 489.7s 直接比较——度量时宿主 MPS 有效算力从
~1.5 降到 ~0.6 TFLOPS,所以只报告结构性指标;不同 Apple 芯片与内存压力下的绝对
耗时没有测。
- 证据:
-
新增本地 Qwen3-Embedding 参考服务工具包
tools/embed-server/(不随 npm 包发布)。
用标准库 HTTP 服务实现 2.0.0 的回环 provider 契约:官方 query 指令前缀、last-token
pooling、按文本长度排序分块、SDPA 注意力、客户端断开后中止剩余分块(修复「客户端
5s 超时放弃的请求仍在服务端占锁与算力,导致重试级联超时」)、/health上报
attention/queryInstruction。含幂等的setup.sh(uv venv、模型下载、LaunchAgent
安装)与记录实测数字的 README。- 证据:分块按长度排序使代表性 16 文本载荷的 forward token 数从 6388 降到 5136
(−19.6%,向量逐条一致);chunk 2/4/8/16 扫描决定 4;--attn交错 A/B 中位数
mixed16 eager 12.155s vs sdpa 11.076s、单长文本 1.144s vs 0.937s;前缀 A/B 在
22 条 paraphrase 上 MRR 0.828(开)vs 0.773(关),30 条标题自检索 0.950 vs
0.961,故默认开启并保留--no-query-instruction开关;kill -9后 launchd
0.27s 重新拉起、/health5.74s 就绪,bootout后搜索降级为
fallback: "provider-error",bootstrap后 2.7s 恢复且无需重新 warm。 - 未验证:绝对耗时随宿主负载漂移较大,README 只承诺相对收益;前缀结论来自单个
vault 的 22+30 条小样本,换 vault 需重新 A/B。
- 证据:分块按长度排序使代表性 16 文本载荷的 forward token 数从 6388 降到 5136
dsh-obsidian-mem v2.0.0
-
破坏性变更:
retrievalEngine的默认值从lexical改为hybrid。 语义检索通道
现在默认在引擎上开启。retrievalProvider仍然默认关闭且为空——每个字段描述的是运维
自己的本地服务,插件不会替你凭空造一个——所以没有任何 provider 配置的默认行,读的
仍然是原样的 lexical 路径:services.search识别出「hybrid + provider 未启用」这一
对组合并直接走 lexical 的单次查询(不是searchHybrid配一个缺失的 runtime,那会白付
HYBRID_LEXICAL_LIMIT的宽候选窗口并给数组挂上 hybrid 字段),返回值与 1.0.7 逐字节
相同。状态不静默:search 诊断事件报的是配置要求的引擎hybrid加
fallback: provider-disabled,所以运维能从诊断窗口看出「配置没有生效、为什么」。- 兼容性分类:
retrievalEngine是 1.0.0 起承诺的配置键之一,默认值属于该承诺的一部分,
因此按 SemVer 走主版本号。六个mem_*工具名与参数、vault 协议(指针文件、frontmatter、
skill)都没有变化。 - 反过来,AMD 前的旧配置显式写着
retrievalEngine: lexical或sparse时行为完全不变,
升级不会改动既有行的语义。 - 这一对默认值不再在配置加载时报错。1.0.7 里
hybrid配未启用的 provider 会抛
RangeError('retrievalProvider.enabled');既然它现在是出厂默认行,那个拒绝就必须撤掉,
否则插件装不上。仍然拒绝的是「provider 打开了但不能用」:非回环 endpoint、空白 model、
非正整数 dimensions,由validateProviderConfig在加载时就抛错。 - 证据:
node --test test/config.test.js test/services-hybrid.test.js test/hybrid-recall.test.js test/hybrid-search.test.js test/search-engine.test.js
69/69 通过。test/services-hybrid.test.js新增一条「the shipped default row reads
lexically and says why」,钉住三件事:validateConfig({}).retrievalEngine === 'hybrid'、
validateConfig({}).retrievalProvider.enabled === false、以及默认行的命中数组
retrievalEngine/hybridFallback都是undefined(证明走的是纯 lexical 路径)、
而诊断是engine: 'hybrid'+fallback: 'provider-disabled'。test/config.test.js
的默认值字面量与「hybrid 需要启用 provider」那条改成「默认这一对必须能加载」。 - 未验证:没有 provider 的真实升级路径(从 1.0.7 装好的现有 vault 升级到 2.0.0)只做了
离线验证,没有在真实会话里跑过一轮;「默认行逐字节等于 1.0.7」是对searchNotes的
调用参数与返回数组结构做的断言,不是对真实 vault 的逐字节比对。
- 兼容性分类:
-
recall 闸门增加校准过的语义分支:hybrid 融合排名前三的候选,只要 dense 分数 ≥ 0.92,
即使 token 命中数只有 1 也可通过闸门并注入摘录。 词法闸门只认字面重叠,会把融合与
向量都判为高信的候选拒掉;新分支只对带 semantic-score/hybrid-rank 信号的 hybrid 命中
生效,token 分支与 lexical 默认路径完全不变。阈值按 e5-small 校准,换模型需重标。- 证据:同一冻结 200 条语料,接受面 +16 个 top-3 槽位、其中 9 条相关;gate pass
24.6%→27.9%、in-gate precision 39.8%→41.8%、no-answer FPR 5.0% 不变、P@3 61.25%
不变;指代类第一次拿到注入(8 条、5 条相关,类内精度 62.5%)。阈值敏感性:
0.90 在 e5-small 上 gate pass 40.0% 但精度降到 36.5%,0.88 时 FPR 升到 62.5%,
0.94 只剩 40.3% 精度且指代无新增。node --test test/prompt-recall.test.js15/15;
相关 8 个测试文件 156/156 通过。 - 未验证:真实会话里这条分支的触发频次;dense-only 命中注入的摘录是正文前 160 字、
不是查询居中的 ±60 字符窗口,生产注入文案未做 A/B。
- 证据:同一冻结 200 条语料,接受面 +16 个 top-3 槽位、其中 9 条相关;gate pass
-
可选的 hybrid 检索引擎:FTS5 词法候选 + 本机回环 embedding 语义候选,按加权 RRF
融合;默认关闭,关闭时读路径逐字节保持原样。retrievalEngine: hybrid必须同时
retrievalProvider.enabled: true,endpoint 只接受回环地址,model/dimensions
决定向量文件,timeoutMs限 50–5000,rerank键仍保留但 hybrid 排序当前不使用
重排。向量存<dataRoot>/index/embeddings-<vaultHash16>-<modelHash16>-<dim>.json:
按笔记内容哈希增量同步,损坏、维度不符或换模型时整份重建,写入原子化、无临时文件
残留;provider 失败时回退 lexical,并在 diagnostics 的 search 事件记录 fallback。
lib/hybrid-search.js的 adaptive guard 在标题/短语命中或 token-hits ≥ 8 时保留
lexical 顺序。- 证据:冻结 200 条语料 + 1361 条笔记快照,
P@3 44.4%→61.25%、P@1 32.5%→43.1%、recall@8 53.6%→68.6%、MRR 39.2%→52.3%;五类 P@3 全部
持平或上升(exact 96.9 持平、paraphrase 53.1→84.4、concept 37.5→59.4、
cross-lingual 15.6→21.9、deictic 18.8→43.8);node --test test/config.test.js test/retrieval-provider.test.js test/embedding-store.test.js test/hybrid-search.test.js test/hybrid-recall.test.js test/services-hybrid.test.js全绿。 - 未验证:真实会话里的触发率与 900 字符注入预算;e5-base/768 没有任何报告;
rerank 路径未接入 hybrid,rerank: true不改变混合排序。
- 证据:冻结 200 条语料 + 1361 条笔记快照,
-
recall 增加第三态
near-miss:语义接近但没有过闸门的候选只注入一行弱提示,
不带正文摘录。 条件是 dense-only 候选的 semantic score ≥ 0.88 且 hybrid rank
≤ 3,且通过与useful()相同的路径/索引/热层/状态资格检查;提示恒为一颗标题行
加一句“需要时用 mem_read 核实全文”。diagnostics 的 recall outcome 新增
near-miss。- 证据:
node --test test/prompt-recall.test.js test/diagnostic-codec.test.js
24/24 通过(含“恰好一行、不含 snippet”与“0.87 或 rank 4 不注入”两条)。 - 未验证:真实会话里 near-miss 的出现频次;语料评测只复算闸门,不回放注入文案。
- 证据:
-
短追问把上一轮用户提示词作为 dense query 的上下文。
lib/hooks.js只在会话
状态里记住最近一次提示词(2000 码点,dense 侧再截 400),不写盘、不进日志;
lexical 分支完全忽略它,因此关闭 provider 时行为不变。- 证据:
test/hooks.test.js的 “a short follow-up carries the previous prompt as
dense context” 通过。
- 证据:
-
token 查询不再整表扫描
notes_fts:过滤列加一元+前缀、显式 bm25 列权重
(10, 1, 4),让 SQLite 走 FTS 索引计划。 旧的带项目/状态过滤的 FTS 查询被规划成
MULTI-INDEX OR,随后对整个虚拟表 SCAN 计算 bm25(同一条 SQL p50 202.5ms;无过滤时
1.82ms),是召回延迟的主因。源码校验同时按VERIFY_CONCURRENCY = 16分批并发,窗口
填充集合与串行逐条一致。- 证据:40 条查询 lex8 p50 233.7ms→24.3ms、p95 268.9→57.5;冻结语料 lexical
p50 22.3ms / hybrid 121.2ms;质量指标逐位不变(lexical P@3 44.375%、hybrid
P@3 61.25%);200 条 token 查询新旧 statement 的路径列表 0 差异;test/search.test.js
新增 EXPLAIN QUERY PLAN 断言(不含 MULTI-INDEX OR、含 notes_fts 虚拟表索引),
改动前失败、改动后 39/39 通过。并发校验只把 lex8 p50 压到 19.0ms(n=40,单机单次)。 - 未验证:其它 SQLite 版本的查询计划未逐个测量;并发校验在慢盘/网络盘上的收益未测。
- 证据:40 条查询 lex8 p50 233.7ms→24.3ms、p95 268.9→57.5;冻结语料 lexical
-
recall 闸门的 token 分支从至多 4 个 token 命中提高到 6 个;标题/短语分支不变。
旧闸门在 hybrid 候选上通过 47.1% 的 top-3 而只有 27.9% 精确率、no-answer 控制项
32.5% 被放行;新阈值(6 个命中,floor 仍为 3–4、取二者较大)把接受面收窄到真正有
字面证据的候选。Phase 2 只改接受/拒绝,不改排序。- 证据:同一语料 hybrid
in-gate precision 27.9%→39.8%、no-answer FPR 32.5%→5.0%;lexical20.1%→33.8%、50.0%→2.5%;两份报告的 P@1/P@3/
recall@8/MRR 与各自旧闸门版本逐位相同。node --test test/prompt-recall.test.js test/architecture.test.js全绿。 - 延迟(2026-11-03 复测):冻结语料上 hybrid p50 从 444.6ms 降到 121.2ms,Phase 1
验收的 350ms 绝对上限已满足;但同一轮修复把 lexical 自身从 233.4ms 降到 22.3ms,
于是「≤ 1.5× lexical」的相对说法不再成立(hybrid 约 5.4×,差的 ~99ms 是每次查询一次
回环 embedding 往返)。两项都记录在
docs/validation/retrieval-latency-and-semantic-gate-2026-11-03.md;不宣称相对达标。
- 证据:同一语料 hybrid
-
诊断窗口补齐召回漏斗:新增
search事件,recall带accepted/injected,brief的 injected 事件带chars。
过去只能看到召回的结果状态,无法区分「闸门接受了几条」「最终注入了几条」「简报注入多少字符」;
mem_search更不产生任何事件,评估时读不到工具侧检索。现在lib/debug.js的事件白名单加
search(落在 skill 与 recall 之间)、FIELDS 加两个非负整数字段;lib/services.js的
services.search计时并记录ok/failed后原样返回或重抛;lib/prompt-recall.js的每个
决策携带 accepted/injected;lib/hooks.js落库两个计数与简报字符数。事件仍然内容无涉:
不记录 query、路径或标题。- 证据:
node --test test/debug.test.js test/prompt-recall.test.js test/diagnostic-codec.test.js
35/35 通过;test/tools.test.js41/41;test/hooks.test.js64 通过、唯一失败是
lib/services.js架构预算(1,920→1,968 行),预算 1,950→2,000 并注明换来的行为;
test/architecture.test.js8/8;完整套件npm test:1,505 项、1,504 通过、0 失败、1 跳过(197 s)。 - 未测:新增事件与字段尚未在真实会话的诊断窗口里采到样本(本轮改动未提交,诊断环仍是
33 run / 3,219 events 的旧窗口);等 Phase 1 在真实会话上复核。
- 证据:
-
三个模块的尺寸预算按实测抬升:
lib/note-health.js300→400、lib/vault.js1800→1850、
lib/curation-scan.js1350→1450。 分别由行内代码链接审计(344 行实测)、非 git 祖先指针
(1,809 行实测)、deadline 先于 manifest 遍历(1,392 行实测)带来,沿用「实测 +30 向上取整
到 50」的既有规则;理由写在test/architecture.test.js的预算表注释里,包括每条增长换来的
行为,而没有新增模块或层。- 证据:
node --test test/architecture.test.js8/8 通过。
- 证据:
-
证据(整套门禁):
npm run check退出码 0——lint、format:check、types通过,测试套件
1,549 项 1,548 通过 / 0 失败 / 1 跳过(161.3 s),verify-pack与真实 tarball 校验通过
(92 个条目 / 82 个 lib 模块)。 -
慢模型(如 e5-base/768)首次 embedding 预热不再必然超时:超过 provider 时限的批次会
递归二分到能在时限内返回的切片。 默认批大小 16,1200 码点正文在 CPU 上约 5.4s/批,
超过timeoutMs允许的 5000ms 上限,e5-base 因此永远 warm 不出来。
lib/embedding-store.js只在provider-timeout且切片大于 1 时二分,单条仍超时就如实
报错,batches只统计成功请求。- 证据:
node --test test/embedding-store.test.js15/15;新增「timed-out batch is
bisected」与「timeout on a single text is reported」两条;e5-base 预嵌入 1361 条
笔记 171 批、423s 完成,同一 harness 上 P@3 62.5%、in-gate 40.0%、FPR 2.5%。 - 未验证:其它慢模型的实际批大小与收益未逐个测。
- 证据:
-
链接目标本身引用行内代码的活链接不再被误报为
dead-wikilink。noteLinkAudit原来先用
proseWithoutCode把行内代码整段抹成空格、再从抹过的行里抽 wikilink,于是目标里带代码的
链接会被打残成不存在的路径:真实 vault 中Conventions/index.md:87指向
…/绝不替用户修改配置文件(含 `$DSH_HOME cordis.patch.yml`).md的链接(文件存在)、
Inbox/index.md:36与Decisions/index.md:40的同形提案都是这样产生的,curation 会把它们
当成待处理的断链。现在lib/note-health.js增加proseLines()、inlineCodeSpans()与
insideSpans(),审计逐行扫原文、只跳过开口[[落在代码 span 内的匹配,目标取原文;
proseWithoutCode仍只服务语言检查,输出不变。- 证据:
node --test test/curation-scan.test.js test/lint.test.js54/54 通过。新增用例
「a live link whose target quotes inline code is not read as dead」在改动前失败
(actual['Projects/p/Conventions/含 的约定'])、改动后通过。
- 证据:
-
非 git 目录树的子目录会话复用祖先指针,不再静默分裂成第二个项目。
resolveBinding
过去只在cwdReal精确位置读.obsidian-mem:在root做过bind local之后,从
root/src开的会话拿到no-git-root并提示再绑一次,照做会铸出第二个 projectId
(实测 root7bbe7fa3-…与 src19ada2c8-…两个 id),同一目录树的记忆被拆成两半。
现在lib/vault.js新增findPointerRoot(),镜像findGitRoot()从 cwd 向上找最近的
指针:最近祖先优先、不跟随 symlink、损坏的指针也采纳(目录本身就是绑定证据);无 git 根时
resolveBinding改用它,fork/retain也作用于持指针的 root,损坏祖先指针报精确的
pointer-*而不是被吞成no-git-root。探测只做 lstat,读路径绝不铸新指针,真正未绑定的
目录仍照旧拒绝。- 证据:
node --test test/binding.test.js73/73 通过(66 条基线 + 7 条新增,新增用例中
6 条在改动前失败);test/lint.test.js33/33 通过。git worktree 的绑定语义未变,
1.0.7 的两条回归保持通过。
- 证据:
-
索引就地修复一条过期候选后重新应用检索过滤,被编辑出范围的来源不会再泄漏一次。
verifyCandidate会重新解析一条源文件已变的候选并回写,但它只重查status,没有重放
matchesFilters:一条被改成 superseded 的约定会在重新索引的那次搜索里仍被返回、下一次
搜索才消失——一次性泄漏。现在候选把plan.filters带进校验,回写后若不匹配即返回 null;
候选窗口本来就是用旧缓存事实选出的,这个重查补的正是那段时间差。- 证据:新增
test/search-source-eligibility.test.js3 条(默认搜索不再返回改为 superseded
的笔记、includeHistory仍可见;type 改变后旧 type 下消失、新 type 下可见;项目移动后
旧 scope 下消失、scope='all'可见),改动前 3/3 失败(actual
['Projects/elig--5f1b2d3c/Pitfalls/race.md']),改动后通过;配套检索套件 36/36 通过,
加test/search-engine.test.js47/47 通过。
- 证据:新增
-
搜索的截断事实穿过 service 与工具边界,
mem_search现在返回truncated、
truncationReason、candidateWindow、rowsExamined、candidatesScored。 索引层早就把
这五个 non-enumerable 事实挂在命中数组上,但lib/services.js的hits.map(toHitView)与
lib/tool-registry.js的{ hits }把它们丢光:被候选窗口截断的搜索看起来和完整成功一样。
现在lib/tool-schema.js导出SEARCH_FACTS与searchEnvelope()(以Array.from复制出
干净数组,再把存在的字段放进信封——带 non-enumerable 属性的数组不是宿主 lossless-JSON
快照接受的“未装饰”数组,直接透传会被判returned invalid output);services.search用
projectHits()让 service 边界保留这些事实,mem_search返回信封,Codex MCP 适配器同样
改用searchEnvelope(),否则 JSON 序列化会静默丢弃。schema 把这五个字段列为可选,因为
...
dsh-obsidian-mem v1.0.7
Fixed
- 非 git 目录
bind local后跨会话写入不再报no-git-root。resolveBinding原来在
读指针之前就按「没有 git 根」拒绝,而mem_write走只读探针(mode: 'show')永远拿不到
bind local已写出的.obsidian-mem,已绑定的 scratch 目录于是在后续会话永久只读。
现在拒绝前先探测cwdReal/.obsidian-mem:有既有指针就采纳、没有照旧拒绝;探测只采纳、
绝不铸新指针。fork/retain保持原 git worktree 语义。新增回归测试
test/binding.test.js「a non-git directory with an existing pointer stays bound on the read path」。
dsh-obsidian-mem v1.0.6
Added
- 图谱默认按记忆类型上色,采用一组莫兰迪(灰调低饱和)配色。 颜色组不再默认为空:
决策[type:decision]#7b8ea6、约定[type:convention]#8ba084、踩坑
[type:gotcha]#ab817c、文档[type:doc]#b39b6f、术语表[type:glossary]
#9a8aa0。这五组是普通颜色组,查询、色块、删除与「恢复默认设置」都按既有行为处理,
渲染器与matches()没有新分支;管理笔记(hub/hot/session-log/method)、未分类笔记
和未创建目标继续用主题中性色,悬停与记忆调用仍是强调色。填充对比度按主题节点底色
实测:深色 4.89–6.22:1、浅色 2.48–3.15:1——莫兰迪灰调在浅色下低于 WCAG 文本 4.5:1,
类型识别同时由颜色组查询与筛选保留。色值、取色过程与中性色清单见
docs/obsidian-graph-source.md新增的「2026-11-01 — 按记忆类型的莫兰迪默认色组」
一节;README 双语图谱段落同步更新。
Changed
-
图谱设置记录版本升到 2,v1 记录整条失效一次。 v1 的默认颜色组是空列表,而读取时
分不清「旧的空默认」与「用户把色组删光了」;不升版本,新的按类型预置会被旧记录盖住。
升版本后旧记录回落到新默认值一次,下一次写入起按 2 保存;此后在同一版本内删空色组
仍然是删空,刷新不会复活。 -
lib/client.js的尺寸预算由 1700 抬到 1750 行(实测 1,711 行,按「实测 + 30 向上
取整」的既有规则),理由写在test/architecture.test.js的预算表注释里:新增的是五组
预置与其说明注释,渲染路径没有新增代码。 -
证据:
node --test test/graph-settings.test.js test/panel-client.test.js44/44 通过
(新增一例真实渲染GraphCanvas、读它交给渲染器的model.colors,钉住五个类型各自的
颜色与 hub/untyped/unresolved 的undefined;英文控件测试改为先断言五组预置、再断言
新增组排在其后;graph-settings的默认值与版本断言同步到 v2,并补「v1 记录被丢弃」
「删空色组保持删空」两例)。完整npm run check退出码 0:lint、格式与类型检查通过,
1,480 项测试 1,479 通过 / 0 失败 / 1 跳过,verify-pack与真实 tarball 校验通过
(89 个条目 / 79 个 lib 模块)。
Fixed
-
图谱四个控件图标改用 Obsidian 自带的 lucide 图形,不再是手绘近似版。 18px 下四个
按钮原来互不成比例:齿轮是 12 齿折线多边形,且viewBox写成0 0 24 28,实际只画
约 14.75×15px、位置偏下;魔杖路径的笔尖与左端压在viewBox边界上,描边被裁掉;复原
箭头的箭尾是 8 单位直角,lucide 原形是 5 单位(十字本来就与 lucidex同形,随整组
重写、视觉不变)。现在lib/client.js的ICONS直接使用 Obsidian 桌面版图标表里的
settings/x/wand-2/rotate-ccw四条 path/circle 数据,icon()支持形状列表,
四个图标统一回到 24×24 网格;描边仍是面板既有的 1.7(与 DSH 自己的图标线宽一致,不改
成 Obsidian 的 2)。逐项问题、提取过程与代价见docs/obsidian-graph-source.md新增的
「2026-11-01 — 图谱控件图标改用 Obsidian 自带 lucide 图形」一节。 -
证据:
node --test test/panel-client.test.js35/35 通过。新增一例从真实渲染的
GraphView取四个graph-controls-button,断言 viewBox 统一 24×24、齿轮为一个 path
加r=3圆、十字两条路径、魔杖八条路径、复原是弧线加M3 3v5h5箭头;该测试在改动前
失败(settings draws on the 24px grid),改动后通过。另用 headless Chrome 按面板
真实的 18px、Retina 2x、深色底渲染改动前后对照图/tmp/icon_compare.png,4× 放大下
折角、裁切与箭头尺寸问题消失。 -
概览「活动热度」的日历年居中,不再在宽侧边栏里靠左。 日历是固定宽度的画布(371 天
= 54 列 × 12px 列距 − 2px 间隙 + 14px 星期槽 = 660px),放在占满面板宽度的横向滚动框
里;框比画布宽时块级元素默认从左边排,右侧留出一大片空白,看起来像图表被钉在一边
(2026-11-01 用户截图)。现在lib/client.js的.mem-heat-canvas用
margin-inline:auto让 CSS 分配剩余空间:宽面板左右各分一半,窄面板自动外边距归零、
保持原有的贴右可滚动行为,不引入 JS 测量。列宽/画布宽的计算同时收进
lib/memory-panel.js的heatCalendar()(heatmap()与悬停提示共用),悬停提示的
锚点加上居中偏移,否则提示会偏到悬停格左侧。- 证据:
node --test test/panel-ui.test.js test/panel-client.test.js121/121 通过。
新增一例以 800px 面板、660px 日历挂载真实OverviewView,断言画布width:660px、
悬停首日 tooltipleft:89px(70px 居中偏移 + 14px 星期槽 + 5px 半格);该测试在改
动前先失败('71px' !== '89px',测试替身的滚动框不 clampscrollLeft,修复方式见
docs/validation/heatmap-centering-2026-11-01.md)。另用真实样式表在 headless
Chrome 里量:1368px 面板下画布 offsetLeft 由 0 变 354px(左右自动边距各 354px);
348px 窄面板下仍为 0,且scrollWidth 660 > clientWidth 348保持可横向滚动;
对照图/tmp/heat_center.png。
- 证据:
dsh-obsidian-mem v1.0.5
Fixed
test/pack.test.js的 npm 可用性探针在 Windows 上必然误判,导致真实归档契约用例被跳过。
探针直接spawnSync('npm', ['--version']):Windows 上 npm 是npm.cmd,无 shell 的
spawn 报 EINVAL,于是 npm 12.0.2 已装好的本机也被判成「npm 不可用」,
the archive this checkout really produces satisfies the contract一直 skip ——
而同一份解析早在 1.0.1 就改成了「用本 Node 跑npm-cli.js」(scripts/verify-tarball.mjs),
只是测试里另写了一份。现在探针复用已导出的npmCommand(),并新增一条用例钉住
「探针走本 Node 的 npm JS 入口,不 spawn.cmd」。- 证据:修复前整仓套件 1,475 tests / 1,440 pass / 0 fail / 35 skipped(其中一条就是它);
修复后test/pack.test.js在本机 Windows 上 27/27 通过、0 skip,真实归档用例
耗时 1.8 s 并打印verify-tarball: OK … 89 entries, 79 lib modules。
- 证据:修复前整仓套件 1,475 tests / 1,440 pass / 0 fail / 35 skipped(其中一条就是它);
Changed
- Windows 兼容验证第 2 轮:全量门禁、面板布局与图谱性能在 Windows 上实测;除上面那条
测试探针外没有发现产品缺陷。 本机 Windows 11(10.0.26220)+ Node v24.20.0 + npm 12.0.2- 真实 Chrome 156.0.8078.74:
npm run check全绿:1,476 tests / 1,442 pass / 0 fail / 34 skipped;
verify-pack与verify-tarball均 OK(89 entries / 79 lib modules);
scripts/build-graph-worker.mjs --check输出graph worker assets: current。- 面板三个视图(概览 / 检索 / 健康)× 中英 × 明暗 × 300–760 px 九个宽度 × DPR
1 / 1.25 / 1.5 / 2:432 个真实 Chrome 状态、0 布局违规 —— 无页面横向滚动、无元素
越出视口;371 天热度图 53×7 格(10×10、列距 12)、月标签 0 重叠;KPI 宽屏单行
space-evenly、≤559 px 2×2;構成桑基铺满实测块宽(320 px 中文 292、760 px 732,
英文窄屏按设计横向滚动);健康卡片 figure 网格空槽 0;DPR 变化时关键盒宽差 ≤0.5 px。
字体解析为 Segoe UI;唯一被截断的是 CSS 明写 ellipsis 的.mem-result-title。 - README 里「Windows 上 >2,000 节点的性能未测」换成本机实测:1,236 节点真实规模
中位 12.8 ms/tick(p95 16.2 ms),2,500 节点 28.8 ms、4,000 节点 50.7 ms、
6,000 节点 99.9 ms —— 与 0.4.4「为 1:1 复刻 Obsidian 付 quadtree 碰撞代价」的已知
取舍一致,不是本轮新缺陷;中英两半同步改写,README.i18n.yaml的 blob hash 一并重记。 - 34 条 skip 全部是平台能力缺失或既有 opt-in(14 file symlink EPERM、10 expect/pty、
8 NTFS mode/ACL、1/dev/fd、1MARKETPLACE_PROBE_LIVE),没有一条为绿而跳。 - 证据:
docs/validation/windows-compat-2026-11-01.md;测量产物在scratch/win-round/
(已 gitignore,不入库)。
dsh-obsidian-mem v1.0.4
Changed
- 语言切换按钮改用翻译图标,不再印
EN/ZH。 两字母徽标读起来像「当前是什么
语言」的状态标签,而不是「点它会切到哪一个」的动作;现在画的是常见翻译标记(文/A 组合,
Material translate 的实心路径,16px),title与aria-label仍然写明目标语言
(英文界面 Switch to Chinese、中文界面「切换到英文」),按钮仍是 28×28 的方形热区,
与刷新按钮同高对齐。外壳与三个视图的语言行为本身不受影响。
Fixed
- 关键数字条的四格按内容宽度分配空白,不再等宽四等分。 等宽时每格内容各自居中,
但读数宽窄差很大:第一条带着 120px 的折线,最后一条 "0 个" 只有 29px。实测 593px
面板下整条墨迹左边空 25px、右边空 69px,看起来既没居中、左右空隙也不一致。改为
display:flex; justify-content:space-evenly:每格宽度由自身内容决定,剩余空白在格间
与两端平均分配;代价是宽布局里去掉了 1px 竖分隔线(格宽不等时它们会落在不均匀的
位置),改由空白分组。559px 及以下仍折成 2×2 等宽网格并恢复分隔线。证据:headless
Chrome 用真实样式实测 593px 中文左右各 46.67px、英文 35.86/35.88px;560/570/600/
640px 四个宽度的格间距与两端空隙逐项相等(误差 <0.03px),
scrollWidth-clientWidth全为 0。 - 健康页三张数字卡片的内容居中,短行不再露出灰色空格。 固定三列网格在项数不是
3 的倍数时,空出的格子会露出网格容器的边框底色:热层数字(2 项)右侧 1 个灰块、
今日摘要(4 项)第二行 2 个灰块。现在figures()把项数写进data-columns
(≤4 项时一项一列铺满,6 项仍是 2 行 3 列),单元格内容从左上对齐改为居中。证据:
headless Chrome 实测三张卡片 holes 均为 0(索引 3 列×2 行、热层 2 列×1 行、摘要
4 列×1 行),每格文字左右墨迹差 <0.03px;559px 及以下摘要的 4 列回退为 2×2。 - 证据:
DSH_HOME=$(mktemp -d) node --test test/panel-client.test.js test/panel-ui.test.js
118/118 通过(断言更新:KPI 条含 flex/space-evenly、折线显式 120px 与
max-width:100%、三张卡片的data-columns为 3/2/4、figure 单元格居中、折叠规则
改为第 2/4 格的左边线与第 3/4 格的上边线并含 4 列→2 列回退;原有中文断言未改)。
dsh-obsidian-mem v1.0.3
Added
- 面板内可以切换界面语言,真实浏览器默认英文。 标签条右上角新增语言按钮:英文界面
显示ZH(提示 Switch to Chinese),中文界面显示EN(提示「切换到英文」)。
点击后外壳与三个视图立即换语言,选择写入localStorage的
dsh-obsidian-mem:locale,下次打开沿用。语言仍是中英两种:没有localStorage的
环境(node --test、旧宿主)保持原来的navigator探测与中文兜底,所以既有中文断言
不需要改写。侧栏标签页的标题与描述在插件注册时确定,切换语言后需刷新页面才更新;面板
内部立即生效。 - 英文界面的排版与漏翻补齐:
- Activity 热图的星期栏在英文下留 32px(
Mon/Wed/Fri不再压住第一列色块),中文
仍是 14px;画布宽度、月份行偏移、网格列宽与 tooltip 锚点用同一个宽度。 - Composition 桑基按本次实际打印的英文标签测宽(
Convention、Superseded等),
图表放进.mem-sankey-scroll:窄面板下横向滚动看完整文字,而不是把标签截成
Con…。中文列宽保持原来的 34/38/36/40,几何与既有断言逐字不变。 - 健康页热层分区的三个标准区名翻成英文(强约束 → Strong constraints、进行中 →
In progress、已完成 → Completed),与lib/panel-copy.js的对话框用词一致;
用户自定义区名是数据,原样保留。
- Activity 热图的星期栏在英文下留 32px(
Fixed
-
搜索框在上下空隙中垂直居中。
.mem-searchbar的 padding 由 14/10 改为 16/8:
加上视图区的 8px 上内边距后,搜索框上方 16px、下方 16px,不再偏上。 -
健康页不再重复标题。 外壳的健康标签页把
onMeta传给视图,页面不再在搜索框下面
再印一个「健康 / Health」标题;独立渲染(无onMeta)仍保留标题,概览页的既有门控
行为不变。 -
热图月份行滑到星期栏下方时不再露出半截。 星期栏的遮罩过去只和网格同高,
横向滚动后月份标签会从Mon/Wed/Fri上方探出半个字。现在给.mem-heat-weekdays
加一个向上 14px 的同色伪元素(::before,与月份行同高),遮罩正好盖住整条月份行,
中文标签只在星期栏右侧出现。中文 14px 与英文 32px 的星期栏通用,布局与网格尺寸未变。 -
证据:
DSH_HOME=$(mktemp -d) node --test test/panel-ui.test.js85/85 通过(新增 4
条:英文热图 gutter、英文桑基测宽与滚动、组合健康页的中英标题门控);同法
test/panel-client.test.js32/32 通过(新增 5 条:默认英文、点击切换并持久化、
存储优先、renderer 随语言重建、居中与滚动条 CSS);test/architecture.test.js
8/8 通过(两个越界文件的预算按实测行数 +30 向上取整后抬到 4300/1700,理由写在
预算注释里)。中文行为与 1.0.2 逐字节一致,test/panel-ui.test.js原有 81 条与
test/panel-client.test.js原有 27 条断言零改动通过(唯一调整是「只有热图横滚」的
断言改为同时承认桑基的滚动容器)。
dsh-obsidian-mem v1.0.2
Added
- 英文界面:面板、图谱外壳与健康操作的对话框都能按系统语言显示英文,默认仍是中文。
只新增一份英文文案,没有引入新的运行时或开发依赖;中文文案与改动前逐字节一致,
所以既有的中文断言不需要改写。语言解析集中在新文件lib/locale.js:- 解析顺序:请求体
locale→ 请求头Accept-Language→(浏览器内)
navigator.languages/navigator.language→ 兜底zh。显式zh赢过英文
请求头;fr-FR这类既非中英的明确偏好显示英文(界面只有两种语言,英文是唯一
能跨语种的兜底);*不算偏好——Node 的fetch默认就发
accept-language: *,把它当成英文会让不带偏好的请求凭空变英文。 - 面板(
lib/memory-panel.js):概览 KPI、热图/流量条/树图/桑基、健康、最近
变化、检索结果、操作单与确认/预览全部按 locale 取词;createMemoryViews({ React, locale })可显式传入,未传时在浏览器里按navigator探测;每个POSTbody
都带locale,面板自己决定语言,不让路由被宿主浏览器的请求头改掉。 - 外壳与图谱(
lib/client.js、lib/graph-renderer.js):标签页、搜索框、错误
与空状态、通知、图谱设置/筛选/滑块、画布aria-label与 worker 失败文案中英
双语。这两个资产由路由按文件原样送出,运行时不能import,所以语言规则在各自
文件里内联一份;无window的环境(node --test)固定回中文,既有中文断言
不变。 - 服务端对话框(新文件
lib/panel-copy.js,lib/panel-actions.js与
lib/panel-route.js接入):重试失败任务、整理热层、发布项目摘要的标题、按钮、
字段、效果与警告按请求 locale 取英文;中文副本与改动前逐字节同一份。 - 证据:
DSH_HOME=$(mktemp -d) node --test定向套件全绿——test/locale.test.js、
test/panel-route.test.js、test/panel-actions.test.js、
test/panel-client.test.js、test/graph-renderer.test.js、
test/panel-ui.test.js;真实路由 seam 上,body 的locale胜过英文请求头、
只有Accept-Language: en-US得英文、两者都没有得中文、只有*得中文。
整仓npm run check全绿:1467 条测试 1466 pass / 0 fail / 1 skipped,verify-pack 与
verify-tarball 均 OK(归档 89 entries、79 lib modules,与 AGENTS.md 记录一致)。独立
验证(scratch/i18n-verify.md)另跑了真实路由与真实视图探针:route 导出与 HEAD 全等、
默认中文渲染文本逐串等于 HEAD、en 三视图与预览/确认单无汉字;唯一发现的空
"最近变化" 中文硬编码已修复并补了回归测试。 - 未验证:真实桌面侧栏在系统语言为英文时的自动探测没有在真实 Electron 窗口里
点过(浏览器探测只有 DOM 级测试);README 中英两半和README.i18n.yaml未改,
因为用户明确说 README 已有双语、不必处理。
- 解析顺序:请求体
dsh-obsidian-mem v1.0.1
Fixed
- CI 的浮动
node平台不再被 npm 12 的npm pack --json输出形状打红。 该平台实际装的是 Node27.0.0-alpha.0.0.0+ npm12.1.0;npm 12 把这个输出从数组[{…}]改成按包名索引的对象{"dsh-obsidian-mem": {…}},而scripts/verify-tarball.mjs原有的const [packed] = JSON.parse(run.stdout)对对象解构会抛object is not iterable,于是the archive this checkout really produces satisfies the contract断言2 !== 0、job 退出 1(run 41176291759)。这不是 1.0.0 引入的:0.4.4 的推送在同一平台上已因同一原因红(run 41093967549)。现在parsePackReport(stdout)同时接受数组与按包名索引的对象两种信封,并且只接受恰好一个归档记录:0 个或多个都抛出带数量的错误,不再静默挑一个;真实打包与归档检查路径不变。- 证据:在隔离目录安装 npm
12.1.0、用假node布局实测本仓库npm pack --json返回isArray false、keys ["dsh-obsidian-mem"]、files 87;同一布局下scripts/verify-tarball.mjs输出87 entries, 77 lib modules, every required asset present,node --test --test-name-pattern='archive this checkout' test/pack.test.js通过;npm 11(本机默认)的同一命令与完整npm run check也通过。test/pack.test.js新增两条纯函数测试:两种信封得到同一记录;0 个、2 个、缺filename的记录全部被拒绝。
- 证据:在隔离目录安装 npm
Changed
- README 的 DSH 支持范围改为实际支持的范围。 需求表此前只列
0.1.5-rc.2与0.1.7-rc.2两个"已验证"版本,读起来像其余版本未获支持;现在写明下限>= 0.1.5-rc.2、当前版本0.2.1-rc.5,以及两者之间的每个版本都受支持(每个中间版本都对照插件实际使用的行内config:与 Cordis 接缝核查过,没有发现不兼容)。面板段落的环境同步为当前实测组合:DSH0.2.1-rc.5+dsh-better-sidebar0.28.1。中英两半同改,README.i18n.yaml的 blob hash 一并重记。