open-memex 0.4.0-alpha.4 → 0.4.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +4 -1
- package/README.md +14 -9
- package/README.zh-CN.md +11 -6
- package/dist/cli.js +10 -10
- package/dist/doctor.js +1 -1
- package/dist/init.js +19 -9
- package/dist/mcp.js +39 -8
- package/dist/store/markdown.js +8 -6
- package/dist/submit.js +23 -15
- package/dist/tools/ops.js +16 -10
- package/docs/USER-GUIDE.md +10 -8
- package/docs/USER-GUIDE.zh-CN.md +7 -6
- package/docs/V2-DESIGN.md +155 -6
- package/package.json +1 -1
- package/src/cli.ts +10 -10
- package/src/doctor.ts +1 -1
- package/src/init.ts +19 -9
- package/src/mcp.ts +38 -8
- package/src/store/markdown.ts +8 -6
- package/src/submit.ts +30 -17
- package/src/tools/ops.ts +19 -10
package/AGENTS.md
CHANGED
|
@@ -31,7 +31,7 @@ npm run typecheck # tsc --noEmit — the only
|
|
|
31
31
|
npm run cli -- where | list | search "q" | add ... | forget <id> | reindex
|
|
32
32
|
npm run cli -- <command> --help # per-command help (AI assistants discover flags this way)
|
|
33
33
|
npm run cli -- sync-status # last sync time/kind + outbox drafts + repo review states + uncommitted files
|
|
34
|
-
npm run cli -- submit <id...> [--
|
|
34
|
+
npm run cli -- submit <id...> [--branch <name>] [--base <branch>] # drafts → .ai/open-memex/ (current branch + local commit; never auto-branches)
|
|
35
35
|
npm run cli -- propose <id...> --to project [--local-approve] # copy personal → project outbox (batch OK)
|
|
36
36
|
npm run cli -- promote <id> [--reject] [--resubmit] [--note "..."] # proposed → approved → published (audit trail appended)
|
|
37
37
|
npm run cli -- pr-status [--apply] # map branch PR's GitHub state onto review_state (report; apply = local only)
|
|
@@ -140,3 +140,6 @@ hold `V2` and `V2/…` simultaneously. Full rules: `CONTRIBUTING.md`.
|
|
|
140
140
|
in-development version. New features on a dev branch bump the minor on the alpha
|
|
141
141
|
line (`0.3.0` → `0.4.0-alpha.1`); fixes bump the patch (`-alpha.1` → `-alpha.2`).
|
|
142
142
|
The bump goes in the same commit as the feature, never as an afterthought.
|
|
143
|
+
The version number serves the publish: no publish, no mandatory bump. But once a
|
|
144
|
+
version has been pushed to the remote (shared), later changes must bump — two
|
|
145
|
+
different code states must never share one version number.
|
package/README.md
CHANGED
|
@@ -295,14 +295,16 @@ open-memex sync-status
|
|
|
295
295
|
# (draft / proposed / approved / published / rejected),
|
|
296
296
|
# and any uncommitted repo memory files.
|
|
297
297
|
|
|
298
|
-
open-memex submit <id...> [--
|
|
298
|
+
open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
299
299
|
# move your named drafts into .ai/open-memex/ as "proposed":
|
|
300
|
-
#
|
|
301
|
-
#
|
|
302
|
-
# (
|
|
303
|
-
#
|
|
304
|
-
#
|
|
305
|
-
#
|
|
300
|
+
# copies, flips review_state, local git commit ON THE CURRENT BRANCH.
|
|
301
|
+
# Never creates a branch on its own — branch creation is your call
|
|
302
|
+
# (or the agent's, only with your explicit approval for the full chain).
|
|
303
|
+
# All-or-nothing; conflicts (same id, different content) abort cleanly.
|
|
304
|
+
# Prints the push + gh pr commands; an agent holding your Yes carries
|
|
305
|
+
# through push/PR itself. --branch <name> creates the branch first
|
|
306
|
+
# (agent full-chain path). Default PR base is the current branch; --base
|
|
307
|
+
# redirects to main or your integration branch.
|
|
306
308
|
|
|
307
309
|
open-memex pr-status [--apply]
|
|
308
310
|
# read the branch's GitHub PR and map its state onto each in-repo memory:
|
|
@@ -373,10 +375,13 @@ server with cwd set to your project root (`init` handles this for you).
|
|
|
373
375
|
**In progress — `0.4.0`:** team sync — shared memory via git: appdata draft
|
|
374
376
|
outbox → `sync-status` → `submit` (local branch+commit, push/PR on your Yes)
|
|
375
377
|
→ `promote` / `resolve` review workflow, in-repo `.ai/open-memex/` dir, 1–2
|
|
376
|
-
colleague pilot.
|
|
378
|
+
colleague pilot; capture — §3.5 checkpoint distillation in MCP handshake +
|
|
379
|
+
init instructions (agent proposes 1–3 captures, human decides).
|
|
377
380
|
|
|
378
381
|
**Coming — `0.3.0` (stable):** org layer — org memory repo, curator convention,
|
|
379
|
-
distill-to-AGENTS.md assist
|
|
382
|
+
distill-to-AGENTS.md assist, `export`/`import` archive for user portability
|
|
383
|
+
(Markdown + manifest, no walled garden; private excluded by default,
|
|
384
|
+
`-a`/`--all` for full migration).
|
|
380
385
|
|
|
381
386
|
**Future (signal-gated, no version committed):** native agent plugins (Claude Code /
|
|
382
387
|
Codex hooks as enhancement paths over the same MCP tools); local embeddings as a
|
package/README.zh-CN.md
CHANGED
|
@@ -291,11 +291,13 @@ open-memex sync-status
|
|
|
291
291
|
# (draft / proposed / approved / published / rejected),
|
|
292
292
|
# 以及 repo 里还没 commit 的记忆文件。
|
|
293
293
|
|
|
294
|
-
open-memex submit <id...> [--
|
|
294
|
+
open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
295
295
|
# 把你点名的草稿移入 .ai/open-memex/,状态变为 proposed:
|
|
296
|
-
#
|
|
297
|
-
#
|
|
298
|
-
#
|
|
296
|
+
# 复制、改 review_state、在当前分支本地 git commit。
|
|
297
|
+
# 永不自动建分支——建分支是你说了算(或 Agent 拿到你明确批准走全链时)。
|
|
298
|
+
# 全有或全无;冲突(同 id 不同内容)干净回滚。
|
|
299
|
+
# 打印 push + gh pr 命令;Agent 拿到你的 Yes 后会自己走完 push/PR。
|
|
300
|
+
# --branch <name> 先建分支再提交(Agent 全链路径)。
|
|
299
301
|
# PR 默认 base 是当前分支;--base 可改到 main 或集成支。
|
|
300
302
|
|
|
301
303
|
open-memex pr-status [--apply]
|
|
@@ -363,10 +365,13 @@ project scope 从进程工作目录解析,所以配置 server 时 cwd 要指
|
|
|
363
365
|
**进行中 —— `0.4.0`:** 团队同步——用 git 做共享记忆:appdata 草稿箱 →
|
|
364
366
|
`sync-status` → `submit`(本地分支+commit,push/PR 拿你的 Yes 才做)
|
|
365
367
|
→ `promote` / `resolve` 评审工作流、仓库内 `.ai/open-memex/` 目录,
|
|
366
|
-
找 1–2 个同事做 pilot
|
|
368
|
+
找 1–2 个同事做 pilot;捕获——§3.5 检查点蒸馏写进 MCP 握手指令和
|
|
369
|
+
init 指令文件(agent 提议 1–3 条,人来定)。
|
|
367
370
|
|
|
368
371
|
**Coming —— `0.3.0`(稳定版):** 组织层——组织记忆仓库、
|
|
369
|
-
curator 约定、distill-to-AGENTS.md
|
|
372
|
+
curator 约定、distill-to-AGENTS.md 辅助、`export`/`import` 归档
|
|
373
|
+
(Markdown + manifest,不造围墙花园,用户可带走;
|
|
374
|
+
private 默认不导出,`-a`/`--all` 全量迁移)。
|
|
370
375
|
|
|
371
376
|
**未来(看信号再定,不承诺版本):** 原生 agent 插件
|
|
372
377
|
(Claude Code / Codex hooks,作为同一套 MCP tools 的增强路径);
|
package/dist/cli.js
CHANGED
|
@@ -106,17 +106,17 @@ and what triggered it, drafts waiting in the outbox (appdata), memories in the
|
|
|
106
106
|
repo awaiting review or published, and repo files not yet committed.
|
|
107
107
|
|
|
108
108
|
Usage: open-memex sync-status`,
|
|
109
|
-
submit: `Move outbox drafts into
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
explicit approval and are never run automatically.
|
|
109
|
+
submit: `Move outbox drafts into the repo for review: copies the drafts into
|
|
110
|
+
the repo memory dir as proposed (a local-approved copy keeps its approval),
|
|
111
|
+
commits locally on the CURRENT branch, and moves the outbox originals out.
|
|
112
|
+
Never creates a branch on its own — branch creation is your call.
|
|
114
113
|
|
|
115
|
-
Usage: open-memex submit <id...> [--
|
|
114
|
+
Usage: open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
116
115
|
|
|
117
116
|
Flags:
|
|
118
|
-
--
|
|
119
|
-
|
|
117
|
+
--branch create this branch and submit onto it (only with your explicit
|
|
118
|
+
approval for the full chain); default: stay on current branch
|
|
119
|
+
--base PR base override (default: the branch you're on)
|
|
120
120
|
|
|
121
121
|
Example:
|
|
122
122
|
open-memex submit 01ABC 01DEF`,
|
|
@@ -195,7 +195,7 @@ Usage:
|
|
|
195
195
|
open-memex promote <id> [--reject] [--resubmit] [--note "..."] [--by NAME]
|
|
196
196
|
open-memex resolve [id-or-path]
|
|
197
197
|
open-memex sync-status
|
|
198
|
-
open-memex submit <id...> [--
|
|
198
|
+
open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
199
199
|
open-memex pr-status [--apply]
|
|
200
200
|
open-memex reindex
|
|
201
201
|
open-memex scopes
|
|
@@ -811,7 +811,7 @@ async function main() {
|
|
|
811
811
|
usage();
|
|
812
812
|
syncScope(project.key, "submit");
|
|
813
813
|
try {
|
|
814
|
-
const r = submitMemories(ids, {
|
|
814
|
+
const r = submitMemories(ids, { branch: flags.branch, base: flags.base });
|
|
815
815
|
for (const s of r.submitted) {
|
|
816
816
|
console.log(`submitted ${s.id} → ${path.relative(process.cwd(), s.filePath)} [${s.reviewState}]`);
|
|
817
817
|
}
|
package/dist/doctor.js
CHANGED
|
@@ -116,7 +116,7 @@ function mcpCheck() {
|
|
|
116
116
|
name,
|
|
117
117
|
ok: missing.length === 0,
|
|
118
118
|
detail: missing.length === 0
|
|
119
|
-
? `handshake OK,
|
|
119
|
+
? `handshake OK, ${names.length} tools listed (${names.join(", ")})`
|
|
120
120
|
: `missing tools: ${missing.join(", ")}`,
|
|
121
121
|
});
|
|
122
122
|
}
|
package/dist/init.js
CHANGED
|
@@ -47,6 +47,14 @@ You have a local memory MCP server (\`open-memex\`) with eleven tools:
|
|
|
47
47
|
- BE PROACTIVE. When the user shares something worth remembering across sessions
|
|
48
48
|
(a decision, a preference, a project convention, a fix and its cause), call
|
|
49
49
|
\`memory_add\` without being asked. Keep each memory to one self-contained statement.
|
|
50
|
+
- At checkpoints (session start, end of a work chunk, after the user commits, after
|
|
51
|
+
any memory_* action), DISTILL the session: propose 1–3 short memories capturing the
|
|
52
|
+
useful conclusion — what was learned or decided, how an issue was resolved, what to
|
|
53
|
+
avoid, where the authoritative doc lives — not the raw transcript. Save NOTHING the
|
|
54
|
+
user did not approve; on approval call \`memory_add\` with source "inference" at the
|
|
55
|
+
confirmed scope. If the knowledge already lives in project docs, save a \`reference\`
|
|
56
|
+
memory pointing at the doc instead of copying it. Long-form notes are fine ONLY when
|
|
57
|
+
the user explicitly asks to save one.
|
|
50
58
|
- Before asking the user about past decisions, conventions, or preferences they may
|
|
51
59
|
have told you before, call \`memory_search\` first — try a few keyword variants
|
|
52
60
|
(including the user's own language) when the first search comes up empty.
|
|
@@ -59,20 +67,22 @@ You have a local memory MCP server (\`open-memex\`) with eleven tools:
|
|
|
59
67
|
Project memories you save land in a local outbox first — they are NOT in git yet.
|
|
60
68
|
Syncing them into the repo for review is an explicit, user-approved step:
|
|
61
69
|
|
|
62
|
-
- At session start,
|
|
70
|
+
- At session start, when you finish a meaningful chunk of work, after the user
|
|
71
|
+
commits (git commit), and after any memory_* action completes, call
|
|
63
72
|
\`memory_status\`. If the outbox has drafts, summarize them (one line each) and ask
|
|
64
73
|
the user which ones to sync. Sync NOTHING the user did not name.
|
|
65
74
|
- When the user says "sync memory" (or "同步记忆"), treat it as a request to run
|
|
66
75
|
the sync flow above: call \`memory_status\`, summarize the outbox drafts, and ask
|
|
67
76
|
which ones to sync.
|
|
68
|
-
- When the user approves,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
77
|
+
- When the user approves, call \`memory_submit\` with the approved ids. It copies
|
|
78
|
+
the drafts into the repo as \`proposed\`, commits locally on the CURRENT branch,
|
|
79
|
+
and prints the push + PR commands. It NEVER creates a branch on its own.
|
|
80
|
+
- After the submit, ask ONE follow-up: "want me to create a branch + push +
|
|
81
|
+
open the PR, or will you handle it yourself?" A "yes, you do it" answer covers
|
|
82
|
+
the whole chain — branch creation, push, PR creation — do NOT re-ask at each
|
|
83
|
+
step. If the user says they will do it themselves, hand them the printed
|
|
84
|
+
push/PR commands and do nothing. NEVER create branches, push, or open PRs
|
|
85
|
+
without their explicit approval.
|
|
76
86
|
- Base branch for the memory PR defaults to the branch you are on; the user may
|
|
77
87
|
redirect it to the integration branch (main) for branch-independent knowledge.
|
|
78
88
|
- If anything conflicts (same id with different content, push rejected), STOP and
|
package/dist/mcp.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* open-memex generic MCP server (stdio transport).
|
|
3
3
|
*
|
|
4
|
-
* Exposes the
|
|
4
|
+
* Exposes the memory tools as the opencode plugin
|
|
5
5
|
* (memory_add / memory_search / memory_list / memory_supersede /
|
|
6
|
-
* memory_forget
|
|
6
|
+
* memory_forget, plus memory_status / memory_submit / memory_propose /
|
|
7
|
+
* memory_promote / memory_resolve / memory_pr_status) over the Model Context Protocol, so any MCP client —
|
|
7
8
|
* VS Code Copilot Chat, Cursor, Claude Code, etc. — can use open-memex
|
|
8
9
|
* without a host-specific plugin.
|
|
9
10
|
*
|
|
@@ -19,13 +20,28 @@
|
|
|
19
20
|
*/
|
|
20
21
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
22
26
|
import { z } from "zod";
|
|
23
27
|
import { loadConfig } from "./config.js";
|
|
24
28
|
import { resolveProjectScope, PERSONAL_SCOPE } from "./scope.js";
|
|
25
29
|
import { db } from "./store/db.js";
|
|
26
30
|
import { syncScope } from "./store/sync.js";
|
|
27
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";
|
|
28
|
-
|
|
32
|
+
// Server version tracks package.json — never hardcode it here again.
|
|
33
|
+
// package.json sits two levels above this file in both layouts
|
|
34
|
+
// (src/mcp.ts and dist/mcp.js), same convention as cli.ts --version.
|
|
35
|
+
const SERVER_VERSION = (() => {
|
|
36
|
+
try {
|
|
37
|
+
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
38
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
39
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0-unknown";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return "0.0.0-unknown";
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
29
45
|
/**
|
|
30
46
|
* D26: session-start guidance delivered through the MCP handshake itself.
|
|
31
47
|
* The init-written instruction files only exist if the user ran
|
|
@@ -37,16 +53,31 @@ const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP
|
|
|
37
53
|
(eleven memory_* tools: add, search, list, supersede, forget, status, submit,
|
|
38
54
|
propose, promote, resolve, pr_status).
|
|
39
55
|
|
|
40
|
-
- At the START of this session,
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
59
|
+
summarize them (one line each) and ask the user which ones to sync into the
|
|
60
|
+
repo. Sync NOTHING the user did not name.
|
|
43
61
|
- When the user says "sync memory" (or "同步记忆"), treat it as a request to run
|
|
44
62
|
the sync flow: call memory_status, summarize the outbox drafts, and ask which
|
|
45
|
-
ones to sync.
|
|
46
|
-
|
|
63
|
+
ones to sync. ALWAYS use the memory_status tool for this — never browse the
|
|
64
|
+
appdata directory directly.
|
|
65
|
+
- After memory_submit, ask ONE follow-up: "want me to create a branch + push +
|
|
66
|
+
open the PR, or will you handle it yourself?" NEVER create branches, push, or
|
|
67
|
+
open PRs without the user's explicit approval. A "yes, you do it" covers the
|
|
68
|
+
whole chain — do NOT re-ask at each step.
|
|
47
69
|
- BE PROACTIVE: when the user shares something worth remembering across sessions
|
|
48
70
|
(a decision, a preference, a project convention, a fix and its cause), call
|
|
49
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.
|
|
50
81
|
- Before asking the user about past decisions, conventions, or preferences they
|
|
51
82
|
may have told you before, call memory_search first.
|
|
52
83
|
- Memories default to this project's scope; use the personal scope for facts
|
package/dist/store/markdown.js
CHANGED
|
@@ -3,19 +3,21 @@ import path from "node:path";
|
|
|
3
3
|
import { randomBytes } from "node:crypto";
|
|
4
4
|
import yaml from "js-yaml";
|
|
5
5
|
import { memoriesDirFor, memoriesDirPath, inRepoMemoriesDirPath, } from "../paths.js";
|
|
6
|
-
/** v2 content-kind taxonomy (V2-DESIGN §3.1). `type` = what the memory IS
|
|
6
|
+
/** v2 content-kind taxonomy (V2-DESIGN §3.1). `type` = what the memory IS
|
|
7
|
+
* (single-valued, drives behavior). D41: 11 types; `warning`→`gotcha`,
|
|
8
|
+
* `workflow`→`howto`, `incident`→`lesson`, `architecture`→`knowledge`. */
|
|
7
9
|
export const MEMORY_TYPE_TAXONOMY = [
|
|
8
|
-
"preference",
|
|
9
10
|
"fact",
|
|
11
|
+
"preference",
|
|
10
12
|
"decision",
|
|
11
|
-
"lesson",
|
|
12
|
-
"warning",
|
|
13
|
-
"workflow",
|
|
14
|
-
"architecture",
|
|
15
13
|
"constraint",
|
|
16
14
|
"todo",
|
|
17
15
|
"knowledge",
|
|
16
|
+
"howto",
|
|
17
|
+
"gotcha",
|
|
18
|
+
"lesson",
|
|
18
19
|
"observation",
|
|
20
|
+
"reference",
|
|
19
21
|
];
|
|
20
22
|
const TAXONOMY = new Set(MEMORY_TYPE_TAXONOMY);
|
|
21
23
|
/** Defensive parse: malformed entries are dropped, never fatal. */
|
package/dist/submit.js
CHANGED
|
@@ -150,7 +150,8 @@ export function submitMemories(ids, opts = {}) {
|
|
|
150
150
|
if (ids.length === 0)
|
|
151
151
|
fail("submit needs at least one memory id");
|
|
152
152
|
const root = projectRoot();
|
|
153
|
-
// submit needs a git repo — the
|
|
153
|
+
// submit needs a git repo — the memories land in .ai/open-memex/ and are
|
|
154
|
+
// committed locally. Branch creation is never automatic (D36).
|
|
154
155
|
git(root, ["rev-parse", "--git-dir"]);
|
|
155
156
|
const scope = resolveProjectScope(root);
|
|
156
157
|
const cfg = loadConfig();
|
|
@@ -197,26 +198,26 @@ export function submitMemories(ids, opts = {}) {
|
|
|
197
198
|
fail(`submit aborted — nothing was written:\n ${problems.join("\n ")}`);
|
|
198
199
|
}
|
|
199
200
|
// 2. Resolve the target branch.
|
|
201
|
+
// D36: submit never creates a branch on its own — it works on the branch
|
|
202
|
+
// you're already on. Pass --branch <name> (explicitly) only when the user
|
|
203
|
+
// approved the full chain (branch + push + PR).
|
|
200
204
|
const startBranch = git(root, ["branch", "--show-current"]) || git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
201
|
-
let branch;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
branch = opts.onto;
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
const stamp = new Date().toISOString().replace(/[-:]/g, "").slice(0, 15);
|
|
210
|
-
branch = `mem/sync-${stamp}`;
|
|
205
|
+
let branch = startBranch;
|
|
206
|
+
let createdBranch = false;
|
|
207
|
+
if (opts.branch) {
|
|
208
|
+
branch = opts.branch;
|
|
211
209
|
let n = 0;
|
|
210
|
+
let candidate = branch;
|
|
212
211
|
while (true) {
|
|
213
|
-
const exists = git(root, ["branch", "--list",
|
|
212
|
+
const exists = git(root, ["branch", "--list", candidate]);
|
|
214
213
|
if (!exists)
|
|
215
214
|
break;
|
|
216
215
|
n++;
|
|
217
|
-
|
|
216
|
+
candidate = `${branch}-${n}`;
|
|
218
217
|
}
|
|
218
|
+
branch = candidate;
|
|
219
219
|
git(root, ["checkout", "-b", branch]);
|
|
220
|
+
createdBranch = true;
|
|
220
221
|
}
|
|
221
222
|
const base = opts.base ?? startBranch;
|
|
222
223
|
const author = currentAuthor();
|
|
@@ -278,7 +279,7 @@ export function submitMemories(ids, opts = {}) {
|
|
|
278
279
|
}
|
|
279
280
|
// If we created the branch and never committed, remove it too — an
|
|
280
281
|
// aborted submit leaves no trace.
|
|
281
|
-
if (
|
|
282
|
+
if (createdBranch) {
|
|
282
283
|
try {
|
|
283
284
|
git(root, ["checkout", "--quiet", startBranch]);
|
|
284
285
|
git(root, ["branch", "--quiet", "-D", branch]);
|
|
@@ -327,13 +328,20 @@ export function submitMemories(ids, opts = {}) {
|
|
|
327
328
|
removed: 0,
|
|
328
329
|
scanned: validated.length,
|
|
329
330
|
});
|
|
331
|
+
// D36: no auto-branch — the commit sits on the branch you were already on.
|
|
332
|
+
// Next steps are printed, not run. For a separate memory PR, create a branch
|
|
333
|
+
// first (the commit comes along), then push + open the PR.
|
|
334
|
+
const branchCmd = `git checkout -b mem/sync-YYYYMMDD`;
|
|
330
335
|
return {
|
|
331
336
|
branch,
|
|
332
337
|
base,
|
|
333
338
|
submitted,
|
|
334
339
|
skippedIdentical,
|
|
335
340
|
committed,
|
|
341
|
+
createdBranch,
|
|
336
342
|
pushCommand: `git push -u origin ${branch}`,
|
|
337
|
-
prCommand:
|
|
343
|
+
prCommand: createdBranch
|
|
344
|
+
? `gh pr create --base ${base} --title "mem: review ${validated.length} ${validated.length === 1 ? "memory" : "memories"}" --body "Submitted from the open-memex outbox: ${ids8}."`
|
|
345
|
+
: `${branchCmd} # if you want a separate memory PR (else push ${branch} directly)\n git push -u origin <new-branch> && gh pr create --base ${base} --title "mem: review ${validated.length}" --body "Submitted from the open-memex outbox: ${ids8}."`,
|
|
338
346
|
};
|
|
339
347
|
}
|
package/dist/tools/ops.js
CHANGED
|
@@ -26,7 +26,7 @@ export const TOOL_DESCRIPTIONS = {
|
|
|
26
26
|
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.",
|
|
27
27
|
memory_forget: "Delete a memory by id. Use when the user asks to forget something.",
|
|
28
28
|
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 and at task checkpoints, then ask the user which drafts to sync. The user may also trigger this flow by saying 'sync memory' (or '同步记忆').",
|
|
29
|
-
memory_submit: "Move outbox drafts into
|
|
29
|
+
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.",
|
|
30
30
|
memory_propose: "Copy personal memories into the project outbox as review drafts. The personal originals stay put.",
|
|
31
31
|
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.",
|
|
32
32
|
memory_resolve: "List git-conflicted memory files, or attempt a field-level 3-way merge of one. Semantic conflicts are reported, never auto-resolved.",
|
|
@@ -42,9 +42,13 @@ export const memoryAddArgs = {
|
|
|
42
42
|
type: z
|
|
43
43
|
.enum(MEMORY_TYPE_TAXONOMY)
|
|
44
44
|
.optional()
|
|
45
|
-
.describe("Category of memory. Default:
|
|
45
|
+
.describe("Category of memory. Default: fact."),
|
|
46
46
|
scope: scopeArg,
|
|
47
47
|
tags: z.array(z.string()).optional().describe("Optional tags for filtering."),
|
|
48
|
+
source: z
|
|
49
|
+
.string()
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Where this memory came from. Default: tool. Pass 'inference' for agent-proposed captures at checkpoints (V2-DESIGN §3.5)."),
|
|
48
52
|
};
|
|
49
53
|
export const memorySearchArgs = {
|
|
50
54
|
query: z
|
|
@@ -78,14 +82,14 @@ export const memoryForgetArgs = {
|
|
|
78
82
|
export const memoryStatusArgs = {};
|
|
79
83
|
export const memorySubmitArgs = {
|
|
80
84
|
ids: z.array(z.string().min(1)).min(1).describe("Outbox draft ids to submit."),
|
|
81
|
-
|
|
85
|
+
branch: z
|
|
82
86
|
.string()
|
|
83
87
|
.optional()
|
|
84
|
-
.describe("
|
|
88
|
+
.describe("Create this branch and submit onto it. If omitted, submit stays on the current branch — branches are never auto-created. Only pass this when the user explicitly approved the full chain (branch + push + PR)."),
|
|
85
89
|
base: z
|
|
86
90
|
.string()
|
|
87
91
|
.optional()
|
|
88
|
-
.describe("PR base branch override. Default: the branch the submit
|
|
92
|
+
.describe("PR base branch override. Default: the branch the submit ran on."),
|
|
89
93
|
};
|
|
90
94
|
export const memoryProposeArgs = {
|
|
91
95
|
ids: z.array(z.string().min(1)).min(1).describe("Personal memory ids to copy into the project outbox."),
|
|
@@ -161,7 +165,7 @@ export async function addMemory(getScope, cfg, args) {
|
|
|
161
165
|
const fm = buildFrontmatter(s, {
|
|
162
166
|
type: args.type ?? "fact",
|
|
163
167
|
tags: args.tags ?? [],
|
|
164
|
-
source: "tool",
|
|
168
|
+
source: args.source ?? "tool",
|
|
165
169
|
});
|
|
166
170
|
const { filePath } = writeMemoryFile(fm, redacted);
|
|
167
171
|
const mf = readMemoryFile(filePath);
|
|
@@ -260,16 +264,18 @@ export async function statusMemories() {
|
|
|
260
264
|
};
|
|
261
265
|
}
|
|
262
266
|
export async function submitMemoriesOp(args) {
|
|
263
|
-
const r = submitMemories(args.ids, {
|
|
267
|
+
const r = submitMemories(args.ids, { branch: args.branch, base: args.base });
|
|
264
268
|
const lines = [];
|
|
265
269
|
for (const s of r.submitted)
|
|
266
270
|
lines.push(`submitted ${s.id} [${s.reviewState}]`);
|
|
267
271
|
for (const id of r.skippedIdentical)
|
|
268
272
|
lines.push(`already on branch: ${id} (outbox copy removed)`);
|
|
269
|
-
lines.push(r.committed ? `committed on ${r.branch}.` : `nothing new to commit on ${r.branch}.`);
|
|
270
|
-
lines.push(`Next
|
|
273
|
+
lines.push(r.committed ? `committed on ${r.branch} (you are still on this branch).` : `nothing new to commit on ${r.branch}.`);
|
|
274
|
+
lines.push(`Next — ask the user: "want me to create a branch + push + open the PR, or will you handle it yourself?"`);
|
|
275
|
+
lines.push(`Never create branches, push, or open PRs without their explicit approval. If they handle it themselves, hand them these:`);
|
|
271
276
|
lines.push(` ${r.pushCommand}`);
|
|
272
|
-
|
|
277
|
+
for (const l of r.prCommand.split("\n"))
|
|
278
|
+
lines.push(` ${l}`);
|
|
273
279
|
return { title: `memory: submitted ${r.submitted.length}`, output: lines.join("\n") };
|
|
274
280
|
}
|
|
275
281
|
export async function proposeMemoriesOp(args) {
|
package/docs/USER-GUIDE.md
CHANGED
|
@@ -99,13 +99,15 @@ personal idea ──propose──▶ outbox draft ──submit──▶ proposed
|
|
|
99
99
|
what triggered it — session start, a request, a CLI run, a submit), the
|
|
100
100
|
outbox (pending sync), the repo review states
|
|
101
101
|
(`draft / proposed / approved / published / rejected`), and any uncommitted
|
|
102
|
-
repo memory files. Your agent calls this at session start
|
|
103
|
-
checkpoints,
|
|
102
|
+
repo memory files. Your agent calls this at session start, at meaningful
|
|
103
|
+
checkpoints, after you commit, and after memory actions — then asks which
|
|
104
|
+
drafts (if any) you want synced. You can also just say "sync memory".
|
|
104
105
|
- `open-memex submit <id...>`: moves **your named drafts** into
|
|
105
|
-
`<repo>/.ai/open-memex/` as `proposed`. It
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
`<repo>/.ai/open-memex/` as `proposed`. It copies the files, flips
|
|
107
|
+
`review_state`, and makes a **local** git commit **on your current branch** —
|
|
108
|
+
it never creates a branch on its own (branch creation is your call, or your
|
|
109
|
+
agent's with your explicit approval via `--branch <name>`).
|
|
110
|
+
All-or-nothing, idempotent, crash-safe. It prints the `git push` +
|
|
109
111
|
`gh pr create` commands; if your agent already has your Yes for this sync,
|
|
110
112
|
it carries through push and PR itself. The PR base defaults to the current
|
|
111
113
|
branch; `--base` redirects to `main` or your integration branch.
|
|
@@ -120,8 +122,8 @@ personal idea ──propose──▶ outbox draft ──submit──▶ proposed
|
|
|
120
122
|
(push, PR, merge).
|
|
121
123
|
- A standalone memory PR contains **only memory files, no code**, reviewed and
|
|
122
124
|
audited separately from code PRs. Reviewers check "is this true? is it safe
|
|
123
|
-
to share? any secrets?" — things a code PR's CI never checks. You
|
|
124
|
-
|
|
125
|
+
to share? any secrets?" — things a code PR's CI never checks. You create the
|
|
126
|
+
branch yourself when you want one (or your agent does, with your approval).
|
|
125
127
|
- "Request changes" needs no command: while the PR is open, the author edits
|
|
126
128
|
the same file (directly, or by asking their agent in chat), commits, and
|
|
127
129
|
pushes. The state stays `proposed`; the PR is the review mechanism.
|
package/docs/USER-GUIDE.zh-CN.md
CHANGED
|
@@ -87,12 +87,13 @@ server 不能主动推送,调不调 `memory_search` 全看 model 的判断。
|
|
|
87
87
|
- `open-memex sync-status`:看索引**上次同步的时间和触发方**(会话开始、
|
|
88
88
|
请求、CLI、submit)、草稿箱(待同步)、repo 里的评审状态
|
|
89
89
|
(`draft / proposed / approved / published / rejected`),以及 repo 里还没
|
|
90
|
-
commit 的记忆文件。你的 agent
|
|
91
|
-
|
|
90
|
+
commit 的记忆文件。你的 agent 会在会话开始、关键节点、你 commit 之后、
|
|
91
|
+
记忆动作之后跑这个,然后问你哪些草稿(如果有)要同步。你也可以直接说"同步记忆"。
|
|
92
92
|
- `open-memex submit <id...>`:把**你点名的草稿**移入 `<repo>/.ai/open-memex/`,
|
|
93
|
-
状态变为 `proposed
|
|
94
|
-
|
|
95
|
-
|
|
93
|
+
状态变为 `proposed`。它复制文件、改 `review_state`、在**当前分支**做一次
|
|
94
|
+
**本地** git commit——永不自动建分支(建分支你说了算,或 agent 拿到你明确
|
|
95
|
+
批准走全链时用 `--branch <name>`)。
|
|
96
|
+
全有或全无、幂等、crash-safe。它打印 `git push` +
|
|
96
97
|
`gh pr create` 命令;如果你的 agent 已经拿到你这次的 Yes,它会自己走完
|
|
97
98
|
push 和 PR。PR 默认 base 是当前分支;`--base` 可改到 `main` 或集成支。
|
|
98
99
|
分支上同 id 但内容不同——**直接中止**,等人裁决,绝不覆盖。
|
|
@@ -104,7 +105,7 @@ server 不能主动推送,调不调 `memory_search` 全看 model 的判断。
|
|
|
104
105
|
**git 负责运输**(push、PR、合并)。
|
|
105
106
|
- 独立的记忆 PR **只含记忆文件,不含代码**,跟代码 PR 分开评审、分开审计。
|
|
106
107
|
审的是"这条是真的吗?能给全团队看吗?有没有 secret?"——代码 PR 的 CI 不会查这些。
|
|
107
|
-
|
|
108
|
+
想开分支时你自己建(或 agent 拿到批准后建)。
|
|
108
109
|
- "要求修改"不需要命令:PR 开着的时候,作者直接改同一个文件(自己改,
|
|
109
110
|
或在 chat 里让 agent 改),commit、push。状态一直是 `proposed`,
|
|
110
111
|
PR 本身就是评审机制。
|
package/docs/V2-DESIGN.md
CHANGED
|
@@ -25,6 +25,12 @@ Decisions log; Prior Art; solo-dev adoption path.
|
|
|
25
25
|
6. **Adapters translate; they never implement memory logic.** All memory logic lives in Core.
|
|
26
26
|
7. **Memory is the entrance of knowledge, not its final form.** Terminal states are docs / ADRs /
|
|
27
27
|
AGENTS.md instructions — memory is how knowledge gets captured and found.
|
|
28
|
+
8. **Distill conversations; do not archive them.** The default unit of memory is the useful
|
|
29
|
+
conclusion from a conversation, not the full transcript: what was learned, how it was resolved,
|
|
30
|
+
and where the authoritative source lives.
|
|
31
|
+
9. **Portable second brain, not a walled garden.** Long-form personal notes are valid memories when
|
|
32
|
+
the user explicitly saves them; Markdown keeps them readable, exportable, and movable to other
|
|
33
|
+
tools or machines.
|
|
28
34
|
|
|
29
35
|
---
|
|
30
36
|
|
|
@@ -40,6 +46,9 @@ Decisions log; Prior Art; solo-dev adoption path.
|
|
|
40
46
|
- **MCP is an interface, not the identity.** MCP / CLI / REST / SDK are access layers over the protocol,
|
|
41
47
|
so the project is never locked to one transport or one agent tool (opencode, VS Code Copilot, Cursor,
|
|
42
48
|
Claude Code, Windsurf, …).
|
|
49
|
+
- **Second-brain lens:** for individuals, OpenMemex can also be a local Markdown second brain for
|
|
50
|
+
AI-assisted work — similar in spirit to users asking AI to save notes into Obsidian or Notion, but
|
|
51
|
+
with agent recall, scopes, review, and redaction built in from the start.
|
|
43
52
|
- **Company lens:** at organizational scale the same pain is tribal knowledge — senior engineers'
|
|
44
53
|
hard-won experience evaporates when they move on, and every incident gets re-debugged by someone
|
|
45
54
|
new. The current phase therefore prioritizes *capture*: valuable knowledge must land in memory
|
|
@@ -132,13 +141,37 @@ canonical_ref: docs/adr-003.md # memory holds a SUMMARY; the doc is canoni
|
|
|
132
141
|
|
|
133
142
|
### 3.1 Type taxonomy (content kind)
|
|
134
143
|
|
|
135
|
-
`
|
|
136
|
-
`
|
|
144
|
+
`fact` `preference` `decision` `constraint` `todo` `knowledge` `howto` `gotcha`
|
|
145
|
+
`lesson` `observation` `reference`
|
|
137
146
|
|
|
138
|
-
`type` describes **what the content is
|
|
147
|
+
`type` describes **what the content is** — single-valued, and it drives behavior
|
|
148
|
+
(lifecycle, review, rendering, retrieval). `role` describes **how it may be used**.
|
|
139
149
|
A `decision` with `role: knowledge` is retrievable history. Only `role: instruction` may enter
|
|
140
150
|
instruction context. (Separation adopted from review: mixing usage semantics into `type` was a design smell.)
|
|
141
151
|
|
|
152
|
+
One-line definitions:
|
|
153
|
+
|
|
154
|
+
- `fact` — a verifiable atomic statement (timezone, version number, path, account).
|
|
155
|
+
- `preference` — user likes, dislikes, working style.
|
|
156
|
+
- `decision` — a choice made + why; participates in supersede chains.
|
|
157
|
+
- `constraint` — a hard rule that must not be violated (release process, permission boundaries).
|
|
158
|
+
- `todo` — an actionable item with completion state.
|
|
159
|
+
- `knowledge` — declarative knowledge (how a system works, concept explanations).
|
|
160
|
+
- `howto` — steps to accomplish X.
|
|
161
|
+
- `gotcha` — a pitfall: don't do X because Y.
|
|
162
|
+
- `lesson` — a takeaway from experience, including incident postmortems (the incident id goes in `tags`).
|
|
163
|
+
- `observation` — noticed but not yet distilled.
|
|
164
|
+
- `reference` — a pointer to the authoritative doc via `canonical_ref`; the memory holds the summary.
|
|
165
|
+
|
|
166
|
+
A type earns its place only if the system treats it differently. If two candidates
|
|
167
|
+
share lifecycle, retrieval, and rendering, the loser becomes a tag. (D41 merged
|
|
168
|
+
`warning`→`gotcha`, `workflow`→`howto`, `incident`→`lesson`, `architecture`→`knowledge`.)
|
|
169
|
+
|
|
170
|
+
`tags` are retrieval hints, not a second type system: `type` says what the memory is, while tags say
|
|
171
|
+
which topics, tools, subsystems, paths, or incidents it relates to. Write paths should preserve
|
|
172
|
+
human-provided tags and may suggest simple normalized tags (for example `vscode`, `mcp`, `windows`,
|
|
173
|
+
`auth`, `onboarding`) to improve search without changing memory semantics.
|
|
174
|
+
|
|
142
175
|
### 3.2 Iron rules
|
|
143
176
|
|
|
144
177
|
- **Iron rule 1 — the instruction gate:** only memories with `role: instruction`
|
|
@@ -170,6 +203,34 @@ Content hash + fuzzy match against existing memories. A superseding write does *
|
|
|
170
203
|
the old memory becomes `status: superseded` with `superseded_by` pointing forward. History preserved;
|
|
171
204
|
queries rank `active` first.
|
|
172
205
|
|
|
206
|
+
### 3.5 Conversation distillation
|
|
207
|
+
|
|
208
|
+
OpenMemex does **not** store raw AI chat transcripts by default. A captured memory should usually be
|
|
209
|
+
a short, human-approved artifact distilled from the conversation: the final conclusion, important
|
|
210
|
+
facts, why the answer matters later, and how the issue was resolved. Good distilled memory candidates
|
|
211
|
+
answer some combination of:
|
|
212
|
+
|
|
213
|
+
- What did we learn or decide?
|
|
214
|
+
- What solved the problem, including key commands, files, links, or steps?
|
|
215
|
+
- What mistake or gotcha should the next developer avoid?
|
|
216
|
+
- Which scope owns it: `personal`, `project`, or future `org`?
|
|
217
|
+
- If the information already exists in project docs, where is the authoritative doc?
|
|
218
|
+
|
|
219
|
+
If stable knowledge already lives in a project document, prefer a `type: reference` memory with
|
|
220
|
+
`canonical_ref` pointing at that document over duplicating the full content. Memory should help the
|
|
221
|
+
agent find and apply authoritative docs, not become an outdated second copy of them.
|
|
222
|
+
|
|
223
|
+
Model-suggested captures use `source: inference` and should default to reviewable draft state. The
|
|
224
|
+
agent may propose 1-3 distilled memories at checkpoints or task end, but humans decide whether to
|
|
225
|
+
save them and which scope they belong to. The product goal is remembering the right conclusion at
|
|
226
|
+
the right scope, not remembering everything.
|
|
227
|
+
|
|
228
|
+
Long-form notes are still valid when the user explicitly asks to save a long note, write-up, meeting
|
|
229
|
+
summary, research log, or troubleshooting record. In that case OpenMemex behaves like a Markdown
|
|
230
|
+
second-brain target: store the note as user-authored content, preserve the body, tag it for retrieval,
|
|
231
|
+
and keep the same scope/privacy rules. The distinction is intent: implicit/model-suggested capture
|
|
232
|
+
distills; explicit "save this note" may preserve long-form text.
|
|
233
|
+
|
|
173
234
|
---
|
|
174
235
|
|
|
175
236
|
## 4. Scopes, Visibility & Namespaces
|
|
@@ -294,11 +355,21 @@ instructions are never candidates.)
|
|
|
294
355
|
| Implicit (opt-in) | end-of-session "should I remember X?"; implicit captures default to `confidence: low` and appear in a separate list view for batch cleanup (regret window). |
|
|
295
356
|
| Redaction (hard) | `<private>…</private>` stripped; secret patterns are **masked in place** (first 4 chars kept, rest → `x`) and the write proceeds (D14); pre-commit hook scans shared scopes. |
|
|
296
357
|
|
|
358
|
+
**Scope routing on capture:** when the user says "save", "remember", or "note this" without an
|
|
359
|
+
explicit scope, the agent should infer ownership from content and current context. Project-specific
|
|
360
|
+
knowledge (repo commands, architecture, code paths, product decisions, team conventions) routes to
|
|
361
|
+
`project`; personal preferences, private working notes, cross-project habits, or content unrelated to
|
|
362
|
+
the current repo routes to `personal`. Ambiguous captures should ask one short clarification instead
|
|
363
|
+
of guessing. `org` capture remains future/curated; a single user's chat never writes org memory
|
|
364
|
+
directly.
|
|
365
|
+
|
|
297
366
|
---
|
|
298
367
|
|
|
299
368
|
## 9. Sync
|
|
300
369
|
|
|
301
|
-
- **Git is transport; the local SQLite index is the query layer.**
|
|
370
|
+
- **Git is a transport, not the product boundary; the local SQLite index is the query layer.**
|
|
371
|
+
Retrieval never walks git. Git/GitHub is the default sharing provider because it gives teams branch,
|
|
372
|
+
PR, review, and audit semantics they already trust, but Core must remain provider-agnostic.
|
|
302
373
|
- one-memory-one-file ⇒ concurrent edits almost never conflict.
|
|
303
374
|
- **Pull is explicit** (`open-memex pull`), never automatic on session start — no surprise context changes.
|
|
304
375
|
`pull` = `fetch` + fast-forward only; never auto-commit/push (hard rule for enterprise environments).
|
|
@@ -314,6 +385,13 @@ instructions are never candidates.)
|
|
|
314
385
|
- **Git unavailable:** projects without git (or with unreachable remotes) remain fully usable.
|
|
315
386
|
`personal` scope works everywhere; `project` scope degrades to local-only and `open-memex status`
|
|
316
387
|
annotates it as such. Sync commands fail with a clear message, never with a broken state.
|
|
388
|
+
- **Portability:** because Markdown is the source of truth, memories should be exportable without
|
|
389
|
+
depending on git. A future `open-memex export` command can archive selected scopes/types/tags into a
|
|
390
|
+
portable zip/tar bundle (markdown + manifest) for moving to another computer or importing into
|
|
391
|
+
another application. Export excludes `visibility: private` memories by default; `--all` / `-a`
|
|
392
|
+
includes everything, for a full personal migration to a new machine. API-backed exporters/providers (Notion, Obsidian-compatible vaults, internal
|
|
393
|
+
knowledge systems, enterprise stores) are extension paths over the same memory files and admission
|
|
394
|
+
rules, not separate products.
|
|
317
395
|
|
|
318
396
|
---
|
|
319
397
|
|
|
@@ -457,9 +535,10 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
457
535
|
|
|
458
536
|
(Star counts / funding as of Sep 2026 — re-verify before quoting publicly.)
|
|
459
537
|
- **Phase 3 — Org layer.** Org memory repo · curator convention · `examples/remote-server/` ·
|
|
460
|
-
distill-to-AGENTS.md assist.
|
|
538
|
+
distill-to-AGENTS.md assist · export/import archive command for user portability.
|
|
461
539
|
- **Phase 4 — Future, signal-gated.** Cloud `RemoteProvider` customization only on: multi-private-repo
|
|
462
|
-
sharing needs, fine-grained ACL, audit/compliance mandates
|
|
540
|
+
sharing needs, fine-grained ACL, audit/compliance mandates · optional API-backed exporters/providers
|
|
541
|
+
for enterprise knowledge systems.
|
|
463
542
|
|
|
464
543
|
## 19. Migration v1 → v2
|
|
465
544
|
|
|
@@ -714,12 +793,82 @@ requirement: personal data never touches third-party services). Benchmarks to tr
|
|
|
714
793
|
init-written instruction files, and the `memory_status` tool description.
|
|
715
794
|
*Rationale: the user should not have to remember command names to sync;
|
|
716
795
|
saying it in words must work. Approved 2026-09-28.*
|
|
796
|
+
- **D36** — `submit` never creates a branch on its own (revises D26/D27): it
|
|
797
|
+
copies the named drafts into `.ai/open-memex/`, commits locally on the
|
|
798
|
+
CURRENT branch, and prints next-step commands. Branch creation is the human's
|
|
799
|
+
call — or the agent's, only with explicit approval for the full chain, via
|
|
800
|
+
`submit --branch <name>` / `memory_submit(branch=...)`. *Rationale: an
|
|
801
|
+
auto-created branch strands the user on it — they forget to switch back.
|
|
802
|
+
After a submit the agent asks ONE follow-up ("want me to create a branch +
|
|
803
|
+
push + open the PR, or will you handle it yourself?") instead of branching
|
|
804
|
+
silently. Checkpoints that trigger `memory_status`: session start, end of a
|
|
805
|
+
work chunk, after the user commits, and after any memory_* action.
|
|
806
|
+
The "sync memory" trigger ALWAYS goes through the `memory_status` tool —
|
|
807
|
+
never by browsing the appdata directory directly. Approved 2026-09-28.*
|
|
717
808
|
- **D33** — Every CLI command answers `open-memex <command> --help` (and `-h`)
|
|
718
809
|
with its own usage, flags, and examples; checked before config/DB load so
|
|
719
810
|
help works even in a broken environment. Unknown commands with `--help`
|
|
720
811
|
fall back to the global usage. *Rationale: AI assistants discover the CLI
|
|
721
812
|
through --help first — a command that silently swallows --help as a flag
|
|
722
813
|
teaches the agent nothing. Approved 2026-09-28.*
|
|
814
|
+
- **D37** — Conversation distillation is the memory unit; transcript storage is
|
|
815
|
+
not the default. Capture should preserve the useful conclusion from a human/AI
|
|
816
|
+
session — what was learned, what resolved the issue, what should be avoided,
|
|
817
|
+
and where the authoritative doc lives — as a short reviewable memory. If the
|
|
818
|
+
knowledge already exists in docs, store a `reference` memory with
|
|
819
|
+
`canonical_ref` instead of duplicating the doc. Tags are retrieval hints, not
|
|
820
|
+
content kinds: `type` classifies the memory, tags describe topics/tools/paths.
|
|
821
|
+
*Rationale: full chat logs are noisy, harder to review, and riskier for
|
|
822
|
+
privacy; the value is remembering the right conclusion at the right scope,
|
|
823
|
+
with enough context for the next human or AI session. Approved 2026-09-28.*
|
|
824
|
+
- **D38** — OpenMemex supports a Markdown second-brain use case while keeping
|
|
825
|
+
distillation as the default for implicit/model-suggested capture. If the user
|
|
826
|
+
explicitly asks to save a long note, meeting summary, troubleshooting record,
|
|
827
|
+
or write-up, preserve it as user-authored Markdown with tags and normal
|
|
828
|
+
scope/privacy rules. Git/GitHub sync is the default team-sharing provider, not
|
|
829
|
+
the product boundary: future export/import archives and API-backed providers
|
|
830
|
+
may move the same markdown memories to other computers, applications, or
|
|
831
|
+
enterprise systems. For generic "save/remember/note this" requests, the agent
|
|
832
|
+
routes project-specific knowledge to `project`, personal or repo-unrelated
|
|
833
|
+
knowledge to `personal`, and asks one clarification if ambiguous; `org` remains
|
|
834
|
+
curated/future, never a direct single-user chat write. *Rationale: users also
|
|
835
|
+
want a local AI-assisted second brain, but portability and ownership must stay
|
|
836
|
+
explicit; sharing mechanisms should be provider choices over the same memory
|
|
837
|
+
model, not the identity of the product. Approved 2026-09-28.*
|
|
838
|
+
- **D39** — `type` and `tags` are complementary axes, not alternatives. `type` is
|
|
839
|
+
single-valued: what the memory IS — it drives behavior (lifecycle, review,
|
|
840
|
+
rendering, retrieval: a `todo` can be completed, a `reference` resolves
|
|
841
|
+
`canonical_ref`, a `decision` participates in supersede chains). `tags` are
|
|
842
|
+
multi-valued: what the memory is ABOUT — pure retrieval hints
|
|
843
|
+
(topics, tools, subsystems, paths, incidents). *Rationale: without type the
|
|
844
|
+
system cannot tell "a todo I must do" from "a fact I must know" even when both
|
|
845
|
+
are tagged `auth`; without tags, cross-cutting retrieval ("everything about
|
|
846
|
+
onboarding") would need a combinatorial type explosion. Type answers "how do I
|
|
847
|
+
handle this?", tags answer "how do I find this?". Approved 2026-09-28.*
|
|
848
|
+
- **D40** — `open-memex export` excludes `visibility: private` memories by
|
|
849
|
+
default; `--all` / `-a` includes everything, for a full personal migration to
|
|
850
|
+
a new machine. *Rationale: the safe default protects privacy; the escape hatch
|
|
851
|
+
keeps the "no walled garden" promise — the user can always take everything
|
|
852
|
+
with them. Approved 2026-09-28.*
|
|
853
|
+
- **D42** — §3.5 checkpoint distillation is taught in the MCP handshake
|
|
854
|
+
instructions (`src/mcp.ts` `SERVER_INSTRUCTIONS`) and the `init`-written
|
|
855
|
+
instruction files (`src/init.ts` `INSTRUCTIONS`): at checkpoints the agent
|
|
856
|
+
DISTILLs the session and proposes 1–3 short memories (conclusion, not
|
|
857
|
+
transcript); nothing is saved without user approval. Approved captures are
|
|
858
|
+
saved via `memory_add` with the new optional `source` param set to
|
|
859
|
+
`"inference"` (default `"tool"`). *Rationale: closes the 0.4.0 TODO from D41 —
|
|
860
|
+
the design's capture loop now reaches the agent. Implemented 2026-09-29.*
|
|
861
|
+
- **D41** — The type taxonomy is reconciled to 11 types with one-line definitions
|
|
862
|
+
(§3.1): `fact` `preference` `decision` `constraint` `todo` `knowledge` `howto`
|
|
863
|
+
`gotcha` `lesson` `observation` `reference`. Merged away: `warning`→`gotcha`,
|
|
864
|
+
`workflow`→`howto`, `incident`→`lesson` (incident id goes in `tags`),
|
|
865
|
+
`architecture`→`knowledge` (tag `architecture`). *Rationale: a type earns its
|
|
866
|
+
place only if the system treats it differently (lifecycle, retrieval,
|
|
867
|
+
rendering); otherwise it is a tag. Fifteen types blur classification and hurt
|
|
868
|
+
agent accuracy — eleven keeps each type's behavioral slot distinct. `lesson`
|
|
869
|
+
is deliberately broader than `incident`: a postmortem's shape (timeline, root
|
|
870
|
+
cause, actions) is a template concern, not a type. Code
|
|
871
|
+
`MEMORY_TYPE_TAXONOMY` updated to match. Approved 2026-09-28.*
|
|
723
872
|
|
|
724
873
|
## Open Questions
|
|
725
874
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "open-memex",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.7",
|
|
4
4
|
"description": "Local-first memory layer and protocol for AI coding agents. Markdown source of truth, SQLite FTS5 index, zero cloud.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
package/src/cli.ts
CHANGED
|
@@ -124,17 +124,17 @@ repo awaiting review or published, and repo files not yet committed.
|
|
|
124
124
|
|
|
125
125
|
Usage: open-memex sync-status`,
|
|
126
126
|
|
|
127
|
-
submit: `Move outbox drafts into
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
explicit approval and are never run automatically.
|
|
127
|
+
submit: `Move outbox drafts into the repo for review: copies the drafts into
|
|
128
|
+
the repo memory dir as proposed (a local-approved copy keeps its approval),
|
|
129
|
+
commits locally on the CURRENT branch, and moves the outbox originals out.
|
|
130
|
+
Never creates a branch on its own — branch creation is your call.
|
|
132
131
|
|
|
133
|
-
Usage: open-memex submit <id...> [--
|
|
132
|
+
Usage: open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
134
133
|
|
|
135
134
|
Flags:
|
|
136
|
-
--
|
|
137
|
-
|
|
135
|
+
--branch create this branch and submit onto it (only with your explicit
|
|
136
|
+
approval for the full chain); default: stay on current branch
|
|
137
|
+
--base PR base override (default: the branch you're on)
|
|
138
138
|
|
|
139
139
|
Example:
|
|
140
140
|
open-memex submit 01ABC 01DEF`,
|
|
@@ -223,7 +223,7 @@ Usage:
|
|
|
223
223
|
open-memex promote <id> [--reject] [--resubmit] [--note "..."] [--by NAME]
|
|
224
224
|
open-memex resolve [id-or-path]
|
|
225
225
|
open-memex sync-status
|
|
226
|
-
open-memex submit <id...> [--
|
|
226
|
+
open-memex submit <id...> [--branch <name>] [--base <branch>]
|
|
227
227
|
open-memex pr-status [--apply]
|
|
228
228
|
open-memex reindex
|
|
229
229
|
open-memex scopes
|
|
@@ -883,7 +883,7 @@ function positionalArgs(argv: string[]): string[] {
|
|
|
883
883
|
if (ids.length === 0) usage();
|
|
884
884
|
syncScope(project.key, "submit");
|
|
885
885
|
try {
|
|
886
|
-
const r = submitMemories(ids, {
|
|
886
|
+
const r = submitMemories(ids, { branch: flags.branch, base: flags.base });
|
|
887
887
|
for (const s of r.submitted) {
|
|
888
888
|
console.log(`submitted ${s.id} → ${path.relative(process.cwd(), s.filePath)} [${s.reviewState}]`);
|
|
889
889
|
}
|
package/src/doctor.ts
CHANGED
|
@@ -134,7 +134,7 @@ function mcpCheck(): Promise<Check> {
|
|
|
134
134
|
ok: missing.length === 0,
|
|
135
135
|
detail:
|
|
136
136
|
missing.length === 0
|
|
137
|
-
? `handshake OK,
|
|
137
|
+
? `handshake OK, ${names.length} tools listed (${names.join(", ")})`
|
|
138
138
|
: `missing tools: ${missing.join(", ")}`,
|
|
139
139
|
});
|
|
140
140
|
}
|
package/src/init.ts
CHANGED
|
@@ -58,6 +58,14 @@ You have a local memory MCP server (\`open-memex\`) with eleven tools:
|
|
|
58
58
|
- BE PROACTIVE. When the user shares something worth remembering across sessions
|
|
59
59
|
(a decision, a preference, a project convention, a fix and its cause), call
|
|
60
60
|
\`memory_add\` without being asked. Keep each memory to one self-contained statement.
|
|
61
|
+
- At checkpoints (session start, end of a work chunk, after the user commits, after
|
|
62
|
+
any memory_* action), DISTILL the session: propose 1–3 short memories capturing the
|
|
63
|
+
useful conclusion — what was learned or decided, how an issue was resolved, what to
|
|
64
|
+
avoid, where the authoritative doc lives — not the raw transcript. Save NOTHING the
|
|
65
|
+
user did not approve; on approval call \`memory_add\` with source "inference" at the
|
|
66
|
+
confirmed scope. If the knowledge already lives in project docs, save a \`reference\`
|
|
67
|
+
memory pointing at the doc instead of copying it. Long-form notes are fine ONLY when
|
|
68
|
+
the user explicitly asks to save one.
|
|
61
69
|
- Before asking the user about past decisions, conventions, or preferences they may
|
|
62
70
|
have told you before, call \`memory_search\` first — try a few keyword variants
|
|
63
71
|
(including the user's own language) when the first search comes up empty.
|
|
@@ -70,20 +78,22 @@ You have a local memory MCP server (\`open-memex\`) with eleven tools:
|
|
|
70
78
|
Project memories you save land in a local outbox first — they are NOT in git yet.
|
|
71
79
|
Syncing them into the repo for review is an explicit, user-approved step:
|
|
72
80
|
|
|
73
|
-
- At session start,
|
|
81
|
+
- At session start, when you finish a meaningful chunk of work, after the user
|
|
82
|
+
commits (git commit), and after any memory_* action completes, call
|
|
74
83
|
\`memory_status\`. If the outbox has drafts, summarize them (one line each) and ask
|
|
75
84
|
the user which ones to sync. Sync NOTHING the user did not name.
|
|
76
85
|
- When the user says "sync memory" (or "同步记忆"), treat it as a request to run
|
|
77
86
|
the sync flow above: call \`memory_status\`, summarize the outbox drafts, and ask
|
|
78
87
|
which ones to sync.
|
|
79
|
-
- When the user approves,
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
- When the user approves, call \`memory_submit\` with the approved ids. It copies
|
|
89
|
+
the drafts into the repo as \`proposed\`, commits locally on the CURRENT branch,
|
|
90
|
+
and prints the push + PR commands. It NEVER creates a branch on its own.
|
|
91
|
+
- After the submit, ask ONE follow-up: "want me to create a branch + push +
|
|
92
|
+
open the PR, or will you handle it yourself?" A "yes, you do it" answer covers
|
|
93
|
+
the whole chain — branch creation, push, PR creation — do NOT re-ask at each
|
|
94
|
+
step. If the user says they will do it themselves, hand them the printed
|
|
95
|
+
push/PR commands and do nothing. NEVER create branches, push, or open PRs
|
|
96
|
+
without their explicit approval.
|
|
87
97
|
- Base branch for the memory PR defaults to the branch you are on; the user may
|
|
88
98
|
redirect it to the integration branch (main) for branch-independent knowledge.
|
|
89
99
|
- If anything conflicts (same id with different content, push rejected), STOP and
|
package/src/mcp.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* open-memex generic MCP server (stdio transport).
|
|
3
3
|
*
|
|
4
|
-
* Exposes the
|
|
4
|
+
* Exposes the memory tools as the opencode plugin
|
|
5
5
|
* (memory_add / memory_search / memory_list / memory_supersede /
|
|
6
|
-
* memory_forget
|
|
6
|
+
* memory_forget, plus memory_status / memory_submit / memory_propose /
|
|
7
|
+
* memory_promote / memory_resolve / memory_pr_status) over the Model Context Protocol, so any MCP client —
|
|
7
8
|
* VS Code Copilot Chat, Cursor, Claude Code, etc. — can use open-memex
|
|
8
9
|
* without a host-specific plugin.
|
|
9
10
|
*
|
|
@@ -19,6 +20,9 @@
|
|
|
19
20
|
*/
|
|
20
21
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
21
22
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
import path from "node:path";
|
|
25
|
+
import { fileURLToPath } from "node:url";
|
|
22
26
|
import { z } from "zod";
|
|
23
27
|
import { loadConfig } from "./config.ts";
|
|
24
28
|
import { resolveProjectScope, PERSONAL_SCOPE, type Scope } from "./scope.ts";
|
|
@@ -51,7 +55,18 @@ import {
|
|
|
51
55
|
type ToolResult,
|
|
52
56
|
} from "./tools/ops.ts";
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
// Server version tracks package.json — never hardcode it here again.
|
|
59
|
+
// package.json sits two levels above this file in both layouts
|
|
60
|
+
// (src/mcp.ts and dist/mcp.js), same convention as cli.ts --version.
|
|
61
|
+
const SERVER_VERSION: string = (() => {
|
|
62
|
+
try {
|
|
63
|
+
const root = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
64
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
|
|
65
|
+
return typeof pkg.version === "string" ? pkg.version : "0.0.0-unknown";
|
|
66
|
+
} catch {
|
|
67
|
+
return "0.0.0-unknown";
|
|
68
|
+
}
|
|
69
|
+
})();
|
|
55
70
|
|
|
56
71
|
/**
|
|
57
72
|
* D26: session-start guidance delivered through the MCP handshake itself.
|
|
@@ -64,16 +79,31 @@ const SERVER_INSTRUCTIONS = `You are connected to an open-memex local memory MCP
|
|
|
64
79
|
(eleven memory_* tools: add, search, list, supersede, forget, status, submit,
|
|
65
80
|
propose, promote, resolve, pr_status).
|
|
66
81
|
|
|
67
|
-
- At the START of this session,
|
|
68
|
-
|
|
69
|
-
|
|
82
|
+
- At the START of this session, when you finish a meaningful chunk of work,
|
|
83
|
+
after the user commits (git commit), and after any memory_* action completes,
|
|
84
|
+
call memory_status. If the project outbox has drafts waiting for review,
|
|
85
|
+
summarize them (one line each) and ask the user which ones to sync into the
|
|
86
|
+
repo. Sync NOTHING the user did not name.
|
|
70
87
|
- When the user says "sync memory" (or "同步记忆"), treat it as a request to run
|
|
71
88
|
the sync flow: call memory_status, summarize the outbox drafts, and ask which
|
|
72
|
-
ones to sync.
|
|
73
|
-
|
|
89
|
+
ones to sync. ALWAYS use the memory_status tool for this — never browse the
|
|
90
|
+
appdata directory directly.
|
|
91
|
+
- After memory_submit, ask ONE follow-up: "want me to create a branch + push +
|
|
92
|
+
open the PR, or will you handle it yourself?" NEVER create branches, push, or
|
|
93
|
+
open PRs without the user's explicit approval. A "yes, you do it" covers the
|
|
94
|
+
whole chain — do NOT re-ask at each step.
|
|
74
95
|
- BE PROACTIVE: when the user shares something worth remembering across sessions
|
|
75
96
|
(a decision, a preference, a project convention, a fix and its cause), call
|
|
76
97
|
memory_add without being asked. Keep each memory to one self-contained statement.
|
|
98
|
+
- At the same checkpoints (session start, end of a work chunk, after the user
|
|
99
|
+
commits, after any memory_* action), DISTILL the session: propose 1–3 short
|
|
100
|
+
memories capturing the useful conclusion — what was learned or decided, how an
|
|
101
|
+
issue was resolved, what to avoid, where the authoritative doc lives — not the
|
|
102
|
+
raw transcript. Save NOTHING the user did not approve; on approval call
|
|
103
|
+
memory_add with source "inference" at the confirmed scope. If the knowledge
|
|
104
|
+
already lives in project docs, save a \`reference\` memory pointing at the doc
|
|
105
|
+
instead of copying it. Long-form notes are fine ONLY when the user explicitly
|
|
106
|
+
asks to save one.
|
|
77
107
|
- Before asking the user about past decisions, conventions, or preferences they
|
|
78
108
|
may have told you before, call memory_search first.
|
|
79
109
|
- Memories default to this project's scope; use the personal scope for facts
|
package/src/store/markdown.ts
CHANGED
|
@@ -8,19 +8,21 @@ import {
|
|
|
8
8
|
inRepoMemoriesDirPath,
|
|
9
9
|
} from "../paths.ts";
|
|
10
10
|
|
|
11
|
-
/** v2 content-kind taxonomy (V2-DESIGN §3.1). `type` = what the memory IS
|
|
11
|
+
/** v2 content-kind taxonomy (V2-DESIGN §3.1). `type` = what the memory IS
|
|
12
|
+
* (single-valued, drives behavior). D41: 11 types; `warning`→`gotcha`,
|
|
13
|
+
* `workflow`→`howto`, `incident`→`lesson`, `architecture`→`knowledge`. */
|
|
12
14
|
export const MEMORY_TYPE_TAXONOMY = [
|
|
13
|
-
"preference",
|
|
14
15
|
"fact",
|
|
16
|
+
"preference",
|
|
15
17
|
"decision",
|
|
16
|
-
"lesson",
|
|
17
|
-
"warning",
|
|
18
|
-
"workflow",
|
|
19
|
-
"architecture",
|
|
20
18
|
"constraint",
|
|
21
19
|
"todo",
|
|
22
20
|
"knowledge",
|
|
21
|
+
"howto",
|
|
22
|
+
"gotcha",
|
|
23
|
+
"lesson",
|
|
23
24
|
"observation",
|
|
25
|
+
"reference",
|
|
24
26
|
] as const;
|
|
25
27
|
|
|
26
28
|
const TAXONOMY = new Set<string>(MEMORY_TYPE_TAXONOMY);
|
package/src/submit.ts
CHANGED
|
@@ -191,15 +191,19 @@ export function formatSyncStatus(st: SyncStatus): string {
|
|
|
191
191
|
// ---------------------------------------------------------------------------
|
|
192
192
|
|
|
193
193
|
export interface SubmitOptions {
|
|
194
|
-
/**
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
/** Create this branch and submit onto it. If omitted, submit stays on the
|
|
195
|
+
current branch — D36: no auto-created branches; branch creation is the
|
|
196
|
+
human's call (or the agent's, only with explicit approval). */
|
|
197
|
+
branch?: string;
|
|
198
|
+
/** PR base override (default: the branch the submit ran on). */
|
|
197
199
|
base?: string;
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
export interface SubmitResult {
|
|
201
203
|
branch: string;
|
|
202
204
|
base: string;
|
|
205
|
+
/** true when --branch created a new branch for this submit */
|
|
206
|
+
createdBranch: boolean;
|
|
203
207
|
submitted: Array<{ id: string; filePath: string; reviewState: ReviewState }>;
|
|
204
208
|
/** already on the branch with identical content — outbox move completed */
|
|
205
209
|
skippedIdentical: string[];
|
|
@@ -219,7 +223,8 @@ interface Validated {
|
|
|
219
223
|
export function submitMemories(ids: string[], opts: SubmitOptions = {}): SubmitResult {
|
|
220
224
|
if (ids.length === 0) fail("submit needs at least one memory id");
|
|
221
225
|
const root = projectRoot();
|
|
222
|
-
// submit needs a git repo — the
|
|
226
|
+
// submit needs a git repo — the memories land in .ai/open-memex/ and are
|
|
227
|
+
// committed locally. Branch creation is never automatic (D36).
|
|
223
228
|
git(root, ["rev-parse", "--git-dir"]);
|
|
224
229
|
|
|
225
230
|
const scope = resolveProjectScope(root);
|
|
@@ -269,24 +274,25 @@ export function submitMemories(ids: string[], opts: SubmitOptions = {}): SubmitR
|
|
|
269
274
|
}
|
|
270
275
|
|
|
271
276
|
// 2. Resolve the target branch.
|
|
277
|
+
// D36: submit never creates a branch on its own — it works on the branch
|
|
278
|
+
// you're already on. Pass --branch <name> (explicitly) only when the user
|
|
279
|
+
// approved the full chain (branch + push + PR).
|
|
272
280
|
const startBranch = git(root, ["branch", "--show-current"]) || git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
273
|
-
let branch: string;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
branch = opts.onto;
|
|
279
|
-
} else {
|
|
280
|
-
const stamp = new Date().toISOString().replace(/[-:]/g, "").slice(0, 15);
|
|
281
|
-
branch = `mem/sync-${stamp}`;
|
|
281
|
+
let branch: string = startBranch;
|
|
282
|
+
let createdBranch = false;
|
|
283
|
+
if (opts.branch) {
|
|
284
|
+
branch = opts.branch;
|
|
282
285
|
let n = 0;
|
|
286
|
+
let candidate = branch;
|
|
283
287
|
while (true) {
|
|
284
|
-
const exists = git(root, ["branch", "--list",
|
|
288
|
+
const exists = git(root, ["branch", "--list", candidate]);
|
|
285
289
|
if (!exists) break;
|
|
286
290
|
n++;
|
|
287
|
-
|
|
291
|
+
candidate = `${branch}-${n}`;
|
|
288
292
|
}
|
|
293
|
+
branch = candidate;
|
|
289
294
|
git(root, ["checkout", "-b", branch]);
|
|
295
|
+
createdBranch = true;
|
|
290
296
|
}
|
|
291
297
|
const base = opts.base ?? startBranch;
|
|
292
298
|
const author = currentAuthor();
|
|
@@ -348,7 +354,7 @@ export function submitMemories(ids: string[], opts: SubmitOptions = {}): SubmitR
|
|
|
348
354
|
}
|
|
349
355
|
// If we created the branch and never committed, remove it too — an
|
|
350
356
|
// aborted submit leaves no trace.
|
|
351
|
-
if (
|
|
357
|
+
if (createdBranch) {
|
|
352
358
|
try {
|
|
353
359
|
git(root, ["checkout", "--quiet", startBranch]);
|
|
354
360
|
git(root, ["branch", "--quiet", "-D", branch]);
|
|
@@ -399,13 +405,20 @@ export function submitMemories(ids: string[], opts: SubmitOptions = {}): SubmitR
|
|
|
399
405
|
removed: 0,
|
|
400
406
|
scanned: validated.length,
|
|
401
407
|
});
|
|
408
|
+
// D36: no auto-branch — the commit sits on the branch you were already on.
|
|
409
|
+
// Next steps are printed, not run. For a separate memory PR, create a branch
|
|
410
|
+
// first (the commit comes along), then push + open the PR.
|
|
411
|
+
const branchCmd = `git checkout -b mem/sync-YYYYMMDD`;
|
|
402
412
|
return {
|
|
403
413
|
branch,
|
|
404
414
|
base,
|
|
405
415
|
submitted,
|
|
406
416
|
skippedIdentical,
|
|
407
417
|
committed,
|
|
418
|
+
createdBranch,
|
|
408
419
|
pushCommand: `git push -u origin ${branch}`,
|
|
409
|
-
prCommand:
|
|
420
|
+
prCommand: createdBranch
|
|
421
|
+
? `gh pr create --base ${base} --title "mem: review ${validated.length} ${validated.length === 1 ? "memory" : "memories"}" --body "Submitted from the open-memex outbox: ${ids8}."`
|
|
422
|
+
: `${branchCmd} # if you want a separate memory PR (else push ${branch} directly)\n git push -u origin <new-branch> && gh pr create --base ${base} --title "mem: review ${validated.length}" --body "Submitted from the open-memex outbox: ${ids8}."`,
|
|
410
423
|
};
|
|
411
424
|
}
|
package/src/tools/ops.ts
CHANGED
|
@@ -53,7 +53,7 @@ export const TOOL_DESCRIPTIONS = {
|
|
|
53
53
|
memory_status:
|
|
54
54
|
"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 and at task checkpoints, then ask the user which drafts to sync. The user may also trigger this flow by saying 'sync memory' (or '同步记忆').",
|
|
55
55
|
memory_submit:
|
|
56
|
-
"Move outbox drafts into
|
|
56
|
+
"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.",
|
|
57
57
|
memory_propose:
|
|
58
58
|
"Copy personal memories into the project outbox as review drafts. The personal originals stay put.",
|
|
59
59
|
memory_promote:
|
|
@@ -77,9 +77,15 @@ export const memoryAddArgs = {
|
|
|
77
77
|
type: z
|
|
78
78
|
.enum(MEMORY_TYPE_TAXONOMY)
|
|
79
79
|
.optional()
|
|
80
|
-
.describe("Category of memory. Default:
|
|
80
|
+
.describe("Category of memory. Default: fact."),
|
|
81
81
|
scope: scopeArg,
|
|
82
82
|
tags: z.array(z.string()).optional().describe("Optional tags for filtering."),
|
|
83
|
+
source: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe(
|
|
87
|
+
"Where this memory came from. Default: tool. Pass 'inference' for agent-proposed captures at checkpoints (V2-DESIGN §3.5).",
|
|
88
|
+
),
|
|
83
89
|
};
|
|
84
90
|
export type MemoryAddArgs = z.infer<z.ZodObject<typeof memoryAddArgs>>;
|
|
85
91
|
|
|
@@ -125,14 +131,16 @@ export type MemoryStatusArgs = z.infer<z.ZodObject<typeof memoryStatusArgs>>;
|
|
|
125
131
|
|
|
126
132
|
export const memorySubmitArgs = {
|
|
127
133
|
ids: z.array(z.string().min(1)).min(1).describe("Outbox draft ids to submit."),
|
|
128
|
-
|
|
134
|
+
branch: z
|
|
129
135
|
.string()
|
|
130
136
|
.optional()
|
|
131
|
-
.describe(
|
|
137
|
+
.describe(
|
|
138
|
+
"Create this branch and submit onto it. If omitted, submit stays on the current branch — branches are never auto-created. Only pass this when the user explicitly approved the full chain (branch + push + PR).",
|
|
139
|
+
),
|
|
132
140
|
base: z
|
|
133
141
|
.string()
|
|
134
142
|
.optional()
|
|
135
|
-
.describe("PR base branch override. Default: the branch the submit
|
|
143
|
+
.describe("PR base branch override. Default: the branch the submit ran on."),
|
|
136
144
|
};
|
|
137
145
|
export type MemorySubmitArgs = z.infer<z.ZodObject<typeof memorySubmitArgs>>;
|
|
138
146
|
|
|
@@ -237,7 +245,7 @@ export async function addMemory(
|
|
|
237
245
|
const fm = buildFrontmatter(s, {
|
|
238
246
|
type: args.type ?? "fact",
|
|
239
247
|
tags: args.tags ?? [],
|
|
240
|
-
source: "tool",
|
|
248
|
+
source: args.source ?? "tool",
|
|
241
249
|
});
|
|
242
250
|
const { filePath } = writeMemoryFile(fm, redacted);
|
|
243
251
|
const mf = readMemoryFile(filePath);
|
|
@@ -359,14 +367,15 @@ export async function statusMemories(): Promise<ToolResult> {
|
|
|
359
367
|
}
|
|
360
368
|
|
|
361
369
|
export async function submitMemoriesOp(args: MemorySubmitArgs): Promise<ToolResult> {
|
|
362
|
-
const r = submitMemories(args.ids, {
|
|
370
|
+
const r = submitMemories(args.ids, { branch: args.branch, base: args.base });
|
|
363
371
|
const lines: string[] = [];
|
|
364
372
|
for (const s of r.submitted) lines.push(`submitted ${s.id} [${s.reviewState}]`);
|
|
365
373
|
for (const id of r.skippedIdentical) lines.push(`already on branch: ${id} (outbox copy removed)`);
|
|
366
|
-
lines.push(r.committed ? `committed on ${r.branch}.` : `nothing new to commit on ${r.branch}.`);
|
|
367
|
-
lines.push(`Next
|
|
374
|
+
lines.push(r.committed ? `committed on ${r.branch} (you are still on this branch).` : `nothing new to commit on ${r.branch}.`);
|
|
375
|
+
lines.push(`Next — ask the user: "want me to create a branch + push + open the PR, or will you handle it yourself?"`);
|
|
376
|
+
lines.push(`Never create branches, push, or open PRs without their explicit approval. If they handle it themselves, hand them these:`);
|
|
368
377
|
lines.push(` ${r.pushCommand}`);
|
|
369
|
-
lines.push(` ${
|
|
378
|
+
for (const l of r.prCommand.split("\n")) lines.push(` ${l}`);
|
|
370
379
|
return { title: `memory: submitted ${r.submitted.length}`, output: lines.join("\n") };
|
|
371
380
|
}
|
|
372
381
|
|