dsh-loci 0.0.0-stage → 2.0.1

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 (49) hide show
  1. package/CHANGELOG.md +321 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE +31 -0
  4. package/README.md +143 -2
  5. package/README.zh.md +111 -0
  6. package/client.js +1369 -0
  7. package/cordis.patch.yml +10 -0
  8. package/docs/ACCEPTANCE.md +279 -0
  9. package/docs/AUDIT-1.1.md +173 -0
  10. package/docs/COMPATIBILITY.md +81 -0
  11. package/docs/DESIGN.md +311 -0
  12. package/docs/OPERATIONS.md +199 -0
  13. package/docs/PRIVACY.md +93 -0
  14. package/docs/RESEARCH.md +374 -0
  15. package/docs/SECURITY.md +76 -0
  16. package/icon.svg +6 -0
  17. package/index.js +13 -0
  18. package/locale/en.json +12 -0
  19. package/locale/zh.json +12 -0
  20. package/package.json +88 -4
  21. package/scripts/cli.mjs +331 -0
  22. package/src/host/api/routes.js +353 -0
  23. package/src/host/capture/capture.js +417 -0
  24. package/src/host/commands.js +115 -0
  25. package/src/host/config.js +221 -0
  26. package/src/host/doctor.js +164 -0
  27. package/src/host/extract/extractor.js +258 -0
  28. package/src/host/index.js +290 -0
  29. package/src/host/inject/index-section.js +252 -0
  30. package/src/host/inject/sanitize.js +304 -0
  31. package/src/host/paths.js +130 -0
  32. package/src/host/scope.js +147 -0
  33. package/src/host/store/activity.js +290 -0
  34. package/src/host/store/audit.js +208 -0
  35. package/src/host/store/db.js +185 -0
  36. package/src/host/store/duplicates.js +140 -0
  37. package/src/host/store/embedding.js +180 -0
  38. package/src/host/store/export.js +246 -0
  39. package/src/host/store/ids.js +58 -0
  40. package/src/host/store/importers.js +259 -0
  41. package/src/host/store/migrate.js +156 -0
  42. package/src/host/store/schema.js +209 -0
  43. package/src/host/store/search.js +519 -0
  44. package/src/host/store/store.js +1548 -0
  45. package/src/host/store/tokenize.js +209 -0
  46. package/src/host/tools/tools.js +548 -0
  47. package/src/host/update.js +163 -0
  48. package/src/mcp/server.js +261 -0
  49. package/src/mcp/store-facade.js +43 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,321 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the project follows
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html) from 1.0.0.
6
+
7
+ ## [2.0.1] — 2026-11-14
8
+
9
+ ### Changed
10
+
11
+ - **Publishing now uses Trusted Publishing (OIDC) only.** `setup-node` no longer
12
+ receives `registry-url` and the publish step no longer reads `NODE_AUTH_TOKEN`:
13
+ the collaboration center's npm runbook records both as `ENEEDAUTH` traps, and
14
+ with a Trusted Publisher bound, the OIDC identity token is the credential. The
15
+ repository therefore holds no npm secret.
16
+ - `release.yml` gained an `npm >= 11.5` check, because npm 10 never performs the
17
+ OIDC exchange.
18
+
19
+ ### Note
20
+
21
+ `2.0.0` was the bootstrap publish (npm does not allow a first publish over OIDC,
22
+ [npm/cli#8544](https://github.com/npm/cli/issues/8544)), so it carries no
23
+ attestation. **2.0.1 is the first version with a provenance attestation.**
24
+
25
+ ## [2.0.0] — 2026-11-14
26
+
27
+ ### Added
28
+
29
+ - **`loci doctor` now looks for near-duplicates.** Two entries that say the same
30
+ thing split the ranking, each take an index line, and force a correction to be
31
+ applied twice. The check reports groups with their ids and a similarity score, and
32
+ leaves the merge to a person (the store can already do it: `loci_write op:'merge'`).
33
+
34
+ The metric is `max(Jaccard, containment)` over character bigrams. Jaccard alone
35
+ scores a restated Chinese title at 0.55 — under any useful threshold — because the
36
+ two titles share most of the *shorter* one rather than most of the union. The scan
37
+ is bounded (verified heads only, ≤2000 entries, candidate pairs from shared tags
38
+ and title windows, report capped at 5 groups).
39
+
40
+ - `test/unit/duplicates.test.mjs`: the metric, the verified-only rule, the clean
41
+ store, the caps, and the doctor row.
42
+
43
+ ### Changed
44
+
45
+ - `publishConfig.access: "public"` is set in the manifest, so a direct `npm publish`
46
+ cannot accidentally create a private package.
47
+
48
+ ### Note
49
+
50
+ This is the last item from the audit's "ten improvements" list: rows 1–3 and 5–10
51
+ are done, and row 2's remaining gap (duplicate detection) closed here. The version
52
+ jumps to 2.0.0 because the feature set that list described is complete, not because
53
+ anything in the on-disk format broke — schemas and the tool surface are unchanged.
54
+
55
+ ## [1.9.0] — 2026-11-14
56
+
57
+ ### Added
58
+
59
+ - **Candidate-only background extraction** (`extractMode: 'on'`, **off by default**).
60
+ The deterministic capture layer only records statements that announce themselves
61
+ ("always", "never", "we agreed"). A preference stated in passing — "honestly I'd
62
+ rather see the diff first" — carries no such marker and was lost. With the feature
63
+ on, the recent conversation is sent to the configured model at turn end and the
64
+ answers land as **candidates** for review.
65
+
66
+ Four rules keep it from becoming the feature a user turns off:
67
+ - off unless `extractMode: 'on'`, and inert when the profile has no `llm` route
68
+ (reports `no-model`, writes nothing);
69
+ - candidates only — an extracted preference can never reach the prompt by itself;
70
+ - budgeted: `extractMaxCallsPerHour` (6), `extractMaxCandidates` (3),
71
+ `extractTimeoutMs` (20 s), and a 12 KB input cap. Exhausting the budget is
72
+ silent, not an error;
73
+ - it can always fail: timeout, provider error, unparseable answer or an invented
74
+ quote all return `{written: 0, reason}` on its own timer, never touching the
75
+ turn. A quote that is not in the transcript is dropped before storage.
76
+
77
+ - `test/unit/extractor.test.mjs` covers the switch, the missing route, the budget,
78
+ the timeout, invented quotes and the unusable-answer paths.
79
+
80
+ ### Unverified
81
+
82
+ - The `ctx.llm` adapter is written against the Host's `llm.stream(request)` call and
83
+ collects text from whatever frame shape the provider emits. **It has not been run
84
+ against a live provider**; the unit tests use a fake completer and a fake stream.
85
+ If a provider's chunk shape is not one of the recognised forms, the extractor
86
+ records `model-error` and stays out of the way — it does not write junk.
87
+
88
+ ## [1.8.0] — 2026-11-14
89
+
90
+ ### Added
91
+
92
+ - **Import bridges** for the memory plugins a user is likely leaving
93
+ (`dsh-loci import --from auto-memory|claude`, `POST /api/loci/import-peer`,
94
+ `--dry-run` to preview):
95
+ - **dsh-auto-memory**: reads one Markdown file per memory with its front matter
96
+ (`name/title/type/disabled/evidence`). `_user/` becomes global scope, each
97
+ `--<project>--/` directory keeps its own scope key, `type: feedback` maps to
98
+ `procedural`, and disabled or already-superseded entries are skipped.
99
+ - **Claude Code memory**: reads `MEMORY.md`'s links plus every topic file that
100
+ is not in the index, so a hand-added file is not silently lost.
101
+ - everything lands as a **candidate**: another plugin's trust level is not
102
+ evidence here, and the source file is recorded as `sourceRef`.
103
+ - format details were taken from the producer's own serializer, not guessed;
104
+ `@tr1v3r/dsh-ltm` is deliberately not read (private SQLite schema) and the
105
+ operations guide says why.
106
+
107
+ ### Fixed
108
+
109
+ - A **byte-order mark** in front matter silently disabled parsing: the whole file
110
+ became body text and the entry imported with a filename-derived title. A BOM is
111
+ now stripped before the fence is matched, with a regression test.
112
+
113
+ ## [1.7.0] — 2026-11-14
114
+
115
+ ### Added
116
+
117
+ - **`sourceRef`**: a fact can cite where it lives (`src/host/store.js:120`).
118
+ - accepted by `loci_write`, stored with the evidence (the quote and the citation
119
+ are both provenance), returned by `loci_get` as a `source:` line, shown under
120
+ **Source** in the panel detail, and written to the export front matter so a
121
+ round trip keeps it;
122
+ - flattened and capped at 160 characters like every other free-text field;
123
+ - a citation that trips the secret scanner is dropped rather than stored;
124
+ - it never raises trust: a model-written entry with a citation is still a
125
+ candidate, because knowing where something is written down does not make it
126
+ true.
127
+
128
+ ## [1.6.0] — 2026-11-14
129
+
130
+ ### Added
131
+
132
+ - **`merge`** (`loci_write` with `op:'merge'`, `store.merge()`, `POST /api/loci/merge`).
133
+ Two entries that say the same thing in different words split the ranking, each
134
+ take an index line, and force a correction to be applied twice. Write-time dedupe
135
+ only catches byte-identical or same-title writes, so the realistic case — written
136
+ months apart, different wording — now has an explicit operation:
137
+ - the sources are **superseded, never deleted**: their history stays readable and
138
+ `loci_get` keeps working;
139
+ - tags and entities are unioned, so the merged entry keeps both entries'
140
+ searchability;
141
+ - an optional `expectedRevisions` list aborts the whole merge on a stale
142
+ expectation, before anything is written;
143
+ - a merge that changes the text re-opens verification (the target lands as a
144
+ candidate), because trust is bound to the words that were confirmed;
145
+ - one `merge` audit line names both sides.
146
+
147
+ ## [1.5.0] — 2026-11-14
148
+
149
+ ### Fixed
150
+
151
+ - **`--json doctor` printed the help text and exited 2.** Flags are now
152
+ position-independent: `--json doctor` and `doctor --json` are the same thing.
153
+ - **`snapshots` was unreachable** — its block sat after the switch, so it never ran.
154
+ It is a real command now, alongside a new `snapshot [--label <name>]` that writes
155
+ one consistent `VACUUM INTO` file under `backups/`.
156
+ - **`stats` reported the current directory's scope only.** Running it from another
157
+ directory showed "0 entries" for a store with entries in it. Diagnostics default
158
+ to the whole store; `--session` narrows to the current scope set.
159
+ - **The audit log accepted arbitrary prose** in a delete `reason`. It is now capped
160
+ and scanned by the same secret detector as memory bodies.
161
+ - **`assertInside` was lexical**: a symlink inside the data directory passed the
162
+ prefix check and then wrote wherever the link pointed. It now also resolves the
163
+ deepest existing part of the path and requires that to stay inside, with a test
164
+ that skips on a host that cannot create symlinks.
165
+
166
+ ### Changed
167
+
168
+ - CI and the release workflow install with **`npm ci`** from a committed
169
+ `package-lock.json`.
170
+ - The release workflow refuses a tag whose commit is not an ancestor of `main`,
171
+ publishes prereleases to the **`next`** dist-tag, and runs the coverage gate
172
+ before publishing.
173
+ - The coverage gate now fails on **lines (<80), branches (<55) and functions
174
+ (<70)**, and prints all three; before, only lines were checked while branches sat
175
+ at 62%.
176
+ - CI installs the packed tarball and runs its `bin`, so a broken `files` list or
177
+ shebang fails the build instead of a user's terminal.
178
+ - `docs/OPERATIONS.md` shows the exact way to invoke the CLI from an installed
179
+ profile (`node <profile>/node_modules/dsh-loci/scripts/cli.mjs`), documents the
180
+ `/loci` slash commands, and replaces the "no snapshot command" note.
181
+
182
+ ## [1.4.0] — 2026-11-14
183
+
184
+ ### Fixed
185
+
186
+ - **`export` then `import` imported nothing.** The exporter wrote entries under
187
+ `<root>/markdown/<scope>/<kind>/` and a one-line summary under
188
+ `<root>/exports/`; the importer read `exports`. The default is now the tree that
189
+ holds the documents, and the original scope and scope key are restored from the
190
+ front matter instead of forcing every import into `workspace:imported`.
191
+ - The CLI granted itself `allowHardDelete`; it now honours the config, with
192
+ `--allow-hard-delete` as the explicit opt-in for one invocation.
193
+ - `cli.mjs` read the version from four hardcoded strings; it now reads
194
+ `package.json`.
195
+
196
+ ### Added
197
+
198
+ - `store.forgetAll()`: soft (or hard, with `allowHardDelete`) deletion of one scope
199
+ or the whole store, gated by an echoed confirmation phrase, one audit line per
200
+ entry. `POST /api/loci/forget-all` exposes it, and **Settings → Delete
201
+ everything** asks for the phrase before running.
202
+ - `test/unit/export.test.mjs` (round trip, scope restore, path guard) and
203
+ `test/unit/forget-all.test.mjs` (confirmation, hard-delete gate, scope limit).
204
+
205
+ ## [1.3.0] — 2026-11-14
206
+
207
+ ### Changed
208
+
209
+ - **A captured statement verifies itself.** With capture on by default, every
210
+ durable-sounding sentence the user said landed in the review queue and was never
211
+ injected — while the same words, written by the model with a quote, went straight
212
+ through. A captured statement now passes the same quote gate as a model-supplied
213
+ quote: if its sentence is still in the session buffer it is verified, otherwise it
214
+ stays a candidate. Imports and session summaries never verify themselves.
215
+ - `autoVerifyCaptured` (default `true`) keeps the old behaviour for a cautious user.
216
+ - The review queue lists only the kinds that could be injected, so past-session
217
+ summaries stop pretending to be pending decisions.
218
+
219
+ ### Added
220
+
221
+ - Reject, verify-all, and a visible error line in the review queue; a failed click
222
+ now says so instead of disappearing into `console.warn`.
223
+
224
+ ## [1.2.0] — 2026-11-14
225
+
226
+ ### Changed
227
+
228
+ - **The sidebar card is a page of notes, not a dashboard.** Counters ("4 today",
229
+ "4 this week") are gone. The card now shows `更新于 <time>` over one short
230
+ paragraph made of the few things the agent currently understands in this
231
+ project — a consensus, a lesson, a fact — so reading it answers "did it
232
+ actually take that in?" instead of "did the number go up?".
233
+ Ordering is by when an entry last *became* trustworthy (`verified_at`, falling
234
+ back to the last edit), so confirming an old entry today puts it at the top.
235
+ - The card is scoped to the current project's read set, not to every scope in the
236
+ store: it answers "what does this conversation's agent know".
237
+ - Pending entries are mentioned as one line of text ("still need your review"),
238
+ never as a badge count.
239
+
240
+ ### Added
241
+
242
+ - `buildKnowledge` + `GET /api/loci/knowledge`: verified entries as notes, with a
243
+ first-sentence `gist` that is withheld when it merely restates the title.
244
+ - `test/unit/knowledge.test.mjs`: notes exclude candidates, respect scope, carry
245
+ the moment they became true, and re-order on a fresh confirmation.
246
+
247
+ ## [1.1.0] — 2026-11-13
248
+
249
+ ### Added
250
+
251
+ - **Sidebar mirror** (`sidebar.footer.action`): a flat card beside Settings that
252
+ shows what memory changed today and in the last seven days, the last three
253
+ changes as one-line entries, pending-review count, the installed version in small
254
+ text, and an explicit "check for updates" that is the only path that touches the
255
+ network.
256
+ - **Feed tab** in the panel: every add, revision, verification, pin, deletion,
257
+ restore, import and maintenance pass, newest first, with the entry it touched.
258
+ - **`/api/loci/activity`**, **`/api/loci/update`**, **`/api/loci/version`** — the
259
+ owner-only endpoints behind those two surfaces; the activity feed carries ids,
260
+ titles and counts, never bodies.
261
+ - **Update checker** (`src/host/update.js`): six-hour cache, 4-second timeout, and
262
+ "unavailable" on any failure instead of an error state. It never installs
263
+ anything; upgrading stays a decision made in the plugin manager.
264
+
265
+ ### Changed
266
+
267
+ - The panel opens on the **Feed** tab (was Index), because "what changed" is the
268
+ question a user actually arrives with.
269
+ - Version text follows the ecosystem convention: `v1.1.0` at 10px in the card,
270
+ and a quiet version line in the Stats tab.
271
+ - Removed the `build:client` script: the client half is hand-authored
272
+ module-loader JavaScript, so there was nothing to build and the entry was broken.
273
+
274
+ ### Fixed
275
+
276
+ - (pending) findings from the four independent production-readiness audits.
277
+
278
+ ## [1.0.0] — 2026-11-13
279
+
280
+ First release.
281
+
282
+ ### Added
283
+
284
+ - **Layered memory** with five kinds (`rule`, `semantic`, `procedural`, `episodic`,
285
+ `working`), where `working` is never injected.
286
+ - **Bi-temporal facts**: `valid_from` / `valid_to` for event time and `recorded_at`
287
+ for transaction time; edits append a revision and close the previous one instead
288
+ of overwriting it. `loci_search(asOf)` answers "what was true then".
289
+ - **One injected section** (`loci:index`, order 4000, `interpolate:false`),
290
+ byte-budgeted (default 3,584 B), stable across read-only turns, empty on an empty
291
+ store.
292
+ - **Five tools**: `loci_write`, `loci_search`, `loci_get`, `loci_forget`,
293
+ `loci_stats`. Search returns `total`, `hasMore` and a cursor; pagination reaches
294
+ everything it counts.
295
+ - **Hybrid retrieval**: CJK bigram full text + entity expansion + optional vectors,
296
+ ranked in process with a coverage admission rule (no `bm25()` scan, no prefix
297
+ query). Measured p95 12.5 ms at 10,000 entries.
298
+ - **Trust gate**: only `verified` entries are injected; status is derived from a
299
+ verbatim quote found in the user's live messages, never declared by the model.
300
+ - **Scope isolation**: `session` / `workspace` / `project` / `global`, explicit keys,
301
+ `workspace` default, `global` only on request.
302
+ - **Deterministic capture**: durable statements become candidates after a completed
303
+ turn; a session summary is written when a root agent is disposed. No background
304
+ model call.
305
+ - **Safety**: `{{}}` neutralization, markup escaping, instruction-shape quarantine,
306
+ credential redaction, fenced injection, audit without content.
307
+ - **Native UI**: a settings page and a four-tab sidebar panel (Index, Search,
308
+ Review, Stats) built on the Host's slots and theme tokens, with `zh`/`en` strings.
309
+ - **Owner-only JSON API** for the panel, over the Host's exact-fetch routes.
310
+ - **Optional MCP facade** (`src/mcp/server.js`), read-only by default.
311
+ - SQLite store with WAL, busy timeout, snapshot migration and rollback, Markdown
312
+ export/import, `dsh-loci` CLI, `/loci` command.
313
+
314
+ ### Known limits
315
+
316
+ - No LLM consolidation (v1.1 candidate).
317
+ - Deterministic capture only records explicitly phrased durable statements.
318
+ - Vector search is brute-force cosine over stored embeddings.
319
+ - Plaintext at rest; no encryption beyond the OS.
320
+
321
+ [1.0.0]: https://github.com/Awenforever/dsh-loci/releases/tag/v1.0.0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Awenforever
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,31 @@
1
+ dsh-loci
2
+ Copyright (c) 2026 Awenforever
3
+ SPDX-License-Identifier: MIT
4
+
5
+ Third-party material
6
+ --------------------
7
+
8
+ No third-party source code is vendored in this package. The design cites the
9
+ following work for specific mechanisms; see docs/RESEARCH.md for the exact
10
+ section and URL behind each claim.
11
+
12
+ - Graphiti / Zep — bi-temporal edge model and edge invalidation
13
+ (arXiv 2501.13956). Mechanism reimplemented from the published description.
14
+ - Mem0 — multi-signal retrieval with BM25 as a boost signal, and write-time
15
+ temporal metadata (arXiv 2504.19413; docs.mem0.ai). Mechanism reimplemented.
16
+ - Cognee — current / superseded / conflicting fact marks and an all-local
17
+ default storage stack (docs.cognee.ai). Mechanism reimplemented.
18
+ - MemOS — layered memory vocabulary and the local RRF/MMR plugin as an
19
+ existence proof (arXiv 2505.22101, arXiv 2507.03724). Vocabulary only.
20
+ - OpenViking — L0/L1 context layer sizes and session-start budget discipline
21
+ (docs.openviking.ai). Budgets reimplemented.
22
+ - ReMe — guidance-only injection into DSH and read-only on-demand retrieval
23
+ (agentscope-ai/ReMe, reme.agentscope.io). Product-level pattern, no code.
24
+ - memsearch — Markdown-as-truth with a rebuildable shadow index, and the
25
+ low-risk-only project configuration rule (zilliztech/memsearch). Pattern only.
26
+ - Letta / MemGPT — core vs archival memory vocabulary (docs.letta.com).
27
+ - Claude Code, Cursor, ChatGPT — product documentation reviewed for what to
28
+ adopt and what not to copy.
29
+
30
+ Runtime dependencies: `@deepseek-ai/schemastery` (MIT), used for the
31
+ plugin configuration schema. It is not modified.
package/README.md CHANGED
@@ -1,3 +1,144 @@
1
- # Temporary Holding Version
1
+ # dsh-loci · Loci 记忆宫殿
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **A long-term memory for DeepSeek Harness where the prompt only ever sees the palace's index — every room is one lookup away.**
4
+
5
+ [中文](README.zh.md) · [Design](docs/DESIGN.md) · [Research](docs/RESEARCH.md) · [Operations](docs/OPERATIONS.md) · [Security](docs/SECURITY.md) · [Acceptance](docs/ACCEPTANCE.md)
6
+
7
+ ```
8
+ system prompt: ~1.3k tokens, stable, one line per fact
9
+ user asks → loci_search "发布顺序" → total=3, one call
10
+ needs detail → loci_get m_01J… → full text + history + evidence
11
+ ```
12
+
13
+ ## Why it exists
14
+
15
+ Every memory plugin tested in this workspace failed one of two ways: it cost a
16
+ fixed toll on every request (mnemon: +4.8 KB of system prompt, 16 tools), or it
17
+ grew without bound (ledger-memory: 7 KB → 27 KB per turn). The ones that stayed
18
+ cheap truncated recall silently (mnemon returned 4 of 20 requested rows;
19
+ auto-memory's index holds about 27 Chinese entries).
20
+
21
+ Loci is built around one rule: **the prompt holds an index, not a memory.**
22
+
23
+ - Empty store → **0 bytes injected**. No header, no advice, nothing.
24
+ - 100 entries → still one stable section, capped at a byte budget with an honest
25
+ "… +N more" footer.
26
+ - A read-only turn → the section is byte-identical, so the prefix cache survives.
27
+ - A broad question → `loci_search` returns `total` and a cursor, so "list them
28
+ all" is answerable.
29
+
30
+ ## What it remembers
31
+
32
+ ![The sidebar note and the memory panel](marketplace/screenshots/sidebar-note.png)
33
+
34
+ | Layer | Kind | Injected? | Example |
35
+ |---|---|---|---|
36
+ | rules | `rule` | yes, first | "删除前必须做路径断言" |
37
+ | semantic | `semantic` | yes, index line | "生产机是 prod-01" |
38
+ | procedural | `procedural` | yes, index line | "发布顺序 staging → canary → 全量" |
39
+ | episodic | `episodic` | yes, index line | "2026-11-13 会话摘要" |
40
+ | working | `working` | no | scratch facts for this session only |
41
+
42
+ Only `verified` entries are injected. Anything written without a verbatim quote
43
+ from the conversation is stored as a **candidate** and waits in the panel for
44
+ one click.
45
+
46
+ ## Install
47
+
48
+ Inside the Harness profile that should have memory:
49
+
50
+ ```text
51
+ Settings → Plugins → install from a directory or the market listing, or
52
+ dsh plugin --profile <name> add dsh-loci
53
+ ```
54
+
55
+ The bundle adds one Host row (`dsh-loci`) and one Client half. Restart the
56
+ Harness, then open **Settings → Loci** or the **Memory** panel in the sidebar.
57
+
58
+ Requirements: DSH `>=0.2.2-rc.1 <0.3.0-0`, Node `>=24.9.0` (the Harness ships it).
59
+ No service and no native module.
60
+
61
+ The package also ships a small CLI (`snapshot`, `export`, `import`, `doctor`,
62
+ `stats`, `review`, `purge`, `migrate`, `rollback`). It is not on your `PATH` after a
63
+ plugin install — call it through the profile:
64
+
65
+ ```text
66
+ node "$HOME/.dsh/profiles/<profile>/node_modules/dsh-loci/scripts/cli.mjs" doctor --json
67
+ ```
68
+
69
+ Inside a chat there are zero-token slash commands too: `/loci`, `/loci review`,
70
+ `/loci doctor`, `/loci export`.
71
+
72
+ ## The five tools
73
+
74
+ | Tool | Use |
75
+ |---|---|
76
+ | `loci_write` | Save, revise, or merge facts. `evidenceQuote` must be verbatim; without it the entry is a candidate. `op:'merge'` folds duplicate ids into one — the sources are superseded, never deleted. |
77
+ | `loci_search` | Hybrid search. Returns `total`, `hasMore`, `cursor`; `detail` is snippet/overview/full. Omit `query` to list the newest entries. |
78
+ | `loci_get` | Full text plus revision history and the evidence quote. |
79
+ | `loci_forget` | Soft-delete (history kept) or restore. |
80
+ | `loci_stats` | Counts, budget use, channel health, database location. |
81
+
82
+ Tool names are prefixed `loci_` on purpose: `ctx.tools.register` throws on a
83
+ duplicate name, so two memory plugins both claiming `memory_search` would break
84
+ one of them.
85
+
86
+ ## Where you see it
87
+
88
+ - **Sidebar note** — a small card beside Settings, like the quota and advisor
89
+ cards, that reads like a page of notes rather than a dashboard: `更新于 <time>`
90
+ over one short paragraph of what the agent currently understands in this
91
+ project (a consensus, a lesson, a fact), plus one quiet line if something is
92
+ waiting for your review. It answers "did it take that in?" — no counters.
93
+ The version sits in small text with an explicit "check for updates", the only
94
+ path that touches the network.
95
+ - **Memory panel** — the full view, opening on a **Feed** of every add, revision,
96
+ verification, pin, deletion and import, newest first.
97
+ - **Settings → Loci** — the budget, capture mode, default scope, secret policy
98
+ and embedding channel.
99
+
100
+ ## Configuration
101
+
102
+ Everything lives in the plugin's Loader row; Loci never reads a project file.
103
+ The settings page writes the fields marked *live* without a restart.
104
+
105
+ | Key | Default | Meaning |
106
+ |---|---|---|
107
+ | `sectionMaxBytes` | `3584` | hard byte budget for the injected section (max 16384) |
108
+ | `rulesMaxBytes` | `1024` | ceiling for the rules block inside that budget |
109
+ | `defaultScope` | `workspace` | scope a write lands in when it does not say |
110
+ | `captureMode` | `rules` | deterministic capture of explicit durable statements |
111
+ | `extractMode` | `off` | `on` asks the configured model, at turn end, what is worth remembering — always as candidates, always budgeted |
112
+ | `recallAssist` | `off` | one bounded first-turn recall block per session |
113
+ | `maintenanceMode` | `sweep` | TTL, index repair, and embedding backfill every 6h |
114
+ | `secretPolicy` | `redact` | redact high-confidence secrets, or refuse the write |
115
+ | `vectorMode` | `off` | optional HTTP embedding channel; lexical always works |
116
+ | `allowHardDelete` | `false` | enable `purge` in the CLI/panel |
117
+
118
+ ## What it will not do
119
+
120
+ - No model call in any automatic path in v1.0 — capture and summaries are
121
+ deterministic, so the cost is visible and zero.
122
+ - No cloud, no telemetry, no network unless you configure `vectorMode: 'http'`.
123
+ - No cross-project leakage: reads use an explicit scope-key set, never a wildcard.
124
+ - No physical deletion by default; `purge` needs a config switch, a confirmation,
125
+ and leaves an audit line.
126
+
127
+ ## Data
128
+
129
+ `$DSH_HOME/memory/loci/` only:
130
+
131
+ ```
132
+ memory.db SQLite (WAL) — the transactional truth
133
+ audit.jsonl append-only: inject / recall / write / verify / delete
134
+ backups/ pre-migration and manual snapshots (VACUUM INTO)
135
+ markdown/ human-readable projection
136
+ exports/ export bundles and import drops
137
+ ```
138
+
139
+ Uninstalling the plugin leaves the directory untouched, by design. Delete it
140
+ yourself when you mean it.
141
+
142
+ ## License
143
+
144
+ MIT. See [NOTICE](NOTICE) for the research lineage of each mechanism.
package/README.zh.md ADDED
@@ -0,0 +1,111 @@
1
+ # dsh-loci · Loci 记忆宫殿
2
+
3
+ **给 DeepSeek Harness 的长期记忆:提示词里只放「宫殿的索引」,每个房间按需推门。**
4
+
5
+ [English](README.md) · [设计](docs/DESIGN.md) · [调研](docs/RESEARCH.md) · [运维](docs/OPERATIONS.md) · [安全](docs/SECURITY.md) · [验收](docs/ACCEPTANCE.md)
6
+
7
+ ```
8
+ 系统提示: 约 1.3k tokens,稳定,一条记忆一行
9
+ 你问: → loci_search "发布顺序" → total=3,一次调用
10
+ 要细节: → loci_get m_01J… → 全文 + 历史 + 逐字证据
11
+ ```
12
+
13
+ ## 为什么需要它
14
+
15
+ 本机实测过的记忆插件,要么在每次请求上交固定「税」(mnemon:系统提示 +4.8KB、16 个工具),要么无界增长(ledger-memory:每轮 7KB 涨到 27KB)。便宜的又都在静默截断召回(mnemon 要 20 条只回 4 条;auto-memory 的索引约 27 条中文就触顶)。
16
+
17
+ Loci 只有一条规则:**提示词里放索引,不放记忆。**
18
+
19
+ - 空库 → **注入 0 字节**,没有标题、没有说明、什么都没有。
20
+ - 100 条 → 仍是同一段,硬字节预算,超出时明确写出「… 还有 N 条」。
21
+ - 只读回合 → 段内容逐字节不变,前缀缓存得以保留。
22
+ - 宽泛提问 → `loci_search` 返回 `total` 和 cursor,「列出全部」真的能列完。
23
+
24
+ ## 它记什么
25
+
26
+ | 层 | kind | 是否注入 | 例子 |
27
+ |---|---|---|---|
28
+ | 硬约束 | `rule` | 是,排最前 | 「删除前必须做路径断言」 |
29
+ | 事实 | `semantic` | 是,索引一行 | 「生产机是 prod-01」 |
30
+ | 流程 | `procedural` | 是,索引一行 | 「发布顺序 staging → canary → 全量」 |
31
+ | 经历 | `episodic` | 是,索引一行 | 「2026-11-13 会话摘要」 |
32
+ | 工作记忆 | `working` | 否 | 只在本次会话有效的草稿事实 |
33
+
34
+ 只有 `verified` 会被注入。写入时若没有来自对话的逐字引文,条目落为 **candidate**,在面板里等你点一下。
35
+
36
+ ## 安装
37
+
38
+ 在需要有记忆的 profile 里:
39
+
40
+ ```text
41
+ 设置 → 插件 → 从目录或市场条目安装,或
42
+ dsh plugin --profile <名字> add dsh-loci
43
+ ```
44
+
45
+ bundle 会插入一行 Host 插件(`dsh-loci`)和一个 Client 半边。重启 Harness,然后打开 **设置 → Loci** 或侧栏的 **记忆** 面板。
46
+
47
+ 侧栏底部还有一张**便签**(和额度、advisor 卡片并排),它不是看板:顶上 `更新于 <时间>`,下面一小段话,是它此刻真正理解的东西——达成的共识、踩过的经验、事实。没有数字,看到的就是"它有没有把这件事接住"。
48
+
49
+ 要求:DSH `>=0.2.2-rc.1 <0.3.0-0`,Node `>=24.9.0`(Harness 自带)。无服务、无原生模块。
50
+
51
+ 包里还带一个小 CLI(`snapshot`/`export`/`import`/`doctor`/`stats`/`review`/`purge`/`migrate`/`rollback`)。插件安装后它不在 PATH 上,按 profile 调用:
52
+
53
+ ```text
54
+ node "$HOME/.dsh/profiles/<profile>/node_modules/dsh-loci/scripts/cli.mjs" doctor --json
55
+ ```
56
+
57
+ 会话里还有零 token 的斜杠命令:`/loci`、`/loci review`、`/loci doctor`、`/loci export`。
58
+
59
+ ## 五个工具
60
+
61
+ | 工具 | 用途 |
62
+ |---|---|
63
+ | `loci_write` | 写入、修订或合并事实。`evidenceQuote` 必须逐字;没有就落为候选。`op:'merge'` 把重复条目折进一条——源条目被 supersede,不删除。 |
64
+ | `loci_search` | 混合检索,返回 `total`、`hasMore`、`cursor`;`detail` 可选 snippet/overview/full。 |
65
+ | `loci_get` | 全文 + 修订历史 + 证据引文。 |
66
+ | `loci_forget` | 软删除(历史保留)或恢复。 |
67
+ | `loci_stats` | 计数、预算占用、通道健康、库位置。 |
68
+
69
+ 工具名用 `loci_` 前缀是有意的:`ctx.tools.register` 同名会抛错,两个记忆插件都叫 `memory_search` 必然坏一个。
70
+
71
+ ## 配置
72
+
73
+ 配置只存在于插件自己的 Loader 行;Loci **不读项目内配置文件**。设置页里标注「即时」的字段无需重启。
74
+
75
+ | 键 | 默认 | 含义 |
76
+ |---|---|---|
77
+ | `sectionMaxBytes` | `3584` | 注入段的硬字节预算(上限 16384) |
78
+ | `rulesMaxBytes` | `1024` | 段内规则块的字节上限 |
79
+ | `defaultScope` | `workspace` | 未显式指定时的写入作用域 |
80
+ | `captureMode` | `rules` | 规则型捕获用户的明确约定 |
81
+ | `extractMode` | `off` | 设为 `on` 时,在回合结束让模型提一遍「这轮有什么值得记」——一律落为候选,且有预算上限 |
82
+ | `recallAssist` | `off` | 每会话一次、有上限的首轮辅助召回块 |
83
+ | `maintenanceMode` | `sweep` | 每 6 小时做 TTL、索引修复、向量补齐 |
84
+ | `secretPolicy` | `redact` | 高置信密钥脱敏,或直接拒绝写入 |
85
+ | `vectorMode` | `off` | 可选 HTTP 向量通道;词法检索始终可用 |
86
+ | `allowHardDelete` | `false` | 是否允许 CLI/面板里的 `purge` |
87
+
88
+ ## 它明确不做
89
+
90
+ - v1.0 的自动路径里没有模型调用 —— 捕获与摘要是确定性的,成本可见且为零。
91
+ - 无云、无遥测;除非你配置 `vectorMode: 'http'`,否则不出网。
92
+ - 不跨项目串味:读取用显式 scope-key 集合,从不通配。
93
+ - 默认不物理删除;`purge` 需要配置开关 + 确认 + 留审计。
94
+
95
+ ## 数据位置
96
+
97
+ 只在 `$DSH_HOME/memory/loci/`:
98
+
99
+ ```
100
+ memory.db SQLite(WAL),事务真源
101
+ audit.jsonl 追加写:inject / recall / write / verify / delete
102
+ backups/ 迁移前与手动快照(VACUUM INTO)
103
+ markdown/ 人类可读投影
104
+ exports/ 导出包与导入投递目录
105
+ ```
106
+
107
+ 卸载插件不会删除这个目录(有意为之);真要删请自己动手。
108
+
109
+ ## 许可
110
+
111
+ MIT。每个机制的研究出处见 [NOTICE](NOTICE)。