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/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\`) with eleven tools:
44
- \`memory_add\`, \`memory_search\`, \`memory_list\`, \`memory_supersede\`, \`memory_forget\`,
45
- \`memory_status\`, \`memory_submit\`, \`memory_propose\`, \`memory_promote\`, \`memory_resolve\`,
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
- - BE PROACTIVE. When the user shares something worth remembering across sessions
49
- (a decision, a preference, a project convention, a fix and its cause), call
50
- \`memory_add\` without being asked. Keep each memory to one self-contained statement.
51
- - At checkpoints (session start, end of a work chunk, after the user commits, after
52
- any memory_* action), DISTILL the session: propose 1–3 short memories capturing the
53
- useful conclusion — what was learned or decided, how an issue was resolved, what to
54
- avoid, where the authoritative doc lives — not the raw transcript. Save NOTHING the
55
- user did not approve; on approval call \`memory_add\` with source "inference" at the
56
- confirmed scope. If the knowledge already lives in project docs, save a \`reference\`
57
- memory pointing at the doc instead of copying it. Long-form notes are fine ONLY when
58
- the user explicitly asks to save one.
59
- - Before asking the user about past decisions, conventions, or preferences they may
60
- have told you before, call \`memory_search\` first — try a few keyword variants
61
- (including the user's own language) when the first search comes up empty.
62
- - Memories default to this project's scope; use the \`personal\` scope for facts about
63
- the user that hold across all projects. When a saved fact becomes outdated, call
64
- \`memory_supersede\` instead of adding a duplicate.
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 yet.
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
- - At session start, when you finish a meaningful chunk of work, after the user
72
- commits (git commit), and after any memory_* action completes, call
73
- \`memory_status\`. If the outbox has drafts, summarize them (one line each) and ask
74
- the user which ones to sync. Sync NOTHING the user did not name.
75
- - When the user says "sync memory" (or "同步记忆"), treat it as a request to run
76
- the sync flow above: call \`memory_status\`, summarize the outbox drafts, and ask
77
- which ones to sync.
78
- - When the user approves, call \`memory_submit\` with the approved ids. It copies
79
- the drafts into the repo as \`proposed\`, commits locally on the CURRENT branch,
80
- and prints the push + PR commands. It NEVER creates a branch on its own.
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 covers
83
- the whole chain — branch creation, push, PR creation — do NOT re-ask at each
84
- step. If the user says they will do it themselves, hand them the printed
85
- push/PR commands and do nothing. NEVER create branches, push, or open PRs
86
- without their explicit approval.
87
- - Base branch for the memory PR defaults to the branch you are on; the user may
88
- redirect it to the integration branch (main) for branch-independent knowledge.
89
- - If anything conflicts (same id with different content, push rejected), STOP and
90
- let the user judge — never overwrite.
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
- (eleven memory_* tools: add, search, list, supersede, forget, status, submit,
54
- propose, promote, resolve, pr_status).
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
- - At the START of this session, when you finish a meaningful chunk of work,
57
- after the user commits (git commit), and after any memory_* action completes,
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. Sync NOTHING the user did not name.
61
- - When the user says "sync memory" (or "同步记忆"), treat it as a request to run
62
- the sync flow: call memory_status, summarize the outbox drafts, and ask which
63
- ones to sync. ALWAYS use the memory_status tool for this — never browse the
64
- appdata directory directly.
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: when the user shares something worth remembering across sessions
70
- (a decision, a preference, a project convention, a fix and its cause), call
71
- memory_add without being asked. Keep each memory to one self-contained statement.
72
- - At the same checkpoints (session start, end of a work chunk, after the user
73
- commits, after any memory_* action), DISTILL the session: propose 1–3 short
74
- memories capturing the useful conclusion — what was learned or decided, how an
75
- issue was resolved, what to avoid, where the authoritative doc lives — not the
76
- raw transcript. Save NOTHING the user did not approve; on approval call
77
- memory_add with source "inference" at the confirmed scope. If the knowledge
78
- already lives in project docs, save a \`reference\` memory pointing at the doc
79
- instead of copying it. Long-form notes are fine ONLY when the user explicitly
80
- asks to save one.
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
- - personal scope memories NEVER leave this machine.`;
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
- const server = new McpServer({ name: "open-memex", version: SERVER_VERSION }, { instructions: SERVER_INSTRUCTIONS });
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})`);
@@ -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 (planned, not yet enforced)
67
+ ## Visibility
68
68
 
69
69
  v2 frontmatter carries a separate `visibility` field (`private` | `internal` |
70
- `shared`). The intended rule: `visibility: private` inside a shared scope is
71
- **physically isolated** — written to a local-only cache directory, never
72
- under `.open-memex/` — rather than relying on `.gitignore`. This is not
73
- implemented yet; today, treat `personal` as the only confidentiality
74
- boundary and review anything you place under `.open-memex/` before pushing.
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.4.0-alpha.10)
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@alpha` 全局安装,`open-memex --version` 显示正确版本
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@alpha init` 走通
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):注意是否丢数据(预期:不丢,只影响退出码)
@@ -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) are implemented on the
6
- > `V2-dev-p2b` branch; features marked **2B** are still to be built;
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** **2B**: `open-memex pull` (always explicit, never automatic) → git fetch +
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–D24).*
196
+ *Companion design record: `docs/V2-DESIGN.md` (decisions D1–D49).*
@@ -2,8 +2,7 @@
2
2
 
3
3
  > 本文档讲的是 open-memex 的**心智模型**:你的记忆住在哪里、怎么流动、谁能看到。
4
4
  > in-repo 目录(§2、§6 的写路径)和 propose → promote → resolve 工作流(§5)
5
- > 已在 `V2-dev-p2b` 分支实现;标有 **2B** 的功能属于 Phase 2B 待实现部分,
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
- - **拉** **2B**:`open-memex pull`(必须显式,没有自动)→ git fetch + fast-forward →
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–D24 决策记录)。*
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 five memory tools —
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