open-memex 0.6.2 → 0.7.0-alpha.11

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 (68) hide show
  1. package/AGENTS.md +25 -7
  2. package/CHANGELOG.md +103 -1
  3. package/README.md +54 -16
  4. package/README.zh-CN.md +30 -15
  5. package/dist/.build-stamp +1 -0
  6. package/dist/capture/keywords.js +41 -8
  7. package/dist/cli.js +259 -40
  8. package/dist/config.js +102 -18
  9. package/dist/distill-agents.js +9 -1
  10. package/dist/doctor.js +180 -79
  11. package/dist/export.js +12 -2
  12. package/dist/index.js +20 -112
  13. package/dist/init.js +103 -11
  14. package/dist/inventory-html.js +150 -0
  15. package/dist/inventory.js +176 -0
  16. package/dist/mcp.js +21 -5
  17. package/dist/opencode-v2.js +43 -0
  18. package/dist/paths.js +34 -2
  19. package/dist/plugin-core.js +138 -0
  20. package/dist/retrieve/display.js +161 -0
  21. package/dist/retrieve/inject.js +1 -1
  22. package/dist/retrieve/search.js +44 -5
  23. package/dist/review.js +9 -3
  24. package/dist/store/db.js +11 -9
  25. package/dist/store/lifecycle.js +20 -4
  26. package/dist/store/markdown.js +53 -7
  27. package/dist/store/migrate.js +14 -3
  28. package/dist/store/sync.js +5 -3
  29. package/dist/store/v2migrate.js +2 -2
  30. package/dist/submit.js +13 -5
  31. package/dist/tools/memory.js +28 -0
  32. package/dist/tools/ops.js +127 -16
  33. package/docs/CURATOR.md +7 -3
  34. package/docs/V2-DESIGN.md +335 -32
  35. package/docs/memory-visibility-design.md +130 -0
  36. package/package.json +2 -2
  37. package/scripts/build-stamp.mjs +63 -0
  38. package/scripts/smoke-mcp.ts +47 -1
  39. package/scripts/smoke-pure.ts +237 -3
  40. package/scripts/test-full.ts +362 -5
  41. package/skills/open-memex/SKILL.md +53 -1
  42. package/src/capture/keywords.ts +56 -6
  43. package/src/cli.ts +282 -39
  44. package/src/config.ts +104 -17
  45. package/src/distill-agents.ts +19 -0
  46. package/src/doctor.ts +181 -71
  47. package/src/export.ts +14 -2
  48. package/src/index.ts +25 -112
  49. package/src/init.ts +108 -11
  50. package/src/inventory-html.ts +191 -0
  51. package/src/inventory.ts +242 -0
  52. package/src/mcp.ts +21 -5
  53. package/src/opencode-v2.ts +119 -0
  54. package/src/paths.ts +35 -1
  55. package/src/plugin-core.ts +152 -0
  56. package/src/retrieve/display.ts +174 -0
  57. package/src/retrieve/inject.ts +1 -1
  58. package/src/retrieve/search.ts +63 -6
  59. package/src/review.ts +9 -2
  60. package/src/store/db.ts +11 -9
  61. package/src/store/lifecycle.ts +24 -3
  62. package/src/store/markdown.ts +60 -7
  63. package/src/store/migrate.ts +15 -2
  64. package/src/store/sync.ts +6 -3
  65. package/src/store/v2migrate.ts +2 -1
  66. package/src/submit.ts +12 -4
  67. package/src/tools/memory.ts +28 -0
  68. package/src/tools/ops.ts +147 -17
package/docs/V2-DESIGN.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # OpenMemex — Design Document (protocol v0.2)
2
2
 
3
- **Status:** FROZEN — protocol v0.2 (2026-09-26). Zero open questions.
3
+ **Status:** FROZEN — protocol v0.2 (2026-09-26). Decisions D1–D70 are settled; open questions are tracked at the end of this document.
4
4
  **Author:** Stone, with 小沐
5
5
  **Changelog vs v1:** incorporates round-3 review from Perplexity, Grok, Gemini, ChatGPT, DeepSeek.
6
6
  Key changes: Design Principles section; `role` separated from `type`; two iron rules;
@@ -39,7 +39,7 @@ Decisions log; Prior Art; solo-dev adoption path.
39
39
  **OpenMemex is an open, local-first memory layer and interoperable protocol for AI coding agents.**
40
40
 
41
41
  - **Tagline:** *"Capture knowledge once, make it available to every AI agent."*
42
- - **Local-first, open source (MIT).** No cloud SaaS, no account, no mandatory network calls.
42
+ - **Local-first, open source (Apache-2.0).** No cloud SaaS, no account, no mandatory network calls.
43
43
  - **The pain it kills:** every developer's AI learns in isolation. Dev A spends three days debugging an
44
44
  environment quirk with AI help; dev B rediscovers it next week. Hard-won knowledge should flow
45
45
  **personal → team → organization** instead of evaporating with each session.
@@ -109,36 +109,35 @@ One memory = one file. `id` = filename = ULID. No other ID scheme.
109
109
  ```yaml
110
110
  id: 01K6AB3XZQ7WVD9J1M2N4P5Q6R7
111
111
  schema_version: 2
112
- revision: 1
113
112
  scope: project # personal | project | org (ownership: WHO owns it)
114
113
  # team, public → schema enum REJECTS writes (reserved)
114
+ scope_key: project__my-repo__9f2c1a4b8e3d # storage identity (repo-derived, or "personal")
115
115
  visibility: internal # private | internal | shared (read access: WHO may read)
116
+ project_name: my-repo
116
117
  type: decision # content kind: what the memory IS (§3.2)
117
118
  role: knowledge # knowledge | instruction — how it may be USED (§3.3)
118
- importance: normal # low | normal | high (ranking hint; replaces 1–10 priority)
119
- instruction_state: null # draft | approved | revoked — only when role=instruction
120
- trust_level: reviewed # untrusted | reviewed | trusted
119
+ importance: normal # low | normal | high (ranking hint; replaces 1–10 priority)
121
120
  status: active # active | superseded | deprecated | retracted | archived
122
- review_state: approved # draft | proposed | approved | rejected | published (promotion)
121
+ tags: [auth, oauth]
122
+ aliases: ["oauth login flow"] # optional: capture-time phrasing variants (D61)
123
123
  source: user # provenance: user | tool | keyword | inference | import
124
- confidence: high # high | medium | low — describes the CONTENT, not the author
125
- created_by: github:stoneskin # namespaced identity: local:… | github:… | oidc:…:…
126
- promoted_by: null
127
- approved_by: null
128
- via: cli # mcp | copilot | opencode | cli | import (cross-agent provenance)
129
124
  created_at: "2026-09-26T12:00:00Z" # RFC 3339, never bare epoch
130
125
  updated_at: "2026-09-26T12:00:00Z"
131
- supersedes: 01K69Z… # on the NEW memory → points BACK to what it replaces
126
+ supersedes: null # on the NEW memory → points BACK to what it replaces
132
127
  superseded_by: null # on the OLD memory → points FORWARD to its replacement
133
- expires_at: null
134
- repo_id: git-origin-sha256:9f2c… # namespace: cross-machine project identity
135
- language: zh # detected or declared; drives tokenizer choice
136
- tags: [auth, oauth]
137
- paths: ["src/auth/**"] # repo-relative globs; mismatch ⇒ downrank, not filter
138
- related: [01K6AC…] # light links between memories (no knowledge graph yet)
139
- canonical_ref: docs/adr-003.md # memory holds a SUMMARY; the doc is canonical
128
+ review_state: approved # draft | proposed | approved | rejected | published (promotion)
129
+ proposed_by: stoneskin
130
+ approved_by: stoneskin
131
+ derived_from: null # for propose-copies: the personal memory this came from
132
+ review_note: null
133
+ review_history: [] # append-only [{at, by, from, to, note}] audit trail
140
134
  ```
