open-memex 0.7.0-alpha.4 → 0.7.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +8 -3
- package/CHANGELOG.md +40 -0
- package/README.md +20 -12
- package/README.zh-CN.md +13 -12
- package/dist/.build-stamp +1 -0
- package/dist/capture/keywords.js +41 -8
- package/dist/cli.js +38 -22
- package/dist/config.js +22 -8
- package/dist/distill-agents.js +6 -1
- package/dist/doctor.js +21 -0
- package/dist/init.js +17 -6
- package/dist/review.js +9 -3
- package/dist/store/lifecycle.js +18 -4
- package/dist/store/markdown.js +14 -6
- package/dist/store/migrate.js +2 -2
- package/dist/store/v2migrate.js +2 -2
- package/dist/submit.js +13 -5
- package/dist/tools/memory.js +28 -0
- package/dist/tools/ops.js +37 -3
- package/docs/CURATOR.md +7 -3
- package/docs/V2-DESIGN.md +138 -28
- package/package.json +2 -2
- package/scripts/build-stamp.mjs +63 -0
- package/scripts/smoke-pure.ts +100 -1
- package/scripts/test-full.ts +133 -1
- package/src/capture/keywords.ts +56 -6
- package/src/cli.ts +43 -23
- package/src/config.ts +22 -8
- package/src/distill-agents.ts +6 -0
- package/src/doctor.ts +24 -0
- package/src/init.ts +16 -6
- package/src/review.ts +9 -2
- package/src/store/lifecycle.ts +21 -3
- package/src/store/markdown.ts +15 -6
- package/src/store/migrate.ts +2 -1
- package/src/store/v2migrate.ts +2 -1
- package/src/submit.ts +12 -4
- package/src/tools/memory.ts +28 -0
- package/src/tools/ops.ts +45 -3
package/AGENTS.md
CHANGED
|
@@ -151,18 +151,23 @@ AGENTS.md hygiene footer, the MCP handshake, and the tool descriptions.
|
|
|
151
151
|
|
|
152
152
|
- Statuses: `active → superseded | deprecated | retracted | archived`. Retrieval excludes `retracted`/`archived`, ranks `active` above `deprecated`.
|
|
153
153
|
- Never hand-write `status: superseded` or half a chain. Use the `supersede` code path (`src/store/lifecycle.ts`): the old record keeps its file, flips to `superseded`, and both sides get `supersedes`/`superseded_by`. Only `active` memories can be superseded.
|
|
154
|
+
- Superseding a memory that had entered review (proposed/approved/published/rejected) starts the replacement at `review_state: proposed` so `promote` can advance it (D64). Never reset it to `draft` — a draft in the repo dir is stranded (submit only moves outbox files).
|
|
155
|
+
- `setStatus` refuses `retracted → active` (D64): retracted content must not silently return to recall. Save a new memory instead.
|
|
156
|
+
- **Every memory-file write goes through `atomicWriteTextSync`** (markdown.ts; tmp + rename). The create path, supersede flips, review transitions, submit landings, migrate/v2migrate rewrites — no direct `fs.writeFileSync` on a memory file. A torn read mid-rewrite parses as a deletion and drops the memory from the index.
|
|
154
157
|
- Chain integrity is self-healing: on read, a missing counterpart is auto-completed with a warning; a dangling pointer warns but is never fabricated. Don't "fix" chains by editing frontmatter directly — let the read path do it.
|
|
155
158
|
- Frontmatter is `schema_version: 2`. The SQLite index schema is versioned separately and rebuilds automatically on version change — never hand-edit `index.db`.
|
|
156
159
|
|
|
157
|
-
Keyword capture fires from `chat.message` on the user's message parts (`UserMessage`). Patterns live in `src/capture/keywords.ts` / config `keywordPatterns`; regex group 1 is the memory body. Scope routing: personal patterns (`cfg.keywordPersonalPatterns`) force the personal scope — the rule is 我 → personal (
|
|
160
|
+
Keyword capture fires from `chat.message` on the user's message parts (`UserMessage`). Patterns live in `src/capture/keywords.ts` / config `keywordPatterns`; regex group 1 is the memory body. Scope routing: personal patterns (`cfg.keywordPersonalPatterns`) force the personal scope — the rule is 我 → personal (记住我/替我记/帮我记/我觉得/我喜欢/remember for me/help me remember), 我们 → current scope (我们认为/我们决定/帮我们记住); personal patterns run first and claim their line so a generic trigger can't double-fire (a rejected personal match keeps the claim — see D67).
|
|
161
|
+
|
|
162
|
+
D67 rules for any new pattern: a trigger must be a **statement to the store**, not narration — 记得… / `remind me to…` are deliberately out, and the ambiguous forms require an explicit marker (`别忘了:…`, `don't forget: …`, `don't forget that …`). The 帮/替/我们 family takes the full verb phrase (`记` + optional `住|录`, never followed by a verb continuation) + optional `一下`; the separator boundary applies to the **bare** verb only, so `帮我记一下这个配置` captures while `帮我记得带伞` cannot produce mid-word garbage. Bodies under `MIN_CAPTURE_CHARS` (3) are rejected as fragments — never lower the floor silently; `scanKeywords` returns the drop so `capture --dry-run` and the debug log can report it.
|
|
158
163
|
|
|
159
164
|
Context injection happens exactly once per session in `experimental.chat.system.transform`, guarded by an in-memory `Set<sessionID>` in `src/index.ts`. It is not persisted — restarting opencode re-injects on the next first turn.
|
|
160
165
|
|
|
161
166
|
## Design constraints — read the frozen design first
|
|
162
167
|
|
|
163
|
-
`docs/V2-DESIGN.md` is the frozen protocol v0.2 (
|
|
168
|
+
`docs/V2-DESIGN.md` is the frozen protocol v0.2 (decisions D1–D64 settled; open questions tracked at the end of the doc). Per its §12:
|
|
164
169
|
AGENTS.md answers "how should AI work here"; the design doc answers "why is it
|
|
165
|
-
built this way" (principles, iron rules,
|
|
170
|
+
built this way" (principles, iron rules, the append-only decision log, currently D1–D67). Before changing
|
|
166
171
|
architecture, scope semantics, lifecycle, or the protocol surface (frontmatter
|
|
167
172
|
schema, MCP tools, CLI contract), read the relevant design section — the decision
|
|
168
173
|
log records what was already considered and rejected.
|
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,46 @@ the `@alpha` tag are development snapshots and are not listed individually.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## Keyword capture: what counts as a trigger (D67)
|
|
13
|
+
|
|
14
|
+
The D66 review left two questions open and introduced one regression. Ruled, not deferred:
|
|
15
|
+
|
|
16
|
+
- **Fixed a regression:** D66's separator boundary was one notch too strict — it also demanded a separator after a *complete* verb phrase, so `帮我记一下这个配置`, `帮我记住这个配置`, `替我记一下我住在杭州` and `帮我们记一下这个约定` stopped matching. The boundary now guards the bare verb only, so `帮我记得…` still can't produce mid-word garbage.
|
|
17
|
+
- **The ≥3-character body floor stays, but is no longer silent.** `记住:这个` is a fragment, not a memory, so it isn't captured — but `open-memex capture --dry-run` now says why, and `logLevel: debug` logs it. A rejected *personal* match also keeps its line, so `记住我:OK` can no longer fall through to the generic 记住 and be saved as `我:OK`.
|
|
18
|
+
- **Narration is not a trigger.** `记得…`, `remind me to…` and friends stay out on purpose (a false trigger writes unreviewed memory; a missed one costs a sentence). The ambiguous forms already in the list now need an explicit marker: `别忘了:…`, `don't forget: …` and `don't forget that …` capture; bare `别忘了带伞` and `don't forget the wifi password` do not. The `note` family is unchanged.
|
|
19
|
+
- **Test tooling:** the stale-`dist/` guard is now a content fingerprint (`scripts/build-stamp.mjs`) instead of mtimes, which a branch switch could defeat; the agent-facing `memory_status` cap (20 per section + a "full list" pointer) is pinned again after D66's test replaced that coverage.
|
|
20
|
+
|
|
21
|
+
## Review follow-ups (D66)
|
|
22
|
+
|
|
23
|
+
Fixes from the follow-up review of the D63–D65 stack:
|
|
24
|
+
|
|
25
|
+
- Keyword patterns: `请帮我记住…` works (the D65 pattern had missed its `请` prefix), `帮我记录一下:…` / `替我记录一下:…` capture cleanly instead of saving mid-word garbage, `帮我记得…` no longer fires, and `help me remember: …` routes personal like `remember for me`.
|
|
26
|
+
- `open-memex sync-status` (the CLI) shows the full list again — the 20-per-section cap now applies only to agent tool results, whose "… and N more" line points at the CLI. `open-memex list --scope both` matches the `memory_list` tool.
|
|
27
|
+
- `open-memex status --help` documents that retraction is one-way.
|
|
28
|
+
- The full test suite is Windows-safe now (file:// probe imports, USERPROFILE-aware uninstall test) and refuses to run against a stale `dist/` build.
|
|
29
|
+
|
|
30
|
+
## Keyword capture gaps (D65)
|
|
31
|
+
|
|
32
|
+
The most natural phrasings were the ones that fell through:
|
|
33
|
+
|
|
34
|
+
- `帮我记住…` / `帮我记一下…` now capture (personal scope, same 我-rule as 替我记; `帮我们记住…` still routes project).
|
|
35
|
+
- A leading `请` no longer defeats capture: `请记住我…`, `请记住(个人)…`, and `请记住:…` all work.
|
|
36
|
+
- English `remember to …` no longer leaves a stray "to" at the front of the captured body.
|
|
37
|
+
|
|
38
|
+
Patterns are code defaults, so these apply automatically on upgrade — unless you hand-customized `keywordPatterns` / `keywordPersonalPatterns` in your config, in which case your lists win and you can port the additions by hand.
|
|
39
|
+
|
|
40
|
+
## P1 fixes: stale answers and stranded files (D64)
|
|
41
|
+
|
|
42
|
+
Second batch from the whole-project review — the index, the files, and the guidance can no longer quietly disagree:
|
|
43
|
+
|
|
44
|
+
- The opencode plugin now re-syncs the index before every tool call (previously only at startup): memories added via the CLI or another client are visible right away instead of after a restart.
|
|
45
|
+
- Superseding a published memory re-enters review as `proposed` instead of a stranded `draft` no command could advance; `promote` can move it forward again.
|
|
46
|
+
- All memory-file rewrites are atomic (tmp + rename), not just creates.
|
|
47
|
+
- A retracted memory can't be flipped back to `active` via status changes — save a new memory if the content is valid again.
|
|
48
|
+
- Proposed copies of personal memories are `visibility: internal` (was: `private`, which export silently excluded from team bundles).
|
|
49
|
+
- `uninstall` finds the real global opencode config (`.jsonc` included), and `doctor` checks all eleven MCP tools plus stranded v1 (`user`-scope) memories.
|
|
50
|
+
- `memory_status` output is capped per section; `memory_list` accepts `scope: both`; `forget` notes when the deleted file was in the repo; `where` prints the config path.
|
|
51
|
+
|
|
12
52
|
## Correctness fixes from the whole-project review (D63)
|
|
13
53
|
|
|
14
54
|
Six silent-wrong-answer bugs found by an external three-lens review, each fixed with a regression test:
|
package/README.md
CHANGED
|
@@ -222,9 +222,10 @@ open-memex init --client vscode
|
|
|
222
222
|
```
|
|
223
223
|
|
|
224
224
|
Writes the MCP server entry; reload the window afterwards and confirm the
|
|
225
|
-
`open-memex` server is started in Copilot Chat's MCP panel. By default
|
|
226
|
-
|
|
227
|
-
`
|
|
225
|
+
`open-memex` server is started in Copilot Chat's MCP panel. By default this
|
|
226
|
+
writes a project-level `.vscode/mcp.json` (an interactive run asks which
|
|
227
|
+
level you want); add `--global` for VS Code's *user-level* config instead —
|
|
228
|
+
one setup that works in every project.
|
|
228
229
|
|
|
229
230
|
**Cursor:**
|
|
230
231
|
|
|
@@ -232,8 +233,8 @@ server entry goes to VS Code's *user-level* config (works in every project);
|
|
|
232
233
|
open-memex init --client cursor
|
|
233
234
|
```
|
|
234
235
|
|
|
235
|
-
Same shape as VS Code:
|
|
236
|
-
|
|
236
|
+
Same shape as VS Code: project-level `.cursor/mcp.json` by default,
|
|
237
|
+
user-level MCP config with `--global`, plus Copilot-style instructions.
|
|
237
238
|
|
|
238
239
|
**opencode** (native plugin — recommended):
|
|
239
240
|
|
|
@@ -363,12 +364,18 @@ snippet to paste instead.
|
|
|
363
364
|
Three ways memories get in:
|
|
364
365
|
|
|
365
366
|
- **Keyword triggers** (opencode native plugin only): say `remember …`,
|
|
366
|
-
`note that …`, `don't forget …`, `TIL …`, `save this …` — or in Chinese
|
|
367
|
-
`记住…` /
|
|
367
|
+
`note that …`, `don't forget: …`, `TIL …`, `save this …` — or in Chinese
|
|
368
|
+
`记住…` / `记一下…` / `记录一下…` / `别忘了:…` — and the sentence is captured
|
|
368
369
|
without any tool call. Captures land in the current **project** by default;
|
|
369
|
-
phrases that signal "this is about me" — `remember for me …`,
|
|
370
|
-
`替我记…`, `我觉得…`, `我喜欢…` — go to **personal** instead, and
|
|
370
|
+
phrases that signal "this is about me" — `remember for me …`, `help me remember: …`,
|
|
371
|
+
`记住我…`, `替我记…`, `帮我记…`, `我觉得…`, `我喜欢…` — go to **personal** instead, and
|
|
371
372
|
team-context phrases (`我们决定…`, `帮我们记住…`) stay in project.
|
|
373
|
+
Two rules keep the noise down (D67): a trigger must be a *statement to the
|
|
374
|
+
store*, so the narration forms `记得…` / `remind me to…` / `别忘了带伞` /
|
|
375
|
+
`don't forget the wifi password` never fire (add a separator — `别忘了:…`,
|
|
376
|
+
`don't forget: …` — or `that` to make it an instruction); and a captured body
|
|
377
|
+
under 3 characters is rejected as a fragment rather than saved — `capture
|
|
378
|
+
--dry-run` says so instead of dropping it silently.
|
|
372
379
|
- **The agent saves it**: in any editor, ask your agent to remember something
|
|
373
380
|
(or it saves on its own when you state a fact worth keeping) — it calls
|
|
374
381
|
`memory_add`. The routing above is a heuristic; you can always say "save this
|
|
@@ -378,7 +385,7 @@ Three ways memories get in:
|
|
|
378
385
|
conventions, gotchas, approaches tried and abandoned — and saves only the
|
|
379
386
|
ones you approve. Nothing is written silently: no draft is created behind
|
|
380
387
|
your back.
|
|
381
|
-
- **The CLI**: `open-memex add "…"` with optional `--scope` / `--
|
|
388
|
+
- **The CLI**: `open-memex add "…"` with optional `--scope` / `--tag` / `--type`
|
|
382
389
|
/ `--aliases`.
|
|
383
390
|
|
|
384
391
|
**Aliases.** Each saved memory can carry up to 4 alternate phrasings
|
|
@@ -409,7 +416,7 @@ they only matter once you share memories through Git.
|
|
|
409
416
|
|---|---|
|
|
410
417
|
| ● `memory_add` | Save a fact, preference, decision, note |
|
|
411
418
|
| ● `memory_search` | Keyword search (BM25) across project + personal memories |
|
|
412
|
-
| ● `memory_list` | List memories in a scope, newest first |
|
|
419
|
+
| ● `memory_list` | List memories in a scope (`project`, `personal`, or `both`), newest first |
|
|
413
420
|
| ● `memory_supersede` | Replace a memory with a newer version (keeps a supersede chain) |
|
|
414
421
|
| ● `memory_forget` | Delete a memory by id |
|
|
415
422
|
| `memory_status` | Show the sync queue: outbox drafts, repo review states, uncommitted files |
|
|
@@ -605,7 +612,7 @@ open-memex config set sync.autoPull true # best-effort pull at MCP session sta
|
|
|
605
612
|
```
|
|
606
613
|
|
|
607
614
|
Settable keys: `maxProjectMemories`, `maxProfileItems`, `injectOnFirstTurn`,
|
|
608
|
-
`keywordCaptureEnabled`, `logLevel`, `memoryDir`, and `sync.autoPull` (a dotted
|
|
615
|
+
`keywordCaptureEnabled`, `captureAliases`, `logLevel`, `memoryDir`, and `sync.autoPull` (a dotted
|
|
609
616
|
key that writes into the nested `sync` object). Full design:
|
|
610
617
|
[docs/V2-DESIGN.md](./docs/V2-DESIGN.md).
|
|
611
618
|
|
|
@@ -635,6 +642,7 @@ open-memex add "This repo uses better-sqlite3" --type fact
|
|
|
635
642
|
open-memex search "auth flow"
|
|
636
643
|
open-memex search "auth flow" --explain # show FTS expression, scores, lifecycle-hidden counts
|
|
637
644
|
open-memex list --scope project
|
|
645
|
+
open-memex list --scope both # each scope's newest under its own header
|
|
638
646
|
open-memex supersede <id> "Updated content"
|
|
639
647
|
open-memex status <id> deprecated
|
|
640
648
|
open-memex forget <id>
|
package/README.zh-CN.md
CHANGED
|
@@ -162,7 +162,7 @@ npx -y open-memex init --yes
|
|
|
162
162
|
open-memex init --client vscode
|
|
163
163
|
```
|
|
164
164
|
|
|
165
|
-
写入 MCP
|
|
165
|
+
写入 MCP 服务器配置;重载窗口,确认 Copilot Chat 的 MCP 面板里 `open-memex` 服务器已启动。默认写到项目级 `.vscode/mcp.json`(交互式运行会问你选哪一级);加 `--global` 改写 VS Code 的*用户级*配置——一次设置,每个项目都生效。
|
|
166
166
|
|
|
167
167
|
**Cursor**:
|
|
168
168
|
|
|
@@ -170,7 +170,7 @@ open-memex init --client vscode
|
|
|
170
170
|
open-memex init --client cursor
|
|
171
171
|
```
|
|
172
172
|
|
|
173
|
-
形态和 VS Code
|
|
173
|
+
形态和 VS Code 一样:默认项目级 `.cursor/mcp.json`,`--global` 改为用户级 MCP 配置,外加 Copilot 风格的指令文件。
|
|
174
174
|
|
|
175
175
|
**opencode**(原生插件——推荐):
|
|
176
176
|
|
|
@@ -252,10 +252,10 @@ open-memex uninstall --yes
|
|
|
252
252
|
|
|
253
253
|
三种存入方式:
|
|
254
254
|
|
|
255
|
-
- **关键词触发**(仅 opencode 原生插件):说 `remember …`、`note that …`、`don't forget …`、`TIL …`、`save this …`——中文 `记住…` /
|
|
255
|
+
- **关键词触发**(仅 opencode 原生插件):说 `remember …`、`note that …`、`don't forget: …`、`TIL …`、`save this …`——中文 `记住…` / `记一下…` / `记录一下…` / `别忘了:…`——这句话就被存下来,不需要任何工具调用。捕获默认进当前 **project**;`remember for me …`、`help me remember: …`、`记住我…`、`替我记…`、`帮我记…`、`我觉得…`、`我喜欢…` 这类"这是关于我"的信号会改为进 **personal**;`我们决定…`、`帮我们记住…` 等团队语境仍留在项目里。两条降噪规则(D67):触发词必须是**对存储说的话**,所以叙述型的 `记得…` / `remind me to…` / `别忘了带伞` / `don't forget the wifi password` 一律不触发(加一个分隔符——`别忘了:…`、`don't forget: …`——或用 `that`,才算指令);捕获内容不足 3 个字符按碎片丢弃,不会存下来——`capture --dry-run` 会明确说明,不再悄悄吞掉。
|
|
256
256
|
- **助手主动保存**:在任何编辑器里,你让助手记住某件事(或者你陈述了一个值得记的事实、它自己判断该保存),它会调用 `memory_add`。上面的路由是启发式规则;你可以明说"存到我的个人记忆",或用 CLI 加 `--scope` 明确指定。
|
|
257
257
|
- **检查点提议**:一个任务收尾时,助手会提议 1–3 条从这轮工作里提炼的候选记忆——决定及其理由、约定、踩过的坑、走过又放弃的路——只有你点头的那几条才存。不会有任何东西在你不知情时被写下来。
|
|
258
|
-
- **CLI**:`open-memex add "…"`,可选 `--scope` / `--
|
|
258
|
+
- **CLI**:`open-memex add "…"`,可选 `--scope` / `--tag` / `--type` / `--aliases`。
|
|
259
259
|
|
|
260
260
|
**别名。** 每条记忆可以带最多 4 个换一种说法(同义词、另一种语言的对应词),和记忆一起进索引,这样问法不同也能找到——比如搜 "vacation days" 能找到假期政策那条。`init` 时会问一次是否开启(默认开);随时可用 `open-memex config set captureAliases false` 关掉。
|
|
261
261
|
|
|
@@ -275,7 +275,7 @@ MCP 服务器暴露十一个工具;opencode 原生插件暴露其中五个核
|
|
|
275
275
|
|---|---|
|
|
276
276
|
| ● `memory_add` | 保存一条事实、偏好、决策、笔记 |
|
|
277
277
|
| ● `memory_search` | 关键词搜索(BM25),横跨项目 + 个人记忆 |
|
|
278
|
-
| ● `memory_list` |
|
|
278
|
+
| ● `memory_list` | 列出某个作用域(`project`、`personal` 或 `both`)的记忆,最新在前 |
|
|
279
279
|
| ● `memory_supersede` | 用新版本替换一条记忆(保留 supersede 链) |
|
|
280
280
|
| ● `memory_forget` | 按 id 删除一条记忆 |
|
|
281
281
|
| `memory_status` | 显示同步队列:outbox 草稿、仓库评审状态、未提交文件 |
|
|
@@ -323,7 +323,7 @@ MCP 服务器暴露十一个工具;opencode 原生插件暴露其中五个核
|
|
|
323
323
|
4. **PR 评审**——记忆是纯 Markdown;评审者走正常分支/PR 流程批准、要求修改或拒绝。
|
|
324
324
|
5. **回想**——已发布的记忆在会话开始时注入,平时也能随时搜,人和助手都一样。
|
|
325
325
|
|
|
326
|
-
看护共享记忆的人遵循 [维护者约定](./docs/CURATOR.md)
|
|
326
|
+
看护共享记忆的人遵循 [维护者约定](./docs/CURATOR.md)(英文):什么该批准、什么该打回、哪些卫生规则让共享记忆不腐烂。
|
|
327
327
|
|
|
328
328
|
## 回想:记忆是怎么回来的
|
|
329
329
|
|
|
@@ -414,7 +414,7 @@ open-memex config set maxProjectMemories 12
|
|
|
414
414
|
open-memex config set sync.autoPull true # MCP 会话开始时尽力而为地 pull
|
|
415
415
|
```
|
|
416
416
|
|
|
417
|
-
可设置的键:`maxProjectMemories`、`maxProfileItems`、`injectOnFirstTurn`、`keywordCaptureEnabled`、`logLevel`、`memoryDir`,以及 `sync.autoPull`(带点的键,写入嵌套的 `sync` 对象)。完整设计:[docs/V2-DESIGN.md](./docs/V2-DESIGN.md)
|
|
417
|
+
可设置的键:`maxProjectMemories`、`maxProfileItems`、`injectOnFirstTurn`、`keywordCaptureEnabled`、`captureAliases`、`logLevel`、`memoryDir`,以及 `sync.autoPull`(带点的键,写入嵌套的 `sync` 对象)。完整设计:[docs/V2-DESIGN.md](./docs/V2-DESIGN.md)(英文)。
|
|
418
418
|
|
|
419
419
|
## CLI 参考
|
|
420
420
|
|
|
@@ -442,6 +442,7 @@ open-memex add "This repo uses better-sqlite3" --type fact
|
|
|
442
442
|
open-memex search "auth flow"
|
|
443
443
|
open-memex search "auth flow" --explain # 显示 FTS 表达式、分数、被生命周期藏掉的计数
|
|
444
444
|
open-memex list --scope project
|
|
445
|
+
open-memex list --scope both # 两个作用域各自最新的,各带一个标题
|
|
445
446
|
open-memex supersede <id> "Updated content"
|
|
446
447
|
open-memex status <id> deprecated
|
|
447
448
|
open-memex forget <id>
|
|
@@ -462,7 +463,7 @@ open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
|
462
463
|
# 把你点名的草稿移进 .ai/open-memex/,标为 "proposed":
|
|
463
464
|
# 复制、翻转 review_state、在当前分支本地 git 提交。
|
|
464
465
|
# 永不自己建分支——建分支是你(或拿到完整链路明确许可的助手)的事。
|
|
465
|
-
#
|
|
466
|
+
# 全有全无;冲突(同 id 不同内容)干净地中止。
|
|
466
467
|
# 打印 push + gh pr 命令;持有你 Yes 的助手可以一并执行 push/PR。
|
|
467
468
|
# --branch <name> 先建分支(助手全链路路径)。PR base 默认是当前分支
|
|
468
469
|
# (记忆 PR 叠在你的工作分支上);--base 可改指到 main 或你的评审分支。
|
|
@@ -473,7 +474,7 @@ open-memex pr-status [--apply]
|
|
|
473
474
|
# 要求修改 → 只给建议。默认只报告;--apply 在本地执行映射的迁移(不推送)。
|
|
474
475
|
|
|
475
476
|
open-memex pull
|
|
476
|
-
# 从 git 远程拉共享记忆:fetch +
|
|
477
|
+
# 从 git 远程拉共享记忆:fetch + 纯 fast-forward。
|
|
477
478
|
# 分支分叉则明确报错失败——open-memex 永不强制合并;手工解决后再 pull。
|
|
478
479
|
# 成功后本地索引重新同步。pull 默认显式触发;设置
|
|
479
480
|
# `open-memex config set sync.autoPull true` 可在 MCP 会话开始时尽力拉一次
|
|
@@ -502,7 +503,7 @@ open-memex distill-agents [--scope project|personal] [--type t1,t2] [--limit N]
|
|
|
502
503
|
|
|
503
504
|
open-memex propose <id...> --to project [--local-approve]
|
|
504
505
|
# 一次提议一条或多条个人记忆(一个分支、一个 PR);
|
|
505
|
-
# 每条复制并生成新 id
|
|
506
|
+
# 每条复制并生成新 id。全有全无:任何一条 id 出错,整批中止,绝不半途。
|
|
506
507
|
# 把一条个人记忆复制到项目作用域作为评审候选
|
|
507
508
|
# (是复制不是移动——个人原件保留)。结果落进 outbox;
|
|
508
509
|
# 准备进仓库时再跑 sync-status / submit。
|
|
@@ -547,7 +548,7 @@ npx -y open-memex mcp # 无需安装
|
|
|
547
548
|
- **project(项目)**——限定在当前仓库,键由 git origin URL 的 hash 派生(所以同一仓库的多个 clone 共享一个作用域),没有远程时退回到当前工作目录路径。新记忆的默认作用域。
|
|
548
549
|
- **personal(个人)**——横跨你的所有项目,仅限本机,永不向外同步。放个人偏好用。(v1 里叫 `user`;`migrate --to-v2` 会改名。)
|
|
549
550
|
|
|
550
|
-
完整的 scope 模型(键派生、迁移、可见性、保留名)见 [docs/SCOPES.md](./docs/SCOPES.md)
|
|
551
|
+
完整的 scope 模型(键派生、迁移、可见性、保留名)见 [docs/SCOPES.md](./docs/SCOPES.md)(英文)。[概念指南](./docs/CONCEPTS.zh-CN.md)把心智模型完整走了一遍。
|
|
551
552
|
|
|
552
553
|
## 故障排查
|
|
553
554
|
|
|
@@ -602,7 +603,7 @@ Windows 上已加载的 DLL 是锁定的:如果 open-memex 的 MCP 服务器
|
|
|
602
603
|
|
|
603
604
|
## 项目状态
|
|
604
605
|
|
|
605
|
-
open-memex 是稳定且日常在用的;当前稳定线以 `latest` 发到 npm 上,`alpha` 构建给测试者。发版历史在 [GitHub Releases](https://github.com/stoneskin/open-memex/releases);设计决策记录在只追加的日志 [docs/V2-DESIGN.md](./docs/V2-DESIGN.md)
|
|
606
|
+
open-memex 是稳定且日常在用的;当前稳定线以 `latest` 发到 npm 上,`alpha` 构建给测试者。发版历史在 [GitHub Releases](https://github.com/stoneskin/open-memex/releases);设计决策记录在只追加的日志 [docs/V2-DESIGN.md](./docs/V2-DESIGN.md) 里(英文)。
|
|
606
607
|
|
|
607
608
|
在望的方向(不承诺版本号):更多编辑器的原生助手插件(作为在同一组 MCP 工具之上的增强路径);本地 embedding 作为可选择开启的实验(没经询问,永不下载任何模型);只有在真实多仓库共享、ACL 或合规需求出现时才做 org 层。
|
|
608
609
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
f4cf4e253bbf0f3254ec439b1344af50e88cc4fd068b04be73cdc20ee770e819
|
package/dist/capture/keywords.js
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* D67: capture-body length bounds. The lower bound rejects fragments — a
|
|
3
|
+
* trigger that yields "这个" or "OK" is a match on the trigger, not a memory,
|
|
4
|
+
* and a 2-char memory is noise in every future session's injection. The floor
|
|
5
|
+
* stays deliberately above 1: D67 considered lowering it and decided against
|
|
6
|
+
* it. What changed is that a rejection is no longer silent (see KeywordDrop).
|
|
7
|
+
*/
|
|
8
|
+
export const MIN_CAPTURE_CHARS = 3;
|
|
9
|
+
export const MAX_CAPTURE_CHARS = 2000;
|
|
10
|
+
/**
|
|
11
|
+
* Scan user text for memory-capture keyword triggers, reporting both what
|
|
12
|
+
* would be captured and what matched but was rejected (D67) — a dropped match
|
|
13
|
+
* that says nothing reads as a broken feature.
|
|
3
14
|
* Each pattern must have a single capture group whose value becomes the memory body.
|
|
4
15
|
* Personal patterns (cfg.keywordPersonalPatterns) force the personal scope.
|
|
5
16
|
*
|
|
6
17
|
* Personal patterns run first and claim their line: an explicit scope signal
|
|
7
18
|
* (e.g. "记住我对花粉过敏") must not also fire the generic 记住 into the project scope.
|
|
8
19
|
*/
|
|
9
|
-
export function
|
|
10
|
-
if (!cfg.keywordCaptureEnabled)
|
|
11
|
-
return [];
|
|
20
|
+
export function scanKeywords(userText, cfg) {
|
|
12
21
|
const hits = [];
|
|
22
|
+
const dropped = [];
|
|
23
|
+
if (!cfg.keywordCaptureEnabled)
|
|
24
|
+
return { hits, dropped };
|
|
13
25
|
const lines = userText.split(/\r?\n/);
|
|
14
26
|
const claimed = new Set();
|
|
15
27
|
const scan = (patterns, personal, skipClaimed) => {
|
|
@@ -28,16 +40,37 @@ export function detectKeywords(userText, cfg) {
|
|
|
28
40
|
if (!m || !m[1])
|
|
29
41
|
continue;
|
|
30
42
|
const content = m[1].trim().replace(/[.!?]$/, "").trim();
|
|
31
|
-
if (
|
|
43
|
+
if (personal)
|
|
44
|
+
claimed.add(i);
|
|
45
|
+
if (content.length >= MIN_CAPTURE_CHARS && content.length <= MAX_CAPTURE_CHARS) {
|
|
32
46
|
hits.push({ content, pattern: src, personal });
|
|
33
|
-
if (personal)
|
|
34
|
-
claimed.add(i);
|
|
35
47
|
break; // one hit per pattern per message
|
|
36
48
|
}
|
|
49
|
+
// Rejected: record it once per pattern. The line stays claimed for
|
|
50
|
+
// personal patterns even here - "记住我:OK" must not fall through to
|
|
51
|
+
// the generic 记住 and be saved as "我:OK" (D67).
|
|
52
|
+
if (!dropped.some((d) => d.pattern === src && d.content === content)) {
|
|
53
|
+
dropped.push({
|
|
54
|
+
content,
|
|
55
|
+
pattern: src,
|
|
56
|
+
personal,
|
|
57
|
+
reason: content.length < MIN_CAPTURE_CHARS ? "too-short" : "too-long",
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
break;
|
|
37
61
|
}
|
|
38
62
|
}
|
|
39
63
|
};
|
|
40
64
|
scan(cfg.keywordPersonalPatterns, true, false);
|
|
41
65
|
scan(cfg.keywordPatterns, false, true);
|
|
42
|
-
|
|
66
|
+
if (cfg.logLevel === "debug" && dropped.length > 0) {
|
|
67
|
+
for (const d of dropped) {
|
|
68
|
+
console.error(`[open-memex] keyword match not captured (${d.reason === "too-short" ? `body ${d.content.length} chars < ${MIN_CAPTURE_CHARS}` : `body ${d.content.length} chars > ${MAX_CAPTURE_CHARS}`}): "${d.content}"`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { hits, dropped };
|
|
72
|
+
}
|
|
73
|
+
/** Capture-only view of {@link scanKeywords} (drops are logged, not returned). */
|
|
74
|
+
export function detectKeywords(userText, cfg) {
|
|
75
|
+
return scanKeywords(userText, cfg).hits;
|
|
43
76
|
}
|
package/dist/cli.js
CHANGED
|
@@ -11,7 +11,7 @@ import { getSyncStatus, formatSyncStatus, submitMemories } from "./submit.js";
|
|
|
11
11
|
import { getPrStatus, formatPrStatus, applyPrStatus } from "./github.js";
|
|
12
12
|
import { search, list, hitStateLabel } from "./retrieve/search.js";
|
|
13
13
|
import { writeMemoryFile, readMemoryFile, ulid, msToRfc3339, normalizeAliases, } from "./store/markdown.js";
|
|
14
|
-
import { loadConfig } from "./config.js";
|
|
14
|
+
import { loadConfig, configSource } from "./config.js";
|
|
15
15
|
import { paths, projectRoot } from "./paths.js";
|
|
16
16
|
import { redact } from "./redact.js";
|
|
17
17
|
import { resolveMcpCommand } from "./init.js";
|
|
@@ -29,10 +29,10 @@ Example:
|
|
|
29
29
|
open-memex where`,
|
|
30
30
|
list: `List memories in a scope, newest first.
|
|
31
31
|
|
|
32
|
-
Usage: open-memex list [--scope project|personal] [--type T] [--limit N]
|
|
32
|
+
Usage: open-memex list [--scope project|personal|both] [--type T] [--limit N]
|
|
33
33
|
|
|
34
34
|
Flags:
|
|
35
|
-
--scope project (default) or
|
|
35
|
+
--scope project (default), personal, or both
|
|
36
36
|
--type filter by memory type
|
|
37
37
|
--limit max results
|
|
38
38
|
|
|
@@ -80,6 +80,9 @@ Example:
|
|
|
80
80
|
|
|
81
81
|
Usage: open-memex status <id> active|deprecated|retracted|archived
|
|
82
82
|
|
|
83
|
+
Retraction is one-way: a retracted memory cannot be set back to active
|
|
84
|
+
(it was withdrawn on purpose). Save the content as a new memory instead.
|
|
85
|
+
|
|
83
86
|
Example:
|
|
84
87
|
open-memex status 01ABC deprecated`,
|
|
85
88
|
forget: `Delete a memory by id.
|
|
@@ -720,12 +723,11 @@ async function main() {
|
|
|
720
723
|
process.exit(1);
|
|
721
724
|
}
|
|
722
725
|
const cfg = loadConfig();
|
|
723
|
-
const {
|
|
726
|
+
const { scanKeywords, MIN_CAPTURE_CHARS, MAX_CAPTURE_CHARS } = await import("./capture/keywords.js");
|
|
724
727
|
const text = rest.slice(1).join(" ");
|
|
725
|
-
const hits =
|
|
728
|
+
const { hits, dropped } = scanKeywords(text, cfg);
|
|
726
729
|
if (hits.length === 0) {
|
|
727
|
-
console.log("no keyword triggers
|
|
728
|
-
return;
|
|
730
|
+
console.log("no keyword triggers - nothing would be captured.");
|
|
729
731
|
}
|
|
730
732
|
for (const h of hits) {
|
|
731
733
|
const r = redact(h.content, cfg.redactPatterns);
|
|
@@ -734,6 +736,13 @@ async function main() {
|
|
|
734
736
|
console.log(` secret hit: ${r.hadSecret ? `yes (${r.matchedPattern}) — will be masked` : "no"}`);
|
|
735
737
|
console.log(` body: ${r.content.slice(0, 160)}${r.content.length > 160 ? "…" : ""}`);
|
|
736
738
|
}
|
|
739
|
+
// D67: a trigger that matched but produced a fragment is reported, not
|
|
740
|
+
// silently dropped - otherwise "记住:这个" just looks broken.
|
|
741
|
+
for (const d of dropped) {
|
|
742
|
+
console.log(d.reason === "too-short"
|
|
743
|
+
? `! matched but NOT captured: body "${d.content}" is ${d.content.length} chars (floor: ${MIN_CAPTURE_CHARS}). A trigger alone isn't a memory - write the fact: "记住:<fact>".`
|
|
744
|
+
: `! matched but NOT captured: body is ${d.content.length} chars (ceiling: ${MAX_CAPTURE_CHARS}) - save it with memory_add instead.`);
|
|
745
|
+
}
|
|
737
746
|
return;
|
|
738
747
|
}
|
|
739
748
|
// `mcp` starts the stdio MCP server (11 tools; the opencode plugin exposes 5).
|
|
@@ -752,6 +761,7 @@ async function main() {
|
|
|
752
761
|
if (cmd === "where") {
|
|
753
762
|
const p = paths();
|
|
754
763
|
console.log(`root: ${p.root}`);
|
|
764
|
+
console.log(`config: ${configSource() ?? "(built-in defaults)"}`);
|
|
755
765
|
console.log(`memories: ${p.memories}`);
|
|
756
766
|
console.log(`index: ${p.indexDb}`);
|
|
757
767
|
console.log(`project: ${project.key}`);
|
|
@@ -766,20 +776,26 @@ async function main() {
|
|
|
766
776
|
}
|
|
767
777
|
if (cmd === "list") {
|
|
768
778
|
const flags = parseFlags(rest);
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
const
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
779
|
+
// D66: --scope both, on par with the memory_list tool — prints each
|
|
780
|
+
// scope's newest under its own header.
|
|
781
|
+
const scopes = flags.scope === "both" ? [project, PERSONAL_SCOPE] : [resolveCliScope(flags, project)];
|
|
782
|
+
for (const s of scopes) {
|
|
783
|
+
syncScope(s.key, "cli");
|
|
784
|
+
const hits = list(s.key, {
|
|
785
|
+
type: flags.type,
|
|
786
|
+
limit: flags.limit ? Number(flags.limit) : undefined,
|
|
787
|
+
});
|
|
788
|
+
if (scopes.length > 1)
|
|
789
|
+
console.log(`## ${s.kind} (${s.key})`);
|
|
790
|
+
if (hits.length === 0) {
|
|
791
|
+
console.log(`(no memories in ${s.key})`);
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
for (const h of hits) {
|
|
795
|
+
const dep = h.status === "deprecated" ? " [deprecated]" : "";
|
|
796
|
+
const rev = hitStateLabel(h);
|
|
797
|
+
console.log(`[${h.type}]${dep}${rev} ${h.id} ${h.snippet.replace(/\s+/g, " ").trim()}`);
|
|
798
|
+
}
|
|
783
799
|
}
|
|
784
800
|
return;
|
|
785
801
|
}
|
|
@@ -1089,7 +1105,7 @@ async function main() {
|
|
|
1089
1105
|
syncScope(project.key, "cli");
|
|
1090
1106
|
syncScope(PERSONAL_SCOPE.key, "cli");
|
|
1091
1107
|
try {
|
|
1092
|
-
console.log(formatSyncStatus(getSyncStatus()));
|
|
1108
|
+
console.log(formatSyncStatus(getSyncStatus(), { cap: Number.POSITIVE_INFINITY }));
|
|
1093
1109
|
}
|
|
1094
1110
|
catch (e) {
|
|
1095
1111
|
console.error(`sync-status failed: ${e.message}`);
|
package/dist/config.js
CHANGED
|
@@ -8,25 +8,39 @@ export const DEFAULT_CONFIG = {
|
|
|
8
8
|
keywordCaptureEnabled: true,
|
|
9
9
|
captureAliases: true,
|
|
10
10
|
keywordPatterns: [
|
|
11
|
-
"^\\s*remember(?!\\s+for\\s+me)(?:\\s+that)?[:,]?\\s+(.+)$",
|
|
12
|
-
"^\\s*(?:please\\s+)?
|
|
11
|
+
"^\\s*remember(?!\\s+for\\s+me)(?:\\s+that|\\s+to)?[:,]?\\s+(.+)$",
|
|
12
|
+
"^\\s*(?:please\\s+)?note(?:\\s+that)?[:,]?\\s+(.+)$",
|
|
13
|
+
// D67: "don't forget the wifi password" is narration addressed to the
|
|
14
|
+
// agent, not a statement to the store. An explicit marker - "that" or a
|
|
15
|
+
// colon - makes it an instruction ("don't forget that ..." / "don't
|
|
16
|
+
// forget: ..."). Mirrors 别忘了 below; see D67.
|
|
17
|
+
"^\\s*(?:please\\s+)?don'?t\\s+forget(?:\\s+that\\s+|[:,]\\s+)(.+)$",
|
|
13
18
|
"^\\s*TIL[:,]?\\s+(.+)$",
|
|
14
19
|
"^\\s*save\\s+(?:this|to\\s+memory)[:,]?\\s+(.+)$",
|
|
15
20
|
// Chinese equivalents
|
|
16
|
-
"^\\s
|
|
21
|
+
"^\\s*(?:请)?记住(?!(个人))[::,,]?\\s*(.+)$",
|
|
17
22
|
"^\\s*(?:请)?(?:记一下|记录一下)[::,,]?\\s*(.+)$",
|
|
18
|
-
"
|
|
23
|
+
// D67: separator required. "别忘了带伞" is narration ("don't forget your
|
|
24
|
+
// umbrella"); "别忘了:周五不发布" is an instruction to the store. Same
|
|
25
|
+
// rule as the English "don't forget" above.
|
|
26
|
+
"^\\s*别忘了[::,,]\\s*(.+)$",
|
|
19
27
|
// First-person plural: team/project context, NOT personal
|
|
20
28
|
"^\\s*我们认为[::,,]?\\s*(.+)$",
|
|
21
29
|
"^\\s*我们决定[::,,]?\\s*(.+)$",
|
|
22
|
-
|
|
30
|
+
// D67: the boundary applies to the BARE verb only. 记 + (住|录) + 一下 is a
|
|
31
|
+
// complete verb phrase, so the noun may follow directly ("帮我记住这个配置",
|
|
32
|
+
// "记一下这个约定"); only a bare 记 needs a separator, otherwise 帮我记得…
|
|
33
|
+
// would capture mid-word garbage. See D67.
|
|
34
|
+
"^\\s*(?:请)?帮我们记(?:(?:住|录)(?!得|着)(?:一下)?|一下|(?=[::,,\\s]|$))[::,,]?\\s*(.+)$",
|
|
23
35
|
],
|
|
24
36
|
keywordPersonalPatterns: [
|
|
25
37
|
"^\\s*remember\\s+for\\s+me(?:\\s+that)?[:,]?\\s+(.+)$",
|
|
26
|
-
"^\\s
|
|
38
|
+
"^\\s*(?:请)?记住(个人)[::,,]?\\s*(.+)$",
|
|
27
39
|
// First-person singular: personal scope ("我" → 个人, "我们" → 项目)
|
|
28
|
-
"^\\s
|
|
29
|
-
"^\\s
|
|
40
|
+
"^\\s*(?:请)?记住我(?!们)[::,,]?\\s*(.+)$",
|
|
41
|
+
"^\\s*(?:请)?替我记(?:(?:住|录)(?!得|着)(?:一下)?|一下|(?=[::,,\\s]|$))[::,,]?\\s*(.+)$",
|
|
42
|
+
"^\\s*(?:请)?帮我记(?:(?:住|录)(?!得|着)(?:一下)?|一下|(?=[::,,\\s]|$))[::,,]?\\s*(.+)$",
|
|
43
|
+
"^\\s*help\\s+me\\s+remember(?:\\s+that)?[:,]?\\s+(.+)$",
|
|
30
44
|
"^\\s*我觉得[::,,]?\\s*(.+)$",
|
|
31
45
|
"^\\s*我喜欢[::,,]?\\s*(.+)$",
|
|
32
46
|
],
|
package/dist/distill-agents.js
CHANGED
|
@@ -57,7 +57,12 @@ export function distillAgentsMarkdown(opts) {
|
|
|
57
57
|
// (D53: the checkpoint mechanism is gone).
|
|
58
58
|
lines.push(`### Memory hygiene (open-memex)`);
|
|
59
59
|
lines.push(``);
|
|
60
|
-
lines.push(`- When you finish a task the user would describe in one sentence, distill`, ` the session: propose 1–3 short memories capturing the useful`, ` conclusion — what was learned or decided, how an issue was resolved, what`, ` to avoid, where the authoritative doc lives — not the raw transcript.`, ` Save nothing without user approval.`, ` Worth keeping: decisions and their reasons, preferences, conventions,`, ` gotchas, approaches tried and abandoned. Not worth keeping: one-off task`, ` details or anything re-derivable from the code.`, `- If the knowledge already lives in project docs, save a \`reference\` memory`, ` pointing at the doc instead of copying it.`, `- When saving via \`memory_add\`, attach 2–4 aliases (alternate phrasings,`, ` synonyms, equivalents in the user's other language) when the install has`, ` capture aliases enabled — they make reworded questions find the memory
|
|
60
|
+
lines.push(`- When you finish a task the user would describe in one sentence, distill`, ` the session: propose 1–3 short memories capturing the useful`, ` conclusion — what was learned or decided, how an issue was resolved, what`, ` to avoid, where the authoritative doc lives — not the raw transcript.`, ` Save nothing without user approval.`, ` Worth keeping: decisions and their reasons, preferences, conventions,`, ` gotchas, approaches tried and abandoned. Not worth keeping: one-off task`, ` details or anything re-derivable from the code.`, `- If the knowledge already lives in project docs, save a \`reference\` memory`, ` pointing at the doc instead of copying it.`, `- When saving via \`memory_add\`, attach 2–4 aliases (alternate phrasings,`, ` synonyms, equivalents in the user's other language) when the install has`, ` capture aliases enabled — they make reworded questions find the memory.`,
|
|
61
|
+
// D64: the skills runbook and the MCP handshake both teach marking
|
|
62
|
+
// inferred captures source: "inference"; this footer omitted it, so
|
|
63
|
+
// opencode agents saved distilled conclusions as if the user had
|
|
64
|
+
// stated them ("tool"). Keep the rubric one voice across surfaces.
|
|
65
|
+
`- Mark captures you infer (rather than the user stating) with`, ` \`source: "inference"\` — provenance matters more than polish.`);
|
|
61
66
|
lines.push(``);
|
|
62
67
|
return lines.join("\n");
|
|
63
68
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -12,12 +12,20 @@ import { paths, homeOverride } from "./paths.js";
|
|
|
12
12
|
import { resolveCwdScope } from "./scope.js";
|
|
13
13
|
import { db, backendName } from "./store/db.js";
|
|
14
14
|
import { LEGACY_PACKAGE_NAME, opencodeGlobalConfigPath, userMcpConfigPath } from "./init.js";
|
|
15
|
+
// D64: the MCP server exposes eleven tools; checking only the five the
|
|
16
|
+
// opencode plugin carries let a half-registered server pass as healthy.
|
|
15
17
|
const EXPECTED_TOOLS = [
|
|
16
18
|
"memory_add",
|
|
17
19
|
"memory_search",
|
|
18
20
|
"memory_list",
|
|
19
21
|
"memory_supersede",
|
|
20
22
|
"memory_forget",
|
|
23
|
+
"memory_status",
|
|
24
|
+
"memory_submit",
|
|
25
|
+
"memory_propose",
|
|
26
|
+
"memory_promote",
|
|
27
|
+
"memory_resolve",
|
|
28
|
+
"memory_pr_status",
|
|
21
29
|
];
|
|
22
30
|
function nodeCheck() {
|
|
23
31
|
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
@@ -535,6 +543,19 @@ function legacyCheck() {
|
|
|
535
543
|
const legacyDir = path.join(path.dirname(root), LEGACY_PACKAGE_NAME);
|
|
536
544
|
if (fs.existsSync(legacyDir) && fs.statSync(legacyDir).isDirectory())
|
|
537
545
|
found.push(`legacy data dir ${legacyDir} — merge it with \`open-memex migrate --to-v2\``);
|
|
546
|
+
// D64: v1 stored personal memories under the scope dir "user"; v2 reads
|
|
547
|
+
// only "personal". Files left there are invisible to every read path —
|
|
548
|
+
// an upgrader silently sees an empty personal scope. Surface them here;
|
|
549
|
+
// the migration itself stays explicit (no silent moves).
|
|
550
|
+
try {
|
|
551
|
+
const userDir = path.join(root, "memories", "user");
|
|
552
|
+
if (fs.existsSync(userDir) &&
|
|
553
|
+
fs.readdirSync(userDir).some((f) => f.endsWith(".md")))
|
|
554
|
+
found.push("v1 personal memories under " + userDir + " are invisible to v2 reads — convert them with `open-memex migrate --to-v2` (it moves them to the personal scope; preview with --dry-run)");
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
/* unreadable — not this check's problem */
|
|
558
|
+
}
|
|
538
559
|
for (const f of [opencodeGlobalConfigPath(), userMcpConfigPath("vscode"), userMcpConfigPath("cursor")]) {
|
|
539
560
|
try {
|
|
540
561
|
if (fs.existsSync(f) && fs.readFileSync(f, "utf8").includes(LEGACY_PACKAGE_NAME))
|
package/dist/init.js
CHANGED
|
@@ -56,11 +56,12 @@ You have a local memory MCP server (\`open-memex\`). Its tools are
|
|
|
56
56
|
these full names.
|
|
57
57
|
|
|
58
58
|
- The server tells you when project outbox drafts are waiting for review — in
|
|
59
|
-
tool results.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
tool results. When it does (or when the user asks), call \`memory_status\`
|
|
60
|
+
to see them; don't call it blindly at session start. When drafts are
|
|
61
|
+
waiting, summarize them (one line each) and ask the user which ones to
|
|
62
|
+
sync into the repo; sync NOTHING the user did not name. If you already
|
|
63
|
+
asked about these drafts this session, don't ask again. When the server
|
|
64
|
+
reports none waiting, do nothing.
|
|
64
65
|
- BE PROACTIVE about facts the user states directly: when the user shares a
|
|
65
66
|
decision, preference, project convention, or fix-and-cause worth remembering
|
|
66
67
|
across sessions, call \`memory_add\` without being asked. Keep each memory to
|
|
@@ -286,7 +287,17 @@ function opencodePluginDir() {
|
|
|
286
287
|
* loads open-memex in every project with no per-project init.
|
|
287
288
|
*/
|
|
288
289
|
export function opencodeGlobalConfigPath(home = os.homedir()) {
|
|
289
|
-
|
|
290
|
+
const dir = opencodeConfigDir(home, process.env.XDG_CONFIG_HOME);
|
|
291
|
+
// D64: init writes into whichever of .json/.jsonc already exists
|
|
292
|
+
// (writeOpencodeGlobalPlugin), so readers must resolve the same file.
|
|
293
|
+
// Hardcoding .json made uninstall report "nothing to remove" on
|
|
294
|
+
// .jsonc-only setups and left the plugin entry behind.
|
|
295
|
+
for (const name of ["opencode.jsonc", "opencode.json"]) {
|
|
296
|
+
const candidate = path.join(dir, name);
|
|
297
|
+
if (fs.existsSync(candidate))
|
|
298
|
+
return candidate;
|
|
299
|
+
}
|
|
300
|
+
return path.join(dir, "opencode.json");
|
|
290
301
|
}
|
|
291
302
|
/**
|
|
292
303
|
* Pure merge of the open-memex plugin URL into a parsed opencode config doc.
|