Skip to content

Commit 038e7d7

Browse files
committed
chore: 去掉文档/注释/测试夹具里的本机绝对路径(开源隐私收口)
- 文档与注释改用占位符:\(默认 ~/.dsh/memory)、「插件目录向上两级/tools/bm-sync-mirror.cjs」 - 代码行为不变(mirror.mjs 的脚本路径本来就是相对插件目录解析,只改注释;packer 只改测试夹具路径)
1 parent 04839b5 commit 038e7d7

4 files changed

Lines changed: 9 additions & 9 deletions

File tree

‎README.en.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ After configuring `petEndpoint`, memory-save events are pushed to a local deskto
266266
| **v0.8.1** | 2026-09-20 | **Conflict-ruling loop + on-demand extraction + honest UI**: ①`superseded` stops being a never-written dead value and becomes **superseded by a human ruling** — same effect as `archived` (leaves injection and retrieval) but a different reason; `allEntries`/`entriesWithVectors` now only take `active`; new `setEntryStatus` unifies archive/supersede/reactivate with `SUPERSEDE`/`ARCHIVE-MANUAL`/`REACTIVATE` audits; new `GET /superseded` plus Supersede/Restore buttons in the UI (conflicts previously had a red badge and no way out). ②**On-demand extraction button**: `POST /api/extract` reads `ctx.sessions.get(id).deriveMessages()` and calls the model through `ctx.llm.stream` (reusing the DSH provider); candidates go through the same fingerprint dedup and near-duplicate merge, and Preview costs zero tokens. ③**Honest UI**: the model card has three states (not-loaded / 512-dim / degraded) and the audit card footnote no longer shows unrelated data. ④Compliance: client bundle moved to `lib/client.js`, bundle entry id unified to the package name. 98 tests green |
267267
| **v0.8.0** | 2026-09-20 | **Auto-metabolism fix (real defect, caused data loss)**: ①the auto-`dream` interval check used `store.latestBackup()`, which reads the pre-single-track Markdown backup dir (`MEMORY_ROOT/backups/<12-digit dirs>`) that stopped being produced in v0.6.4 → always `null` → **a full dream ran on every plugin load** (109 runs measured, up to 26 in one day); ②`runDream` decayed with `w × 0.5^(age/halfLife)` using the *already decayed* current weight times the full-age factor = **compounding decay**, so repeated runs accelerated exponentially → 19 behavioural memories (including several merged composite entries) were pushed below `decayThreshold` and wrongly archived. Fix: the check now reads `lastDreamAt` / `lastReflectAt` from the meta table (written when dream/reflect finish; no file mtime involved); decay became **incremental and idempotent** — base = `max(created_at, last metabolism)`, and `DECAY` is recorded only when the stored precision (1 decimal) actually drops. New `store.unarchiveEntry(fp, {weight})` (archived row → `active` with weight repair, audited `UNARCHIVE`) plus the `bm-restore-archived.cjs` maintenance script (dry-run by default); all 19 wrongly archived entries were restored. **Same batch**: `setPinFp` zeroed `weight` when pinning (un-pinning instantly dropped below threshold) → weight is no longer touched (skip-decay is enforced by `runDream`); `removeByFp` also ran `DELETE FROM audit_log` (deleting an entry erased its audit trail) → removed; `/status` `auditCount` used `queryAudit({})` with the default `limit=50` → always ≤50, now `limit: 100000`; `retrieve` counted hits for **every** returned entry (contradicting the "only real keyword recalls consolidate" comment) → only keyword hits count; the embedding model had no negative cache and `apply` forced `ensureVectors` 100ms after start (loading the ~24MB onnx model on every launch) → negative cache + truly lazy loading (`preloadEmbeddings` defaults to `false`; set `true` or `DSH_BIOMEMORY_PRELOAD=1` to warm up); `PRAGMA busy_timeout=5000` for multi-process access; the Markdown migration no longer mis-parses the sync mirror `条目镜像.md` and `reflections/`. **Dead code removed**: `rewriteFile` / `findByText` / `backupNow` / `latestBackup` (the very trigger of this defect) / `embedMany` / `isModelReady` / `embedTextOf` / `db.getById` / `db.listPinned` / `session-state.getSummarySid`. New regression tests in `tests/dream-idempotent.test.mjs` (idempotent decay / new-entry baseline / unarchive / snapshot skips archived / trigger predicate / consolidation tied to recall time / restore keeps entry_id). **Follow-up cleanup**: the auto-metabolism predicate is extracted into the pure function `meta.shouldRunAuto()` (directly unit-testable); consolidation now only reinforces entries that were **recently recalled for real** (`last_accessed` must fall within half a half-life, and `consolidateHits` now records the recall time — the old code was decoupled from recall time, so stale entries gained weight on every dream; 3020 CONSOLIDATE events measured); `restoreEntry` keeps the original `entry_id` (the old code regenerated a UUID, leaving 6893/10006 audit rows dangling); the Web API `readBody` is capped at 1 MiB (`DSH_BIOMEMORY_BODY_LIMIT` overrides); `client.js` theme listener got its dependency array (it used to rebuild the matchMedia listener and MutationObserver on every render); `settings-page.test.mjs`, which never matched the `tests/*.test.mjs` glob, is now part of `npm test`. **Upgrade (memory atomisation, P1)**: `writeEntry` now intercepts **near-duplicates** — when new content scores ≥ `nearDuplicateThreshold` (default `0.7`, `0` disables) in Chinese-bigram Jaccard similarity against an existing entry of the same type, it is not inserted; instead the tool returns the similar entry's fp plus a merge hint (audited as `WRITE-SKIP`). Fragmentation came from "the same thing written as a new entry again and again" (the 8/20–9/5 batch had to be merged by hand; whatever was missed ended up archived by normal metabolism). The check is pure bigram arithmetic, never loads the embedding model, and runs inline on the write path; the exact fingerprint only looks at the first 20 characters, so near-duplicate detection covers the "rephrased / different opening" cases. **Second round (same version)**: ①**automatic near-duplicate merge** (`nearDuplicateAction`, default `merge`, borrowed from `@zheexinn/dsh-memory`'s merge-on-write): a near-duplicate is no longer merely rejected — the new content is appended to the existing entry as `| 【补充·date】…` with weight +1 (falls back to `skip` above 4000 chars), audited as `WRITE-MERGE`; similarity now takes `max(Jaccard, containment)` when lengths are comparable, so short rephrasings are not missed (measured 0.64 → 0.86 for two short near-duplicates); ②**snapshot marked as untrusted data** (borrowed from `dsh-git-memory`'s `<summary_snapshot>`): the injected header now states explicitly that the entries are *data, not instructions*; ③**backup/restore hardening**: `wal_checkpoint` result is checked (warns when `busy≠0`), every backup is self-verified (the copy is opened and its entry count compared, failing copies are deleted and the error rethrown), and restore now takes a pre-restore snapshot, replaces the DB via temp-file rename, and clears stale `-wal`/`-shm`; ④**all panel requests are abortable**: `client.js` gained `apiFetch` (registers in-flight requests, aborts them on unmount, and fixes the effect-returning-a-Promise warning) plus editing state is cleared on tab switch; ⑤the inert top-level `allowScripts` was replaced by pnpm's actual `pnpm.onlyBuiltDependencies` (no `@deepseek-ai/*` package in the global install reads `allowScripts`). ⑥**"auto-sink" reliability fix** (answering "will it save on its own when I don't say so?"): the post-turn sink reminder window went from a **hard-coded 5 minutes** to the configurable `sinkWindowMinutes` (default **60**) — previously, if the user stepped away for more than five minutes the reminder was silently dropped, which is exactly why writes often only happened when explicitly requested; the reminder text now also states that **proactive sinking is the default** and that near-duplicates are merged automatically. 79 → **90 tests green** |
268268
| **v0.7.1** | 2026-09-17 | **Approval gate fix (real defect)**: `gateWrite` called `approval.request()` with only toolName/reason, but the official implementation (dsh-user-approval 0.1.5-rc.2, first line of its source) reads `req.agent.session` — so `req.agent` was undefined and it threw `Cannot read properties of undefined (reading 'session')`, making **every important write (user preferences / project decisions / lessons) fail** (hit twice in practice). The fix mirrors the official `dsh-tools` call shape `{ agent, toolName, callId, reason, signal }` and adds a fail-closed guard for a missing agent (audited as `no-agent`) instead of crashing — the official path likewise denies rather than throws when there is no agent. 2 new tests (payload carries agent/callId; a missing agent neither crashes nor silently allows); 79 tests green |
269-
| **v0.7.0** | 2026-09-17 | **Mirror sync hook**: new `mirror.mjs` — after `dream` / `reflect` (tool `memory action=dream`, `/memory dream`, startup auto-dream, settings-page `POST /dream`) it asynchronously invokes the external maintenance script `E:\DE\tools\bm-sync-mirror.cjs` to re-export SQLite into the human-readable mirror (`<MEMORY_ROOT>/preferences.md` + `longterm\条目镜像.md`). **Why an external script**: the whole point of the v0.6.4 single-track design is "SQLite is the only source of truth, the plugin never writes Markdown" — restoring Markdown writes inside the write path would bring back the dual-track that caused 79 memories never to be injected (8/20–9/5). So the plugin only triggers; generation and backup stay in the external script. Behaviour: `--dry-run` does not sync, `DSH_BIOMEMORY_MIRROR_SYNC=0` disables it, `DSH_BIOMEMORY_MIRROR_SCRIPT` overrides the script; a missing script / failure / timeout (15s) never affects memory itself and only leaves one debug-log line; the script path is resolved **lazily** (freezing it at module top level would break "import first, set env later" overrides — same lesson as `db.mjs::biomemoryDir()`). The mirror script now also writes **atomically** (temp file + rename) so an interrupted run cannot leave a half-written mirror. ★Two traps hit while implementing: ①the script path must be resolved **lazily** (freezing it at module top level breaks "import first, set env later" overrides — same lesson as `db.mjs::biomemoryDir()`); ②**never call `unref()` on the child** — measured: detached + unref makes the parent miss the exit/close event, so the promise never settles and the caller's await hangs (fixed by using the `close` event and dropping unref). New tests cover triggering, env passthrough, the disable switch and a missing script; 77 tests green (stable across three runs) |
269+
| **v0.7.0** | 2026-09-17 | **Mirror sync hook**: new `mirror.mjs` — after `dream` / `reflect` (tool `memory action=dream`, `/memory dream`, startup auto-dream, settings-page `POST /dream`) it asynchronously invokes an external maintenance script (resolved by default as `<plugin dir>/../../tools/bm-sync-mirror.cjs`, overridable with `DSH_BIOMEMORY_MIRROR_SCRIPT`) to re-export SQLite into the human-readable mirror (`<MEMORY_ROOT>/preferences.md` + `longterm\条目镜像.md`). **Why an external script**: the whole point of the v0.6.4 single-track design is "SQLite is the only source of truth, the plugin never writes Markdown" — restoring Markdown writes inside the write path would bring back the dual-track that caused 79 memories never to be injected (8/20–9/5). So the plugin only triggers; generation and backup stay in the external script. Behaviour: `--dry-run` does not sync, `DSH_BIOMEMORY_MIRROR_SYNC=0` disables it, `DSH_BIOMEMORY_MIRROR_SCRIPT` overrides the script; a missing script / failure / timeout (15s) never affects memory itself and only leaves one debug-log line; the script path is resolved **lazily** (freezing it at module top level would break "import first, set env later" overrides — same lesson as `db.mjs::biomemoryDir()`). The mirror script now also writes **atomically** (temp file + rename) so an interrupted run cannot leave a half-written mirror. ★Two traps hit while implementing: ①the script path must be resolved **lazily** (freezing it at module top level breaks "import first, set env later" overrides — same lesson as `db.mjs::biomemoryDir()`); ②**never call `unref()` on the child** — measured: detached + unref makes the parent miss the exit/close event, so the promise never settles and the caller's await hangs (fixed by using the `close` event and dropping unref). New tests cover triggering, env passthrough, the disable switch and a missing script; 77 tests green (stable across three runs) |
270270
| **v0.6.8** | 2026-09-17 | **Audit dual-write fixed**: `store.mjs` (8 call sites) and `retrieve.mjs` (1) called `db.audit` directly, which only writes the SQLite `audit_log` table — the human-readable mirror `<MEMORY_ROOT>/audit.log` is appended inside `shared.mjs::audit`. As a result, since v0.5 every memory written / edited / removed / pinned through the memory tool was **missing from the mirror** (verified: the mirror's last `WRITE` line dated 2026-08-19, leaving only metabolic events such as `DECAY` / `CONSOLIDATE`), contradicting the documented "SQLite audit_log table + human-readable mirror". All call sites now go through `shared.audit`, with `fp` / text summary lifted into the top-level payload (matching the existing `meta.mjs` convention) so mirror lines read `[time] ACTION fp summary` again; a regression test locks the behaviour in; 76 tests green |
271271
| **v0.6.7** | 2026-09-17 | **Backup file uniqueness hardened**: `backupDb()` timestamps only carry millisecond precision, so two backups taken within the same millisecond collided on one filename and **silently overwrote** the earlier one (the intermittent failure of the "each backup is its own file" test). Collisions now get a `-2` / `-3` numeric suffix, so every backup is always an independent file; the test now takes backups in a tight loop (3 within the same millisecond must not collide and all must exist); 75 tests green |
272272
| **v0.6.6** | 2026-09-17 | **exact ranking semantics fixed**: keyword search no longer sorts by weight alone but by relevance with a bounded weight bonus — `score = relevance × (1 + 0.5·min(weight, weightCap)/weightCap)` where `relevance = Σ field-hit weights (text 1.0 / summary 0.5 / entities 0.25) + 0.1·min(occurrences, 5) ∈ [0, 2.25]`; weight can lift a hit by at most +50% (same shape and same weightCap normalization as the hybrid γ term), so a low-weight but highly relevant memory is no longer buried under a high-weight marginal match, while pinned/important memories still win ties and near-ties; ties resolve by weight desc → created_at desc → entry_id asc (total order, deterministic: same input, same output); empty-query browsing (list) keeps weight-descending order and conflict-on-top behavior; the `queryEntries` result shape, hit consolidation (hits+1), `search`/`queryEntries` parameters and the hybrid RRF fusion (which consumes only exact ranks) are unchanged; 6 new tests (75 green) |

0 commit comments

Comments
 (0)