141
135
 
136
+ Field set as implemented (`src/store/markdown.ts`). Earlier drafts of this
137
+ example listed aspirational fields (`revision`, `trust_level`, `confidence`,
138
+ `via`, `expires_at`, `paths`, `related`, `canonical_ref`) that never shipped;
139
+ they are not part of protocol v0.2.
140
+
142
141
  ### 3.1 Type taxonomy (content kind)
143
142
 
144
143
  `fact` `preference` `decision` `constraint` `todo` `knowledge` `howto` `gotcha`
@@ -240,14 +239,17 @@ distills; explicit "save this note" may preserve long-form text.
240
239
  | Scope | Owner | Lives where | Synced? |
241
240
  |------------|-------|-------------|---------|
242
241
  | `personal` | you | this machine only | **never** |
243
- | `project` | repo collaborators | `<repo>/.open-memex/` | via git (opt-in per repo) |
242
+ | `project` | repo collaborators | `<repo>/.ai/open-memex/` | via git (opt-in per repo) |
244
243
  | `org` | org members | dedicated org memory repo | via git |
245
244
 
246
245
  Legal combinations: `personal/*` (any visibility, stays local); `project/{internal,shared}`;
247
- `org/{internal,shared}`. `visibility: private` inside a shared scope is **physically isolated**:
248
- private memories are written to a local-only cache directory and never land under `.open-memex/`
249
- — never relying on `.gitignore` or filename conventions alone. (Pre-commit scanning is defense in
250
- depth, not the boundary.)
246
+ `org/{internal,shared}`. `visibility: private` inside a shared scope is a logical boundary
247
+ today, not a physical one: `export` excludes private memories by default (D40), and the
248
+ `personal` scope never leaves the machine — but a private file placed under
249
+ `<repo>/.ai/open-memex/` still travels with git, so review before you push. Physical
250
+ isolation of private memories into a local-only cache directory remains planned
251
+ (see docs/SCOPES.md); `.gitignore` and pre-commit scanning are defense in depth, not
252
+ the boundary.
251
253
 
252
254
  **Namespace:** `repo_id` (`git-origin-sha256:…`, falling back to a normalized cwd hash) identifies
253
255
  the same project across machines — carried over from v1's scope-key design.
@@ -567,8 +569,10 @@ requirement: personal data never touches third-party services). Benchmarks to tr
567
569
  - Times: epoch ms → RFC 3339. `priority: N` → `importance: low|normal|high` (1–3 low, 4–7 normal, 8–10 high).
568
570
  - `type: instruction` (if any v1 memory used it) → `type: <content-kind>` + `role: instruction`.
569
571
  - Index is discarded and rebuilt from markdown files. `open-memex migrate --dry-run` previews everything.
570
- - Legacy paths: v1 `.my-o-memory/` repo dirs and `~/.my-o-memory/` config are moved to `.open-memex/` /
571
- `~/.open-memex/` during migration (originals kept as backup until the user confirms).
572
+ - Legacy paths: v1 `.my-o-memory/` repo dirs and `~/.my-o-memory/` config are moved to the
573
+ v2 locations (in-repo `.ai/open-memex/`; data root per §4/paths — `OPEN_MEMEX_HOME`, else
574
+ `%APPDATA%\open-memex` or `$XDG_DATA_HOME/open-memex`) by `open-memex migrate --to-v2`,
575
+ with the pre-migration tree kept as a dated backup.
572
576
 
573
577
  ---
574
578
 
@@ -1210,13 +1214,312 @@ requirement: personal data never touches third-party services). Benchmarks to tr
1210
1214
  `@opencode-ai/plugin`, the exact D58 death. It checks only what can be
1211
1215
  attributed; per-project and MCP wiring are out of scope.
1212
1216
 
