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/dist/init.js
CHANGED
|
@@ -7,6 +7,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
7
7
|
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
8
8
|
import { DEFAULT_CONFIG, saveConfig } from "./config.js";
|
|
9
9
|
import { projectRoot } from "./paths.js";
|
|
10
|
+
import { markFirstRunDone, clearFirstRunMarker } from "./first-run.js";
|
|
10
11
|
const MARKER = "<!-- open-memex -->";
|
|
11
12
|
/** npm dist-tag carrying the 0.3.x preview line. */
|
|
12
13
|
const ALPHA_TAG = "open-memex@alpha";
|
|
@@ -40,54 +41,70 @@ const INSTRUCTIONS = `${MARKER}
|
|
|
40
41
|
> Applies only when the \`open-memex\` MCP server is available in this session
|
|
41
42
|
> (the \`memory_*\` tools exist). Otherwise ignore this section.
|
|
42
43
|
|
|
43
|
-
You have a local memory MCP server (\`open-memex\`)
|
|
44
|
-
\`memory_add\`, \`memory_search\`, \`memory_list\`, \`memory_supersede\`,
|
|
45
|
-
\`memory_status\`, \`memory_submit\`, \`memory_propose\`,
|
|
46
|
-
\`memory_pr_status
|
|
44
|
+
You have a local memory MCP server (\`open-memex\`). Its tools are
|
|
45
|
+
\`memory_add\`, \`memory_search\`, \`memory_list\`, \`memory_supersede\`,
|
|
46
|
+
\`memory_forget\`, \`memory_status\`, \`memory_submit\`, \`memory_propose\`,
|
|
47
|
+
\`memory_promote\`, \`memory_resolve\`, \`memory_pr_status\` — always call them by
|
|
48
|
+
these full names.
|
|
47
49
|
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
- The server tells you when project outbox drafts are waiting for review — in
|
|
51
|
+
tool results. At session start, call \`memory_status\` once to check. When
|
|
52
|
+
drafts are waiting, summarize them (one line each) and ask the user which
|
|
53
|
+
ones to sync into the repo; sync NOTHING the user did not name. If you
|
|
54
|
+
already asked about these drafts this session, don't ask again. When the
|
|
55
|
+
server reports none waiting, do nothing.
|
|
56
|
+
- BE PROACTIVE about facts the user states directly: when the user shares a
|
|
57
|
+
decision, preference, project convention, or fix-and-cause worth remembering
|
|
58
|
+
across sessions, call \`memory_add\` without being asked. Keep each memory to
|
|
59
|
+
one self-contained statement, and add a brief "(noted in memory)" so the
|
|
60
|
+
user sees it worked.
|
|
61
|
+
- For conclusions YOU infer (the user never stated them): when you finish a
|
|
62
|
+
task the user would describe in one sentence, consider distilling the
|
|
63
|
+
session — if there is something worth keeping,
|
|
64
|
+
propose 1–3 short memories capturing the useful conclusion (what was learned
|
|
65
|
+
or decided, how an issue was resolved, what to avoid, where the authoritative
|
|
66
|
+
doc lives — not the raw transcript), each with its proposed scope. Save
|
|
67
|
+
NOTHING the user did not approve; on approval call \`memory_add\` with source
|
|
68
|
+
"inference" at the approved scope. If the knowledge already lives in project
|
|
69
|
+
docs, save it as type "reference" pointing at the doc instead of copying it.
|
|
70
|
+
Long-form notes are fine ONLY when the user explicitly asks to save one.
|
|
71
|
+
- Before asking the user about past decisions, conventions, or preferences
|
|
72
|
+
they may have told you before, call \`memory_search\` first — try a few
|
|
73
|
+
keyword variants (including the user's own language) when the first search
|
|
74
|
+
comes up empty.
|
|
75
|
+
- Memories default to this project's scope; use the \`personal\` scope for facts
|
|
76
|
+
about the user that hold across all projects. When a saved fact becomes
|
|
77
|
+
outdated, call \`memory_supersede\` (find the old memory's id with
|
|
78
|
+
\`memory_search\` first) instead of adding a duplicate.
|
|
65
79
|
|
|
66
80
|
## Syncing project memories for review (D26)
|
|
67
81
|
|
|
68
|
-
Project memories you save land in a local outbox first — they are NOT in git
|
|
69
|
-
Syncing them into the repo for review is an explicit, user-approved step:
|
|
82
|
+
Project memories you save land in a local outbox first — they are NOT in git
|
|
83
|
+
yet. Syncing them into the repo for review is an explicit, user-approved step:
|
|
70
84
|
|
|
71
|
-
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
- When the user approves, call \`memory_submit\` with the approved ids. It
|
|
79
|
-
the drafts into the
|
|
80
|
-
and
|
|
85
|
+
- When the server reports drafts waiting for review, call \`memory_status\` to
|
|
86
|
+
see them. Summarize the drafts (one line each) and ask the user which ones
|
|
87
|
+
to sync. Sync NOTHING the user did not name.
|
|
88
|
+
- When the user says "sync memory" (or "同步记忆"), run the sync
|
|
89
|
+
flow above: call \`memory_status\`, summarize the outbox drafts, and ask which
|
|
90
|
+
ones to sync. ALWAYS use the \`memory_status\` tool for this — never browse
|
|
91
|
+
the memory data directory directly.
|
|
92
|
+
- When the user approves, call \`memory_submit\` with the approved ids. It
|
|
93
|
+
copies the drafts into the current project's \`.ai/open-memex/\` directory as
|
|
94
|
+
\`proposed\` and commits locally on the CURRENT branch. It NEVER creates a
|
|
95
|
+
branch on its own.
|
|
81
96
|
- After the submit, ask ONE follow-up: "want me to create a branch + push +
|
|
82
|
-
open the PR, or will you handle it yourself?" A "yes, you do it" answer
|
|
83
|
-
the whole chain — branch creation, push, PR creation — do NOT re-ask
|
|
84
|
-
step. If the user says they will do it themselves, hand them the
|
|
85
|
-
push/PR commands and do nothing. NEVER create branches, push, or open
|
|
86
|
-
without their explicit approval.
|
|
87
|
-
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
open the PR, or will you handle it yourself?" A "yes, you do it" answer
|
|
98
|
+
covers the whole chain — branch creation, push, PR creation — do NOT re-ask
|
|
99
|
+
at each step. If the user says they will do it themselves, hand them the
|
|
100
|
+
printed push/PR commands and do nothing. NEVER create branches, push, or open
|
|
101
|
+
PRs without their explicit approval.
|
|
102
|
+
- If the user wants the memories reviewed on a separate branch, create the
|
|
103
|
+
branch first (the commit comes along), then push and open the PR. The PR base
|
|
104
|
+
defaults to the branch submit ran on; \`--base\` overrides it (e.g. \`main\` for
|
|
105
|
+
branch-independent knowledge).
|
|
106
|
+
- If anything conflicts (same id with different content, push rejected), STOP
|
|
107
|
+
and let the user judge — never overwrite.
|
|
91
108
|
- After the PR merges, call \`memory_pr_status\` (with \`apply\` when the user
|
|
92
109
|
approves) to map the PR's review state back onto each memory — merged means
|
|
93
110
|
\`published\`, an approval means \`approved\` (credited to the reviewer).
|
|
@@ -656,6 +673,11 @@ export async function initProject(opts) {
|
|
|
656
673
|
writeInstructions(root, scope, clients.find((c) => c !== "opencode"));
|
|
657
674
|
}
|
|
658
675
|
console.log(`\nDone. Reload your editor window to start the open-memex MCP server.`);
|
|
676
|
+
// D51: close the init→first-use gap — one concrete next step so a new user
|
|
677
|
+
// sees what "it works" looks like instead of stopping at "it's wired".
|
|
678
|
+
console.log(` Next step: \`open-memex add "standup is at 9:30"\` — then ask your agent what it remembers.`);
|
|
679
|
+
// D50: init completed — the bare-`open-memex` first-run offer won't ask again.
|
|
680
|
+
markFirstRunDone("initialized");
|
|
659
681
|
}
|
|
660
682
|
// ---------------------------------------------------------------------------
|
|
661
683
|
// uninstall — the reverse of init (D48). Removes the editor wiring init wrote:
|
|
@@ -844,4 +866,8 @@ export async function uninstallProject(opts) {
|
|
|
844
866
|
? `\nDone. Removed open-memex wiring from ${changed} file(s).`
|
|
845
867
|
: "\nDone. Nothing to remove — no open-memex wiring found.");
|
|
846
868
|
console.log("Your memories are untouched (uninstall never deletes data).");
|
|
869
|
+
// D50: unwiring is the reverse of init — drop the first-run marker so the
|
|
870
|
+
// next bare `open-memex` offers to wire again.
|
|
871
|
+
if (changed > 0)
|
|
872
|
+
clearFirstRunMarker();
|
|
847
873
|
}
|
package/dist/mcp.js
CHANGED
|
@@ -28,7 +28,8 @@ import { loadConfig } from "./config.js";
|
|
|
28
28
|
import { resolveProjectScope, PERSONAL_SCOPE } from "./scope.js";
|
|
29
29
|
import { db } from "./store/db.js";
|
|
30
30
|
import { syncScope } from "./store/sync.js";
|
|
31
|
-
import { addMemory, searchMemories, listMemories, supersedeMemory, forgetMemory, statusMemories, submitMemoriesOp, proposeMemoriesOp, promoteMemoryOp, resolveMemoryOp, prStatusOp, memoryAddArgs, memorySearchArgs, memoryListArgs, memorySupersedeArgs, memoryForgetArgs, memoryStatusArgs, memorySubmitArgs, memoryProposeArgs, memoryPromoteArgs, memoryResolveArgs, memoryPrStatusArgs, TOOL_DESCRIPTIONS, } from "./tools/ops.js";
|
|
31
|
+
import { addMemory, searchMemories, listMemories, supersedeMemory, forgetMemory, statusMemories, submitMemoriesOp, proposeMemoriesOp, promoteMemoryOp, resolveMemoryOp, prStatusOp, memoryAddArgs, memorySearchArgs, memoryListArgs, memorySupersedeArgs, memoryForgetArgs, memoryStatusArgs, memorySubmitArgs, memoryProposeArgs, memoryPromoteArgs, memoryResolveArgs, memoryPrStatusArgs, TOOL_DESCRIPTIONS, withOutboxNote, } from "./tools/ops.js";
|
|
32
|
+
import { outboxDraftCount } from "./submit.js";
|
|
32
33
|
// Server version tracks package.json — never hardcode it here again.
|
|
33
34
|
// package.json sits two levels above this file in both layouts
|
|
34
35
|
// (src/mcp.ts and dist/mcp.js), same convention as cli.ts --version.
|
|
@@ -49,40 +50,49 @@ const SERVER_VERSION = (() => {
|
|
|
49
50
|
* client at connect time. Still advisory — no MCP consumer offers a hard
|
|
50
51
|
* session-start hook — but it is the strongest signal available.
|
|
51
52
|
*/
|
|
52
|
-
const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP server
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP server.
|
|
54
|
+
Its tools are memory_add, memory_search, memory_list, memory_supersede,
|
|
55
|
+
memory_forget, memory_status, memory_submit, memory_propose, memory_promote,
|
|
56
|
+
memory_resolve, memory_pr_status — always call them by these full names.
|
|
55
57
|
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
call memory_status. If the project outbox has drafts waiting for review,
|
|
58
|
+
- The server tells you when project outbox drafts are waiting for review —
|
|
59
|
+
in tool results, and in these session-start instructions. When it does,
|
|
59
60
|
summarize them (one line each) and ask the user which ones to sync into the
|
|
60
|
-
repo
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
61
|
+
repo; sync NOTHING the user did not name. If you already asked about these
|
|
62
|
+
drafts this session, don't ask again. When the server reports none waiting,
|
|
63
|
+
do nothing.
|
|
64
|
+
- When the user says "sync memory" (or "同步记忆"), call memory_status,
|
|
65
|
+
summarize the outbox drafts (one line each), and ask which ones to sync.
|
|
66
|
+
ALWAYS use the memory_status tool for this — never browse the memory data
|
|
67
|
+
directory directly.
|
|
65
68
|
- After memory_submit, ask ONE follow-up: "want me to create a branch + push +
|
|
66
69
|
open the PR, or will you handle it yourself?" NEVER create branches, push, or
|
|
67
70
|
open PRs without the user's explicit approval. A "yes, you do it" covers the
|
|
68
71
|
whole chain — do NOT re-ask at each step.
|
|
69
|
-
- BE PROACTIVE
|
|
70
|
-
|
|
71
|
-
memory_add without being asked. Keep each memory to one
|
|
72
|
-
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
- BE PROACTIVE about facts the user states directly: when the user shares a
|
|
73
|
+
decision, preference, project convention, or fix-and-cause worth remembering
|
|
74
|
+
across sessions, call memory_add without being asked. Keep each memory to one
|
|
75
|
+
self-contained statement, and add a brief "(noted in memory)" so the user
|
|
76
|
+
sees it worked.
|
|
77
|
+
- For conclusions YOU infer (the user never stated them): when you finish a
|
|
78
|
+
task the user would describe in one sentence, consider distilling the
|
|
79
|
+
session — if there is something worth keeping,
|
|
80
|
+
propose 1–3 short memories capturing the useful conclusion (what was learned
|
|
81
|
+
or decided, how an issue was resolved, what to avoid, where the authoritative
|
|
82
|
+
doc lives — not the raw transcript), each with its proposed scope. Save
|
|
83
|
+
NOTHING the user did not approve; on approval call memory_add with source
|
|
84
|
+
"inference" at the approved scope. If the knowledge already lives in project
|
|
85
|
+
docs, save it as type "reference" pointing at the doc instead of copying it.
|
|
86
|
+
Long-form notes are fine ONLY when the user explicitly asks to save one.
|
|
81
87
|
- Before asking the user about past decisions, conventions, or preferences they
|
|
82
|
-
may have told you before, call memory_search first
|
|
88
|
+
may have told you before, call memory_search first — try a few keyword
|
|
89
|
+
variants (including the user's own language) when the first search comes up
|
|
90
|
+
empty.
|
|
83
91
|
- Memories default to this project's scope; use the personal scope for facts
|
|
84
|
-
about the user that hold across all projects.
|
|
85
|
-
|
|
92
|
+
about the user that hold across all projects. When a saved fact becomes
|
|
93
|
+
outdated, call memory_supersede (find the old memory's id with memory_search
|
|
94
|
+
first) instead of adding a duplicate.
|
|
95
|
+
`;
|
|
86
96
|
/** Adapt a framework-agnostic op result to an MCP tool response. */
|
|
87
97
|
function toMcp(p) {
|
|
88
98
|
return p.then((r) => ({ content: [{ type: "text", text: r.output }] }), (e) => ({
|
|
@@ -128,11 +138,17 @@ export async function runMcpServer() {
|
|
|
128
138
|
return fn(args);
|
|
129
139
|
};
|
|
130
140
|
};
|
|
131
|
-
|
|
141
|
+
// D53: session-start outbox state, pushed. The stdio server starts fresh per
|
|
142
|
+
// session, so construction-time state ≈ session-start state.
|
|
143
|
+
const n = outboxDraftCount(scope.key);
|
|
144
|
+
const instructions = n > 0
|
|
145
|
+
? `${SERVER_INSTRUCTIONS}\n\nSession start: the project outbox has ${n} draft${n === 1 ? "" : "s"} waiting for review — call memory_status to see them.`
|
|
146
|
+
: SERVER_INSTRUCTIONS;
|
|
147
|
+
const server = new McpServer({ name: "open-memex", version: SERVER_VERSION }, { instructions });
|
|
132
148
|
server.registerTool("memory_add", {
|
|
133
149
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
134
150
|
inputSchema: z.object(memoryAddArgs),
|
|
135
|
-
}, withSync((args) => toMcp(addMemory(getScope, cfg, args))));
|
|
151
|
+
}, withSync((args) => toMcp(withOutboxNote(getScope().key, addMemory(getScope, cfg, args), "call memory_status to review"))));
|
|
136
152
|
server.registerTool("memory_search", {
|
|
137
153
|
description: TOOL_DESCRIPTIONS.memory_search,
|
|
138
154
|
inputSchema: z.object(memorySearchArgs),
|
|
@@ -146,12 +162,12 @@ export async function runMcpServer() {
|
|
|
146
162
|
server.registerTool("memory_supersede", {
|
|
147
163
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
148
164
|
inputSchema: z.object(memorySupersedeArgs),
|
|
149
|
-
}, withSync((args) => toMcp(supersedeMemory(cfg, args))));
|
|
165
|
+
}, withSync((args) => toMcp(withOutboxNote(getScope().key, supersedeMemory(cfg, args), "call memory_status to review"))));
|
|
150
166
|
server.registerTool("memory_forget", {
|
|
151
167
|
description: TOOL_DESCRIPTIONS.memory_forget,
|
|
152
168
|
inputSchema: z.object(memoryForgetArgs),
|
|
153
169
|
annotations: { destructiveHint: true },
|
|
154
|
-
}, withSync((args) => toMcp(forgetMemory(args))));
|
|
170
|
+
}, withSync((args) => toMcp(withOutboxNote(getScope().key, forgetMemory(args), "call memory_status to review"))));
|
|
155
171
|
server.registerTool("memory_status", {
|
|
156
172
|
description: TOOL_DESCRIPTIONS.memory_status,
|
|
157
173
|
inputSchema: z.object(memoryStatusArgs),
|
|
@@ -160,11 +176,11 @@ export async function runMcpServer() {
|
|
|
160
176
|
server.registerTool("memory_submit", {
|
|
161
177
|
description: TOOL_DESCRIPTIONS.memory_submit,
|
|
162
178
|
inputSchema: z.object(memorySubmitArgs),
|
|
163
|
-
}, withSync((args) => toMcp(submitMemoriesOp(args))));
|
|
179
|
+
}, withSync((args) => toMcp(withOutboxNote(getScope().key, submitMemoriesOp(args), "call memory_status to review"))));
|
|
164
180
|
server.registerTool("memory_propose", {
|
|
165
181
|
description: TOOL_DESCRIPTIONS.memory_propose,
|
|
166
182
|
inputSchema: z.object(memoryProposeArgs),
|
|
167
|
-
}, withSync((args) => toMcp(proposeMemoriesOp(args))));
|
|
183
|
+
}, withSync((args) => toMcp(withOutboxNote(getScope().key, proposeMemoriesOp(args), "call memory_status to review"))));
|
|
168
184
|
server.registerTool("memory_promote", {
|
|
169
185
|
description: TOOL_DESCRIPTIONS.memory_promote,
|
|
170
186
|
inputSchema: z.object(memoryPromoteArgs),
|
package/dist/paths.js
CHANGED
|
@@ -13,6 +13,14 @@ function dataRoot() {
|
|
|
13
13
|
return path.join(xdg, "open-memex");
|
|
14
14
|
}
|
|
15
15
|
let _cached = null;
|
|
16
|
+
/**
|
|
17
|
+
* Read-only data-root path — never creates the directory. For existence
|
|
18
|
+
* checks (D50 first-run detection) that must not pollute storage; contrast
|
|
19
|
+
* `paths()`, which mkdirs as a side effect.
|
|
20
|
+
*/
|
|
21
|
+
export function dataRootPath() {
|
|
22
|
+
return dataRoot();
|
|
23
|
+
}
|
|
16
24
|
export function paths() {
|
|
17
25
|
if (_cached)
|
|
18
26
|
return _cached;
|
package/dist/submit.js
CHANGED
|
@@ -114,6 +114,19 @@ export function getSyncStatus() {
|
|
|
114
114
|
uncommitted: uncommittedList,
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* D53: cheap outbox draft count — one indexed query, no git I/O — for the
|
|
119
|
+
* push-not-poll note appended to mutating tool results and to the MCP
|
|
120
|
+
* session-start instructions. Same outbox definition as getSyncStatus
|
|
121
|
+
* (file under the scope's outbox dir), without the expensive parts.
|
|
122
|
+
*/
|
|
123
|
+
export function outboxDraftCount(scopeKey) {
|
|
124
|
+
const outboxDir = memoriesDirPath(scopeKey);
|
|
125
|
+
const row = db()
|
|
126
|
+
.prepare(`SELECT COUNT(*) AS n FROM memories WHERE scope_key = ? AND instr(file_path, ?) = 1`)
|
|
127
|
+
.get(scopeKey, outboxDir + path.sep);
|
|
128
|
+
return row?.n ?? 0;
|
|
129
|
+
}
|
|
117
130
|
export function formatSyncStatus(st) {
|
|
118
131
|
const lines = [];
|
|
119
132
|
lines.push(`project: ${st.projectName} (${st.scopeKey})`);
|
package/dist/tools/memory.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { tool } from "@opencode-ai/plugin/tool";
|
|
2
|
-
import { addMemory, searchMemories, listMemories, supersedeMemory, forgetMemory, memoryAddArgs, memorySearchArgs, memoryListArgs, memorySupersedeArgs, memoryForgetArgs, TOOL_DESCRIPTIONS, } from "./ops.js";
|
|
2
|
+
import { addMemory, searchMemories, listMemories, supersedeMemory, forgetMemory, memoryAddArgs, memorySearchArgs, memoryListArgs, memorySupersedeArgs, memoryForgetArgs, TOOL_DESCRIPTIONS, withOutboxNote, } from "./ops.js";
|
|
3
3
|
export function makeTools(getScope, cfg) {
|
|
4
4
|
const memory_add = tool({
|
|
5
5
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
6
6
|
args: memoryAddArgs,
|
|
7
7
|
async execute(args) {
|
|
8
|
-
return addMemory(getScope, cfg, args);
|
|
8
|
+
return withOutboxNote(getScope().key, addMemory(getScope, cfg, args));
|
|
9
9
|
},
|
|
10
10
|
});
|
|
11
11
|
const memory_search = tool({
|
|
@@ -26,14 +26,14 @@ export function makeTools(getScope, cfg) {
|
|
|
26
26
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
27
27
|
args: memorySupersedeArgs,
|
|
28
28
|
async execute(args) {
|
|
29
|
-
return supersedeMemory(cfg, args);
|
|
29
|
+
return withOutboxNote(getScope().key, supersedeMemory(cfg, args));
|
|
30
30
|
},
|
|
31
31
|
});
|
|
32
32
|
const memory_forget = tool({
|
|
33
33
|
description: TOOL_DESCRIPTIONS.memory_forget,
|
|
34
34
|
args: memoryForgetArgs,
|
|
35
35
|
async execute(args) {
|
|
36
|
-
return forgetMemory(args);
|
|
36
|
+
return withOutboxNote(getScope().key, forgetMemory(args));
|
|
37
37
|
},
|
|
38
38
|
});
|
|
39
39
|
return { memory_add, memory_search, memory_list, memory_forget, memory_supersede };
|
package/dist/tools/ops.js
CHANGED
|
@@ -15,9 +15,24 @@ import { upsertFromFile, deleteFromIndex } from "../store/sync.js";
|
|
|
15
15
|
import { findDuplicates, supersede } from "../store/lifecycle.js";
|
|
16
16
|
import { db } from "../store/db.js";
|
|
17
17
|
import { redact } from "../redact.js";
|
|
18
|
-
import { getSyncStatus, formatSyncStatus, submitMemories } from "../submit.js";
|
|
18
|
+
import { getSyncStatus, formatSyncStatus, submitMemories, outboxDraftCount } from "../submit.js";
|
|
19
19
|
import { getPrStatus, formatPrStatus, applyPrStatus } from "../github.js";
|
|
20
20
|
import { proposeMemories, promoteMemory, listConflicts, resolveConflict, formatReviewHistory, } from "../review.js";
|
|
21
|
+
/**
|
|
22
|
+
* D53: push, don't poll. Append the project-outbox pending count to mutating
|
|
23
|
+
* tool results so agents learn about drafts waiting for review without a
|
|
24
|
+
* checkpoint poll. Silent when the outbox is empty. `reviewHint` names the
|
|
25
|
+
* tool to call (MCP); transports without that tool leave it generic.
|
|
26
|
+
*/
|
|
27
|
+
export async function withOutboxNote(scopeKey, p, reviewHint) {
|
|
28
|
+
const r = await p;
|
|
29
|
+
const n = outboxDraftCount(scopeKey);
|
|
30
|
+
if (n > 0) {
|
|
31
|
+
const tail = reviewHint ? ` — ${reviewHint}` : " for review";
|
|
32
|
+
r.output += `\n[open-memex: ${n} draft${n === 1 ? "" : "s"} waiting in the project outbox${tail}]`;
|
|
33
|
+
}
|
|
34
|
+
return r;
|
|
35
|
+
}
|
|
21
36
|
/** LLM-facing tool descriptions, shared by the opencode plugin and the MCP server. */
|
|
22
37
|
export const TOOL_DESCRIPTIONS = {
|
|
23
38
|
memory_add: "Save a fact, preference, decision, or note to persistent local memory. Call this PROACTIVELY whenever the user shares something worth remembering across sessions — project conventions, tool choices, personal preferences, decisions made, error fixes and their causes. Do not wait to be asked. Keep each memory to one self-contained statement. Default scope is the current project; use the personal scope for facts about the user that apply across all projects.",
|
package/docs/SCOPES.md
CHANGED
|
@@ -64,14 +64,15 @@ moves `memories/user/` → `memories/personal/` and rewrites the frontmatter
|
|
|
64
64
|
tree is kept. Reads remain backward compatible: a v1 file with `scope: user`
|
|
65
65
|
is interpreted as `personal`.
|
|
66
66
|
|
|
67
|
-
## Visibility
|
|
67
|
+
## Visibility
|
|
68
68
|
|
|
69
69
|
v2 frontmatter carries a separate `visibility` field (`private` | `internal` |
|
|
70
|
-
`shared`)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
boundary and review anything you
|
|
70
|
+
`shared`), defaulting to `private` for the personal scope and `internal` for
|
|
71
|
+
the project scope. `open-memex export` excludes `visibility: private` memories
|
|
72
|
+
by default (`--all` / `-a` includes them, D40). Physical isolation of private
|
|
73
|
+
memories into a local-only cache directory is still planned; today, treat
|
|
74
|
+
`personal` as the only hard confidentiality boundary and review anything you
|
|
75
|
+
place under `.ai/open-memex/` before pushing.
|
|
75
76
|
|
|
76
77
|
## Reserved names
|
|
77
78
|
|
package/docs/TEST-PLAN.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# OpenMemex 测试计划(v0.
|
|
1
|
+
# OpenMemex 测试计划(v0.5.1)
|
|
2
2
|
|
|
3
3
|
> 自动化部分:`node --experimental-strip-types scripts/test-full.ts`
|
|
4
4
|
> 62 项全过(26 个 CLI 命令 + 11 个 MCP tool),隔离环境运行,不碰真实数据。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
## A. Windows 真机 + VS Code Copilot
|
|
8
8
|
|
|
9
|
-
- [ ] `npm i -g open-memex
|
|
9
|
+
- [ ] `npm i -g open-memex` 全局安装(稳定版),`open-memex --version` 显示正确版本
|
|
10
10
|
- [ ] 在一个真实项目目录跑 `open-memex init`(不加 `--yes`,走一遍交互)
|
|
11
11
|
- 确认 `.vscode/mcp.json` 生成,`~/.copilot/copilot-instructions.md` 合并写入(不覆盖已有内容)
|
|
12
12
|
- [ ] 重启 VS Code,Copilot Chat 里问 "what do you remember about this project?"
|
|
@@ -60,10 +60,17 @@
|
|
|
60
60
|
|
|
61
61
|
## G. 同事 pilot(1–2 人,Stone 私下选)
|
|
62
62
|
|
|
63
|
-
- [ ] 对方 `npx open-memex
|
|
63
|
+
- [ ] 对方 `npx -y open-memex init` 走通
|
|
64
64
|
- [ ] 对方能 propose → 你这边能看到 PR → promote 流程走通
|
|
65
65
|
- [ ] 收集反馈:哪里卡、哪里不符合直觉
|
|
66
66
|
|
|
67
|
+
## I. init/uninstall 行为(D47–D49)
|
|
68
|
+
|
|
69
|
+
- [ ] 空的 `mcp.json`:`open-memex init --client vscode` 直接写入,不再报 "not valid JSON"(D49)
|
|
70
|
+
- [ ] 带注释的 `mcp.json`:`init` 不动文件,只打印手贴片段(D47)
|
|
71
|
+
- [ ] `open-memex uninstall --client vscode` 移除接线条目,记忆数据不动;再跑 `init` 可恢复(D48)
|
|
72
|
+
- [ ] 裸 `open-memex uninstall`(交互终端)会先确认再清所有编辑器;`--yes` 跳过确认
|
|
73
|
+
|
|
67
74
|
## H. 已知问题观察
|
|
68
75
|
|
|
69
76
|
- [ ] better-sqlite3 在 Node 24 退出时偶发 crash(exit 134):注意是否丢数据(预期:不丢,只影响退出码)
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
> This document describes open-memex's **mental model**: where your memories live,
|
|
4
4
|
> how they flow, and who can see them. The in-repo directory (§2, §6 write path)
|
|
5
|
-
> and the propose → promote → resolve workflow (§5)
|
|
6
|
-
>
|
|
7
|
-
> everything else is 0.3.0 behavior.
|
|
5
|
+
> and the propose → promote → resolve workflow (§5) shipped in 0.4.0
|
|
6
|
+
> (Phase 2B); everything described here is current as of 0.5.0.
|
|
8
7
|
|
|
9
8
|
## In one sentence
|
|
10
9
|
|
|
@@ -172,7 +171,7 @@ personal idea ──propose──▶ outbox draft ──submit──▶ proposed
|
|
|
172
171
|
- **Submit** (explicit, your call): `open-memex submit <id...>` → local branch
|
|
173
172
|
+ local commit into `.ai/open-memex/`; push/PR are printed for you (or done
|
|
174
173
|
by your agent on your Yes).
|
|
175
|
-
- **Pull
|
|
174
|
+
- **Pull**: `open-memex pull` (always explicit, never automatic) → git fetch +
|
|
176
175
|
fast-forward → scans `.ai/open-memex/*.md` → merges into the local `index.db` by
|
|
177
176
|
file mtime. Retrieval always goes through SQLite, never walks git.
|
|
178
177
|
- **personal scope**: never syncs (§1 iron rule).
|
|
@@ -194,4 +193,4 @@ personal idea ──propose──▶ outbox draft ──submit──▶ proposed
|
|
|
194
193
|
|
|
195
194
|
---
|
|
196
195
|
|
|
197
|
-
*Companion design record: `docs/V2-DESIGN.md` (decisions D1–
|
|
196
|
+
*Companion design record: `docs/V2-DESIGN.md` (decisions D1–D49).*
|
package/docs/USER-GUIDE.zh-CN.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> 本文档讲的是 open-memex 的**心智模型**:你的记忆住在哪里、怎么流动、谁能看到。
|
|
4
4
|
> in-repo 目录(§2、§6 的写路径)和 propose → promote → resolve 工作流(§5)
|
|
5
|
-
>
|
|
6
|
-
> 其余为 0.3.0 已有行为。
|
|
5
|
+
> 已随 0.4.0(Phase 2B)发布;本文描述的均为 0.5.0 现行行为。
|
|
7
6
|
|
|
8
7
|
## 一句话
|
|
9
8
|
|
|
@@ -143,7 +142,7 @@ server 不能主动推送,调不调 `memory_search` 全看 model 的判断。
|
|
|
143
142
|
**不碰 repo、不自动 commit、不自动 push**。
|
|
144
143
|
- **交**(显式,你说了算):`open-memex submit <id...>` → 本地分支 + 本地 commit
|
|
145
144
|
进 `.ai/open-memex/`;push/PR 命令打印给你(或你的 agent 拿着你的 Yes 自己做)。
|
|
146
|
-
-
|
|
145
|
+
- **拉**:`open-memex pull`(必须显式,没有自动)→ git fetch + fast-forward →
|
|
147
146
|
扫描 `.ai/open-memex/*.md` → 按文件 mtime 合进本地 `index.db`。检索永远走 SQLite,不 walk git。
|
|
148
147
|
- **personal scope**:永远不同步(§1 铁律)。
|
|
149
148
|
- **没 git 的项目**:照常用,project scope 降级为纯本地并明确提示,不会坏掉。
|
|
@@ -158,4 +157,4 @@ server 不能主动推送,调不调 `memory_search` 全看 model 的判断。
|
|
|
158
157
|
|
|
159
158
|
---
|
|
160
159
|
|
|
161
|
-
*配套设计文档:`docs/V2-DESIGN.md`(D1–
|
|
160
|
+
*配套设计文档:`docs/V2-DESIGN.md`(D1–D49 决策记录)。*
|
package/docs/V2-DESIGN.md
CHANGED
|
@@ -494,7 +494,7 @@ Zero-config is survival for an open-source project. The opencode plugin remains
|
|
|
494
494
|
- **Phase 1 — Local hardening (1–2 wks).** CJK default (bigram+FTS5) · v1→v2 migration · dedup +
|
|
495
495
|
lifecycle · redaction hardening · scope docs. No external dependencies.
|
|
496
496
|
- **Phase 2A — MCP server (shipped 2026-09-27, D15).** Core/adapters split
|
|
497
|
-
(`src/tools/ops.ts`) · MCP server (`src/mcp.ts`, stdio) exposing all
|
|
497
|
+
(`src/tools/ops.ts`) · MCP server (`src/mcp.ts`, stdio) exposing all eleven memory tools —
|
|
498
498
|
read-only-first phasing dropped per D15 · query-aware injection stays host-side.
|
|
499
499
|
Ships in **`0.3.0-alpha`** (with bin/npx user-friendliness polish per §17 adoption path).
|
|
500
500
|
- **Phase 2B — Team sync.** GitProvider · `propose/promote/resolve` · in-repo dir · 1–2 colleague pilot
|
|
@@ -538,6 +538,23 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
538
538
|
curator convention ✅ `docs/CURATOR.md` (2026-09-29, pulled forward) ·
|
|
539
539
|
distill-to-AGENTS.md assist ✅ `open-memex distill-agents` (2026-09-29, pulled forward) ·
|
|
540
540
|
export/import archive command ✅ `open-memex export` / `import` (2026-09-29, pulled forward, D40).
|
|
541
|
+
- **0.5.0 (stable, 2026-09-29).** Init UX pass: `init --global` user-level editor
|
|
542
|
+
wiring (D45) · bare-init auto-detect wires all installed editors (D46) · JSONC
|
|
543
|
+
configs left untouched with a paste-ready snippet (D47) · `uninstall` reverses
|
|
544
|
+
`init` without touching memory data (D48) · empty config files treated as
|
|
545
|
+
blank, not corrupt (D49).
|
|
546
|
+
- **0.5.1 (stable).** `--help` accuracy: `mcp` help states the server exposes 11
|
|
547
|
+
tools (a superset of the opencode plugin's five memory tools); install hints point
|
|
548
|
+
at the stable line instead of `@alpha` (F27).
|
|
549
|
+
- **0.6.0 (in development).** Close the install→init gap (D50): postinstall
|
|
550
|
+
prints the `open-memex init` pointer (never prompts — CI-safe); bare
|
|
551
|
+
`open-memex` on a fresh machine offers to run init on a TTY. Close the
|
|
552
|
+
init→first-use gap (D51): init ends with a one-line next-step hint
|
|
553
|
+
(`open-memex add` + ask the agent to recall it). Agent-prompt clarity pass
|
|
554
|
+
(D52): rewrite both agent-facing prompts (MCP handshake + init template) so
|
|
555
|
+
agents execute them correctly. Push-not-poll outbox (D53): the checkpoint
|
|
556
|
+
mechanism is retired — the server reports the outbox draft count at session
|
|
557
|
+
start and appends it to mutating tool results when non-zero.
|
|
541
558
|
- **Phase 4 — Future, signal-gated.** Cloud `RemoteProvider` customization only on: multi-private-repo
|
|
542
559
|
sharing needs, fine-grained ACL, audit/compliance mandates · optional API-backed exporters/providers
|
|
543
560
|
for enterprise knowledge systems.
|
|
@@ -967,6 +984,78 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
967
984
|
*Rationale: an empty file is the safest write target, not a corrupt file;
|
|
968
985
|
refusing it sent the user down a manual path for no reason. Triggered by
|
|
969
986
|
Stone's report 2026-09-29.*
|
|
987
|
+
- **D50** — close the install→init gap (0.6.0-alpha.1). `npm install -g`
|
|
988
|
+
only puts the CLI on PATH; the editor wiring is `init`'s job, and a clean
|
|
989
|
+
reinstall wipes it — Stone hit exactly this on 2026-09-30 (fresh opencode
|
|
990
|
+
reinstall + `npm i -g open-memex`, then no open-memex in `opencode.jsonc`).
|
|
991
|
+
Two changes, both CI-safe: (1) a `postinstall` script that **prints**
|
|
992
|
+
`Run \`open-memex init\`…` — postinstall must never prompt, it runs in CI /
|
|
993
|
+
Docker / `npm ci` where stdin isn't a terminal; (2) bare `open-memex` on a
|
|
994
|
+
machine where init never completed **offers** to run it (default yes) when
|
|
995
|
+
stdin+stdout are TTYs, otherwise prints usage exactly as before. Asked-state
|
|
996
|
+
is a `.init.json` marker at the data root — init writes it on success, a
|
|
997
|
+
declined offer writes it too, so the question is asked once; `uninstall`
|
|
998
|
+
removes it (unwiring is the reverse of init, so the next bare run offers to
|
|
999
|
+
wire again). The marker is a dotfile and export builds from DB rows, so it
|
|
1000
|
+
can't leak into bundles. The offer re-execs `open-memex init` as a child
|
|
1001
|
+
with inherited stdio rather than calling init in-process — the offer's own
|
|
1002
|
+
readline already consumed stdin's buffer, and a second readline on the same
|
|
1003
|
+
stream would see EOF on burst input (verified with a pty test).
|
|
1004
|
+
*Rationale: install ≠ setup, and the gap only shows up on a fresh machine —
|
|
1005
|
+
exactly when the user has the least context. A printed hint covers the
|
|
1006
|
+
install moment; the interactive offer covers the first-run moment; neither
|
|
1007
|
+
can hang a pipeline. Approved 2026-09-30.*
|
|
1008
|
+
- **D51** — close the init→first-use gap (0.6.0-alpha.2). D50 gets the user to
|
|
1009
|
+
a wired editor; a first-time user then stops at "it's wired" with no idea
|
|
1010
|
+
what to do next. `init` now ends with one concrete next step:
|
|
1011
|
+
``Next step: `open-memex add "standup is at 9:30"` — then ask your agent what
|
|
1012
|
+
it remembers.`` One line, printed unconditionally — the cheapest possible
|
|
1013
|
+
onboarding after wiring. *Rationale: the CLI is editor-independent, so the
|
|
1014
|
+
hint works no matter which client was wired; `add` + recall is the smallest
|
|
1015
|
+
loop that proves the whole system works. Approved 2026-09-30.*
|
|
1016
|
+
|
|
1017
|
+
- **D52** — agent-prompt clarity rewrite (0.6.0-alpha.3). Both agent-facing
|
|
1018
|
+
prompts (MCP `initialize` instructions in `src/mcp.ts`, init instruction
|
|
1019
|
+
template in `src/init.ts`) are rewritten to fix ambiguities found on review:
|
|
1020
|
+
(1) the proactive-save vs approval-gate contradiction is resolved by naming
|
|
1021
|
+
the two cases — facts the user *states* are saved proactively, conclusions
|
|
1022
|
+
the agent *infers* are proposed first and saved only on approval;
|
|
1023
|
+
(2) `memory_status`/`memory_search` are excluded from the "after any
|
|
1024
|
+
memory_* action" checkpoint trigger (self-trigger loop);
|
|
1025
|
+
(3) tool names use the full `memory_*` form in both prompts;
|
|
1026
|
+
(4) the four checkpoints are defined once as "Checkpoints" and referenced,
|
|
1027
|
+
with an operational heuristic ("a task the user would describe in one
|
|
1028
|
+
sentence") replacing "meaningful chunk of work";
|
|
1029
|
+
(5) empty outbox → do nothing; "the user commits" → "any git commit in this
|
|
1030
|
+
session"; `type "reference"` named explicitly; PR-base mechanics spelled out
|
|
1031
|
+
per D28. *Rationale: these prompts are the product's UI for agents — a
|
|
1032
|
+
literal-minded agent must execute them correctly without guessing.
|
|
1033
|
+
Approved 2026-10-01.*
|
|
1034
|
+
|
|
1035
|
+
- **D53** — push-not-poll outbox: the checkpoint mechanism is retired; code
|
|
1036
|
+
pushes state to the agent instead (0.6.0-alpha.4). `src/submit.ts` gains
|
|
1037
|
+
`outboxDraftCount()` (one indexed SQLite COUNT on the current scope's outbox,
|
|
1038
|
+
no git I/O); `src/tools/ops.ts` gains `withOutboxNote()`, which appends
|
|
1039
|
+
`[open-memex: N draft(s) waiting in the project outbox — call memory_status
|
|
1040
|
+
to review]` to mutating tool results, only when N > 0. It is wired into the
|
|
1041
|
+
five MCP mutating tools (`memory_add`, `memory_supersede`, `memory_forget`,
|
|
1042
|
+
`memory_submit`, `memory_propose`) and the three opencode-plugin mutating
|
|
1043
|
+
tools (`memory_add`, `memory_supersede`, `memory_forget` — there the note
|
|
1044
|
+
stays generic because the plugin has no `memory_status`). The MCP server
|
|
1045
|
+
also appends the live draft count to the `initialize` instructions when N >
|
|
1046
|
+
0 (stdio servers start fresh per session, so construction-time state is
|
|
1047
|
+
session-start state). Both agent-facing prompts drop the Checkpoints section
|
|
1048
|
+
and the post-`memory_submit`/`memory_propose` status checks; the sync rule
|
|
1049
|
+
becomes one line ("when the server reports drafts waiting, call
|
|
1050
|
+
`memory_status`") plus the existing "sync memory" trigger. The static init
|
|
1051
|
+
template keeps one explicit session-start `memory_status` call, since a
|
|
1052
|
+
static file cannot carry live state. An anti-nag clause is added: if the
|
|
1053
|
+
agent already asked about these drafts this session, it does not ask again.
|
|
1054
|
+
*Rationale: the server knows the outbox state; making the agent poll for it
|
|
1055
|
+
on a timer wastes tool calls and teaches a habit that scales badly. The note
|
|
1056
|
+
is silent when the outbox is empty, so the common case costs nothing.
|
|
1057
|
+
`memory_submit` drains the outbox, so its own note is naturally silent.
|
|
1058
|
+
Approved 2026-10-01.*
|
|
970
1059
|
|
|
971
1060
|
## Open Questions
|
|
972
1061
|
|