open-memex 0.7.3-alpha.1 → 0.7.3
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/CHANGELOG.md +4 -0
- package/dist/.build-stamp +1 -1
- package/dist/mcp.js +11 -5
- package/dist/tools/ops.js +15 -15
- package/docs/V2-DESIGN.md +16 -2
- package/package.json +1 -1
- package/scripts/smoke-mcp.ts +3 -0
- package/server.json +3 -3
- package/src/mcp.ts +11 -5
- package/src/tools/ops.ts +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,10 @@ the `@alpha` tag are development snapshots and are not listed individually.
|
|
|
9
9
|
|
|
10
10
|
## [Unreleased]
|
|
11
11
|
|
|
12
|
+
## [0.7.3] - 2026-10-07
|
|
13
|
+
|
|
14
|
+
- Tool-definition quality pass (Glama TDQS 4.0/5 on v0.7.2): fixed `memory_pr_status` declaring `readOnlyHint` while `apply=true` mutates local files; added explicit MCP annotations (read-only / destructive / open-world) to all 11 tools; descriptions now route between sibling tools (propose vs submit vs promote, resolve vs pr_status, forget vs supersede), state error behavior and real defaults (search limit 8, list limit 20), and the search/list `type` and `limit` parameters are documented. D76 records the rule; `scripts/smoke-mcp.ts` guards the pr_status annotation.
|
|
15
|
+
|
|
12
16
|
- Design doc maintenance (D75): pruned resolved Open Questions (decided in D56/D57/D61/D68, plus the shipped retrieval-robustness layers) and recorded four competitor-scan directions — retrieval evaluation gate, per-client trust levels, cold-start import, and a `doctor` mis-registration check.
|
|
13
17
|
|
|
14
18
|
- Dropped the deprecated `user` value from the MCP tools' advertised `scope` enums (`personal` is the only global-scope name now; the storage layer still migrates v1 `user` data automatically).
|
package/dist/.build-stamp
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
c5a82fa9f711dea3b1e8787d551424e978d606435b08f83d51ba5aaf0996e9ff
|
package/dist/mcp.js
CHANGED
|
@@ -164,51 +164,57 @@ export async function runMcpServer() {
|
|
|
164
164
|
server.registerTool("memory_add", {
|
|
165
165
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
166
166
|
inputSchema: z.object(memoryAddArgs),
|
|
167
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
167
168
|
}, withSync((args) => toMcp(withOutboxNote(getScope().key, addMemory(getScope, cfg, args), "call memory_status to review"))));
|
|
168
169
|
server.registerTool("memory_search", {
|
|
169
170
|
description: TOOL_DESCRIPTIONS.memory_search,
|
|
170
171
|
inputSchema: z.object(memorySearchArgs),
|
|
171
|
-
annotations: { readOnlyHint: true },
|
|
172
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
172
173
|
}, withSync((args) => toMcp(searchMemories(getScope, args))));
|
|
173
174
|
server.registerTool("memory_list", {
|
|
174
175
|
description: TOOL_DESCRIPTIONS.memory_list,
|
|
175
176
|
inputSchema: z.object(memoryListArgs),
|
|
176
|
-
annotations: { readOnlyHint: true },
|
|
177
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
177
178
|
}, withSync((args) => toMcp(listMemories(getScope, args))));
|
|
178
179
|
server.registerTool("memory_supersede", {
|
|
179
180
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
180
181
|
inputSchema: z.object(memorySupersedeArgs),
|
|
182
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
181
183
|
}, withSync((args) => toMcp(withOutboxNote(getScope().key, supersedeMemory(cfg, args), "call memory_status to review"))));
|
|
182
184
|
server.registerTool("memory_forget", {
|
|
183
185
|
description: TOOL_DESCRIPTIONS.memory_forget,
|
|
184
186
|
inputSchema: z.object(memoryForgetArgs),
|
|
185
|
-
annotations: { destructiveHint: true },
|
|
187
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
186
188
|
}, withSync((args) => toMcp(withOutboxNote(getScope().key, forgetMemory(args), "call memory_status to review"))));
|
|
187
189
|
server.registerTool("memory_status", {
|
|
188
190
|
description: TOOL_DESCRIPTIONS.memory_status,
|
|
189
191
|
inputSchema: z.object(memoryStatusArgs),
|
|
190
|
-
annotations: { readOnlyHint: true },
|
|
192
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
191
193
|
}, withSync((_args) => toMcp(statusMemories())));
|
|
192
194
|
server.registerTool("memory_submit", {
|
|
193
195
|
description: TOOL_DESCRIPTIONS.memory_submit,
|
|
194
196
|
inputSchema: z.object(memorySubmitArgs),
|
|
197
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
195
198
|
}, withSync((args) => toMcp(withOutboxNote(getScope().key, submitMemoriesOp(args), "call memory_status to review"))));
|
|
196
199
|
server.registerTool("memory_propose", {
|
|
197
200
|
description: TOOL_DESCRIPTIONS.memory_propose,
|
|
198
201
|
inputSchema: z.object(memoryProposeArgs),
|
|
202
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
199
203
|
}, withSync((args) => toMcp(withOutboxNote(getScope().key, proposeMemoriesOp(args), "call memory_status to review"))));
|
|
200
204
|
server.registerTool("memory_promote", {
|
|
201
205
|
description: TOOL_DESCRIPTIONS.memory_promote,
|
|
202
206
|
inputSchema: z.object(memoryPromoteArgs),
|
|
207
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
203
208
|
}, withSync((args) => toMcp(promoteMemoryOp(args))));
|
|
204
209
|
server.registerTool("memory_resolve", {
|
|
205
210
|
description: TOOL_DESCRIPTIONS.memory_resolve,
|
|
206
211
|
inputSchema: z.object(memoryResolveArgs),
|
|
212
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
207
213
|
}, withSync((args) => toMcp(resolveMemoryOp(args))));
|
|
208
214
|
server.registerTool("memory_pr_status", {
|
|
209
215
|
description: TOOL_DESCRIPTIONS.memory_pr_status,
|
|
210
216
|
inputSchema: z.object(memoryPrStatusArgs),
|
|
211
|
-
annotations: { readOnlyHint:
|
|
217
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
212
218
|
}, withSync((args) => toMcp(prStatusOp(args))));
|
|
213
219
|
const transport = new StdioServerTransport();
|
|
214
220
|
await server.connect(transport);
|
package/dist/tools/ops.js
CHANGED
|
@@ -38,17 +38,17 @@ export async function withOutboxNote(scopeKey, p, reviewHint) {
|
|
|
38
38
|
}
|
|
39
39
|
/** LLM-facing tool descriptions, shared by the opencode plugin and the MCP server. */
|
|
40
40
|
export const TOOL_DESCRIPTIONS = {
|
|
41
|
-
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. Worth saving: decisions and their reasons, preferences, conventions, gotchas, approaches tried and abandoned. Not worth saving: one-off task details or anything re-derivable from the code. Do not wait to be asked. For facts you infer yourself rather than the user stating, propose them first and save only on approval. Keep each memory to one self-contained statement; attach aliases when the parameter is available. If a note tells you the user's own wording was already stored verbatim this turn, that statement is already saved: do not retry it in a rewording — save only what that text does not contain, or use memory_supersede on the given id. Default scope is the current project; use the personal scope for facts about the user that apply across all projects.",
|
|
42
|
-
memory_search: "Search persistent memory by keyword (BM25 full-text). Returns matching memories from the current project and/or personal scope. Call before asking the user about past decisions, conventions, or preferences they may have told you before. Search well: break the question into its concepts and try 2–3 phrasings per concept — synonyms, the user's other language, shorter keyword forms — and check the other scope too, before concluding nothing is stored.",
|
|
43
|
-
memory_list: "List memories in a scope, newest first, as a numbered inventory. Each line keeps the raw fields — [type] id=… created=… source=… — say them back to the user in plain words (source=user → 'you told me this'; inference → 'I inferred this, check me'; keyword → 'caught from your own wording'), never read the raw id aloud unless they ask. When the user asks 'what do you remember about me?', use scope=both and present the inventory conversationally. The user may point at an entry by its number ('delete #3'): numbers are only valid for the listing you just produced — re-run memory_list, read the candidate back in full, and get a confirmation before calling memory_forget (deletion is permanent). include=all is the audit view (superseded versions, retracted, archived also shown).",
|
|
44
|
-
memory_supersede: "Replace an existing memory with a newer version. The old memory is kept as history (status: superseded) and retrieval returns the new one. Use when a saved fact becomes outdated and should be replaced rather than duplicated.",
|
|
45
|
-
memory_forget: "Delete a memory by id. Use when the user asks to forget something. Deletion is permanent: before deleting, restate the memory's content to the user and get a confirmation. With soft=true the memory is hidden instead (retracted: out of lists and search, file kept, one-way).",
|
|
46
|
-
memory_status: "Show the project memory sync pipeline: drafts waiting in the outbox (appdata), memories in the repo awaiting review or published, and any repo files not yet committed. Call this at session start, when the server reports drafts waiting for review, or when the user says 'sync memory' (or '同步记忆'); then ask the user which drafts to sync. (MCP server and the `open-memex sync-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
47
|
-
memory_submit: "Move outbox drafts into the repo memory dir for review: copies the drafts in as proposed (or keeps a local approval), commits locally on the current branch, and moves the outbox originals out. Never creates a branch on its own — pass branch= only with the user's explicit approval for the full chain. Prints the push and PR commands — those need the user's explicit approval and are never run automatically. (MCP server and the `open-memex submit` CLI; the opencode native plugin does not expose this tool.)",
|
|
48
|
-
memory_propose: "Copy personal memories into the project outbox as review drafts. The personal originals stay put. (MCP server and the `open-memex propose` CLI; the opencode native plugin does not expose this tool.)",
|
|
49
|
-
memory_promote: "Advance a project memory one step up the review ladder (proposed → approved → published), or reject it with a note. Rejected memories are never deleted — they can be revised and resubmitted. (MCP server and the `open-memex promote` CLI; the opencode native plugin does not expose this tool.)",
|
|
50
|
-
memory_resolve: "List git-conflicted memory files, or attempt a field-level 3-way merge of one. Semantic conflicts are reported, never auto-resolved. (MCP server and the `open-memex resolve` CLI; the opencode native plugin does not expose this tool.)",
|
|
51
|
-
memory_pr_status: "Read the current branch's GitHub PR and map its review state onto each in-repo memory: merged PR → published, PR approval → approved (approved_by = reviewer), changes-requested → suggestion only. Report by default; apply=true
|
|
41
|
+
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. Worth saving: decisions and their reasons, preferences, conventions, gotchas, approaches tried and abandoned. Not worth saving: one-off task details or anything re-derivable from the code. Do not wait to be asked. For facts you infer yourself rather than the user stating, propose them first and save only on approval. Keep each memory to one self-contained statement; attach aliases when the parameter is available. If a note tells you the user's own wording was already stored verbatim this turn, that statement is already saved: do not retry it in a rewording — save only what that text does not contain, or use memory_supersede on the given id. Default scope is the current project; use the personal scope for facts about the user that apply across all projects. Returns the new memory's id; an identical fact already stored returns that id instead of duplicating.",
|
|
42
|
+
memory_search: "Search persistent memory by keyword (BM25 full-text). Returns matching memories from the current project and/or personal scope. Call before asking the user about past decisions, conventions, or preferences they may have told you before. Search well: break the question into its concepts and try 2–3 phrasings per concept — synonyms, the user's other language, shorter keyword forms — and check the other scope too, before concluding nothing is stored. Results are ranked by BM25 within review tiers — reviewed (approved/published) project memories come first. `limit` caps how many come back (default 8) and `type` restricts to one memory type.",
|
|
43
|
+
memory_list: "List memories in a scope, newest first, as a numbered inventory. Each line keeps the raw fields — [type] id=… created=… source=… — say them back to the user in plain words (source=user → 'you told me this'; inference → 'I inferred this, check me'; keyword → 'caught from your own wording'), never read the raw id aloud unless they ask. When the user asks 'what do you remember about me?', use scope=both and present the inventory conversationally. The user may point at an entry by its number ('delete #3'): numbers are only valid for the listing you just produced — re-run memory_list, read the candidate back in full, and get a confirmation before calling memory_forget (deletion is permanent). include=all is the audit view (superseded versions, retracted, archived also shown). `type` filters to one memory type; `limit` caps how many entries come back.",
|
|
44
|
+
memory_supersede: "Replace an existing memory with a newer version. The old memory is kept as history (status: superseded) and retrieval returns the new one. Use when a saved fact becomes outdated and should be replaced rather than duplicated. Only active memories can be superseded; the new memory inherits type, tags, and aliases unless you pass replacements. An unknown id is reported back as a failure; nothing is written.",
|
|
45
|
+
memory_forget: "Delete a memory by id. Use when the user asks to forget something. To replace an outdated fact with a corrected one instead of deleting it, use memory_supersede. Deletion is permanent: before deleting, restate the memory's content to the user and get a confirmation. With soft=true the memory is hidden instead (retracted: out of lists and search, file kept, one-way).",
|
|
46
|
+
memory_status: "Show the project memory sync pipeline: drafts waiting in the outbox (appdata), memories in the repo awaiting review or published, and any repo files not yet committed. Call this at session start, when the server reports drafts waiting for review, or when the user says 'sync memory' (or '同步记忆'); then ask the user which drafts to sync. For the memories themselves use memory_list; for PR review state use memory_pr_status. (MCP server and the `open-memex sync-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
47
|
+
memory_submit: "Move outbox drafts into the repo memory dir for review: copies the drafts in as proposed (or keeps a local approval), commits locally on the current branch, and moves the outbox originals out. Never creates a branch on its own — pass branch= only with the user's explicit approval for the full chain. Prints the push and PR commands — those need the user's explicit approval and are never run automatically. (MCP server and the `open-memex submit` CLI; the opencode native plugin does not expose this tool.) Unknown ids or a failed local commit abort the submit with the reason; nothing is published automatically.",
|
|
48
|
+
memory_propose: "Copy personal memories into the project outbox as review drafts. The personal originals stay put. Use this when personal knowledge should enter a project's review flow; memory_submit then moves the drafts into the repo, and memory_promote advances drafts already there. (MCP server and the `open-memex propose` CLI; the opencode native plugin does not expose this tool.)",
|
|
49
|
+
memory_promote: "Advance a project memory one step up the review ladder (proposed → approved → published), or reject it with a note. Rejected memories are never deleted — they can be revised and resubmitted. One step per call; an unknown id or a memory already published errors. Pass `by` to record the reviewer when it should differ from the default (e.g. approving on the strength of someone else's review). For brand-new outbox drafts, memory_submit is the entry point. (MCP server and the `open-memex promote` CLI; the opencode native plugin does not expose this tool.)",
|
|
50
|
+
memory_resolve: "List git-conflicted memory files, or attempt a field-level 3-way merge of one. Semantic conflicts are reported, never auto-resolved. Run it when a git pull or merge leaves memory files conflicted; the merged file is written in place (undo with git if the merge is wrong). For PR review state rather than file conflicts, use memory_pr_status. (MCP server and the `open-memex resolve` CLI; the opencode native plugin does not expose this tool.)",
|
|
51
|
+
memory_pr_status: "Read the current branch's GitHub PR and map its review state onto each in-repo memory: merged PR → published, PR approval → approved (approved_by = reviewer), changes-requested → suggestion only. Report by default — that mode changes nothing; apply=true writes the mapped transitions to the local files (still no push). (MCP server and the `open-memex pr-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
52
52
|
};
|
|
53
53
|
/** Shared zod input shapes (raw shape, not z.object — hosts wrap as needed). */
|
|
54
54
|
export const scopeArg = z
|
|
@@ -82,16 +82,16 @@ export const memorySearchArgs = {
|
|
|
82
82
|
.enum(["project", "personal", "both"])
|
|
83
83
|
.optional()
|
|
84
84
|
.describe("Which scope(s) to search. Default: both."),
|
|
85
|
-
type: z.string().optional().describe("Restrict to memories of this type."),
|
|
86
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
85
|
+
type: z.string().optional().describe("Restrict to memories of this type (e.g. decision, preference, gotcha)."),
|
|
86
|
+
limit: z.number().int().min(1).max(50).optional().describe("Max results to return (default 8)."),
|
|
87
87
|
};
|
|
88
88
|
export const memoryListArgs = {
|
|
89
89
|
scope: z
|
|
90
90
|
.enum(["project", "personal", "both"])
|
|
91
91
|
.optional()
|
|
92
92
|
.describe("Which scope(s) to list. `project` = tied to this repo. `personal` = global across all your projects. `both` lists each scope's newest. Default: project."),
|
|
93
|
-
type: z.string().optional(),
|
|
94
|
-
limit: z.number().int().min(1).max(100).optional(),
|
|
93
|
+
type: z.string().optional().describe("Only list memories of this type (e.g. decision, preference, gotcha)."),
|
|
94
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max entries to list, newest first (default 20)."),
|
|
95
95
|
include: z
|
|
96
96
|
.enum(["active", "all"])
|
|
97
97
|
.optional()
|
package/docs/V2-DESIGN.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# OpenMemex — Design Document (protocol v0.2)
|
|
2
2
|
|
|
3
|
-
**Status:** FROZEN — protocol v0.2 (2026-09-26). Decisions D1–
|
|
3
|
+
**Status:** FROZEN — protocol v0.2 (2026-09-26). Decisions D1–D78 are settled; open questions are tracked at the end of this document.
|
|
4
4
|
**Author:** Stone, with 小沐
|
|
5
5
|
**Changelog vs v1:** incorporates round-3 review from Perplexity, Grok, Gemini, ChatGPT, DeepSeek.
|
|
6
6
|
Key changes: Design Principles section; `role` separated from `type`; two iron rules;
|
|
@@ -562,7 +562,7 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
562
562
|
- **Phase 4 — Future, signal-gated.** Cloud `RemoteProvider` customization only on: multi-private-repo
|
|
563
563
|
sharing needs, fine-grained ACL, audit/compliance mandates · optional API-backed exporters/providers
|
|
564
564
|
for enterprise knowledge systems.
|
|
565
|
-
- **Phase 5 — Candidates recorded 2026-10-04 (not committed).** From the competitor scan (D75): an offline FTS5 retrieval eval with a published recall number (gates any semantic-layer decision) · per-client trust levels on the review ladder · cold-start Markdown import into outbox drafts · `doctor` check that each editor's MCP registration points at the intended data root (mis-rooted servers are a real failure mode elsewhere). Sequencing and scope TBD per candidate.
|
|
565
|
+
- **Phase 5 — Candidates recorded 2026-10-04 (not committed).** From the competitor scan (D75): an offline FTS5 retrieval eval with a published recall number (gates any semantic-layer decision) [promoted to next build, D77] · per-client trust levels on the review ladder · cold-start Markdown import into outbox drafts · `doctor` check that each editor's MCP registration points at the intended data root (mis-rooted servers are a real failure mode elsewhere). Sequencing and scope TBD per candidate.
|
|
566
566
|
|
|
567
567
|
## 19. Migration v1 → v2
|
|
568
568
|
|
|
@@ -1592,6 +1592,12 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
1592
1592
|
|
|
1593
1593
|
- **D75 — closed questions leave the list; competitor-inspired directions enter it (2026-10-04).** The Open Questions section is a working list, not an archive: the entries decided in D56 (host parity), D57 (retrieval explainability / memory health), D61 (checkpoint proactivity, capture criteria, retrieval layers 2–3), and D68 (memory visibility), plus the retrieval-robustness entry whose three cheap layers all shipped, are removed; the decisions remain the record. A same-day scan of five Glama-listed memory servers (auxly-memory-cli, agent-memory, remnic, Agent-Memory-Bridge, memtomem, memryzed) confirmed the current wedge — proactive in-session capture + one memory shared across editors + PR-based team sharing — appears in none of them whole. Borrowable directions are recorded instead: four new open questions (retrieval evaluation gate, per-client trust levels, cold-start import, session working state) and one roadmap candidate (`doctor` mis-registration check). Nothing here commits implementation; each direction needs its own design pass.
|
|
1594
1594
|
|
|
1595
|
+
- **D76 — tool annotations must tell the truth (2026-10-05).** `memory_pr_status` declared `readOnlyHint: true` while `apply=true` writes local review states (caught by Glama's TDQS evaluation of v0.7.2, 4.0/5). Structured hints are part of the protocol surface: all 11 tools now declare read-only / destructive / open-world hints that match their behavior, descriptions route between sibling tools instead of leaving agents to guess, and `scripts/smoke-mcp.ts` guards the pr_status combination. Description honesty is held to the same standard as behavior: ranking claims, defaults, and error semantics in tool text must match the code.
|
|
1596
|
+
|
|
1597
|
+
- **D77 — measure usefulness by reuse, not storage (2026-10-07).** The Spark Tank submission commits the metric: "measured by reuse — fewer repeated questions, less context-prep time, approved memories reused by non-authors — not by memory count." Three layers, in build order: (1) **Offline retrieval eval.** A synthetic fixture (memories + queries with gold ids) run against the FTS5 baseline; publish recall@k/MRR in the README. This also answers the D75 "retrieval evaluation gate" open question — the number gates any future semantic-layer decision (reference points: agentmemory's published LongMemEval-S R@5 95.2%; lotargo/memory_plugin's BM25-only vs hybrid table). Later, run the MemoryAgentBench harness for directly comparable numbers (Knowl 90 / Mem0 18 / MIRIX 14 / Zep 7). (2) **Online reuse counters.** Per memory: `retrieved_count` (returned by search/list/first-turn injection) and `useful_count`, split into `useful_self` (author's own reuse — the personal-utility story) and `useful_team` (reuse of published project memories across sessions — the Spark Tank "reused by non-authors" story; no identity system needed: published state plus session diversity is the proxy). Both count as useful; reporting them separately keeps the team-knowledge claim honest. Explicit confirmation: a `memory_useful` tool call, human approve at review; behavioral: supersede/promote referencing it. Passive surfacing must never refresh usefulness — the opencode-memory lesson: unguarded exposure creates a self-reinforcing retrieval loop. Derived: `reuse_rate = useful / retrieved`; "zombie memories" (retrieved but never confirmed useful) surface in `memory_status` as decay/prune candidates. Counters live in SQLite, not frontmatter; local-only. (3) **Status surfaces.** `memory_status` gains a reuse report: top reused, zero-reuse list, per-scope reuse rate — feeding the Spark Tank pilot metrics. Moat note: competitors' "useful" is agent self-reported; our review ladder gives a human-grounded signal — approved/published means a human already said it is worth sharing. **Priority: this is the next build after 0.7.3** — fixture eval first (1–2 days, publishable number), then counters + `memory_useful`, then the status report, then the external bench.
|
|
1598
|
+
|
|
1599
|
+
- **D78 — pre-submit curation: human-on-the-loop, not in-the-loop (2026-10-07).** Fragmented captures must be organized before submit, but the submitter's patience is the scarce resource — every manual gate lowers the submit rate, so curation defaults to automatic and the human handles exceptions. Confidence tiers: **auto-apply** (exact duplicates, trivial fragments, inferred type/tags, title normalization); **batch-confirm** (semantic merges, splits — one summary screen, one tap to accept or undo, never per-item approval); **explicit human decision** (dropping substantive content, and submit itself — the D-hard boundary "project scope never auto-submits" stands: automation covers organizing, sharing stays human). The safety net is the supersede chain: every auto-merge preserves provenance and is undoable, so pre-approval is replaced by reversibility (undo-send, not confirm-dialog). Trigger: no new step to remember — `submit` becomes curate+submit in one command, with the curation summary attached to the PR description; session wrap-up is the alternative hook (batch-silent, per the D61 checkpoint posture). Closed loop: human undos feed back into confidence thresholds, consuming the D77 `useful_count` signals. This revises the earlier review-ladder instinct of per-item approval: the human moves from in-the-loop to on-the-loop.
|
|
1600
|
+
|
|
1595
1601
|
## Open Questions
|
|
1596
1602
|
|
|
1597
1603
|
_Pruned 2026-10-04 (D75): entries decided in D56/D57/D61/D68 and the shipped layers of the retrieval-robustness question were removed — the decisions are the record. What remains below is genuinely open._
|
|
@@ -1610,6 +1616,14 @@ _Pruned 2026-10-04 (D75): entries decided in D56/D57/D61/D68 and the shipped lay
|
|
|
1610
1616
|
|
|
1611
1617
|
- **Session working state: resume where you left off (open, 2026-10-04):** open-memex stores durable knowledge, not the state of an interrupted task. memryzed's third layer — per-repository session state (open files, last commands, where the work stands) so a fresh session resumes mid-task — names the gap. Candidate shape, if pursued: an ephemeral per-project working-state note, agent-written at wrap-up and surfaced at session start alongside memory injection, *outside* the review ladder (it is scratch state, not knowledge) with a short TTL. Open questions: what belongs in it (task summary? file list? next step?), who writes it (agent at Stop? user command?), and the overlap with the retrospective-distillation and checkpoint-proactivity threads. Defer until there is signal that users lose work context, not just facts.
|
|
1612
1618
|
|
|
1619
|
+
- **The three Copilots: three integration surfaces (open, 2026-10-05):** "Copilot" is three different products with three different extension models, and open-memex's posture differs per surface. (Stone's machines: home has all three; the work machine has no Microsoft Copilot app.)
|
|
1620
|
+
- **GitHub Copilot (editor, developer) — decided / shipped:** `open-memex init --client vscode|cursor|…` writes the MCP server entry; all 11 tools via MCP; the Agent Skill + instruction files drive proactive capture. This is the production path today.
|
|
1621
|
+
- **Microsoft Copilot (Windows consumer app; formerly "Windows Copilot") — open, unverified:** the app itself claims MCP registration via `%LocalAppData%\Microsoft\Copilot\mcp\mcp.json` (a `servers` array with `id` / `type: "stdio"` / `command` / `args` / `cwd`), but no public documentation exists for this path, and it is not the same thing as the OS-level On-Device Agent Registry (ODR, `odr.exe` + MSIX manifest) — the documented mechanism, still in public preview. Two verified technical constraints for any implementation: (1) on Windows the npm global bin is `open-memex.cmd`, which cannot be spawned without a shell — the entry must be `cmd /c open-memex mcp` (or `node` pointed at `dist/mcp.js`); (2) the MCP server resolves project scope from the process cwd, so a global assistant config needs a deliberate "home base" `cwd`, not a source checkout. No Skills/instructions layer loads here (Copilot Studio Skills packages are M365-only), so proactive capture degrades to "save when asked". Deferred until verified on a real machine (write file → restart app → tools appear in the tool list); if verified, implement as a new `init --client windows-copilot` target.
|
|
1622
|
+
- **Microsoft 365 Copilot (enterprise) — open, reframed 2026-10-05:** extension runs through Copilot Studio (agents, Skills packages, declarative agents, Graph connectors) — tenant-cloud surfaces, not local stdio — and this is where the non-programmer audience actually lives (the "MS Copilot" named in the non-programmer question above meant the consumer app; in practice, company non-programmers are on M365 Copilot, so this surface is not optional if we want that audience). The question is no longer *whether* but *how*: personal memory stays local-first and never touches the tenant; team/project memory — shared by design — is served by a company-hosted team-tier endpoint that Copilot Studio agents call as a remote MCP server (Copilot Studio supports MCP over URL). Pointing tenant agents at individual laptops is a non-starter (laptops aren't servers) and an IP mess under Roper-style assignment terms — work memory belongs on the company side, personal memory never leaves the machine, and that split is the clean story. Until the team tier exists, the no-new-infrastructure path is asymmetric participation: non-programmers contribute as attributed sources (captured by others' agents) and review as curators on GitHub web, reading via inventory reports.
|
|
1623
|
+
|
|
1624
|
+
- **Enterprise / pro edition: team server + remote MCP (open, 2026-10-05):** the M365 question above reframes the enterprise surface as a *team-tier* endpoint: personal memory stays local-first, team/project memory is served company-hosted and called by Copilot Studio agents as a remote MCP server. The packaging question is whether that team tier ships as an open-memex "enterprise/pro" edition (hosted service or deployable server + remote MCP) or stays a documented deployment pattern companies run themselves. Adoption insight from large-company anecdotes: internally-built programmer tools get pulled in by BAs/QAs/POs on their own — the wedge is developers, and the non-programmer entry point may not need to be built up front; it arrives via the team surface (M365/Teams) once the team server exists. Open: packaging and monetization model (Stone's current goal for open-memex is personal influence, not user count or revenue — an enterprise edition is a support burden, so timing and shape need a deliberate decision); hosting and auth model for the team server; how the personal/team split is enforced at the protocol level.
|
|
1625
|
+
- *Refinement (2026-10-05): the entry point is not built, it is placed.* Employees already live in team chat — asking questions, getting work done. A team-chat AI agent (e.g. a Copilot Studio agent in Teams backed by the team memory server) becomes the entry point by standing where the questions already get asked. Adoption is pull, not push: the agent answers and does work in the chat, and usage follows. This dissolves the "non-programmer entry point" problem rather than solving it — there is no separate door to build.
|
|
1626
|
+
|
|
1613
1627
|
---
|
|
1614
1628
|
|
|
1615
1629
|
*End of draft v2.*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-memex",
|
|
3
|
-
"version": "0.7.3
|
|
3
|
+
"version": "0.7.3",
|
|
4
4
|
"mcpName": "io.github.stoneskin/open-memex",
|
|
5
5
|
"description": "Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.",
|
|
6
6
|
"keywords": [
|
package/scripts/smoke-mcp.ts
CHANGED
|
@@ -85,6 +85,9 @@ try {
|
|
|
85
85
|
JSON.stringify(["memory_add", "memory_forget", "memory_list", "memory_pr_status", "memory_promote", "memory_propose", "memory_resolve", "memory_search", "memory_status", "memory_submit", "memory_supersede"]),
|
|
86
86
|
names.join(","),
|
|
87
87
|
);
|
|
88
|
+
// D76: annotations must tell the truth — pr_status mutates with apply=true.
|
|
89
|
+
const prStatusTool = tools.result.tools.find((t: any) => t.name === "memory_pr_status");
|
|
90
|
+
check("pr_status is not read-only (D76)", prStatusTool?.annotations?.readOnlyHint === false, JSON.stringify(prStatusTool?.annotations));
|
|
88
91
|
const addSchema = tools.result.tools.find((t: any) => t.name === "memory_add").inputSchema;
|
|
89
92
|
check("memory_add schema has content+type+scope+tags", !!addSchema.properties?.content && !!addSchema.properties?.type, Object.keys(addSchema.properties ?? {}).join(","));
|
|
90
93
|
|
package/server.json
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.stoneskin/open-memex",
|
|
4
4
|
"title": "open-memex",
|
|
5
|
-
"description": "Local-first memory for AI coding agents
|
|
6
|
-
"version": "0.7.
|
|
5
|
+
"description": "Local-first memory for AI coding agents — Markdown + SQLite FTS5, shared across editors via MCP.",
|
|
6
|
+
"version": "0.7.3",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/stoneskin/open-memex",
|
|
9
9
|
"source": "github"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "open-memex",
|
|
16
|
-
"version": "0.7.
|
|
16
|
+
"version": "0.7.3",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"packageArguments": [
|
|
19
19
|
{
|
package/src/mcp.ts
CHANGED
|
@@ -209,6 +209,7 @@ export async function runMcpServer() {
|
|
|
209
209
|
{
|
|
210
210
|
description: TOOL_DESCRIPTIONS.memory_add,
|
|
211
211
|
inputSchema: z.object(memoryAddArgs),
|
|
212
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
212
213
|
},
|
|
213
214
|
withSync((args) => toMcp(withOutboxNote(getScope().key, addMemory(getScope, cfg, args), "call memory_status to review"))),
|
|
214
215
|
);
|
|
@@ -218,7 +219,7 @@ export async function runMcpServer() {
|
|
|
218
219
|
{
|
|
219
220
|
description: TOOL_DESCRIPTIONS.memory_search,
|
|
220
221
|
inputSchema: z.object(memorySearchArgs),
|
|
221
|
-
annotations: { readOnlyHint: true },
|
|
222
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
222
223
|
},
|
|
223
224
|
withSync((args) => toMcp(searchMemories(getScope, args))),
|
|
224
225
|
);
|
|
@@ -228,7 +229,7 @@ export async function runMcpServer() {
|
|
|
228
229
|
{
|
|
229
230
|
description: TOOL_DESCRIPTIONS.memory_list,
|
|
230
231
|
inputSchema: z.object(memoryListArgs),
|
|
231
|
-
annotations: { readOnlyHint: true },
|
|
232
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
232
233
|
},
|
|
233
234
|
withSync((args) => toMcp(listMemories(getScope, args))),
|
|
234
235
|
);
|
|
@@ -238,6 +239,7 @@ export async function runMcpServer() {
|
|
|
238
239
|
{
|
|
239
240
|
description: TOOL_DESCRIPTIONS.memory_supersede,
|
|
240
241
|
inputSchema: z.object(memorySupersedeArgs),
|
|
242
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
241
243
|
},
|
|
242
244
|
withSync((args) => toMcp(withOutboxNote(getScope().key, supersedeMemory(cfg, args), "call memory_status to review"))),
|
|
243
245
|
);
|
|
@@ -247,7 +249,7 @@ export async function runMcpServer() {
|
|
|
247
249
|
{
|
|
248
250
|
description: TOOL_DESCRIPTIONS.memory_forget,
|
|
249
251
|
inputSchema: z.object(memoryForgetArgs),
|
|
250
|
-
annotations: { destructiveHint: true },
|
|
252
|
+
annotations: { readOnlyHint: false, destructiveHint: true, openWorldHint: false },
|
|
251
253
|
},
|
|
252
254
|
withSync((args) => toMcp(withOutboxNote(getScope().key, forgetMemory(args), "call memory_status to review"))),
|
|
253
255
|
);
|
|
@@ -257,7 +259,7 @@ export async function runMcpServer() {
|
|
|
257
259
|
{
|
|
258
260
|
description: TOOL_DESCRIPTIONS.memory_status,
|
|
259
261
|
inputSchema: z.object(memoryStatusArgs),
|
|
260
|
-
annotations: { readOnlyHint: true },
|
|
262
|
+
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
261
263
|
},
|
|
262
264
|
withSync((_args) => toMcp(statusMemories())),
|
|
263
265
|
);
|
|
@@ -267,6 +269,7 @@ export async function runMcpServer() {
|
|
|
267
269
|
{
|
|
268
270
|
description: TOOL_DESCRIPTIONS.memory_submit,
|
|
269
271
|
inputSchema: z.object(memorySubmitArgs),
|
|
272
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
270
273
|
},
|
|
271
274
|
withSync((args) => toMcp(withOutboxNote(getScope().key, submitMemoriesOp(args), "call memory_status to review"))),
|
|
272
275
|
);
|
|
@@ -276,6 +279,7 @@ export async function runMcpServer() {
|
|
|
276
279
|
{
|
|
277
280
|
description: TOOL_DESCRIPTIONS.memory_propose,
|
|
278
281
|
inputSchema: z.object(memoryProposeArgs),
|
|
282
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
279
283
|
},
|
|
280
284
|
withSync((args) => toMcp(withOutboxNote(getScope().key, proposeMemoriesOp(args), "call memory_status to review"))),
|
|
281
285
|
);
|
|
@@ -285,6 +289,7 @@ export async function runMcpServer() {
|
|
|
285
289
|
{
|
|
286
290
|
description: TOOL_DESCRIPTIONS.memory_promote,
|
|
287
291
|
inputSchema: z.object(memoryPromoteArgs),
|
|
292
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
288
293
|
},
|
|
289
294
|
withSync((args) => toMcp(promoteMemoryOp(args))),
|
|
290
295
|
);
|
|
@@ -294,6 +299,7 @@ export async function runMcpServer() {
|
|
|
294
299
|
{
|
|
295
300
|
description: TOOL_DESCRIPTIONS.memory_resolve,
|
|
296
301
|
inputSchema: z.object(memoryResolveArgs),
|
|
302
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
297
303
|
},
|
|
298
304
|
withSync((args) => toMcp(resolveMemoryOp(args))),
|
|
299
305
|
);
|
|
@@ -303,7 +309,7 @@ export async function runMcpServer() {
|
|
|
303
309
|
{
|
|
304
310
|
description: TOOL_DESCRIPTIONS.memory_pr_status,
|
|
305
311
|
inputSchema: z.object(memoryPrStatusArgs),
|
|
306
|
-
annotations: { readOnlyHint:
|
|
312
|
+
annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: false },
|
|
307
313
|
},
|
|
308
314
|
withSync((args) => toMcp(prStatusOp(args))),
|
|
309
315
|
);
|
package/src/tools/ops.ts
CHANGED
|
@@ -66,27 +66,27 @@ export async function withOutboxNote(
|
|
|
66
66
|
/** LLM-facing tool descriptions, shared by the opencode plugin and the MCP server. */
|
|
67
67
|
export const TOOL_DESCRIPTIONS = {
|
|
68
68
|
memory_add:
|
|
69
|
-
"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. Worth saving: decisions and their reasons, preferences, conventions, gotchas, approaches tried and abandoned. Not worth saving: one-off task details or anything re-derivable from the code. Do not wait to be asked. For facts you infer yourself rather than the user stating, propose them first and save only on approval. Keep each memory to one self-contained statement; attach aliases when the parameter is available. If a note tells you the user's own wording was already stored verbatim this turn, that statement is already saved: do not retry it in a rewording — save only what that text does not contain, or use memory_supersede on the given id. Default scope is the current project; use the personal scope for facts about the user that apply across all projects.",
|
|
69
|
+
"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. Worth saving: decisions and their reasons, preferences, conventions, gotchas, approaches tried and abandoned. Not worth saving: one-off task details or anything re-derivable from the code. Do not wait to be asked. For facts you infer yourself rather than the user stating, propose them first and save only on approval. Keep each memory to one self-contained statement; attach aliases when the parameter is available. If a note tells you the user's own wording was already stored verbatim this turn, that statement is already saved: do not retry it in a rewording — save only what that text does not contain, or use memory_supersede on the given id. Default scope is the current project; use the personal scope for facts about the user that apply across all projects. Returns the new memory's id; an identical fact already stored returns that id instead of duplicating.",
|
|
70
70
|
memory_search:
|
|
71
|
-
"Search persistent memory by keyword (BM25 full-text). Returns matching memories from the current project and/or personal scope. Call before asking the user about past decisions, conventions, or preferences they may have told you before. Search well: break the question into its concepts and try 2–3 phrasings per concept — synonyms, the user's other language, shorter keyword forms — and check the other scope too, before concluding nothing is stored.",
|
|
71
|
+
"Search persistent memory by keyword (BM25 full-text). Returns matching memories from the current project and/or personal scope. Call before asking the user about past decisions, conventions, or preferences they may have told you before. Search well: break the question into its concepts and try 2–3 phrasings per concept — synonyms, the user's other language, shorter keyword forms — and check the other scope too, before concluding nothing is stored. Results are ranked by BM25 within review tiers — reviewed (approved/published) project memories come first. `limit` caps how many come back (default 8) and `type` restricts to one memory type.",
|
|
72
72
|
memory_list:
|
|
73
|
-
"List memories in a scope, newest first, as a numbered inventory. Each line keeps the raw fields — [type] id=… created=… source=… — say them back to the user in plain words (source=user → 'you told me this'; inference → 'I inferred this, check me'; keyword → 'caught from your own wording'), never read the raw id aloud unless they ask. When the user asks 'what do you remember about me?', use scope=both and present the inventory conversationally. The user may point at an entry by its number ('delete #3'): numbers are only valid for the listing you just produced — re-run memory_list, read the candidate back in full, and get a confirmation before calling memory_forget (deletion is permanent). include=all is the audit view (superseded versions, retracted, archived also shown).",
|
|
73
|
+
"List memories in a scope, newest first, as a numbered inventory. Each line keeps the raw fields — [type] id=… created=… source=… — say them back to the user in plain words (source=user → 'you told me this'; inference → 'I inferred this, check me'; keyword → 'caught from your own wording'), never read the raw id aloud unless they ask. When the user asks 'what do you remember about me?', use scope=both and present the inventory conversationally. The user may point at an entry by its number ('delete #3'): numbers are only valid for the listing you just produced — re-run memory_list, read the candidate back in full, and get a confirmation before calling memory_forget (deletion is permanent). include=all is the audit view (superseded versions, retracted, archived also shown). `type` filters to one memory type; `limit` caps how many entries come back.",
|
|
74
74
|
memory_supersede:
|
|
75
|
-
"Replace an existing memory with a newer version. The old memory is kept as history (status: superseded) and retrieval returns the new one. Use when a saved fact becomes outdated and should be replaced rather than duplicated.",
|
|
75
|
+
"Replace an existing memory with a newer version. The old memory is kept as history (status: superseded) and retrieval returns the new one. Use when a saved fact becomes outdated and should be replaced rather than duplicated. Only active memories can be superseded; the new memory inherits type, tags, and aliases unless you pass replacements. An unknown id is reported back as a failure; nothing is written.",
|
|
76
76
|
memory_forget:
|
|
77
|
-
"Delete a memory by id. Use when the user asks to forget something. Deletion is permanent: before deleting, restate the memory's content to the user and get a confirmation. With soft=true the memory is hidden instead (retracted: out of lists and search, file kept, one-way).",
|
|
77
|
+
"Delete a memory by id. Use when the user asks to forget something. To replace an outdated fact with a corrected one instead of deleting it, use memory_supersede. Deletion is permanent: before deleting, restate the memory's content to the user and get a confirmation. With soft=true the memory is hidden instead (retracted: out of lists and search, file kept, one-way).",
|
|
78
78
|
memory_status:
|
|
79
|
-
"Show the project memory sync pipeline: drafts waiting in the outbox (appdata), memories in the repo awaiting review or published, and any repo files not yet committed. Call this at session start, when the server reports drafts waiting for review, or when the user says 'sync memory' (or '同步记忆'); then ask the user which drafts to sync. (MCP server and the `open-memex sync-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
79
|
+
"Show the project memory sync pipeline: drafts waiting in the outbox (appdata), memories in the repo awaiting review or published, and any repo files not yet committed. Call this at session start, when the server reports drafts waiting for review, or when the user says 'sync memory' (or '同步记忆'); then ask the user which drafts to sync. For the memories themselves use memory_list; for PR review state use memory_pr_status. (MCP server and the `open-memex sync-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
80
80
|
memory_submit:
|
|
81
|
-
"Move outbox drafts into the repo memory dir for review: copies the drafts in as proposed (or keeps a local approval), commits locally on the current branch, and moves the outbox originals out. Never creates a branch on its own — pass branch= only with the user's explicit approval for the full chain. Prints the push and PR commands — those need the user's explicit approval and are never run automatically. (MCP server and the `open-memex submit` CLI; the opencode native plugin does not expose this tool.)",
|
|
81
|
+
"Move outbox drafts into the repo memory dir for review: copies the drafts in as proposed (or keeps a local approval), commits locally on the current branch, and moves the outbox originals out. Never creates a branch on its own — pass branch= only with the user's explicit approval for the full chain. Prints the push and PR commands — those need the user's explicit approval and are never run automatically. (MCP server and the `open-memex submit` CLI; the opencode native plugin does not expose this tool.) Unknown ids or a failed local commit abort the submit with the reason; nothing is published automatically.",
|
|
82
82
|
memory_propose:
|
|
83
|
-
"Copy personal memories into the project outbox as review drafts. The personal originals stay put. (MCP server and the `open-memex propose` CLI; the opencode native plugin does not expose this tool.)",
|
|
83
|
+
"Copy personal memories into the project outbox as review drafts. The personal originals stay put. Use this when personal knowledge should enter a project's review flow; memory_submit then moves the drafts into the repo, and memory_promote advances drafts already there. (MCP server and the `open-memex propose` CLI; the opencode native plugin does not expose this tool.)",
|
|
84
84
|
memory_promote:
|
|
85
|
-
"Advance a project memory one step up the review ladder (proposed → approved → published), or reject it with a note. Rejected memories are never deleted — they can be revised and resubmitted. (MCP server and the `open-memex promote` CLI; the opencode native plugin does not expose this tool.)",
|
|
85
|
+
"Advance a project memory one step up the review ladder (proposed → approved → published), or reject it with a note. Rejected memories are never deleted — they can be revised and resubmitted. One step per call; an unknown id or a memory already published errors. Pass `by` to record the reviewer when it should differ from the default (e.g. approving on the strength of someone else's review). For brand-new outbox drafts, memory_submit is the entry point. (MCP server and the `open-memex promote` CLI; the opencode native plugin does not expose this tool.)",
|
|
86
86
|
memory_resolve:
|
|
87
|
-
"List git-conflicted memory files, or attempt a field-level 3-way merge of one. Semantic conflicts are reported, never auto-resolved. (MCP server and the `open-memex resolve` CLI; the opencode native plugin does not expose this tool.)",
|
|
87
|
+
"List git-conflicted memory files, or attempt a field-level 3-way merge of one. Semantic conflicts are reported, never auto-resolved. Run it when a git pull or merge leaves memory files conflicted; the merged file is written in place (undo with git if the merge is wrong). For PR review state rather than file conflicts, use memory_pr_status. (MCP server and the `open-memex resolve` CLI; the opencode native plugin does not expose this tool.)",
|
|
88
88
|
memory_pr_status:
|
|
89
|
-
"Read the current branch's GitHub PR and map its review state onto each in-repo memory: merged PR → published, PR approval → approved (approved_by = reviewer), changes-requested → suggestion only. Report by default; apply=true
|
|
89
|
+
"Read the current branch's GitHub PR and map its review state onto each in-repo memory: merged PR → published, PR approval → approved (approved_by = reviewer), changes-requested → suggestion only. Report by default — that mode changes nothing; apply=true writes the mapped transitions to the local files (still no push). (MCP server and the `open-memex pr-status` CLI; the opencode native plugin does not expose this tool.)",
|
|
90
90
|
} as const;
|
|
91
91
|
|
|
92
92
|
/** Shared zod input shapes (raw shape, not z.object — hosts wrap as needed). */
|
|
@@ -131,8 +131,8 @@ export const memorySearchArgs = {
|
|
|
131
131
|
.enum(["project", "personal", "both"])
|
|
132
132
|
.optional()
|
|
133
133
|
.describe("Which scope(s) to search. Default: both."),
|
|
134
|
-
type: z.string().optional().describe("Restrict to memories of this type."),
|
|
135
|
-
limit: z.number().int().min(1).max(50).optional(),
|
|
134
|
+
type: z.string().optional().describe("Restrict to memories of this type (e.g. decision, preference, gotcha)."),
|
|
135
|
+
limit: z.number().int().min(1).max(50).optional().describe("Max results to return (default 8)."),
|
|
136
136
|
};
|
|
137
137
|
export type MemorySearchArgs = z.infer<z.ZodObject<typeof memorySearchArgs>>;
|
|
138
138
|
|
|
@@ -143,8 +143,8 @@ export const memoryListArgs = {
|
|
|
143
143
|
.describe(
|
|
144
144
|
"Which scope(s) to list. `project` = tied to this repo. `personal` = global across all your projects. `both` lists each scope's newest. Default: project.",
|
|
145
145
|
),
|
|
146
|
-
type: z.string().optional(),
|
|
147
|
-
limit: z.number().int().min(1).max(100).optional(),
|
|
146
|
+
type: z.string().optional().describe("Only list memories of this type (e.g. decision, preference, gotcha)."),
|
|
147
|
+
limit: z.number().int().min(1).max(100).optional().describe("Max entries to list, newest first (default 20)."),
|
|
148
148
|
include: z
|
|
149
149
|
.enum(["active", "all"])
|
|
150
150
|
.optional()
|