1213
- ## Open Questions
1217
+ - **D60** — one plugin serves both opencode generations (0.7.0-alpha.1).
1218
+ Opencode 2 changes the plugin contract end to end: config reads
1219
+ `plugins` (directories or `{package}` objects) instead of v1's
1220
+ `plugin` file entries, a v1-style file entry only earns a log warning
1221
+ ("configured plugin path must be a directory") and is silently skipped
1222
+ — the D58 death in a new shape; and the plugin value itself is a
1223
+ structurally decoded `{ id, setup(ctx) }` rather than the v1 server
1224
+ factory. A single `open-memex init --client opencode --global` now
1225
+ writes both keys (`plugin` file URL and `plugins` directory): each
1226
+ host loads from its own key — verified: opencode 1.18.34 ignores the
1227
+ `plugins` array with a config-normalization warning, and opencode 2
1228
+ warns per `plugin` file entry but loads the directory entry — so users on either generation get a
1229
+ working plugin from the same install. `src/index.ts` default-exports
1230
+ `{ id, setup, server }`: v2 calls `setup`, v1 calls `server`, the
1231
+ shared logic (bootstrap, keyword capture, one-time injection) lives in
1232
+ `src/plugin-core.ts`. Cost of the bridge on the v1 side: v1 hosts
1233
+ older than 1.18.29 only invoke function default exports and would
1234
+ skip this object; 1.18.29+ accepts it — the README states the new
1235
+ minimum. The v2 adapter (src/opencode-v2.ts) obeys D58's rule — no
1236
+ runtime `@opencode/plugin` import (a devDependency again); the
1237
+ context is duck-typed from the 2.0 surface, tool arg shapes compile
1238
+ from the same zod shapes via `z.toJSONSchema`, and hooks map
1239
+ one-to-one (session `prompt` → keyword capture, session `context` →
1240
+ first-turn injection). One registration subtlety cost real debugging
1241
+ time and is worth recording: on 2.0.1 a plugin tool added without
1242
+ options is exposed to the model only in code mode — callable through
1243
+ an `execute` meta-tool that runs model-written JS
1244
+ (`tools.memory_search(...)`) — and a model that instead calls the
1245
+ tool by name gets a host-side "Unknown tool" error, so whether the
1246
+ memory tools work at all depends on the model's calling style. Each
1247
+ tool therefore registers with `options: { codemode: false }`, which
1248
+ restores plain function calling (verified: direct calls execute
1249
+ first try). The D59 doctor check extends to both keys:
1250
+ v2 entries are checked for an existing, resolvable entry file and the
1251
+ closure scan now covers `@opencode/plugin` too, plus a generation
1252
+ advisory — when the installed host and the config point at different
1253
+ generations, doctor fails and names the fix (`init --force`; package-
1254
+ name entries installed via `opencode plugin add` are recorded but not
1255
+ path-checked). Field-verified on real hosts 1.18.34 and 2.0.1.
1256
+
1257
+ - **D61 — proactive capture cluster: propose posture, a shared
1258
+ memorability rubric, and capture-time aliasing (retrieval layer 3).**
1259
+ Builds on the "proactive capture" open question below and the
1260
+ CHECKPOINT_PROACTIVITY / MEMORY_VISIBILITY notes. Three pieces,
1261
+ shipped together because they are one user-visible behavior — the
1262
+ agent capturing well without being asked each time:
1263
+ (1) **Posture: propose, not auto-draft.** When the agent finishes a
1264
+ task the user would describe in one sentence, it proposes 1–3 short
1265
+ candidate memories in the conversation and saves only what the user
1266
+ approves (approved inferences are saved with `source: "inference"`,
1267
+ the §3.5 convention). The guidance is text, not mechanism — the same
1268
+ rubric written into the bundled skill, the AGENTS.md memory-hygiene
1269
+ footer, the MCP handshake instructions, the `memory_add` tool
1270
+ description, and a one-line summary in the injected context block —
1271
+ with throttling rules (task end / decision / gotcha moments only, no
1272
+ mid-task interruptions, never re-propose a declined candidate). The
1273
+ auto-draft posture (write first, surface later in bulk) is deferred
1274
+ until memory visibility ships: without an inventory the user can
1275
+ read, silent drafting reads as surveillance, so D61 deliberately
1276
+ does not build it.
1277
+ (2) **Rubric.** One memorability standard, shared by this live
1278
+ capture path and the future retrospective-distillation group:
1279
+ worth saving — decisions and their reasons, preferences,
1280
+ conventions, gotchas, approaches tried and abandoned, and
1281
+ `reference` memories pointing at authoritative docs; not worth
1282
+ saving — one-off task details and anything re-derivable from the
1283
+ code. Retrieval robustness layer 2 (agent-side query expansion:
1284
+ decompose into concepts, 2–3 phrasings each, cross-language, check
1285
+ the other scope) ships as guidance in the same texts, on top of the
1286
+ D56 server-side layer 1.
1287
+ (3) **Capture-time aliases (layer 3).** When a memory is saved, the
1288
+ agent attaches up to 4 aliases — alternate phrasings, synonyms,
1289
+ equivalents in the user's other language — stored in a new optional
1290
+ frontmatter field `aliases` (additive; files without it parse
1291
+ unchanged, `schema_version` stays 2) and indexed in their own FTS
1292
+ column (index schema 6→7, rebuilt from markdown automatically).
1293
+ Gated by a new `captureAliases` config flag (init asks once, default
1294
+ on, `config set captureAliases off` opts out): when off, aliases
1295
+ passed by agents are dropped and the alias guidance disappears from
1296
+ the MCP handshake. Turning it off never rewrites existing memory
1297
+ files — aliases already stored stay stored and searchable, and
1298
+ supersede keeps carrying them forward; the flag gates new captures
1299
+ only (no silent data destruction). Aliases pass through the same
1300
+ secret redaction as memory content before they are stored, and a
1301
+ malformed alias list (blanks, more than 4) is normalized silently —
1302
+ never a tool-call error.
1303
+ Keyword capture (no agent in the loop) leaves
1304
+ aliases empty rather than inventing them; `supersede` carries
1305
+ aliases forward unless the caller replaces them. Aliases are
1306
+ normalized (trim, case-insensitive dedupe, cap 4) and bad ones cost
1307
+ nothing — they rank as bm25 noise, never an error. Retrospective
1308
+ distillation over host transcripts stays its own group; it will
1309
+ reuse this rubric.
1310
+
1311
+ - **D62 — environment variable names grow current-name aliases.** The
1312
+ data-root and config-path overrides predate the rename and kept the
1313
+ old package's name (`MY_O_MEMORY_HOME`, `MY_O_MEMORY_CONFIG`) — a
1314
+ stale-looking name is a trap for users and agents scripting against
1315
+ the current one. `OPEN_MEMEX_HOME` / `OPEN_MEMEX_CONFIG` are now the
1316
+ preferred names; the legacy names still work as fallbacks (new name
1317
+ wins when both are set), so existing setups and scripts never break.
1318
+ The data root itself does not move — only the variable names change.
1319
+ `doctor`'s storage check notes when the root arrived via the legacy
1320
+ variable, and its pre-rename-dir leftovers check reads whichever
1321
+ variable is in effect.
1322
+
1323
+ - **D63 — correctness fixes from a whole-project external review.** Six
1324
+ verified silent-wrong-answer bugs, fixed as one batch: (1) VS init
1325
+ dropped the parsed `.mcp.json` instead of merging it, wiping other
1326
+ servers; (2) forget reported success when unlink failed — only ENOENT
1327
+ is success now, both in the tool op and the CLI; (3) the config
1328
+ loader's regex comment-stripper was not string-aware and corrupted
1329
+ redact patterns (`src/**/secrets` → `srcsecrets`) — replaced with a
1330
+ string-aware scanner now shared with doctor (one parser, not two);
1331
+ (4) `loadConfig` enforces the `memoryDir` no-escape invariant on
1332
+ hand-edited files, falling back to the default with a warning;
1333
+ (5) `migrate` rewrites `scope`/`visibility` with the key, and
1334
+ conflict resolution compares parsed timestamps (lexicographic
1335
+ RFC-3339 order breaks at fractional seconds); (6) bundle import
1336
+ enforces staging-dir containment (resolve + realpath), rejecting
1337
+ `../` and symlink escapes. Rule going forward: any behavior that can
1338
+ fail must fail loudly in its return value; "already gone" is the only
1339
+ swallowed delete error.
1340
+
1341
+ - **D64 — second batch from the same review: stale answers, stranded
1342
+ files, and holes between surfaces.** (1) The opencode plugin hosts
1343
+ synced the index only at bootstrap, so memories written by the CLI or
1344
+ another client stayed invisible all session (and exact duplicates
1345
+ accumulated); plugin tool calls now reconcile from disk first,
1346
+ mirroring the MCP server's per-call sync. (2) Superseding a published
1347
+ memory reset `review_state` to `draft` in the repo, where submit
1348
+ could not reach it and promote refuses drafts — the replacement now
1349
+ re-enters at `proposed` whenever the old memory had entered review.
1350
+ (3) Every memory-file rewrite now goes through the atomic
1351
+ tmp+rename helper (D57 covered creates only); a torn read mid-rewrite
1352
+ previously parsed as a deletion. (4) `setStatus` refuses
1353
+ `retracted → active`: retraction is one-way through this path, or
1354
+ withdrawn content silently returns to recall. (5) Propose copies get
1355
+ `visibility: internal` instead of inheriting `private`, which export
1356
+ excludes by default — the team copy no longer vanishes from bundles.
1357
+ (6) `opencodeGlobalConfigPath` resolves whichever of `.json`/`.jsonc`
1358
+ actually exists, as init's writer does; uninstall no longer reports
1359
+ "nothing to remove" while leaving the plugin entry behind.
1360
+ (7) `doctor` now checks all eleven MCP tool names (it checked the
1361
+ plugin's five) and flags v1 memories stranded under `memories/user/`,
1362
+ which v2 read paths cannot see. (8) `memory_status` output is capped
1363
+ per section with an "… and N more" line; `memory_list` accepts
1364
+ `scope: both`; `forget` says when the deleted file lived in the repo
1365
+ (commit the deletion); the Copilot instructions and the MCP handshake
1366
+ now tell the agent the same thing about when to call
1367
+ `memory_status`; the AGENTS.md distillation footer teaches
1368
+ `source: "inference"` like every other surface; `where` prints the
1369
+ config path it always promised. Standing rule reinforced: any surface
1370
+ that answers a question must be reading current truth, and every
1371
+ workflow state must have a next step.
1372
+
1373
+ - **D65 — keyword capture: natural phrasings fell through.** Default-pattern
1374
+ fixes found while verifying a review claim: (1) `帮我记住…` — arguably the
1375
+ most natural Chinese phrasing — matched no pattern at all; it now captures
1376
+ to personal, following the established 我-rule (same as 替我记), while
1377
+ `帮我们记住…` still routes project. (2) A leading `请` defeated every
1378
+ 记住-led pattern (`请记住我…`, `请记住:…`, `请记住(个人)…`); the
1379
+ `(?:请)?` prefix the 记一下 patterns already had is now uniform.
1380
+ (3) English `remember to …` left a stray leading "to" in the captured
1381
+ body; the pattern consumes it (with backtracking verified so
1382
+ "remember Toronto…" is not mangled). Patterns are code defaults, so
1383
+ upgrades pick these up automatically unless the user customized the
1384
+ pattern lists. Deliberately NOT changed: multi-line paste triggering
1385
+ and the secret-masking windows stay as they are — narrowing them is a
1386
+ capture-recall tradeoff that needs an explicit decision, not a drive-by.
1387
+
1388
+ - **D66 — follow-up review of the D63–D65 stack.** (1) The 帮/替
1389
+ keyword family is rebuilt around full verb phrases: an optional `请`,
1390
+ `记` + optional `住|录` + optional `一下`, then a separator boundary —
1391
+ `请帮我记住:…` captures (D65's new pattern lacked its own `请`),
1392
+ `帮我记录一下:…` saves `周五不发布` instead of the mid-word garbage
1393
+ `录一下:…`, and `帮我记得…` does not fire. `help me remember: …`
1394
+ joins the personal patterns. Still deliberately unchanged: shortening
1395
+ the ≥3-char content floor (it eats `记一下这个`-type captures, but
1396
+ lowering it invites junk) and adding narrative triggers like `记得…` /
1397
+ `remind me to…` (imperative-vs-narration ambiguity) — both need an
1398
+ explicit decision. (2) `sync-status` CLI shows the full list; the
1399
+ 20-line cap binds only agent renders, whose "… and N more" line names
1400
+ the CLI. `list --scope both` reaches CLI parity with the tool, and
1401
+ `status --help` documents the retracted one-way door. (3) The test
1402
+ suite is Windows-safe (probe imports are `file://` URLs, the uninstall
1403
+ test sets `USERPROFILE` since `os.homedir()` ignores `HOME` there)
1404
+ and refuses to run against a stale `dist/`. (4) Sync-error posture is
1405
+ now a stated asymmetry: the MCP adapter lets pre-call sync errors
1406
+ propagate as tool errors; the plugin adapter logs them at debug and
1407
+ answers from a possibly-stale index rather than taking the tools
1408
+ down. (5) §19's legacy-paths text now names the shipped locations;
1409
+ historical decision entries keep their as-decided wording.
1410
+
1411
+ - **D67 — keyword capture: a trigger must be a statement to the store.**
1412
+ Three rulings, all from the D66 review's open items plus one bug D66
1413
+ introduced. (1) **The boundary guard guards the bare verb only.** D66 put
1414
+ a separator requirement *after* the whole verb phrase, which killed the
1415
+ commonest Chinese form — `帮我记一下这个配置`, `帮我记住这个配置`,
1416
+ `替我记一下我住在杭州`, `帮我们记一下这个约定` all stopped matching, because
1417
+ the noun follows 记一下/记住 directly. The guard now applies only to a
1418
+ bare 记: `记` + (`住`|`录`, never followed by a verb continuation like
1419
+ 得/着) + optional `一下`, or a separator. So mid-word garbage (`帮我记得带伞`
1420
+ → `带伞`) is still impossible while the natural form captures. (2) **The
1421
+ ≥3-char body floor stays, but stops being silent.** A trigger that yields
1422
+ `这个` or `OK` is a match on the trigger, not a memory, and a 2-char memory
1423
+ is noise in every later injection — so the floor is deliberately *not*
1424
+ lowered (a script-aware floor is the escape hatch if short Latin bodies
1425
+ ever matter). What changed is honesty: `capture --dry-run` prints the
1426
+ rejection with the reason, and `logLevel: debug` logs it, so "nothing
1427
+ happened" is never a mystery. A rejected **personal** match also keeps
1428
+ its line claimed — otherwise `记住我:OK` fell through to the generic
1429
+ 记住 and was saved as the nonsense project memory `我:OK`. (3) **Narrative
1430
+ forms are not triggers.** `记得…`, `remind me to…`, `I always forget to…`
1431
+ stay out: they are imperatives aimed at the agent, they are the most
1432
+ frequent sentences in any conversation that mentions memory, and a false
1433
+ trigger writes permanent state nobody reviewed — while a missed trigger
1434
+ costs one sentence. The asymmetry is deliberate: precision over recall.
1435
+ For the two ambiguous forms already in the list, an explicit marker now
1436
+ separates narration from instruction: `别忘了:…` / `don't forget: …` /
1437
+ `don't forget that …` capture; bare `别忘了带伞` / `don't forget the wifi
1438
+ password` do not. The `note` family is untouched — `note the API is v2`
1439
+ was never ambiguous. (4) Test/tooling honesty: `test-full.ts` guards the
1440
+ built `dist/` with a content fingerprint (`scripts/build-stamp.mjs`)
1441
+ rather than mtimes — `git checkout` rewrites mtimes, so a branch switch
1442
+ could leave an older-branch dist looking fresh — and the agent-facing
1443
+ `memory_status` cap is pinned again (20 entries per section plus a
1444
+ "full list: open-memex sync-status" pointer) after D66's CLI-parity test
1445
+ had dropped that coverage.
1446
+
1447
+ - **D68 — memory visibility: the inventory is a protocol surface.** Group 3-C
1448
+ (design: `docs/memory-visibility-design.md` v2). `memory_list` output
1449
+ becomes a numbered, structured inventory (`[type] id=… created=…
1450
+ source=…`, state tags for project scopes; `created=` is when the fact
1451
+ was learned, `updated=` shown only when it moved materially later). The
1452
+ raw enums stay in the data; plain-language renderings live in the
1453
+ guidance surfaces (tool descriptions, MCP handshake, SKILL.md,
1454
+ distill-agents footer) so agents rephrase in the conversation's
1455
+ language. New input `include: "active" | "all"` (default `active`) —
1456
+ a deliberate behavior change: superseded versions and retracted /
1457
+ archived memories leave the default listing (audit view via `all`).
1458
+ Every listing states its true total; truncation is disclosed, never
1459
+ silent (the D66 lesson applied to this surface). `open-memex
1460
+ inventory` renders the same data layer as text (default) or JSON
1461
+ (`open-memex-inventory/1`); a report containing personal memories
1462
+ refuses to be written inside a git working tree without
1463
+ `--allow-personal`. `memory_forget` gains `soft` (CLI `forget
1464
+ --soft`): retraction as a first-class tool action — hidden from lists
1465
+ and search, file kept, still one-way per D64 (restoring the fact means
1466
+ saving it again; no un-hide, no undo, no recycle bin). Deletion stays
1467
+ a confirmed conversation: agents re-list before acting on a number
1468
+ (numbers are only valid for the listing just produced) and read the
1469
+ entry back before deleting. The HTML rendering of the same data layer
1470
+ follows as a separate change; auto-draft stays gated on the unlock
1471
+ conditions in the design doc.
1472
+
1473
+ - **D69 — inventory HTML rendering lands (3-C complete on the visibility side).**
1474
+ `open-memex inventory --format html` renders the D68 data layer into a
1475
+ single local page: scopes grouped, outbox drafts in their own section,
1476
+ replaced/hidden history folded with supersede-chain pointers, all
1477
+ content escaped, filter box is local show/hide (no innerHTML, no
1478
+ network). Default output is the data dir's `inventory.html`; the D68
1479
+ worktree refusal applies unchanged. Sections fold above 50 current
1480
+ entries. With this, the 3-C visibility surfaces are complete (agent
1481
+ inventory in chat, text/JSON reports, HTML page; hide + confirmed
1482
+ delete as the correction actions). Auto-draft remains gated on the
1483
+ unlock conditions in `docs/memory-visibility-design.md`.
1484
+
1485
+ - **D70 — visibility follow-ups: one number, one guard, one truth.** The
1486
+ 3-C review of D68/D69 found the surfaces honest individually and
1487
+ inconsistent with each other. (1) **Every visibility surface discloses
1488
+ truncation.** `memory_list` did; `open-memex list` — the command a
1489
+ sceptical user runs first — silently showed 20 of 25. It now renders
1490
+ the same structured line as the tool and states how many entries it
1491
+ dropped, naming the command that shows everything. (2) **Numbers are
1492
+ unique within a listing.** Ordinals ran per scope, so a single
1493
+ `scope: both` output contained two `#1`s and "delete #3" was a guess;
1494
+ the counter now runs across every section of one listing (tool, CLI and
1495
+ the text report). (3) **A correction that touches the repo says so.**
1496
+ The hard delete already did (D64); the soft hide did not, so a
1497
+ retraction of an in-repo memory read as "hidden" while it was only a
1498
+ local working-tree edit — the team still had the memory. Both surfaces
1499
+ now name the commit and push. (4) **The report guard fails closed and
1500
+ exists once.** `isInsideWorkTree` ran `git -C` on the output's parent
1501
+ directory, so a not-yet-created directory made the check throw and
1502
+ report "not in a worktree" — a fail-open on the one path where personal
1503
+ memories would ride a commit; it now resolves the nearest existing
1504
+ ancestor, reports a missing directory as itself, and both formats share
1505
+ a single refusal helper instead of two copies. (5) **`open-memex-inventory/2`**
1506
+ drops the absolute `file` path: the JSON renderer exists to be handed to
1507
+ an agent, i.e. pasted into a cloud conversation, and it carried the
1508
+ user's home directory. (6) Smaller honesty fixes: an empty scope is
1509
+ reported as `(0)` instead of vanishing; only the HTML audit view loads
1510
+ the hidden rows themselves (text/json pay for counts); the page's
1511
+ "no network requests" claim is now enforced by a `default-src 'none'`
1512
+ CSP rather than asserted; and `countList`'s comment states its one known
1513
+ divergence (a self-healed dangling chain pointer) instead of claiming
1514
+ equivalence. Deliberately NOT done, pending a decision: HTML i18n and a
1515
+ size cap for very large stores.
1214
1516
 
1215
- _All resolved — see D10 (rename), D11 (type/role split), D12 (explicit pull)._
1517
+ ## Open Questions
1216
1518
 
1217
- - **Checkpoint proactivity for capture (open, 2026-10-02):** D53 retired checkpoint *polling* — the server pushes draft counts instead. But agent proactivity at task checkpoints genuinely helps knowledge capture (§3.5 distillation), so the open question is the degree: proactive enough to catch distillable moments, without nagging the user or burning tokens at every checkpoint. Candidate directions (server-pushed hints in tool results, client-side heuristics, or a middle setting) to be evaluated in a later version; default posture TBD.
1519
+ _Some early questions are resolved — see D10 (rename), D11 (type/role split), D12 (explicit pull). The entries below are open unless marked decided._
1520
+ - **Checkpoint proactivity for capture (open, 2026-10-02):** D53 retired checkpoint *polling* — the server pushes draft counts instead. But agent proactivity at task checkpoints genuinely helps knowledge capture (§3.5 distillation), so the open question is the degree: proactive enough to catch distillable moments, without nagging the user or burning tokens at every checkpoint. Candidate directions (server-pushed hints in tool results, client-side heuristics, or a middle setting) to be evaluated in a later version; default posture TBD. **Update (2026-10-03): decided in D61 — propose posture** (agent proposes 1–3 candidates at task checkpoints, saves only on approval; auto-draft deferred until memory visibility ships, see the MEMORY_VISIBILITY question below).
1218
1521
 
1219
- - **Retrieval robustness: phrasing, synonyms, extra words (open, 2026-10-03):** keyword search matches word forms, not meaning — the asker phrases it differently, adds a word, or uses a synonym and the memory is missed. The current FTS5 setup (porter stemming + pre-tokenized CJK bigrams) already covers morphology and substrings; it cannot bridge semantics. Layered candidate directions, cheapest first: (1) query construction — never feed a raw sentence to FTS; extract content terms, drop stopwords, OR them and let bm25 rank, so extra words hurt ranking instead of killing the match; (2) agent-side query expansion as an explicit protocol in the skill/instructions (decompose the question into concepts, generate 2–3 synonym variants per concept, run several searches before giving up — the LLM is the synonym dictionary, no embedding model needed); (3) capture-time aliasing — when a memory is written, the agent attaches keyword/synonym aliases so future phrasings can hit it. Embeddings are the design's reserved optional capability: real semantic matching, but they require a design decision and a local model to distribute, so they stay deferred unless (1)–(3) prove insufficient. Note (3) pairs naturally with the proactive-capture question below: the same capture pass that proposes a memory can attach its aliases. **Status (2026-10-03): layer (1) shipped (D56, 0.6.1)** — query construction now filters EN/CJK function words, dedupes, and caps terms. Layers (2) agent-side expansion and (3) capture-time aliasing remain open; embeddings stay deferred.
1522
+ - **Retrieval robustness: phrasing, synonyms, extra words (open, 2026-10-03):** keyword search matches word forms, not meaning — the asker phrases it differently, adds a word, or uses a synonym and the memory is missed. The current FTS5 setup (porter stemming + pre-tokenized CJK bigrams) already covers morphology and substrings; it cannot bridge semantics. Layered candidate directions, cheapest first: (1) query construction — never feed a raw sentence to FTS; extract content terms, drop stopwords, OR them and let bm25 rank, so extra words hurt ranking instead of killing the match; (2) agent-side query expansion as an explicit protocol in the skill/instructions (decompose the question into concepts, generate 2–3 synonym variants per concept, run several searches before giving up — the LLM is the synonym dictionary, no embedding model needed); (3) capture-time aliasing — when a memory is written, the agent attaches keyword/synonym aliases so future phrasings can hit it. Embeddings are the design's reserved optional capability: real semantic matching, but they require a design decision and a local model to distribute, so they stay deferred unless (1)–(3) prove insufficient. Note (3) pairs naturally with the proactive-capture question below: the same capture pass that proposes a memory can attach its aliases. **Status (2026-10-03): layer (1) shipped (D56, 0.6.1)** — query construction now filters EN/CJK function words, dedupes, and caps terms. Layers (2) agent-side expansion and (3) capture-time aliasing remain open; embeddings stay deferred. **Update (2026-10-03): layers (2) and (3) shipped in D61** — agent-side expansion as guidance text on every surface, capture-time aliases indexed in their own FTS column behind the `captureAliases` flag. Embeddings stay deferred.
1220
1523
 
1221
1524
  - **Proactive capture: agent judgment of what is worth remembering (open, 2026-10-03):** companion to the checkpoint-proactivity question above — that one asks *when* to prompt; this one asks *what qualifies*. Agent hosts with good long-term memory (e.g. Muse) get their results from explicit memorability criteria plus checkpoint consolidation, not from silent background magic; open-memex can adopt both halves. Candidate criteria to teach via the skill/instructions: decisions **and their rationale**, preferences, conventions, gotchas, and abandoned paths ("we tried X, it failed because…") are worth proposing; one-off task details and anything re-derivable from the code are not. Two postures to evaluate: propose-at-checkpoint (agent asks "worth saving?", user vetoes, then `memory_add`) versus auto-draft (agent writes outbox drafts unasked and surfaces them in a batch at the next checkpoint — drafts are git-invisible and deletable, so the cost of a wrong guess is low). Hard boundaries either way: project scope never auto-submits, and no cross-session background consolidation is possible — the plugin lives inside sessions, so consolidation happens at in-session checkpoints and wrap-up passes only. This question also decides how much aliasing (retrieval question above) the capture pass should generate.
1222
1525
 
@@ -1230,7 +1533,7 @@ _All resolved — see D10 (rename), D11 (type/role split), D12 (explicit pull)._
1230
1533
 
1231
1534
  - **Novice experience: first success and plain language (open, 2026-10-03):** from an AI-novice review. The install ends with instructions, not an experience: candidate fix is a 60-second magic moment — init offers to save one sample memory and immediately has the user's agent answer a question using it, so the user *sees* recall work before being asked to trust it. Same screen should state the privacy model in plain words (personal never leaves this machine, no cloud, detected secrets are masked — all already true, all currently buried in docs), and the first time a suspected secret is masked, the capture path should say so. Underneath: a plain-language layer over the state machine ("saved on this computer" / "shared with the team") with outbox/submit/promote vocabulary becoming the advanced view — this is the same vocabulary wall the PM/BA question above hits from the other side. **Status (2026-10-03): partially addressed** — `init` now states the privacy model in plain words (D56); the 60-second magic moment and the plain-language layer remain open.
1232
1535
 
1233
- - **Memory visibility and correctability (open, 2026-10-03):** also from the novice review, and arguably the trust foundation for every other feature. A non-CLI user currently cannot answer "what does my assistant remember about me?" without reading Markdown files. Candidate directions: a human-readable inventory (local HTML export or equivalent) listing memories in plain language with provenance shown the way a person asks for it ("saved 3 days ago while you were discussing deploys in project X") and one-step forget/undo from the same view. Provenance fields already exist in frontmatter; this is presentation, not schema. Without it, proactive capture (above) will read to novices as surveillance rather than help — so this question probably gates the auto-draft posture decision in the capture question.
1536
+ - **Memory visibility and correctability (open, 2026-10-03):** also from the novice review, and arguably the trust foundation for every other feature. A non-CLI user currently cannot answer "what does my assistant remember about me?" without reading Markdown files. Candidate directions: a human-readable inventory (local HTML export or equivalent) listing memories in plain language with provenance shown the way a person asks for it ("saved 3 days ago while you were discussing deploys in project X") and one-step forget/undo from the same view. Provenance fields already exist in frontmatter; this is presentation, not schema. Without it, proactive capture (above) will read to novices as surveillance rather than help — so this question probably gates the auto-draft posture decision in the capture question. **Status (2026-10-03): decided (D68)** — the inventory ships as structured list + text/JSON report with conversational rendering taught to agents; "one-step forget/undo from the view" was deliberately not built (deletion stays a confirmed conversation; soft-hide added instead); the HTML rendering follows separately. Auto-draft remains gated on the unlock conditions in `docs/memory-visibility-design.md`.
1234
1537
 
1235
1538
  ---
1236
1539
 
@@ -0,0 +1,130 @@
1
+ # OpenMemex 3-C 设计稿:记忆可见性 inventory(v2 · 已按 review 修订)
2
+
3
+ ## 为什么先做这个(已定)
4
+
5
+ 2026-10-03 拍板:auto-draft 姿态等 3-C 记忆可见性出来再开。理由是信任顺序不能反——在用户能一眼看清"你到底记了我什么、每条是哪来的、怎么删"之前,agent 不经询问就往草稿箱里塞东西,只会读作监控而不是帮忙。3-C 就是补上这个"看得见、删得掉"的面。
6
+
7
+ 现状盘点(已核对代码,见文末"事实校正"):
8
+
9
+ - `memory_list`(D64 已支持 `scope: both`)输出是开发者格式:`- [type] [review_state] id=ULID — 片段`。**没有**生命周期状态、没有时间、没有来源(`source`/`created_at` 在 DB 里但没进 SELECT)。
10
+ - `memory_status` 管的是同步队列(草稿箱/待审),不是"记忆全貌"。
11
+ - 删除只有 `memory_forget`(硬删文件,无 undo)和 CLI `status retracted`(软藏,工具面没有这个动作)。
12
+ - provenance 字段其实齐全:`source`(user/tool/keyword/inference/import)、`created_at`、`review_state`、`review_history`、`derived_from` 都在 frontmatter 里。**这是展示问题,不是 schema 问题**——3-C 不动 schema。
13
+
14
+ ## 三条硬约束(review 追加,先于功能)
15
+
16
+ 1. **personal 不出机器。** 单一产物不得默认落在 git 工作树里。报告同时含 personal + project 内容,`-o` 指到仓库里就是一次泄露——这不是概率问题,是一次误操作就到。
17
+ 2. **协议是冻结的(v0.2)。** 动 `memory_list` 的入参和输出格式就是动协议面:形状必须在实现前定死,实现 PR 里追加 D 条目,事后加就是破坏性变更。
18
+ 3. **可见性面不许静默截断。** 截断必须自报(与 D66 `sync-status` 的教训同源)。用户问"你记得我什么"而答案被悄悄砍掉一半,比没有这个面更伤信任。
19
+
20
+ ## 3-C 做什么
21
+
22
+ ### 一、聊天内清单(A 面,先做)
23
+
24
+ 把 `memory_list` 的输出改成**结构化行 + 稳定句柄**,让任何宿主的 agent 能直接拿它回答"你记得我什么":
25
+
26
+ ```
27
+ ## 关于你(personal)
28
+ 3. [fact] id=01K6AB3XZQ7WVD9J1M2N4P5Q6R7 created=3d source=user — 喜欢简洁的 diff
29
+ 7. [preference] id=01K6AB3XZQ7WVD9J1M2N4P5Q6R8 created=12d updated=2d source=inference — 家里网络是双线负载
30
+ ## 当前项目(open-memex)
31
+ 1. [decision] id=01K6…R9 created=2d source=keyword [draft] — 发布走 alpha 线
32
+ … 另有 34 条未显示(--limit 100,或用 memory_search 查具体内容)
33
+ ```
34
+
35
+ 要点:
36
+
37
+ - **序号与 id 永远同时出现。** 序号是给嘴用的("删掉第 3 条"),id 是给机器用的契约。删除确认语里带 id。
38
+ - **时间用 `created_at`(学会的时间),不是 `updated_at`。** list 的排序键是 `updated_at`,而它在 supersede/promote 时会动;两者不同才补一个 `updated=`。别把"3 天前学的、2 天前改过"说成"2 天前"。
39
+ - **输出里保留原始枚举,不写死中文短语。** `source=user` 对模型比对"你亲口说的"更可靠,也不锁语言、不锁措辞;人话短语(user→"你亲口说的"、inference→"我推断的"、keyword→"关键词自动抓的")放进工具描述 + SKILL.md + MCP 握手,由 agent 按对话语言转述。仓库规则是"一个声音说指引",不是"一个声音说数据"。
40
+ - **入参(协议面,D68)**:`include: "active" | "all"`,默认 `active`。注意:**现在的 list 完全没有状态过滤**,所以"默认只列 active"是新行为——必须显式参数化并在 CHANGELOG 写明,不能悄悄改默认(依赖旧默认的 agent 会突然找不到 superseded 的链条)。`archived`/`retracted` 只在 `all` 下出现。
41
+ - **截断自报。** `limit` 默认 20、上限 100 是既有事实。输出必须带真实总数 + 截断说明行(文案见上)。
42
+ - **序号只在本次清单内有效**(顺序依赖 `updated_at`,会话中途一次 supersede 就可能重排)。指引里写死:被要求删某条时若只记得序号,先重新 `memory_list` 再复述内容确认,不要凭上一轮的序号映射。
43
+ - review_state 标签沿用现状(personal 不显示,因为 `hitStateLabel` 对 personal 返回空);`published` 常态不提。`memory_status` 的待审队列另有去处,两个面不混。
44
+ - 指引文案四处同步(SKILL.md、`memory_list` 工具描述、MCP 握手、`distill-agents` 输出的 AGENTS.md 片段):被问"你记得我什么"时调 `memory_list(scope: both)` 并用大白话复述、别甩内部 id;用户点名删某条时先复述那条内容确认再调 `memory_forget`。
45
+
46
+ ### 二、inventory 命令(B 面,三段走:text → json → html)
47
+
48
+ CLI 新增 `open-memex inventory`,一个数据源、三种渲染:
49
+
50
+ - `--format text`(默认,先行):纯文本清单,~30 行代码,可在任何终端/CI 里 diff。**先用它验证清单格式本身对不对**,再谈 UI。
51
+ - `--format json`:给 agent 用。agent 不该去解析 HTML——这条也让 A 面和 B 面共用同一份数据,未来加字段只改一处。
52
+ - `--format html`(最后):本地单文件,浏览器打开即看。按 scope 分组、纯静态、无脚本外链、无网络请求;页内一个本地过滤框(内嵌小 JS)。
53
+
54
+ 硬规则:
55
+
56
+ - **HTML 必须全量转义。** 记忆正文可能来自 `import` 出来的包或粘贴的网页内容,`file://` 下一个没转义的 `<script>` 就是在用户自己浏览器里执行。过滤逻辑用 `input` + 事件委托,不用 `innerHTML`。
57
+ - **默认输出到数据目录,不落工作树。** `--output` 指到 git 工作树内时**拒绝**,除非显式 `--allow-personal`(报告含 personal,这是 D62/铁律的边界,不是洁癖)。`export` 已有同类先例可循。
58
+ - **每条给可复制的 id。** 不再只给"内容前 12 字"——截断、引号、内容改动都会让那句提示失效,而 id 不会。
59
+ - **独立分节**:还没进仓库的(outbox,与 `memory_status` 计数一致)、折叠的"已替换 / 已隐藏"(含 `supersedes` 链接)。聊天清单里不出现历史,但报告里必须可审计——否则用户发现答案过时了却查不出它怎么变成过时的,最可能的反应是把旧事实重新加一遍。
60
+ - **复用 `export.ts` 的 manifest/序列化**,不写第二个序列化器。两份实现必然漂移。
61
+ - **规模**:条数很大时给分页或显式截断说明,不静默砍。默认 `--scope` 全量(personal + 当前项目),可加 `--scope` 过滤。
62
+ - 报告只读,不放删除按钮、不回连本地服务(起 server 管端口违背本地优先的简单性,删除本来就该发生在有确认语的对话里)。
63
+
64
+ ### 三、纠正动作的诚实边界
65
+
66
+ - **删除 = 硬删,无 undo。** 不假装有回收站。防呆全在对话层:agent 删除前**复述内容**、用户点头(写进指引)。复述的是内容不是序号。
67
+ - **新增并列的软动作:"先藏起来"(`retracted`)。** 语义现成(检索会排除 `retracted`),只是工具面没有——把它暴露成 `memory_forget --soft` 或一个 `memory_status` 入参即可。理由:不可撤销的动作有两种时,用户才敢选;只给"删"的清单会让人不敢删。不确定是不是该删的,就先藏。
68
+ - **删除在序号上不做猜测。** 用户只给序号时,agent 先重新列清单、把候选内容念出来让用户点名。
69
+ - 已进仓库的记忆被删 = 工作树删除,要 commit 才算数(`memory_forget` 已有该提示,保持;报告页脚注提一句,不当卖点)。
70
+ - 清单和报告默认都不显示 `retracted`/`archived`/`superseded`(走 `include`/`--all`),报告的"已替换/已隐藏"折叠节是唯一的例外。
71
+
72
+ ### 四、与 auto-draft 的解锁关系
73
+
74
+ 3-C 落地后 auto-draft 的解锁条件(将来开 auto-draft 时回来对照):
75
+
76
+ 1. A 面指引已教会 agent 在检查点汇报草稿箱存量("我新存了 2 条草稿,你可以随时让我列出来");
77
+ 2. B 面报告存在,且草稿箱一节与 `memory_status` 计数一致;
78
+ 3. 删除路径在清单语境里验证过(序号→id 映射、复述确认语、仓库内删除的 commit 提示);
79
+ 4. 软藏(retract)路径在清单语境里验证过(agent 能藏、能列出被藏的、用户知道怎么取消——若不做,则明确记录"只有硬删"是当前的诚实边界)。
80
+
81
+ **回滚条款(现在就写下来):** 开了 auto-draft 之后,如果用户仍说不清库里到底有什么、或者第一反应是要求清库而不是用起来,就退回 D61 的 propose 姿态(agent 提议、用户点头),而不是继续加提示。
82
+
83
+ **度量:** 本地优先、不带遥测,所以度量只能是人工的:找 5–10 个真实用户做同一个任务——"在 30 秒内找到并删掉一条你知道被记下来的事",记录卡在哪一步(找不到 / 不敢删 / 不知道删的是哪条)。这个任务同时验证可见性和纠正动作,比任何问卷都准。
84
+
85
+ ## 事实校正(review 逐条核对代码,与原稿有出入的地方)
86
+
87
+ - `list()` 只显示 `review_state`,**不显示生命周期 `status`**;而且 `hitStateLabel` 对 personal 直接返回空串(`src/retrieve/search.ts:73`)。原稿"有 status/review_state"只对了一半。
88
+ - 索引里的 `created_at`/`updated_at` 是 INTEGER epoch ms(`src/store/db.ts:46-47`),RFC3339 只存在于 frontmatter。
89
+ - `list()` 的排序键是 `updated_at DESC`,`limit` 默认 20、上限 100(`src/retrieve/search.ts:217-225`)——"3 天前"和"截断"两个问题都源于此。
90
+ - 索引里 `source` 列已存在(`src/store/db.ts:43`),补 SELECT 即可,不需要重建索引。
91
+ - `loadConfig` 是浅合并 `{...DEFAULT_CONFIG, ...parsed}`(`src/config.ts:226`):用户自定义 `keywordPatterns` 会**整表替换** 12 条默认 pattern。这是"后续一件"的形态依据,也是现在的脚枪。
92
+ - 坏正则被静默跳过(`src/capture/keywords.ts` 的 `catch { continue }`)——自定义 pattern 校验要补的就是这一条。
93
+ - `loadConfig` 已有"对用户可写的字段做加载期校验"的先例(`memoryDir`,`src/config.ts:231`),列表键照此办理。
94
+
95
+ ## 不做(明确划出)
96
+
97
+ - 本地 Web 服务 / 可点击回连的 UI(起 server 那套)。
98
+ - undo / 回收站机制(软藏 `retracted` 不是回收站:它不进清单,但也不可恢复)。
99
+ - 新 frontmatter 字段、新 schema 版本("你确认过"这类概念本稿不承诺——schema 里没有确认态,只有 `source` 和 `review_history`)。
100
+ - 报告的自动定时生成(用户想看时手动跑,或让 agent 跑)。
101
+ - 任意正则的自定义触发词(先做 curated 开关,见下)。
102
+
103
+ ## 实现清单(估大小)
104
+
105
+ | 块 | 内容 | 量 |
106
+ |---|---|---|
107
+ | 清单格式 | list 查询补 `source`/`created_at`;`include` 入参;输出改结构化行(序号+id+相对时间+来源)、截断自报 | 小 |
108
+ | 纠正动作 | `memory_forget --soft`(或 status 入参);指引文案里的复述确认语与序号失效规则 | 小 |
109
+ | inventory text | `--format text`:复用 manifest 序列化,两 scope + outbox 一节 | 小 |
110
+ | inventory json | `--format json`:同一数据源,给 agent | 小 |
111
+ | inventory html | 单文件 HTML:转义、过滤框、`--output` 工作树拒绝、折叠历史节、规模处理 | 中 |
112
+ | 测试/文档 | test-full 加清单格式/截断自报/软藏/html 转义/工作树拒绝;README 双语、CHANGELOG、V2-DESIGN 追加 D68 同 commit | 小 |
113
+
114
+ 建议 PR 切分:A(清单格式 + 纠正动作 + text/json)一个 PR,HTML 一个 PR——HTML 是呈现层,格式先被 text/json 验证过再上。
115
+
116
+ 版本号:本稿不动版本;实现时按当时 alpha 线递增(Stone 2026-10-03 定:"3C 时换版本号")。实现 PR 必须同时在 `docs/V2-DESIGN.md` 追加 D68(协议面改动:`memory_list` 入参 + 输出格式)。
117
+
118
+ ## 后续一件(3-C 之后单独实现,不进本组)
119
+
120
+ **用户自定义关键词触发模式**(Stone 2026-10-03 提出):D67 把 `don't forget …` / `别忘了…` 裸形收紧为需要分隔符或 `that`,但裸形正是部分用户的习惯说法(Stone 本人就是)。默认保持收紧,另给一条加回来的路,分两步:
121
+
122
+ 1. **curated 开关(先做)**:`narrationTriggers: false`(默认关)。打开即恢复 D67 收紧的那几个裸形。代价写在文档里——召回上去了,误捕叙事的风险也回来了,这是明确的取舍而不是 bug。
123
+ 2. **追加式自定义 pattern(后做)**:`keywordPatternsExtra` / `keywordPersonalPatternsExtra`,**追加**在默认表之后(不是替换——`config.ts:226` 的浅合并会让整表替换,用户加一条就丢掉其余 12 条)。需要:`config set` 的列表键校验、set 时编译正则并报错(现在坏正则被静默跳过)。
124
+
125
+ ## 待你拍板(剩余项;不回我按推荐走)
126
+
127
+ 1. **软藏进不进工具面?** 推荐进:`memory_forget --soft`(最小改动,复用现有语义与检索排除逻辑),可撤回的前提是"能藏"这个动作存在。
128
+ 2. **HTML 过滤框的交互?** 推荐:条数 ≤ 50 全展开,超过则历史折叠、内容按 scope 折叠;过滤框始终可见。
129
+ 3. **`inventory --scope` 默认值?** 推荐默认全量(personal + 当前项目),因为"看不见"正是本组要治的病。
130
+ 4. **D68 落在哪个 PR?** 推荐随 A 面那个 PR 一起落(协议面与实现同 commit)。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-memex",
3
- "version": "0.6.2",
3
+ "version": "0.7.0-alpha.11",
4
4
  "description": "Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -17,7 +17,7 @@
17
17
  "typecheck": "tsc --noEmit",
18
18
  "cli": "node --experimental-strip-types src/cli.ts",
19
19
  "mcp": "node --experimental-strip-types src/mcp.ts",
20
- "build": "tsc -p tsconfig.build.json",
20
+ "build": "tsc -p tsconfig.build.json && node scripts/build-stamp.mjs",
21
21
  "prepublishOnly": "npm run build",
22
22
  "postinstall": "node scripts/postinstall.js"
23
23
  },