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/src/mcp.ts
CHANGED
|
@@ -52,8 +52,10 @@ import {
|
|
|
52
52
|
memoryResolveArgs,
|
|
53
53
|
memoryPrStatusArgs,
|
|
54
54
|
TOOL_DESCRIPTIONS,
|
|
55
|
+
withOutboxNote,
|
|
55
56
|
type ToolResult,
|
|
56
57
|
} from "./tools/ops.ts";
|
|
58
|
+
import { outboxDraftCount } from "./submit.ts";
|
|
57
59
|
|
|
58
60
|
// Server version tracks package.json — never hardcode it here again.
|
|
59
61
|
// package.json sits two levels above this file in both layouts
|
|
@@ -75,40 +77,49 @@ const SERVER_VERSION: string = (() => {
|
|
|
75
77
|
* client at connect time. Still advisory — no MCP consumer offers a hard
|
|
76
78
|
* session-start hook — but it is the strongest signal available.
|
|
77
79
|
*/
|
|
78
|
-
const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP server
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP server.
|
|
81
|
+
Its tools are memory_add, memory_search, memory_list, memory_supersede,
|
|
82
|
+
memory_forget, memory_status, memory_submit, memory_propose, memory_promote,
|
|
83
|
+
memory_resolve, memory_pr_status — always call them by these full names.
|
|
81
84
|
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
call memory_status. If the project outbox has drafts waiting for review,
|
|
85
|
+
- The server tells you when project outbox drafts are waiting for review —
|
|
86
|
+
in tool results, and in these session-start instructions. When it does,
|
|
85
87
|
summarize them (one line each) and ask the user which ones to sync into the
|
|
86
|
-
repo
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
88
|
+
repo; sync NOTHING the user did not name. If you already asked about these
|
|
89
|
+
drafts this session, don't ask again. When the server reports none waiting,
|
|
90
|
+
do nothing.
|
|
91
|
+
- When the user says "sync memory" (or "同步记忆"), call memory_status,
|
|
92
|
+
summarize the outbox drafts (one line each), and ask which ones to sync.
|
|
93
|
+
ALWAYS use the memory_status tool for this — never browse the memory data
|
|
94
|
+
directory directly.
|
|
91
95
|
- After memory_submit, ask ONE follow-up: "want me to create a branch + push +
|
|
92
96
|
open the PR, or will you handle it yourself?" NEVER create branches, push, or
|
|
93
97
|
open PRs without the user's explicit approval. A "yes, you do it" covers the
|
|
94
98
|
whole chain — do NOT re-ask at each step.
|
|
95
|
-
- BE PROACTIVE
|
|
96
|
-
|
|
97
|
-
memory_add without being asked. Keep each memory to one
|
|
98
|
-
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
99
|
+
- BE PROACTIVE about facts the user states directly: when the user shares a
|
|
100
|
+
decision, preference, project convention, or fix-and-cause worth remembering
|
|
101
|
+
across sessions, call memory_add without being asked. Keep each memory to one
|
|
102
|
+
self-contained statement, and add a brief "(noted in memory)" so the user
|
|
103
|
+
sees it worked.
|
|
104
|
+
- For conclusions YOU infer (the user never stated them): when you finish a
|
|
105
|
+
task the user would describe in one sentence, consider distilling the
|
|
106
|
+
session — if there is something worth keeping,
|
|
107
|
+
propose 1–3 short memories capturing the useful conclusion (what was learned
|
|
108
|
+
or decided, how an issue was resolved, what to avoid, where the authoritative
|
|
109
|
+
doc lives — not the raw transcript), each with its proposed scope. Save
|
|
110
|
+
NOTHING the user did not approve; on approval call memory_add with source
|
|
111
|
+
"inference" at the approved scope. If the knowledge already lives in project
|
|
112
|
+
docs, save it as type "reference" pointing at the doc instead of copying it.
|
|
113
|
+
Long-form notes are fine ONLY when the user explicitly asks to save one.
|
|
107
114
|
- Before asking the user about past decisions, conventions, or preferences they
|
|
108
|
-
may have told you before, call memory_search first
|
|
115
|
+
may have told you before, call memory_search first — try a few keyword
|
|
116
|
+
variants (including the user's own language) when the first search comes up
|
|
117
|
+
empty.
|
|
109
118
|
- Memories default to this project's scope; use the personal scope for facts
|
|
110
|
-
about the user that hold across all projects.
|
|
111
|
-
|
|
119
|
+
about the user that hold across all projects. When a saved fact becomes
|
|
120
|
+
outdated, call memory_supersede (find the old memory's id with memory_search
|
|
121
|
+
first) instead of adding a duplicate.
|
|
122
|
+
`;
|
|
112
123
|
|
|
113
124
|
/** Adapt a framework-agnostic op result to an MCP tool response. */
|
|
114
125
|
function toMcp(p: Promise<ToolResult>) {
|
|
@@ -164,9 +175,17 @@ export async function runMcpServer() {
|
|
|
164
175
|
};
|
|
165
176
|
};
|
|
166
177
|
|
|
178
|
+
// D53: session-start outbox state, pushed. The stdio server starts fresh per
|
|
179
|
+
// session, so construction-time state ≈ session-start state.
|
|
180
|
+
const n = outboxDraftCount(scope.key);
|
|
181
|
+
const instructions =
|
|
182
|
+
n > 0
|
|
183
|
+
? `${SERVER_INSTRUCTIONS}\n\nSession start: the project outbox has ${n} draft${n === 1 ? "" : "s"} waiting for review — call memory_status to see them.`
|
|
184
|
+
: SERVER_INSTRUCTIONS;
|
|
185
|
+
|
|
167
186
|
const server = new McpServer(
|
|
168
187
|
{ name: "open-memex", version: SERVER_VERSION },
|
|
169
|
-
{ instructions
|
|
188
|
+
{ instructions },
|
|
170
189
|
);
|
|
171
190
|
|
|
172
191
|
server.registerTool(
|
|
@@ -175,7 +194,7 @@ export async function runMcpServer() {
|
|
|
175
194
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
176
195
|
inputSchema: z.object(memoryAddArgs),
|
|
177
196
|
},
|
|
178
|
-
withSync((args) => toMcp(addMemory(getScope, cfg, args))),
|
|
197
|
+
withSync((args) => toMcp(withOutboxNote(getScope().key, addMemory(getScope, cfg, args), "call memory_status to review"))),
|
|
179
198
|
);
|
|
180
199
|
|
|
181
200
|
server.registerTool(
|
|
@@ -204,7 +223,7 @@ export async function runMcpServer() {
|
|
|
204
223
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
205
224
|
inputSchema: z.object(memorySupersedeArgs),
|
|
206
225
|
},
|
|
207
|
-
withSync((args) => toMcp(supersedeMemory(cfg, args))),
|
|
226
|
+
withSync((args) => toMcp(withOutboxNote(getScope().key, supersedeMemory(cfg, args), "call memory_status to review"))),
|
|
208
227
|
);
|
|
209
228
|
|
|
210
229
|
server.registerTool(
|
|
@@ -214,7 +233,7 @@ export async function runMcpServer() {
|
|
|
214
233
|
inputSchema: z.object(memoryForgetArgs),
|
|
215
234
|
annotations: { destructiveHint: true },
|
|
216
235
|
},
|
|
217
|
-
withSync((args) => toMcp(forgetMemory(args))),
|
|
236
|
+
withSync((args) => toMcp(withOutboxNote(getScope().key, forgetMemory(args), "call memory_status to review"))),
|
|
218
237
|
);
|
|
219
238
|
|
|
220
239
|
server.registerTool(
|
|
@@ -233,7 +252,7 @@ export async function runMcpServer() {
|
|
|
233
252
|
description: TOOL_DESCRIPTIONS.memory_submit,
|
|
234
253
|
inputSchema: z.object(memorySubmitArgs),
|
|
235
254
|
},
|
|
236
|
-
withSync((args) => toMcp(submitMemoriesOp(args))),
|
|
255
|
+
withSync((args) => toMcp(withOutboxNote(getScope().key, submitMemoriesOp(args), "call memory_status to review"))),
|
|
237
256
|
);
|
|
238
257
|
|
|
239
258
|
server.registerTool(
|
|
@@ -242,7 +261,7 @@ export async function runMcpServer() {
|
|
|
242
261
|
description: TOOL_DESCRIPTIONS.memory_propose,
|
|
243
262
|
inputSchema: z.object(memoryProposeArgs),
|
|
244
263
|
},
|
|
245
|
-
withSync((args) => toMcp(proposeMemoriesOp(args))),
|
|
264
|
+
withSync((args) => toMcp(withOutboxNote(getScope().key, proposeMemoriesOp(args), "call memory_status to review"))),
|
|
246
265
|
);
|
|
247
266
|
|
|
248
267
|
server.registerTool(
|
package/src/paths.ts
CHANGED
|
@@ -21,6 +21,15 @@ export interface Paths {
|
|
|
21
21
|
|
|
22
22
|
let _cached: Paths | null = null;
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Read-only data-root path — never creates the directory. For existence
|
|
26
|
+
* checks (D50 first-run detection) that must not pollute storage; contrast
|
|
27
|
+
* `paths()`, which mkdirs as a side effect.
|
|
28
|
+
*/
|
|
29
|
+
export function dataRootPath(): string {
|
|
30
|
+
return dataRoot();
|
|
31
|
+
}
|
|
32
|
+
|
|
24
33
|
export function paths(): Paths {
|
|
25
34
|
if (_cached) return _cached;
|
|
26
35
|
const root = dataRoot();
|
package/src/submit.ts
CHANGED
|
@@ -153,6 +153,22 @@ export function getSyncStatus(): SyncStatus {
|
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* D53: cheap outbox draft count — one indexed query, no git I/O — for the
|
|
158
|
+
* push-not-poll note appended to mutating tool results and to the MCP
|
|
159
|
+
* session-start instructions. Same outbox definition as getSyncStatus
|
|
160
|
+
* (file under the scope's outbox dir), without the expensive parts.
|
|
161
|
+
*/
|
|
162
|
+
export function outboxDraftCount(scopeKey: string): number {
|
|
163
|
+
const outboxDir = memoriesDirPath(scopeKey);
|
|
164
|
+
const row = db()
|
|
165
|
+
.prepare(
|
|
166
|
+
`SELECT COUNT(*) AS n FROM memories WHERE scope_key = ? AND instr(file_path, ?) = 1`,
|
|
167
|
+
)
|
|
168
|
+
.get(scopeKey, outboxDir + path.sep) as { n: number } | undefined;
|
|
169
|
+
return row?.n ?? 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
156
172
|
export function formatSyncStatus(st: SyncStatus): string {
|
|
157
173
|
const lines: string[] = [];
|
|
158
174
|
lines.push(`project: ${st.projectName} (${st.scopeKey})`);
|
package/src/tools/memory.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
memorySupersedeArgs,
|
|
14
14
|
memoryForgetArgs,
|
|
15
15
|
TOOL_DESCRIPTIONS,
|
|
16
|
+
withOutboxNote,
|
|
16
17
|
} from "./ops.ts";
|
|
17
18
|
|
|
18
19
|
export function makeTools(getScope: () => Scope, cfg: MyOMemoryConfig) {
|
|
@@ -20,7 +21,7 @@ export function makeTools(getScope: () => Scope, cfg: MyOMemoryConfig) {
|
|
|
20
21
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
21
22
|
args: memoryAddArgs,
|
|
22
23
|
async execute(args) {
|
|
23
|
-
return addMemory(getScope, cfg, args);
|
|
24
|
+
return withOutboxNote(getScope().key, addMemory(getScope, cfg, args));
|
|
24
25
|
},
|
|
25
26
|
});
|
|
26
27
|
|
|
@@ -44,7 +45,7 @@ export function makeTools(getScope: () => Scope, cfg: MyOMemoryConfig) {
|
|
|
44
45
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
45
46
|
args: memorySupersedeArgs,
|
|
46
47
|
async execute(args) {
|
|
47
|
-
return supersedeMemory(cfg, args);
|
|
48
|
+
return withOutboxNote(getScope().key, supersedeMemory(cfg, args));
|
|
48
49
|
},
|
|
49
50
|
});
|
|
50
51
|
|
|
@@ -52,7 +53,7 @@ export function makeTools(getScope: () => Scope, cfg: MyOMemoryConfig) {
|
|
|
52
53
|
description: TOOL_DESCRIPTIONS.memory_forget,
|
|
53
54
|
args: memoryForgetArgs,
|
|
54
55
|
async execute(args) {
|
|
55
|
-
return forgetMemory(args);
|
|
56
|
+
return withOutboxNote(getScope().key, forgetMemory(args));
|
|
56
57
|
},
|
|
57
58
|
});
|
|
58
59
|
|
package/src/tools/ops.ts
CHANGED
|
@@ -24,7 +24,7 @@ import { upsertFromFile, deleteFromIndex } from "../store/sync.ts";
|
|
|
24
24
|
import { findDuplicates, supersede } from "../store/lifecycle.ts";
|
|
25
25
|
import { db } from "../store/db.ts";
|
|
26
26
|
import { redact } from "../redact.ts";
|
|
27
|
-
import { getSyncStatus, formatSyncStatus, submitMemories } from "../submit.ts";
|
|
27
|
+
import { getSyncStatus, formatSyncStatus, submitMemories, outboxDraftCount } from "../submit.ts";
|
|
28
28
|
import { getPrStatus, formatPrStatus, applyPrStatus } from "../github.ts";
|
|
29
29
|
import {
|
|
30
30
|
proposeMemories,
|
|
@@ -39,6 +39,26 @@ export interface ToolResult {
|
|
|
39
39
|
output: string;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* D53: push, don't poll. Append the project-outbox pending count to mutating
|
|
44
|
+
* tool results so agents learn about drafts waiting for review without a
|
|
45
|
+
* checkpoint poll. Silent when the outbox is empty. `reviewHint` names the
|
|
46
|
+
* tool to call (MCP); transports without that tool leave it generic.
|
|
47
|
+
*/
|
|
48
|
+
export async function withOutboxNote(
|
|
49
|
+
scopeKey: string,
|
|
50
|
+
p: Promise<ToolResult>,
|
|
51
|
+
reviewHint?: string,
|
|
52
|
+
): Promise<ToolResult> {
|
|
53
|
+
const r = await p;
|
|
54
|
+
const n = outboxDraftCount(scopeKey);
|
|
55
|
+
if (n > 0) {
|
|
56
|
+
const tail = reviewHint ? ` — ${reviewHint}` : " for review";
|
|
57
|
+
r.output += `\n[open-memex: ${n} draft${n === 1 ? "" : "s"} waiting in the project outbox${tail}]`;
|
|
58
|
+
}
|
|
59
|
+
return r;
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
/** LLM-facing tool descriptions, shared by the opencode plugin and the MCP server. */
|
|
43
63
|
export const TOOL_DESCRIPTIONS = {
|
|
44
64
|
memory_add:
|