@memfork/cli 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -25,7 +25,7 @@ const { version } = createRequire(import.meta.url)("../package.json");
25
25
  import { cmdInit } from "./commands/init.js";
26
26
  import { cmdDoctor, cmdDoctorEnv } from "./commands/doctor.js";
27
27
  import { cmdInstall } from "./commands/install.js";
28
- import { cmdStatus, cmdLog, cmdRecall, cmdCommit, cmdMerge, cmdProposals, cmdUi, cmdShow, cmdDiff, cmdDelegates, cmdGrant, cmdGrantMemwal, cmdRevoke, cmdBranch, cmdCheckout, } from "./commands/ops.js";
28
+ import { cmdStatus, cmdLog, cmdRecall, cmdCommit, cmdMerge, cmdProposals, cmdResolverCreate, cmdPrComment, cmdUi, cmdShow, cmdDiff, cmdDelegates, cmdGrant, cmdGrantMemwal, cmdRevoke, cmdBranch, cmdCheckout, } from "./commands/ops.js";
29
29
  import { cmdJoin } from "./commands/join.js";
30
30
  const program = new Command();
31
31
  program
@@ -90,13 +90,29 @@ program
90
90
  program
91
91
  .command("merge <from> <into>")
92
92
  .description("merge memory from one branch into another")
93
- .option("-r, --resolver <id>", "ResolverRef object ID (default: LastWriteWins, or MEMFORK_RESOLVER_ID env var)")
93
+ .option("-r, --resolver <id>", "ResolverRef object ID enables governed jury merge (or set MEMFORK_RESOLVER_ID)")
94
+ .option("--lww", "force LastWriteWins even when MEMFORK_RESOLVER_ID is set")
94
95
  .option("--ttl <ms>", "proposal TTL in milliseconds", parseInt, 86_400_000)
95
96
  .action(wrap((from, into, opts) => cmdMerge(from, into, opts)));
96
97
  program
97
98
  .command("proposals")
98
99
  .description("list open merge proposals")
99
100
  .action(wrap(cmdProposals));
101
+ const resolverCmd = new Command("resolver").description("manage resolver objects");
102
+ resolverCmd
103
+ .command("create")
104
+ .description("create a jury resolver (k-of-n)")
105
+ .requiredOption("--jury <addresses>", "comma-separated judge Sui addresses")
106
+ .option("-k, --k <n>", "approval threshold (default: majority)", parseInt)
107
+ .action(wrap((opts) => cmdResolverCreate({ jury: opts.jury, k: opts.k ?? 2 })));
108
+ program.addCommand(resolverCmd);
109
+ program
110
+ .command("pr-comment")
111
+ .description("post a MemForks decision summary to a GitHub PR")
112
+ .requiredOption("--pr <number>", "PR number", parseInt)
113
+ .option("--repo <owner/repo>", "GitHub repo (default: inferred from git remote)")
114
+ .option("--branch <name>", "branch to recall decided fact from (default: into_branch of last merge)")
115
+ .action(wrap((opts) => cmdPrComment(opts)));
100
116
  program
101
117
  .command("ui")
102
118
  .description("open the MemForks DAG visualizer")
@@ -21,8 +21,8 @@ import path from "node:path";
21
21
  import { fileURLToPath } from "node:url";
22
22
  import { readCredentials, readProjectConfig, MEMWAL_CONSTANTS } from "../config.js";
23
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
- // dist/commands/install.js → packages/clipackages → repo root → plugins/
25
- const PLUGIN_ROOT = path.resolve(__dirname, "..", "..", "..", "..", "plugins");
24
+ // dist/commands/install.js → dist/ → package root → plugins/
25
+ const PLUGIN_ROOT = path.resolve(__dirname, "..", "..", "plugins");
26
26
  function ok(s) { return chalk.green("✓") + " " + s; }
27
27
  function warn(s) { return chalk.yellow("⚠") + " " + s; }
28
28
  function tip(s) { return chalk.cyan("→") + " " + s; }
