open-memex 0.5.0 → 0.6.0-alpha.4
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 +11 -4
- package/CONTRIBUTING.md +8 -5
- package/README.md +49 -11
- package/README.zh-CN.md +35 -11
- package/dist/cli.js +31 -10
- package/dist/distill-agents.js +3 -2
- package/dist/doctor.js +140 -2
- package/dist/first-run.js +75 -0
- package/dist/init.js +68 -42
- package/dist/mcp.js +49 -33
- package/dist/paths.js +8 -0
- package/dist/submit.js +13 -0
- package/dist/tools/memory.js +4 -4
- package/dist/tools/ops.js +16 -1
- package/docs/SCOPES.md +7 -6
- package/docs/TEST-PLAN.md +10 -3
- package/docs/USER-GUIDE.md +4 -5
- package/docs/USER-GUIDE.zh-CN.md +3 -4
- package/docs/V2-DESIGN.md +90 -1
- package/package.json +3 -2
- package/scripts/postinstall.js +9 -0
- package/src/cli.ts +32 -10
- package/src/distill-agents.ts +4 -3
- package/src/doctor.ts +139 -2
- package/src/first-run.ts +96 -0
- package/src/init.ts +69 -44
- package/src/mcp.ts +51 -32
- package/src/paths.ts +9 -0
- package/src/submit.ts +16 -0
- package/src/tools/memory.ts +4 -3
- package/src/tools/ops.ts +21 -1
package/AGENTS.md
CHANGED
|
@@ -12,7 +12,7 @@ via `prepublishOnly` — Node refuses `--experimental-strip-types` for files und
|
|
|
12
12
|
|
|
13
13
|
- **opencode host** loads `src/index.ts` under embedded **Bun**. SQLite here is `bun:sqlite` (built-in).
|
|
14
14
|
- **CLI** (`src/cli.ts`) and smoke tests run under **Node 22+** with `--experimental-strip-types`. SQLite here is `better-sqlite3` (native module).
|
|
15
|
-
- **MCP server** (`src/mcp.ts`, stdio) runs under **Node 22+** with `--experimental-strip-types`. It exposes the same
|
|
15
|
+
- **MCP server** (`src/mcp.ts`, stdio) runs under **Node 22+** with `--experimental-strip-types`. It exposes the same eleven memory tools to any MCP client (VS Code Copilot, Cursor, Claude Code). **stdout is the protocol channel — never log to stdout in `mcp.ts`; diagnostics go to stderr.**
|
|
16
16
|
|
|
17
17
|
`src/store/db.ts` picks the backend at runtime by sniffing `globalThis.Bun`. Both backends share the same surface (`new Database(path)`, `.exec`, `.prepare().run/all/get`, `.close`). Any DB code you write must stay on that common subset — do not import `better-sqlite3` or `bun:sqlite` directly outside `db.ts`.
|
|
18
18
|
|
|
@@ -61,9 +61,16 @@ resolves the server command
|
|
|
61
61
|
at init time — npx fallback when no durable bin is on PATH, D17),
|
|
62
62
|
`open-memex config` prints the effective config, `open-memex capture --dry-run "text"`
|
|
63
63
|
previews keyword capture without writing, `open-memex doctor` runs health checks
|
|
64
|
-
(node version, config, scope resolution, storage writability, MCP handshake).
|
|
64
|
+
(node version, config, scope resolution, storage writability, VS Code MCP enablement, MCP handshake).
|
|
65
65
|
`open-memex init` with no --client auto-detects and wires every installed editor
|
|
66
66
|
(`--yes` skips, scripts never prompt); `open-memex config set <key> <value>` edits settings after install.
|
|
67
|
+
`npm install -g` prints a pointer to `open-memex init` via a postinstall script
|
|
68
|
+
(print-only — postinstall must never prompt, it runs in CI/Docker; D50).
|
|
69
|
+
Bare `open-memex` on a machine where init never completed offers to run it on a
|
|
70
|
+
TTY (usage as before when non-interactive); init/uninstall maintain a
|
|
71
|
+
`.init.json` first-run marker at the data root so the offer is asked once (D50);
|
|
72
|
+
init ends with a one-line next-step hint (`open-memex add` + ask the agent to
|
|
73
|
+
recall it) so a first-time user sees what "it works" looks like (D51).
|
|
67
74
|
`open-memex uninstall [--client vscode|cursor|opencode|visualstudio] [--global] [--yes]`
|
|
68
75
|
reverses init — removes the MCP server entry / opencode plugin line / Copilot
|
|
69
76
|
instructions section; memory data never touched (D48); no --client → auto-detect
|
|
@@ -125,7 +132,7 @@ Context injection happens exactly once per session in `experimental.chat.system.
|
|
|
125
132
|
|
|
126
133
|
`docs/V2-DESIGN.md` is the frozen protocol v0.2 (zero open questions). Per its §12:
|
|
127
134
|
AGENTS.md answers "how should AI work here"; the design doc answers "why is it
|
|
128
|
-
built this way" (principles, iron rules, D1–
|
|
135
|
+
built this way" (principles, iron rules, D1–D49 decision log). Before changing
|
|
129
136
|
architecture, scope semantics, lifecycle, or the protocol surface (frontmatter
|
|
130
137
|
schema, MCP tools, CLI contract), read the relevant design section — the decision
|
|
131
138
|
log records what was already considered and rejected.
|
|
@@ -138,7 +145,7 @@ build roadmap; the design doc tracks the *why*.
|
|
|
138
145
|
|
|
139
146
|
## Branch workflow
|
|
140
147
|
|
|
141
|
-
`dev/<topic>` → PR → `main` (
|
|
148
|
+
`dev/<topic>` → PR → `main` (alpha versions published with
|
|
142
149
|
`npm publish --tag alpha`, npm `latest` moves only on stable releases).
|
|
143
150
|
The `V2` integration branch was retired 2026-09-29 — its job (isolating the
|
|
144
151
|
breaking v1→v2 transition) shipped with 0.3.0. Full rules: `CONTRIBUTING.md`.
|
package/CONTRIBUTING.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## Branch workflow
|
|
4
4
|
|
|
5
|
-
- `main` — the
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
- `main` — the only line (the `V2` integration branch was retired 2026-09-29).
|
|
6
|
+
Land work via pull request from a `dev/<topic>` branch. Exception: tiny
|
|
7
|
+
text-only doc tweaks may go directly to `main`, but only after the owner
|
|
8
|
+
has previewed and approved the exact change. Alpha versions
|
|
9
|
+
(e.g. `0.5.0-alpha.x`) live on `main`; publish them with
|
|
10
|
+
`npm publish --tag alpha` so the npm `latest` tag only moves on stable
|
|
11
|
+
releases.
|
|
9
12
|
- `dev/<topic>` — feature/fix branches (e.g. `dev/init-ux`). Open as **draft**
|
|
10
13
|
PRs against `main`; mark ready and merge after local testing passes.
|
|
11
14
|
|
|
@@ -25,7 +28,7 @@ branches are cheap.)
|
|
|
25
28
|
## Design authority
|
|
26
29
|
|
|
27
30
|
Protocol decisions live in `docs/V2-DESIGN.md` (frozen v0.2, decision log
|
|
28
|
-
D1–
|
|
31
|
+
D1–D49). Changing architecture, scope semantics, lifecycle, or the protocol
|
|
29
32
|
surface (frontmatter schema, MCP tools, CLI contract) requires updating the
|
|
30
33
|
design doc first. `AGENTS.md` has the working notes for AI agents; this file
|
|
31
34
|
has the contributor workflow.
|
package/README.md
CHANGED
|
@@ -23,6 +23,20 @@ automatically.
|
|
|
23
23
|
|
|
24
24
|
> No capture, nothing to inherit.
|
|
25
25
|
|
|
26
|
+
### One memory, every agent
|
|
27
|
+
|
|
28
|
+
Most developers don't use one AI tool — they use several on the same machine:
|
|
29
|
+
Copilot in VS Code, Cursor, opencode, Claude Code. Each tool keeps
|
|
30
|
+
its own silo: a decision made in one is invisible to the others.
|
|
31
|
+
|
|
32
|
+
open-memex is tool-agnostic by design. Memory lives as Markdown + SQLite next
|
|
33
|
+
to your project, and every editor talks to it through the same MCP interface.
|
|
34
|
+
Wire up two, three, five clients with `open-memex init` — they all read and
|
|
35
|
+
write the same memory on that machine. A constraint captured in VS Code is respected in
|
|
36
|
+
opencode; a lesson learned in Cursor shows up in Claude Code.
|
|
37
|
+
|
|
38
|
+
> Your memory belongs to you — not to your tools.
|
|
39
|
+
|
|
26
40
|
It also complements agentic development workflows (spec-driven development,
|
|
27
41
|
plan/implement/verify loops): plans produce decisions, verification produces
|
|
28
42
|
rules — open-memex is the memory layer that keeps them across sessions instead
|
|
@@ -36,6 +50,7 @@ of re-deriving them on every run.
|
|
|
36
50
|
| Review before sharing | Yes — outbox + PR | Varies | Yes | No |
|
|
37
51
|
| Agent recall | Session-start injection + search | API calls | Manual lookup | No |
|
|
38
52
|
| Human-readable | Plain Markdown files | Dashboard / API | Yes | No |
|
|
53
|
+
| Works across AI tools | Yes — any MCP client (same machine) | Per-integration | No | No |
|
|
39
54
|
|
|
40
55
|
- **Markdown files** as the source of truth (human-editable, git-friendly)
|
|
41
56
|
- **SQLite FTS5** as a rebuildable index (BM25 keyword search, via `better-sqlite3`)
|
|
@@ -90,7 +105,7 @@ personal scope: this machine only — never synced, never enters a repo.
|
|
|
90
105
|
npm install -g open-memex
|
|
91
106
|
```
|
|
92
107
|
|
|
93
|
-
This installs the `0.
|
|
108
|
+
This installs the `0.5.1` stable release.
|
|
94
109
|
|
|
95
110
|
**Alpha** (bleeding edge, for testers) — the `alpha` tag:
|
|
96
111
|
|
|
@@ -174,6 +189,14 @@ project has a solution file. Prefer to pick a single editor? Pass `--client`:
|
|
|
174
189
|
> project: init once, the wiring works in every project. It works the same
|
|
175
190
|
> whether the package was installed globally or run via npx.
|
|
176
191
|
|
|
192
|
+
Installing the package also prints a reminder to run `open-memex init` — the
|
|
193
|
+
wiring is a separate step. And if you run bare `open-memex` on a machine where
|
|
194
|
+
init never completed, it offers to run it for you (only on an interactive
|
|
195
|
+
terminal; scripts and CI just see the usual usage text). When init finishes,
|
|
196
|
+
it prints one concrete next step — save a memory with `open-memex add`, then
|
|
197
|
+
ask your agent to recall it — so a first-time user sees what "it works" looks
|
|
198
|
+
like.
|
|
199
|
+
|
|
177
200
|
**VS Code** (Copilot):
|
|
178
201
|
|
|
179
202
|
```sh
|
|
@@ -219,7 +242,8 @@ open-memex init --client opencode --global --yes
|
|
|
219
242
|
```
|
|
220
243
|
|
|
221
244
|
Merges `"plugin": ["file:///absolute/path/to/open-memex/src/index.ts"]` into your
|
|
222
|
-
user-level `~/.config/opencode/opencode.json`
|
|
245
|
+
user-level `~/.config/opencode/opencode.json` (or `opencode.jsonc` if that is
|
|
246
|
+
the file you already have) — one-time, every project picks it
|
|
223
247
|
up, no per-project init. You get keyword auto-capture and first-turn context
|
|
224
248
|
injection on top of the tools. (A config file with comments is left untouched —
|
|
225
249
|
add the `plugin` line by hand in that case.)
|
|
@@ -492,8 +516,9 @@ branches and PRs. Nothing moves without you naming it.
|
|
|
492
516
|
|
|
493
517
|
In an AI chat with the MCP server connected, just say **"sync memory"**
|
|
494
518
|
(or "同步记忆") — the agent runs the status check, summarizes the outbox drafts,
|
|
495
|
-
and asks which ones to sync. The
|
|
496
|
-
start
|
|
519
|
+
and asks which ones to sync. The server also tells the agent on its own: at
|
|
520
|
+
session start the handshake reports how many drafts are waiting, and every
|
|
521
|
+
memory-changing tool result carries the current count when it is non-zero.
|
|
497
522
|
|
|
498
523
|
```sh
|
|
499
524
|
open-memex sync-status
|
|
@@ -547,8 +572,8 @@ open-memex distill-agents [--scope project|personal] [--type t1,t2] [--limit N]
|
|
|
547
572
|
# (decisions, constraints, lessons, gotchas, howtos). Prints markdown;
|
|
548
573
|
# -o writes it to a file. You review and merge by hand — open-memex
|
|
549
574
|
# never rewrites your AGENTS.md on its own. The snippet ends with a
|
|
550
|
-
# "memory hygiene" section (§3.5
|
|
551
|
-
# AGENTS.md learn to propose distilled captures
|
|
575
|
+
# "memory hygiene" section (§3.5 distillation guidance) so agents reading
|
|
576
|
+
# AGENTS.md learn to propose distilled captures when a task ends.
|
|
552
577
|
|
|
553
578
|
open-memex propose <id...> --to project [--local-approve]
|
|
554
579
|
# propose one or several personal memories at once (one branch, one PR);
|
|
@@ -603,8 +628,9 @@ server with cwd set to your project root (`init` handles this for you).
|
|
|
603
628
|
> **Note:** MCP is request/response — it gives the agent tools, not the opencode
|
|
604
629
|
> plugin's automatic keyword capture or first-turn context injection. Proactive
|
|
605
630
|
> memory use depends on the agent's instructions: the server sends session-start
|
|
606
|
-
> guidance
|
|
607
|
-
>
|
|
631
|
+
> guidance in the MCP handshake `instructions` (including the live outbox draft
|
|
632
|
+
> count at session start, plus the pending count appended to memory-changing
|
|
633
|
+
> tool results when non-zero), and `init` writes the fuller version into the
|
|
608
634
|
> editor's instruction files. Both are advisory — no MCP consumer offers a hard
|
|
609
635
|
> session-start hook.
|
|
610
636
|
|
|
@@ -620,8 +646,20 @@ outbox → `sync-status` → `submit` (local branch+commit, push/PR on your Yes)
|
|
|
620
646
|
`export` / `import` archive for user portability (Markdown + manifest, no walled
|
|
621
647
|
garden; private excluded by default, `-a` / `--all` for full migration);
|
|
622
648
|
distill-to-AGENTS.md assist (`distill-agents`, propose-only — you merge by hand);
|
|
623
|
-
§3.5
|
|
624
|
-
|
|
649
|
+
§3.5 distillation in the MCP handshake + init instructions (the agent proposes
|
|
650
|
+
1–3 captures when a task ends, the human decides); 1–2 colleague pilot.
|
|
651
|
+
|
|
652
|
+
**`0.5.0` (stable):** init UX pass — `init --global` writes the editor wiring
|
|
653
|
+
once at user level (D45); bare `init` auto-detects installed editors and wires
|
|
654
|
+
them all (D46); non-JSON configs are left untouched with a paste-ready snippet
|
|
655
|
+
instead of an error (D47); `uninstall` reverses `init` without touching memory
|
|
656
|
+
data (D48); empty config files are treated as blank, not corrupt (D49).
|
|
657
|
+
"One memory, every agent": every editor on the same machine reads and writes
|
|
658
|
+
the same memory through one MCP interface.
|
|
659
|
+
|
|
660
|
+
**`0.5.1` (stable):** `--help` accuracy fixes — the `mcp` help text now states the
|
|
661
|
+
server exposes 11 tools (a superset of the opencode plugin's five memory tools),
|
|
662
|
+
and install hints point at the stable line instead of `@alpha` (F27).
|
|
625
663
|
|
|
626
664
|
**Future (signal-gated, no version committed):** org layer — org memory repo,
|
|
627
665
|
curator convention; native agent plugins (Claude Code / Codex hooks as
|
|
@@ -669,7 +707,7 @@ project. A per-project `.vscode/mcp.json` still wins when present, and the
|
|
|
669
707
|
entry keeps `cwd=${workspaceFolder}` so project-scope resolution keeps
|
|
670
708
|
working per window. If your user-level `mcp.json` has comments (VS Code
|
|
671
709
|
accepts JSONC), `init` leaves it alone and prints the exact snippet to add
|
|
672
|
-
by hand.
|
|
710
|
+
by hand. An empty file is treated as blank and written to directly.
|
|
673
711
|
|
|
674
712
|
**How do I remove the editor wiring?**
|
|
675
713
|
`open-memex uninstall` reverses `init`: it removes the MCP server entry,
|
package/README.zh-CN.md
CHANGED
|
@@ -21,6 +21,18 @@ open-memex 把值得记住的部分——决策、约束、教训——存成可
|
|
|
21
21
|
|
|
22
22
|
> No capture, nothing to inherit.(不记录,就无从传承。)
|
|
23
23
|
|
|
24
|
+
### 一份记忆,所有 Agent 通用
|
|
25
|
+
|
|
26
|
+
大多数开发者并不是只用一个 AI 工具——同一台电脑上可能装着 VS Code Copilot、
|
|
27
|
+
Cursor、opencode、Claude Code。但每个工具的记忆都是孤岛:在 A 里定好的决策,B 一无所知。
|
|
28
|
+
|
|
29
|
+
open-memex 天生与工具无关。记忆以 Markdown + SQLite 的形式存在项目旁边,
|
|
30
|
+
所有编辑器都通过同一个 MCP 接口读写。用 `open-memex init` 接上两三个客户端,
|
|
31
|
+
它们读写的是这台机器上的同一份记忆:在 VS Code 里记下的约束,opencode 会遵守;
|
|
32
|
+
在 Cursor 里学到的教训,Claude Code 也看得到。
|
|
33
|
+
|
|
34
|
+
> 记忆属于你,不属于工具。
|
|
35
|
+
|
|
24
36
|
它也补齐了 agentic 开发工作流(spec 驱动开发、plan/implement/verify 循环)缺的那一块:
|
|
25
37
|
plan 产出决策,verify 产出规则——open-memex 是让它们跨会话留存的记忆层,
|
|
26
38
|
而不是每次从头重新推导。
|
|
@@ -33,6 +45,7 @@ plan 产出决策,verify 产出规则——open-memex 是让它们跨会话留
|
|
|
33
45
|
| 分享前 review | 有——outbox + PR | 不一定 | 有 | 没有 |
|
|
34
46
|
| Agent 回忆 | 会话开始注入 + 搜索 | 调 API | 人工去查 | 没有 |
|
|
35
47
|
| 人类可读 | 纯 Markdown 文件 | 后台 / API | 有 | 没有 |
|
|
48
|
+
| 跨 AI 工具通用 | 可以——同一台机器上的任何 MCP 客户端 | 按集成逐个对接 | 不可以 | 不可以 |
|
|
36
49
|
|
|
37
50
|
- **Markdown 文件**是 source of truth(人类可读、git 友好)
|
|
38
51
|
- **SQLite FTS5** 做可重建索引(BM25 关键词检索,`better-sqlite3`)
|
|
@@ -87,7 +100,7 @@ personal scope:只属于这台机器——永不同步,永远进不了仓库
|
|
|
87
100
|
npm install -g open-memex
|
|
88
101
|
```
|
|
89
102
|
|
|
90
|
-
安装的是 `0.
|
|
103
|
+
安装的是 `0.5.1` 正式版。
|
|
91
104
|
|
|
92
105
|
**Alpha 版**(最新开发版,给测试者)——`alpha` 标签:
|
|
93
106
|
|
|
@@ -168,6 +181,12 @@ npx -y open-memex init --yes
|
|
|
168
181
|
> - `init --global` 是把*编辑器配置*写到用户级而不是项目里:init 一次,
|
|
169
182
|
> 每个项目都生效。不管包是全局安装的还是用 npx 临时跑的,效果一样。
|
|
170
183
|
|
|
184
|
+
装完包还会打印一句提醒,让你跑 `open-memex init`——接线是独立的一步。
|
|
185
|
+
如果你在从没跑过 init 的机器上直接敲 `open-memex`,它会问你要不要现在
|
|
186
|
+
init(只在交互终端里问;脚本和 CI 里看到的还是原来的 usage)。
|
|
187
|
+
init 跑完会打印一个具体的下一步——用 `open-memex add` 存一条记忆,再让
|
|
188
|
+
agent 回忆它——让第一次用的用户一眼看到"跑起来了"是什么样子。
|
|
189
|
+
|
|
171
190
|
**VS Code**(Copilot):
|
|
172
191
|
|
|
173
192
|
```sh
|
|
@@ -213,7 +232,8 @@ open-memex init --client opencode --global --yes
|
|
|
213
232
|
```
|
|
214
233
|
|
|
215
234
|
把 `"plugin": ["file:///absolute/path/to/open-memex/src/index.ts"]` 合并进用户级
|
|
216
|
-
`~/.config/opencode/opencode.json
|
|
235
|
+
`~/.config/opencode/opencode.json`(如果你用的是 `opencode.jsonc`,就合并进那个)
|
|
236
|
+
——一次配置,每个项目自动生效,不用逐个项目
|
|
217
237
|
init。在 tools 之外还能获得关键词自动捕获和首轮上下文注入。(带注释的配置文件
|
|
218
238
|
不会被改动——那种情况请手动加 `plugin` 这一行。)
|
|
219
239
|
|
|
@@ -468,8 +488,8 @@ project 草稿先住在 **appdata outbox**(git 看不见、跟分支无关)
|
|
|
468
488
|
没经过你点名,什么都不会动。
|
|
469
489
|
|
|
470
490
|
在接了 MCP 服务器的 AI 对话里,直接说 **"同步记忆"**(或 "sync memory")——
|
|
471
|
-
agent 会查状态、把 outbox
|
|
472
|
-
|
|
491
|
+
agent 会查状态、把 outbox 草稿逐条摘要、问你同步哪几条。服务器也会主动告诉
|
|
492
|
+
agent:新对话开始时握手里带待审草稿数,每次改记忆的 tool 返回里也带当前数(为零时不带)。
|
|
473
493
|
|
|
474
494
|
```sh
|
|
475
495
|
open-memex sync-status
|
|
@@ -516,7 +536,7 @@ open-memex distill-agents [--scope project|personal] [--type t1,t2] [--limit N]
|
|
|
516
536
|
# 把项目记忆(decision/constraint/lesson/gotcha/howto)提炼成
|
|
517
537
|
# AGENTS.md 片段。默认打印到 stdout;-o 写文件。人工审阅后手工合并——
|
|
518
538
|
# open-memex 永不自动改写你的 AGENTS.md。片段末尾带一段"记忆卫生"
|
|
519
|
-
# (§3.5
|
|
539
|
+
# (§3.5 蒸馏指引),让读 AGENTS.md 的 agent 学会在任务结束时提议蒸馏捕获。
|
|
520
540
|
|
|
521
541
|
open-memex propose <id...> --to project [--local-approve]
|
|
522
542
|
# 一次 propose 一条或多条(一个分支、一个 PR),每条独立新 id。
|
|
@@ -568,9 +588,9 @@ project scope 从进程工作目录解析,所以配置 server 时 cwd 要指
|
|
|
568
588
|
> **注意:** MCP 是请求/响应式的——它给 agent 提供 tools,但没有 opencode
|
|
569
589
|
> 插件的关键词自动捕获和首轮上下文注入。想让 agent 主动用记忆,
|
|
570
590
|
> 靠的是 agent 的 instructions:服务器在 MCP 握手的 `instructions` 里自带
|
|
571
|
-
> session-start
|
|
572
|
-
>
|
|
573
|
-
>
|
|
591
|
+
> session-start 指引(含开场时的 outbox 待审草稿数;改记忆的 tool 返回里也会
|
|
592
|
+
> 带当前数,为零时不带),`init` 则把更完整的版本写进编辑器的 instruction
|
|
593
|
+
> 文件。两者都是建议性的——MCP 客户端没有强制的 session-start hook。
|
|
574
594
|
|
|
575
595
|
## 路线图(Roadmap)
|
|
576
596
|
|
|
@@ -584,8 +604,12 @@ project scope 从进程工作目录解析,所以配置 server 时 cwd 要指
|
|
|
584
604
|
`export` / `import` 归档做用户可携带(Markdown + manifest,不造围墙花园;
|
|
585
605
|
private 默认不导出,`-a` / `--all` 全量迁移);
|
|
586
606
|
distill-to-AGENTS.md 辅助(`distill-agents`,只提议不改写——人工合并);
|
|
587
|
-
§3.5
|
|
588
|
-
(agent
|
|
607
|
+
§3.5 蒸馏写进 MCP 握手指令和 init 指令文件
|
|
608
|
+
(agent 在任务结束时提议 1–3 条捕获,人来定);找 1–2 个同事做 pilot。
|
|
609
|
+
|
|
610
|
+
**`0.5.0`(稳定版):** init 体验整修——`init --global` 一次写好用户级编辑器接线(D45);裸 `init` 自动检测已装编辑器并一次全接上(D46);非标准 JSON 配置不再报错,而是原样保留并打印手贴片段(D47);`uninstall` 逆转 `init` 且永不碰记忆数据(D48);空配置文件按空白处理、不再误判为损坏(D49)。"一份记忆,所有 Agent 通用":同一台机器上的每个编辑器,经由同一个 MCP 接口读写同一份记忆。
|
|
611
|
+
|
|
612
|
+
**`0.5.1`(稳定版):** `--help` 文案准确性修正——`mcp` 帮助写明 server 暴露 11 个工具(含 opencode 插件的 5 个 memory 工具),安装提示改指稳定版而非 `@alpha`(F27)。
|
|
589
613
|
|
|
590
614
|
**未来(看信号再定,不承诺版本):** 组织层——组织记忆仓库、curator 约定;
|
|
591
615
|
原生 agent 插件(Claude Code / Codex hooks,作为同一套 MCP tools 的增强路径);
|
|
@@ -625,7 +649,7 @@ Linux:`~/.config/Code/User/mcp.json`),每个项目打开 server 都在。
|
|
|
625
649
|
项目里如果有 `.vscode/mcp.json` 仍然优先;entry 里保留了
|
|
626
650
|
`cwd=${workspaceFolder}`,project scope 按窗口照常工作。
|
|
627
651
|
如果你的用户级 `mcp.json` 带注释(VS Code 接受 JSONC),`init` 不会碰它,
|
|
628
|
-
|
|
652
|
+
只打印可直接手贴的配置片段。空文件会被当作空白直接写入。
|
|
629
653
|
|
|
630
654
|
**怎么拆掉编辑器接线?**
|
|
631
655
|
`open-memex uninstall` 就是 `init` 的逆操作:删掉 MCP server 条目、opencode
|
package/dist/cli.js
CHANGED
|
@@ -296,8 +296,9 @@ Usage: open-memex capture --dry-run "text"
|
|
|
296
296
|
Example:
|
|
297
297
|
open-memex capture --dry-run "remember: we deploy on Fridays"`,
|
|
298
298
|
doctor: `Environment health check: Node version, config source, scope resolution,
|
|
299
|
-
storage writability,
|
|
300
|
-
|
|
299
|
+
storage writability, VS Code MCP enablement (settings.json + system policy),
|
|
300
|
+
then boots a real MCP server and runs initialize + tools/list against it —
|
|
301
|
+
all eleven tools must show up.
|
|
301
302
|
|
|
302
303
|
Usage: open-memex doctor
|
|
303
304
|
|
|
@@ -343,7 +344,7 @@ Usage:
|
|
|
343
344
|
Every command has its own help with description and examples:
|
|
344
345
|
open-memex <command> --help (or -h)
|
|
345
346
|
|
|
346
|
-
One-command project setup: \`open-memex init\` (or \`npx open-memex
|
|
347
|
+
One-command project setup: \`open-memex init\` (or \`npx -y open-memex init\`) detects
|
|
347
348
|
your installed editors and wires them all — user-level where the editor supports it
|
|
348
349
|
(VS Code / Cursor MCP config, opencode native plugin), so one init covers every project;
|
|
349
350
|
Visual Studio is included when the project has a solution file (its \`.mcp.json\`
|
|
@@ -358,9 +359,9 @@ confirms the detected editors and asks a couple of settings (keyword capture,
|
|
|
358
359
|
first-turn injection); \`--yes\` accepts all defaults, and non-terminal runs never prompt.
|
|
359
360
|
\`open-memex config set <key> <value>\` changes those settings after install.
|
|
360
361
|
|
|
361
|
-
Once installed globally (\`npm i -g open-memex
|
|
362
|
-
available directly: \`open-memex mcp\` starts the stdio MCP server (
|
|
363
|
-
|
|
362
|
+
Once installed globally (\`npm i -g open-memex\`) the \`open-memex\` command is
|
|
363
|
+
available directly: \`open-memex mcp\` starts the stdio MCP server (11 tools, a superset of
|
|
364
|
+
the opencode plugin's five memory_* tools); \`open-memex mcp --print-config <client>\`
|
|
364
365
|
prints a copy-paste MCP client config snippet.
|
|
365
366
|
|
|
366
367
|
Scope defaults to \`project\` (derived from cwd's git remote or path).
|
|
@@ -433,7 +434,7 @@ function resolveCliScope(flags, project) {
|
|
|
433
434
|
: project;
|
|
434
435
|
}
|
|
435
436
|
/** Print a copy-paste MCP client config snippet. Requires a global install
|
|
436
|
-
* (`npm i -g open-memex
|
|
437
|
+
* (`npm i -g open-memex`) so the `open-memex` command is on PATH. */
|
|
437
438
|
function printMcpConfig(client) {
|
|
438
439
|
const c = client.toLowerCase();
|
|
439
440
|
// D17: resolve the server command the same way `init` does.
|
|
@@ -489,13 +490,33 @@ function printMcpConfig(client) {
|
|
|
489
490
|
process.exit(1);
|
|
490
491
|
}
|
|
491
492
|
if (!mc.durable) {
|
|
492
|
-
console.error(`\n# note: no durable \`open-memex\` on PATH — snippet uses npx. \`npm i -g open-memex
|
|
493
|
+
console.error(`\n# note: no durable \`open-memex\` on PATH — snippet uses npx. \`npm i -g open-memex\` for faster startup.`);
|
|
493
494
|
}
|
|
494
495
|
process.exit(0);
|
|
495
496
|
}
|
|
496
497
|
async function main() {
|
|
497
498
|
const [cmd, ...rest] = process.argv.slice(2);
|
|
498
|
-
if (!cmd
|
|
499
|
+
if (!cmd) {
|
|
500
|
+
// D50: fresh machine + interactive terminal → offer init instead of bare usage.
|
|
501
|
+
// Non-interactive (CI/scripts/pipes) prints usage exactly as before.
|
|
502
|
+
const { offerFirstRunInit } = await import("./first-run.js");
|
|
503
|
+
const outcome = await offerFirstRunInit(async () => {
|
|
504
|
+
// Re-exec `open-memex init` as a child with inherited stdio instead of
|
|
505
|
+
// calling initProject() in-process: the offer's readline already
|
|
506
|
+
// consumed stdin's buffer, and a second readline on the same stream
|
|
507
|
+
// would see EOF on burst input instead of the user's next answers.
|
|
508
|
+
const { spawnSync } = await import("node:child_process");
|
|
509
|
+
const r = spawnSync(process.execPath, [...process.execArgv, fileURLToPath(import.meta.url), "init"], { stdio: "inherit" });
|
|
510
|
+
if (r.error)
|
|
511
|
+
throw r.error;
|
|
512
|
+
if ((r.status ?? 1) !== 0)
|
|
513
|
+
process.exit(r.status ?? 1);
|
|
514
|
+
});
|
|
515
|
+
if (outcome !== "initialized")
|
|
516
|
+
usage(0);
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
if (cmd === "--help" || cmd === "-h" || cmd === "help")
|
|
499
520
|
usage(0);
|
|
500
521
|
if (cmd === "--version" || cmd === "-v") {
|
|
501
522
|
// package.json sits two levels above this file in both layouts
|
|
@@ -648,7 +669,7 @@ async function main() {
|
|
|
648
669
|
}
|
|
649
670
|
return;
|
|
650
671
|
}
|
|
651
|
-
// `mcp` starts the stdio MCP server (
|
|
672
|
+
// `mcp` starts the stdio MCP server (11 tools; the opencode plugin exposes 5).
|
|
652
673
|
// Branched before db() — runMcpServer() does its own init, and stdout must
|
|
653
674
|
// stay clean for the MCP protocol.
|
|
654
675
|
if (cmd === "mcp") {
|
package/dist/distill-agents.js
CHANGED
|
@@ -53,10 +53,11 @@ export function distillAgentsMarkdown(opts) {
|
|
|
53
53
|
}
|
|
54
54
|
// D43 — §3.5 memory-hygiene footer (double insurance for opencode users,
|
|
55
55
|
// who never see the MCP handshake / init instructions): teach the agent
|
|
56
|
-
// reading this AGENTS.md to propose distilled captures
|
|
56
|
+
// reading this AGENTS.md to propose distilled captures when a task ends
|
|
57
|
+
// (D53: the checkpoint mechanism is gone).
|
|
57
58
|
lines.push(`### Memory hygiene (open-memex)`);
|
|
58
59
|
lines.push(``);
|
|
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.`, `- If the knowledge already lives in project docs, save a \`reference\` memory`, ` pointing at the doc instead of copying it.`);
|
|
60
61
|
lines.push(``);
|
|
61
62
|
return lines.join("\n");
|
|
62
63
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
// Read-only except that paths() and the MCP handshake may create the (empty)
|
|
4
4
|
// data directories, exactly like a normal `open-memex mcp` start would.
|
|
5
5
|
import fs from "node:fs";
|
|
6
|
+
import os from "node:os";
|
|
6
7
|
import path from "node:path";
|
|
7
|
-
import { spawn } from "node:child_process";
|
|
8
|
+
import { spawn, execFileSync } from "node:child_process";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
9
10
|
import { loadConfig, configSource, DEFAULT_CONFIG } from "./config.js";
|
|
10
11
|
import { paths } from "./paths.js";
|
|
@@ -136,9 +137,146 @@ function mcpCheck() {
|
|
|
136
137
|
});
|
|
137
138
|
});
|
|
138
139
|
}
|
|
140
|
+
/** Parse JSON tolerating line/block comments plus trailing commas
|
|
141
|
+
* (VS Code's settings.json is JSONC). Health-check grade, not a full parser. */
|
|
142
|
+
function parseLenientJson(raw) {
|
|
143
|
+
try {
|
|
144
|
+
return JSON.parse(raw);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
/* fall through to comment stripping */
|
|
148
|
+
}
|
|
149
|
+
let out = "";
|
|
150
|
+
let i = 0;
|
|
151
|
+
let inStr = false;
|
|
152
|
+
let esc = false;
|
|
153
|
+
while (i < raw.length) {
|
|
154
|
+
const c = raw[i];
|
|
155
|
+
const n = raw[i + 1];
|
|
156
|
+
if (inStr) {
|
|
157
|
+
out += c;
|
|
158
|
+
if (esc)
|
|
159
|
+
esc = false;
|
|
160
|
+
else if (c === "\\")
|
|
161
|
+
esc = true;
|
|
162
|
+
else if (c === '"')
|
|
163
|
+
inStr = false;
|
|
164
|
+
i++;
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (c === '"') {
|
|
168
|
+
inStr = true;
|
|
169
|
+
out += c;
|
|
170
|
+
i++;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (c === "/" && n === "/") {
|
|
174
|
+
while (i < raw.length && raw[i] !== "\n")
|
|
175
|
+
i++;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (c === "/" && n === "*") {
|
|
179
|
+
i += 2;
|
|
180
|
+
while (i < raw.length && !(raw[i] === "*" && raw[i + 1] === "/"))
|
|
181
|
+
i++;
|
|
182
|
+
i += 2;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
out += c;
|
|
186
|
+
i++;
|
|
187
|
+
}
|
|
188
|
+
out = out.replace(/,\s*([}\]])/g, "$1");
|
|
189
|
+
return JSON.parse(out);
|
|
190
|
+
}
|
|
191
|
+
function vscodeSettingsPath() {
|
|
192
|
+
const home = os.homedir();
|
|
193
|
+
if (process.platform === "win32") {
|
|
194
|
+
const appdata = process.env.APPDATA;
|
|
195
|
+
return appdata ? path.join(appdata, "Code", "User", "settings.json") : null;
|
|
196
|
+
}
|
|
197
|
+
if (process.platform === "darwin") {
|
|
198
|
+
return path.join(home, "Library", "Application Support", "Code", "User", "settings.json");
|
|
199
|
+
}
|
|
200
|
+
return path.join(home, ".config", "Code", "User", "settings.json");
|
|
201
|
+
}
|
|
202
|
+
/** True when a Windows system policy disables MCP (value name contains "mcp",
|
|
203
|
+
* data is 0/0x0/false). Checks HKLM and HKCU policy keys. */
|
|
204
|
+
function windowsMcpPolicyDisabled() {
|
|
205
|
+
if (process.platform !== "win32")
|
|
206
|
+
return null;
|
|
207
|
+
for (const hive of ["HKLM", "HKCU"]) {
|
|
208
|
+
try {
|
|
209
|
+
const stdout = execFileSync("reg", ["query", `${hive}\\SOFTWARE\\Policies\\Microsoft\\VSCode`], { encoding: "utf8", timeout: 5000, stdio: ["ignore", "pipe", "ignore"] });
|
|
210
|
+
for (const line of stdout.split("\n")) {
|
|
211
|
+
const m = line.match(/^\s{2,}(\S+)\s+REG_\w+\s+(\S+)/);
|
|
212
|
+
if (m && /mcp/i.test(m[1]) && /^(0x0|0|false)$/i.test(m[2])) {
|
|
213
|
+
return `${hive}\\SOFTWARE\\Policies\\Microsoft\\VSCode!${m[1]}`;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
/* policy key absent — no policy */
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
function settingsMcpDisabled(file) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = parseLenientJson(fs.readFileSync(file, "utf8"));
|
|
226
|
+
return parsed["chat.mcp.enabled"] === false;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return false; // unreadable file: don't claim anything
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** VS Code ignores MCP server entries entirely when MCP is switched off —
|
|
233
|
+
* either in settings.json or, on managed machines, by system policy. */
|
|
234
|
+
function vscodeMcpCheck() {
|
|
235
|
+
const name = "vscode-mcp";
|
|
236
|
+
const policyHit = windowsMcpPolicyDisabled();
|
|
237
|
+
if (policyHit) {
|
|
238
|
+
return {
|
|
239
|
+
name,
|
|
240
|
+
ok: false,
|
|
241
|
+
detail: `MCP is disabled by system policy (${policyHit}) — managed by your organization. ` +
|
|
242
|
+
`VS Code will ignore open-memex's MCP entry on this machine.`,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
const files = [];
|
|
246
|
+
const user = vscodeSettingsPath();
|
|
247
|
+
if (user)
|
|
248
|
+
files.push(user);
|
|
249
|
+
const ws = path.join(process.cwd(), ".vscode", "settings.json");
|
|
250
|
+
if (!files.includes(ws))
|
|
251
|
+
files.push(ws);
|
|
252
|
+
const found = [];
|
|
253
|
+
const disabled = [];
|
|
254
|
+
for (const f of files) {
|
|
255
|
+
if (!fs.existsSync(f))
|
|
256
|
+
continue;
|
|
257
|
+
found.push(f);
|
|
258
|
+
if (settingsMcpDisabled(f))
|
|
259
|
+
disabled.push(f);
|
|
260
|
+
}
|
|
261
|
+
if (disabled.length > 0) {
|
|
262
|
+
return {
|
|
263
|
+
name,
|
|
264
|
+
ok: false,
|
|
265
|
+
detail: `chat.mcp.enabled is false in ${disabled.join(", ")} — VS Code will not load ` +
|
|
266
|
+
`MCP servers. Set it to true (or ask IT if the setting shows as managed).`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
name,
|
|
271
|
+
ok: true,
|
|
272
|
+
detail: found.length > 0
|
|
273
|
+
? `MCP enabled (checked ${found.join(", ")})`
|
|
274
|
+
: "VS Code settings not found on this machine — nothing to check",
|
|
275
|
+
};
|
|
276
|
+
}
|
|
139
277
|
export async function runDoctor() {
|
|
140
278
|
console.log("open-memex doctor");
|
|
141
|
-
const checks = [nodeCheck(), configCheck(), scopeCheck(), storageCheck()];
|
|
279
|
+
const checks = [nodeCheck(), configCheck(), scopeCheck(), storageCheck(), vscodeMcpCheck()];
|
|
142
280
|
checks.push(await mcpCheck());
|
|
143
281
|
let allOk = true;
|
|
144
282
|
for (const c of checks) {
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D50: first-run init offer. `npm install -g open-memex` only puts the CLI on
|
|
3
|
+
* PATH — the editor wiring is `init`'s job, and a clean reinstall wipes it.
|
|
4
|
+
* When bare `open-memex` runs on a machine where init never completed, offer
|
|
5
|
+
* to run it instead of just printing usage.
|
|
6
|
+
*
|
|
7
|
+
* The "asked" state is a marker file at the data root (`init` writes it on
|
|
8
|
+
* success, a declined offer writes it too), so the question is asked exactly
|
|
9
|
+
* once. `uninstall` removes it — unwiring is the reverse of init, so the next
|
|
10
|
+
* bare run offers to wire again. The marker is a dotfile: export builds from
|
|
11
|
+
* DB rows, never by walking the data root, so it can't leak into bundles.
|
|
12
|
+
*/
|
|
13
|
+
import fs from "node:fs";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import readline from "node:readline";
|
|
16
|
+
import { dataRootPath } from "./paths.js";
|
|
17
|
+
const MARKER = ".init.json";
|
|
18
|
+
export function firstRunMarkerPath(root = dataRootPath()) {
|
|
19
|
+
return path.join(root, MARKER);
|
|
20
|
+
}
|
|
21
|
+
/** True when init has neither run nor been declined on this machine. */
|
|
22
|
+
export function isFirstRun(root) {
|
|
23
|
+
return !fs.existsSync(firstRunMarkerPath(root ?? dataRootPath()));
|
|
24
|
+
}
|
|
25
|
+
/** Record the outcome — best effort; a missing marker just asks again next time. */
|
|
26
|
+
export function markFirstRunDone(state) {
|
|
27
|
+
const file = firstRunMarkerPath();
|
|
28
|
+
try {
|
|
29
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
30
|
+
fs.writeFileSync(file, JSON.stringify({ v: 1, state, at: new Date().toISOString() }) + "\n");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
/* ignore */
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function clearFirstRunMarker() {
|
|
37
|
+
try {
|
|
38
|
+
fs.rmSync(firstRunMarkerPath(), { force: true });
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* ignore */
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Pure decision, kept separate for tests: prompt only on an interactive
|
|
45
|
+
* terminal — scripts, CI and piped runs never get the question. */
|
|
46
|
+
export function shouldOfferFirstRun(tty = { stdinTTY: process.stdin.isTTY, stdoutTTY: process.stdout.isTTY }, firstRun = isFirstRun()) {
|
|
47
|
+
return firstRun && !!tty.stdinTTY && !!tty.stdoutTTY;
|
|
48
|
+
}
|
|
49
|
+
function askYesNo(question) {
|
|
50
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
rl.question(question, (ans) => {
|
|
53
|
+
rl.close();
|
|
54
|
+
resolve(!/^\s*(n|no)\s*$/i.test(ans));
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Bare-`open-memex` first-run flow. `runInit` is injected so tests don't need
|
|
60
|
+
* the real init. Returns "skipped" when there's nothing to ask (already set
|
|
61
|
+
* up, or non-interactive) — the caller then prints usage as before.
|
|
62
|
+
*/
|
|
63
|
+
export async function offerFirstRunInit(runInit) {
|
|
64
|
+
if (!shouldOfferFirstRun())
|
|
65
|
+
return "skipped";
|
|
66
|
+
console.log(`It looks like open-memex hasn't been set up on this machine yet.\n` +
|
|
67
|
+
"`open-memex init` wires it into your editors (auto-detects VS Code, Cursor and opencode).\n");
|
|
68
|
+
if (await askYesNo("Run it now? [Y/n] ")) {
|
|
69
|
+
await runInit();
|
|
70
|
+
return "initialized";
|
|
71
|
+
}
|
|
72
|
+
markFirstRunDone("declined");
|
|
73
|
+
console.log("No problem — run `open-memex init` any time.");
|
|
74
|
+
return "declined";
|
|
75
|
+
}
|