@@ -22,8 +22,18 @@ export declare function cmdCommit(opts: {
22
22
  export declare function cmdMerge(from: string, into: string, opts: {
23
23
  resolver?: string;
24
24
  ttl?: number;
25
+ lww?: boolean;
25
26
  }): Promise<void>;
26
27
  export declare function cmdProposals(): Promise<void>;
28
+ export declare function cmdResolverCreate(opts: {
29
+ jury: string;
30
+ k: number;
31
+ }): Promise<void>;
32
+ export declare function cmdPrComment(opts: {
33
+ pr: number;
34
+ repo?: string;
35
+ branch?: string;
36
+ }): Promise<void>;
27
37
  export declare function cmdUi(opts?: {
28
38
  share?: boolean;
29
39
  port?: number;
@@ -129,11 +129,12 @@ export async function cmdMerge(from, into, opts) {
129
129
  const cfg = resolveConfig();
130
130
  const clientCfg = {
131
131
  ...toClientConfig(cfg),
132
- // --resolver flag overrides MEMFORK_RESOLVER_ID env var for this call
133
- ...(opts.resolver ? { defaultResolverId: opts.resolver } : {}),
132
+ // --resolver flag overrides MEMFORK_RESOLVER_ID env var for this call.
133
+ // --lww forces the LWW path even when MEMFORK_RESOLVER_ID is set.
134
+ ...(opts.lww ? { defaultResolverId: undefined } : opts.resolver ? { defaultResolverId: opts.resolver } : {}),
134
135
  };
135
136
  const client = await MemForksClient.connect(clientCfg);
136
- const governed = !!(opts.resolver ?? process.env["MEMFORK_RESOLVER_ID"]);
137
+ const governed = !opts.lww && !!(opts.resolver ?? process.env["MEMFORK_RESOLVER_ID"]);
137
138
  process.stdout.write(chalk.dim(`Merging ${chalk.green(from)} → ${chalk.green(into)}`) +
138
139
  chalk.dim(governed ? " (governed — awaiting resolver…)" : " (LWW — self-finalizing…)") +
139
140
  " ");
@@ -158,20 +159,192 @@ export async function cmdMerge(from, into, opts) {
158
159
  }
159
160
  // ─── proposals ────────────────────────────────────────────────────────────────
160
161
  export async function cmdProposals() {
161
- const { client, cfg } = await getClient();
162
- // Fetch open MergeProposed events from the indexer.
163
- // For now this polls recent events (the indexer / app/ maintains the live view).
162
+ const { cfg } = await getClient();
163
+ const { SuiJsonRpcClient, JsonRpcHTTPTransport, getJsonRpcFullnodeUrl } = await import("@mysten/sui/jsonRpc");
164
+ const rpcUrl = cfg.rpcUrl ?? getJsonRpcFullnodeUrl(cfg.network ?? "testnet");
165
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
166
+ const sui = new SuiJsonRpcClient({ transport: new JsonRpcHTTPTransport({ url: rpcUrl }), network: cfg.network ?? "testnet" });
164
167
  console.log("");
165
- console.log(chalk.bold("Open merge proposals"));
166
- console.log(chalk.dim(" (live status in the visualizer: memfork ui)"));
168
+ console.log(chalk.bold("Merge proposals") + chalk.dim(" tree: " + cfg.treeId.slice(0, 12) + "…"));
167
169
  console.log("");
168
- console.log(chalk.dim(" Tree: " + cfg.treeId));
170
+ const PROPOSAL_STATUS = { PENDING: 0, FINALIZED: 1, ABORTED: 2 };
171
+ let events;
172
+ try {
173
+ const result = await sui.queryEvents({
174
+ query: { MoveEventType: `${cfg.packageId}::resolver::MergeProposed` },
175
+ limit: 20,
176
+ order: "descending",
177
+ });
178
+ events = result.data.filter((e) => e.parsedJson["tree_id"] === cfg.treeId);
179
+ }
180
+ catch {
181
+ console.log(chalk.dim(" Could not query Sui events."));
182
+ console.log(chalk.cyan(" →") + " Run " + chalk.bold("memfork ui") + " for the live view.");
183
+ console.log("");
184
+ return;
185
+ }
186
+ if (events.length === 0) {
187
+ console.log(chalk.dim(" No proposals found for this tree."));
188
+ console.log("");
189
+ return;
190
+ }
191
+ for (const ev of events.slice(0, 10)) {
192
+ const p = ev.parsedJson;
193
+ const id = String(p["proposal_id"] ?? "");
194
+ // Fetch live status from the proposal object.
195
+ let statusLabel = chalk.yellow("pending");
196
+ try {
197
+ const obj = await sui.getObject({ id, options: { showContent: true } });
198
+ if (obj.data?.content && obj.data.content.dataType === "moveObject") {
199
+ const status = Number(obj.data.content.fields["status"]);
200
+ if (status === PROPOSAL_STATUS.FINALIZED)
201
+ statusLabel = chalk.green("finalized");
202
+ else if (status === PROPOSAL_STATUS.ABORTED)
203
+ statusLabel = chalk.red("aborted");
204
+ }
205
+ }
206
+ catch { /* proposal may be consumed */ }
207
+ console.log(` ${statusLabel} ` +
208
+ chalk.green(String(p["from_branch"]) + " → " + String(p["into_branch"])) + " " +
209
+ chalk.dim(id.slice(0, 12) + "…"));
210
+ }
169
211
  console.log("");
170
- console.log(chalk.dim(" Polling Sui events…"));
171
- // TODO: drive through MemForksIndexer once it's wired into the CLI.
172
- // For the hackathon: redirect to the visualizer.
212
+ console.log(chalk.dim(" Full detail: memfork ui → Merges view"));
173
213
  console.log("");
174
- console.log(chalk.cyan(" →") + " Run " + chalk.bold("memfork ui") + " for the full live proposal view.");
214
+ }
215
+ // ─── resolver create ──────────────────────────────────────────────────────────
216
+ export async function cmdResolverCreate(opts) {
217
+ const { client } = await getClient();
218
+ const { resolvers } = await import("@memfork/core");
219
+ const juryAddrs = opts.jury.split(",").map((a) => a.trim()).filter(Boolean);
220
+ if (juryAddrs.length === 0) {
221
+ console.error(chalk.red("Pass at least one judge address via --jury <addr1,addr2,...>"));
222
+ process.exit(1);
223
+ }
224
+ const k = opts.k ?? Math.ceil(juryAddrs.length / 2 + 0.5);
225
+ process.stdout.write(chalk.dim(`Creating jury resolver (${k}-of-${juryAddrs.length}) … `));
226
+ const def = resolvers.jury(juryAddrs, k, juryAddrs.length);
227
+ const { digest, resolverId } = await client.createResolver(def);
228
+ console.log(chalk.green("done"));
229
+ console.log("");
230
+ console.log(chalk.dim(" ResolverRef: ") + chalk.cyan(resolverId));
231
+ console.log(chalk.dim(" tx: ") + chalk.dim(digest));
232
+ console.log("");
233
+ console.log(chalk.bold(" Save this to your environment:"));
234
+ console.log(" " + chalk.cyan(`export MEMFORK_RESOLVER_ID=${resolverId}`));
235
+ console.log("");
236
+ console.log(chalk.dim(" Or add resolverId to .memfork/config.json for project-wide use."));
237
+ console.log("");
238
+ }
239
+ // ─── pr-comment ───────────────────────────────────────────────────────────────
240
+ export async function cmdPrComment(opts) {
241
+ const { client, cfg } = await getClient();
242
+ const { SuiJsonRpcClient, JsonRpcHTTPTransport, getJsonRpcFullnodeUrl } = await import("@mysten/sui/jsonRpc");
243
+ const rpcUrl = cfg.rpcUrl ?? getJsonRpcFullnodeUrl(cfg.network ?? "testnet");
244
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
245
+ const sui = new SuiJsonRpcClient({ transport: new JsonRpcHTTPTransport({ url: rpcUrl }), network: cfg.network ?? "testnet" });
246
+ console.log("");
247
+ console.log(chalk.dim("Fetching latest merge anchor…"));
248
+ // Find the most recent MergeFinalized event for this tree.
249
+ let anchorId = "";
250
+ let proposalId = "";
251
+ let suiTx = "";
252
+ let walrusBlob = "";
253
+ let fromBranch = "";
254
+ let intoBranch = "";
255
+ try {
256
+ const result = await sui.queryEvents({
257
+ query: { MoveEventType: `${cfg.packageId}::resolver::MergeFinalized` },
258
+ limit: 10,
259
+ order: "descending",
260
+ });
261
+ const ev = result.data
262
+ .find((e) => e.parsedJson["tree_id"] === cfg.treeId);
263
+ if (!ev) {
264
+ console.error(chalk.red("No finalized merges found for this tree. Run `memfork merge` first."));
265
+ process.exit(1);
266
+ }
267
+ anchorId = String(ev.parsedJson["merge_commit_id"] ?? "");
268
+ walrusBlob = String(ev.parsedJson["resolved_blob_id"] ?? "");
269
+ suiTx = ev.id.txDigest;
270
+ proposalId = String(ev.parsedJson["proposal_id"] ?? "");
271
+ }
272
+ catch (e) {
273
+ console.error(chalk.red("Failed to query Sui: " + String(e)));
274
+ process.exit(1);
275
+ }
276
+ // Fetch proposal for branch names and attestation count.
277
+ let voteCount = "?";
278
+ let threshold = "?";
279
+ try {
280
+ const obj = await sui.getObject({ id: proposalId, options: { showContent: true } });
281
+ if (obj.data?.content && obj.data.content.dataType === "moveObject") {
282
+ const fields = obj.data.content.fields;
283
+ fromBranch = String(fields["from_branch"] ?? "");
284
+ intoBranch = String(fields["into_branch"] ?? "");
285
+ const attests = fields["attestations"];
286
+ voteCount = String(attests?.length ?? "?");
287
+ }
288
+ }
289
+ catch { /* non-critical */ }
290
+ // Get the decided fact from the into_branch via recall.
291
+ const targetBranch = opts.branch ?? intoBranch ?? currentGitBranch();
292
+ let decision = `Use ${fromBranch || "winning branch"} approach.`;
293
+ try {
294
+ const results = await client.recall("decided", { branch: targetBranch, limit: 1 });
295
+ if (results.length > 0) {
296
+ decision = results[0].text.replace(/^decided:\s*/i, "").split(".")[0] + ".";
297
+ }
298
+ }
299
+ catch { /* fallback to default */ }
300
+ // Find rejected paths via recall on the into_branch.
301
+ let rejectedPath = "";
302
+ try {
303
+ const rejected = await client.recall("rejected-path", { branch: targetBranch, limit: 1 });
304
+ if (rejected.length > 0) {
305
+ const match = rejected[0].text.match(/(\S+)\s+was not merged/);
306
+ if (match)
307
+ rejectedPath = match[1];
308
+ }
309
+ }
310
+ catch { /* non-critical */ }
311
+ const shortAnchor = anchorId.replace(/^0x/, "").slice(0, 7);
312
+ const shortTx = suiTx.replace(/^0x/, "").slice(0, 8);
313
+ const shortBlob = walrusBlob.slice(0, 12);
314
+ const vizUrl = `memforks.dev/${cfg.treeId.replace(/^0x/, "").slice(0, 8)}#${shortAnchor}`;
315
+ const body = [
316
+ `🔗 **MemForks decision attached**`,
317
+ ``,
318
+ `**Decision:**`,
319
+ decision,
320
+ ``,
321
+ `**How it was decided:**`,
322
+ `Jury vote, ${voteCount} of ${threshold} — enforced on Sui`,
323
+ ``,
324
+ `**Merge:** \`${shortAnchor}\``,
325
+ ``,
326
+ `**Sui:** \`${shortTx}…\``,
327
+ ``,
328
+ `**Walrus:** \`${shortBlob}…\``,
329
+ rejectedPath
330
+ ? [``, `**Rejected path:**`, `\`${rejectedPath}@latest\` remains queryable`].join("\n")
331
+ : "",
332
+ ``,
333
+ `**Full audit trail:** ${vizUrl}`,
334
+ ].filter((l) => l !== undefined).join("\n");
335
+ // Post via gh CLI.
336
+ const repoFlag = opts.repo ? `--repo ${opts.repo}` : "";
337
+ try {
338
+ execSync(`gh pr comment ${opts.pr} ${repoFlag} --body ${JSON.stringify(body)}`, {
339
+ stdio: ["ignore", "inherit", "inherit"],
340
+ });
341
+ console.log(chalk.green("✓") + " Comment posted to PR #" + opts.pr);
342
+ }
343
+ catch {
344
+ console.log(chalk.yellow("gh CLI not available or auth required. Copy this comment:"));
345
+ console.log("");
346
+ console.log(body);
347
+ }
175
348
  console.log("");
176
349
  }
177
350
  // ─── ui ───────────────────────────────────────────────────────────────────────
@@ -441,15 +614,21 @@ function extractFacts(response) {
441
614
  }
442
615
  // ─── Helpers ──────────────────────────────────────────────────────────────────
443
616
  function findAppDir() {
444
- // dist/commands/ops.js → packages/cli → packages → repo root → apps/visualizer
617
+ // Resolution order:
618
+ // 1. packages/cli/ui/ — bundled at publish time (npm install path)
619
+ // 2. apps/visualizer/ — monorepo dev path (two depths to handle symlinks)
445
620
  const candidates = [
446
- new URL("../../../../apps/visualizer", import.meta.url).pathname,
447
- new URL("../../../../../apps/visualizer", import.meta.url).pathname,
621
+ new URL("../../ui", import.meta.url).pathname, // dist/commands/ → cli root → ui/
622
+ new URL("../../../../apps/visualizer", import.meta.url).pathname, // monorepo: packages/cli
623
+ new URL("../../../../../apps/visualizer", import.meta.url).pathname, // monorepo: alternate depth
448
624
  ];
449
625
  for (const c of candidates) {
450
626
  try {
451
- if (fs.existsSync(c + "/package.json"))
627
+ // Bundled path: presence of index.html is the signal (no package.json shipped).
628
+ // Monorepo path: package.json marks the source root.
629
+ if (fs.existsSync(path.join(c, "index.html")) || fs.existsSync(path.join(c, "package.json"))) {
452
630
  return c;
631
+ }
453
632
  }
454
633
  catch {
455
634
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memfork/cli",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "MemForks CLI — init, commit, recall, merge, install plugins",
5
5
  "repository": {
6
6
  "type": "git",
@@ -21,7 +21,7 @@
21
21
  "./config": "./dist/config.js"
22
22
  },
23
23
  "scripts": {
24
- "build": "tsc",
24
+ "build": "node scripts/copy-plugins.mjs && tsc && node scripts/bundle-ui.mjs",
25
25
  "dev": "tsc --watch",
26
26
  "start": "node dist/index.js"
27
27
  },
@@ -29,11 +29,13 @@
29
29
  "access": "public"
30
30
  },
31
31
  "files": [
32
- "dist"
32
+ "dist",
33
+ "plugins",
34
+ "ui"
33
35
  ],
34
36
  "dependencies": {
35
37
  "@inquirer/prompts": "^8.5.2",
36
- "@memfork/core": "^0.1.4",
38
+ "@memfork/core": "^0.1.9",
37
39
  "chalk": "^5.6.2",
38
40
  "commander": "^15.0.0"
39
41
  },
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "memforks",
3
+ "version": "0.2.0",
4
+ "description": "On-chain, branch-aware memory DAG for Codex. MemForks anchors decisions to Sui and proposes cross-branch merges — memory storage is handled by the MemWal MCP server.",
5
+ "author": {
6
+ "name": "MemForks",
7
+ "email": "team@memforks.dev"
8
+ },
9
+ "homepage": "https://github.com/memforks-dev/memforks",
10
+ "repository": "https://github.com/memforks-dev/memforks",
11
+ "license": "Apache-2.0",
12
+ "keywords": [
13
+ "memory",
14
+ "sui",
15
+ "blockchain",
16
+ "provenance",
17
+ "branching",
18
+ "codex",
19
+ "agent-memory",
20
+ "memwal"
21
+ ],
22
+ "skills": "./skills/",
23
+ "interface": {
24
+ "displayName": "MemForks",
25
+ "shortDescription": "On-chain, branch-aware memory DAG",
26
+ "longDescription": "MemForks gives Codex a tamper-proof, branch-synced memory DAG anchored on Sui. Memory recall and storage are handled natively by the MemWal MCP server. MemForks adds the version-control layer: immutable on-chain commits, Git branch-scoped history, and conflict-free cross-branch merges via an on-chain resolver.",
27
+ "developerName": "MemForks",
28
+ "category": "Productivity",
29
+ "capabilities": ["Read", "Write"],
30
+ "websiteURL": "https://github.com/memforks-dev/memforks",
31
+ "privacyPolicyURL": "https://github.com/memforks-dev/memforks",
32
+ "termsOfServiceURL": "https://github.com/memforks-dev/memforks/blob/main/LICENSE",
33
+ "defaultPrompt": [
34
+ "Recall any relevant context for this branch",
35
+ "Show my MemForks memory status",
36
+ "Commit the decisions we made today"
37
+ ],
38
+ "brandColor": "#1f9d72",
39
+ "screenshots": []
40
+ }
41
+ }
@@ -0,0 +1,81 @@
1
+ # MemForks — Codex Plugin
2
+
3
+ On-chain, branch-aware memory DAG for Codex.
4
+
5
+ **Memory storage** is handled by the MemWal MCP server — the agent calls
6
+ `memwal_recall` and `memwal_remember` natively as tool calls.
7
+
8
+ **On-chain versioning** is handled by the `memfork` CLI — decisions get
9
+ cryptographically anchored to Sui with branch context and a full commit DAG.
10
+
11
+ ## Setup (one time, per machine)
12
+
13
+ ```bash
14
+ npm install -g @memfork/cli
15
+
16
+ # Recommended — zero copy-paste, ~30 seconds on testnet:
17
+ memfork init --quick
18
+
19
+ # Or manual if you already have a Sui key + MemWal account:
20
+ memfork init
21
+ ```
22
+
23
+ ## Install the plugin
24
+
25
+ ```bash
26
+ memfork install codex
27
+ ```
28
+
29
+ This does two things:
30
+
31
+ 1. **Writes `~/.codex/config.toml`** — adds a `[mcp_servers.memwal]` entry using
32
+ the delegate key provisioned by `memfork init`. No browser login needed.
33
+
34
+ 2. **Copies `.codex-plugin/`** — installs the plugin skills into the current project.
35
+
36
+ Then register with Codex:
37
+
38
+ ```bash
39
+ codex plugin add .codex-plugin
40
+ ```
41
+
42
+ ## Verify
43
+
44
+ ```bash
45
+ memfork doctor
46
+ ```
47
+
48
+ ## What the agent can do
49
+
50
+ | Tool / Command | What it does |
51
+ |----------------|-------------|
52
+ | `memwal_recall(query, namespace)` | Semantic search over branch memory (MCP tool) |
53
+ | `memwal_remember(text, namespace)` | Save a fact to branch memory (MCP tool) |
54
+ | `memwal_analyze(text)` | Extract and save multiple facts at once (MCP tool) |
55
+ | `memfork commit --facts …` | Anchor a decision on-chain with full provenance |
56
+ | `memfork merge <src> <dst>` | Propose a cross-branch memory merge |
57
+ | `memfork status / log / proposals` | Inspect the on-chain DAG |
58
+
59
+ Memory is namespaced by Git branch — `namespace="branch/<branch-name>"`.
60
+
61
+ ## What gets installed
62
+
63
+ ```
64
+ ~/.codex/config.toml ← MemWal MCP server entry (auto-configured)
65
+ .codex-plugin/
66
+ plugin.json ← plugin metadata
67
+ skills/
68
+ memory-recall/ ← when/how to use memwal_recall
69
+ memforks-status/ ← when/how to use memfork commit/merge/status
70
+ ```
71
+
72
+ No shell hooks. The MCP server is the transport.
73
+
74
+ ## Override for CI / headless use
75
+
76
+ ```bash
77
+ MEMFORK_TREE_ID=0x…
78
+ MEMFORK_PRIVATE_KEY=suiprivkey1…
79
+ MEMFORK_MEMWAL_ACCOUNT=0x…
80
+ MEMFORK_MEMWAL_KEY=<hex>
81
+ ```
@@ -0,0 +1,86 @@
1
+ ---
2
+ name: memforks-status
3
+ description: >-
4
+ Show MemForks on-chain status: branch DAG, open merge proposals, recent commits.
5
+ Use when the user asks about memory status, proposals, or the commit log.
6
+ Also use when committing decisions or proposing a merge.
7
+ ---
8
+
9
+ # MemForks On-Chain Operations
10
+
11
+ MemForks is the version-control layer on top of MemWal. Use the `memfork` CLI for
12
+ DAG operations — not for routine recall/remember (that's the MCP server's job).
13
+
14
+ ## Check status
15
+
16
+ ```bash
17
+ memfork status # current tree, branch, signer, head commit
18
+ memfork log --branch <branch> # recent on-chain commits
19
+ memfork proposals # open merge proposals
20
+ memfork ui # open the DAG visualizer
21
+ ```
22
+
23
+ ## Commit a decision on-chain
24
+
25
+ Use this after significant architectural decisions — not for routine facts.
26
+ (Routine facts go through `memwal_remember` via MCP.)
27
+
28
+ ```bash
29
+ memfork commit \
30
+ --branch $(git rev-parse --abbrev-ref HEAD) \
31
+ --message "decided: <one-line summary>" \
32
+ --facts "<fact 1>" "<fact 2>"
33
+ ```
34
+
35
+ ## Merge branches
36
+
37
+ When two branches need to reconcile their memory:
38
+
39
+ ```bash
40
+ # Zero-config — LastWriteWins, self-finalizes immediately
41
+ memfork merge <from-branch> <into-branch>
42
+
43
+ # Governed — jury / LLM resolver (requires MEMFORK_RESOLVER_ID env var or --resolver flag)
44
+ memfork merge <from-branch> <into-branch> --resolver <resolver-id>
45
+ ```
46
+
47
+ With no `--resolver` flag and no `MEMFORK_RESOLVER_ID` set, the merge uses
48
+ LastWriteWins and finalizes immediately — no resolver service required.
49
+
50
+ When a resolver is configured, the command waits for the on-chain resolver service
51
+ to collect attestations and finalize before returning.
52
+
53
+ ---
54
+
55
+ ## Suggesting a merge — proactive but not autonomous
56
+
57
+ You may **suggest** a merge when you notice the current branch has accumulated
58
+ durable facts not yet on `main`. Phrase it as an offer:
59
+
60
+ > "This branch has several facts that aren't on main yet — want me to merge them?
61
+ > I'll run `memfork merge <branch> main`."
62
+
63
+ **Never run `memfork merge` without the user explicitly confirming.** Merging
64
+ changes shared team memory and creates an on-chain anchor — it is a governance
65
+ act, not a routine commit.
66
+
67
+ Suggest a merge when:
68
+ - The user says "we're done with this branch" or "I'm about to open a PR"
69
+ - You've committed 3+ significant facts this session and the user hasn't merged
70
+ - The user asks "what should I do next?" at the end of a long session
71
+
72
+ Once the user confirms, run:
73
+
74
+ ```bash
75
+ memfork merge <current-branch> main
76
+ ```
77
+
78
+ ---
79
+
80
+ ## When to use this skill
81
+
82
+ - User asks "what's the status of my memory?"
83
+ - User asks "are there any pending merges?"
84
+ - User says "commit what we decided today"
85
+ - User confirms they want to merge memory from one branch into another
86
+ - User wants to open the DAG visualizer
@@ -0,0 +1,102 @@
1
+ ---
2
+ name: memory-fork
3
+ description: >-
4
+ Fork agent memory into parallel branches to explore competing hypotheses.
5
+ Use when the user asks to explore multiple approaches, compare options, or
6
+ says phrases like "explore both", "try two paths", "compare A vs B".
7
+ ---
8
+
9
+ # Memory Fork
10
+
11
+ When you detect a multi-hypothesis prompt — the user wants to explore two or
12
+ more competing approaches — fork the MemForks memory tree so each path stays
13
+ isolated. Never collapse competing ideas into a single stream.
14
+
15
+ ## When to trigger
16
+
17
+ Trigger this skill when the user prompt contains signals like:
18
+ - "explore both paths" / "try both" / "compare X and Y"
19
+ - "what if we did X instead of Y" (two real alternatives)
20
+ - "should we do A or B?" (genuine decision fork, not a rhetorical question)
21
+ - Any request to investigate multiple competing solutions side-by-side
22
+
23
+ ## Procedure
24
+
25
+ ### 1. Announce the fork
26
+
27
+ Print exactly:
28
+
29
+ ```
30
+ [memforks] Multi-hypothesis detected.
31
+ [memforks] Forking agent memory from <current-branch>@HEAD
32
+ ```
33
+
34
+ Then list the branches you will create, one per hypothesis:
35
+
36
+ ```
37
+ ├── dev/<short-hypothesis-a>
38
+ └── dev/<short-hypothesis-b>
39
+ ```
40
+
41
+ Use kebab-case branch names derived from the hypothesis (e.g. `dev/redis-first`,
42
+ `dev/bcrypt-cost`, `dev/approach-a`).
43
+
44
+ ### 2. Create the branches
45
+
46
+ For each hypothesis, run:
47
+
48
+ ```bash
49
+ memfork branch dev/<hypothesis> --from <current-branch>
50
+ ```
51
+
52
+ ### 3. Investigate hypothesis A
53
+
54
+ Switch to the first branch and investigate:
55
+
56
+ ```bash
57
+ memfork checkout dev/<hypothesis-a>
58
+ ```
59
+
60
+ Work through the hypothesis. As you discover facts, commit them:
61
+
62
+ ```bash
63
+ memfork commit \
64
+ --branch dev/<hypothesis-a> \
65
+ --message "<what you found>" \
66
+ --facts "<concrete measurable fact>" "<another fact>"
67
+ ```
68
+
69
+ Commit at each meaningful step — hypothesis statement, baseline measurement,
70
+ result. Three commits is normal; more is fine.
71
+
72
+ ### 4. Investigate hypothesis B
73
+
74
+ ```bash
75
+ memfork checkout dev/<hypothesis-b>
76
+ ```
77
+
78
+ Repeat the same commit cadence.
79
+
80
+ ### 5. Summarise
81
+
82
+ After both branches have evidence, summarise findings side by side and tell
83
+ the user which branch has stronger evidence. Do NOT merge — merging is a
84
+ human governance act (`memfork merge`).
85
+
86
+ ## Output format for each commit
87
+
88
+ Use this fact structure for clarity and later recall:
89
+
90
+ ```
91
+ hypothesis: <one-sentence statement of what this branch is testing>
92
+ fact: <measured or researched datum — numbers are better than adjectives>
93
+ result: <conclusion or outcome of the investigation>
94
+ ```
95
+
96
+ ## Rules
97
+
98
+ - Never commit to `main` or the parent branch during a fork investigation.
99
+ - Never type `memfork merge` — that is the operator's call.
100
+ - If the user asks "which won?", answer from memory; do not merge.
101
+ - Keep branch names short and descriptive (`dev/redis-first` not `dev/add-redis-caching-to-auth-flow`).
102
+ - If `memfork branch` fails because the branch already exists, use `memfork checkout` and continue.