@memfork/cli 0.1.20 → 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 +18 -2
- package/dist/commands/ops.d.ts +10 -0
- package/dist/commands/ops.js +196 -17
- package/package.json +5 -4
- package/plugins/codex/.codex-plugin/plugin.json +6 -9
- package/plugins/codex/skills/memory-fork/SKILL.md +102 -0
- package/plugins/cursor/rules/memforks.mdc +34 -0
- package/ui/assets/index-kBDA9A8R.js +67 -0
- package/ui/assets/index-kBDA9A8R.js.map +1 -0
- package/ui/assets/index-paYDwRGH.css +1 -0
- package/ui/index.html +14 -0
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
|
|
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")
|
package/dist/commands/ops.d.ts
CHANGED
|
@@ -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;
|
package/dist/commands/ops.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
162
|
-
|
|
163
|
-
|
|
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("
|
|
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
|
-
|
|
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("
|
|
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
|
-
|
|
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
|
-
//
|
|
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("
|
|
447
|
-
new URL("
|
|
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
|
-
|
|
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.
|
|
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
|
},
|
|
@@ -30,11 +30,12 @@
|
|
|
30
30
|
},
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
|
-
"plugins"
|
|
33
|
+
"plugins",
|
|
34
|
+
"ui"
|
|
34
35
|
],
|
|
35
36
|
"dependencies": {
|
|
36
37
|
"@inquirer/prompts": "^8.5.2",
|
|
37
|
-
"@memfork/core": "^0.1.
|
|
38
|
+
"@memfork/core": "^0.1.9",
|
|
38
39
|
"chalk": "^5.6.2",
|
|
39
40
|
"commander": "^15.0.0"
|
|
40
41
|
},
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
"name": "MemForks",
|
|
7
7
|
"email": "team@memforks.dev"
|
|
8
8
|
},
|
|
9
|
-
"homepage": "https://github.com/memforks/memforks",
|
|
10
|
-
"repository": "https://github.com/memforks/memforks",
|
|
9
|
+
"homepage": "https://github.com/memforks-dev/memforks",
|
|
10
|
+
"repository": "https://github.com/memforks-dev/memforks",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"memory",
|
|
@@ -26,13 +26,10 @@
|
|
|
26
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
27
|
"developerName": "MemForks",
|
|
28
28
|
"category": "Productivity",
|
|
29
|
-
"capabilities": [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"websiteURL": "https://github.com/memforks/memforks",
|
|
34
|
-
"privacyPolicyURL": "https://github.com/memforks/memforks",
|
|
35
|
-
"termsOfServiceURL": "https://github.com/memforks/memforks/blob/main/LICENSE",
|
|
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",
|
|
36
33
|
"defaultPrompt": [
|
|
37
34
|
"Recall any relevant context for this branch",
|
|
38
35
|
"Show my MemForks memory status",
|
|
@@ -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.
|
|
@@ -72,6 +72,40 @@ Use it for decisions that matter for audit trail, not routine facts.
|
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
+
## Forking — exploring competing approaches
|
|
76
|
+
|
|
77
|
+
When the user asks you to explore multiple competing approaches (e.g. "try both",
|
|
78
|
+
"compare X vs Y", "explore both paths"), fork the memory tree so each hypothesis
|
|
79
|
+
stays isolated. Never collapse competing ideas into a single branch.
|
|
80
|
+
|
|
81
|
+
**Step 1 — announce and create branches:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
memfork branch dev/<hypothesis-a> --from $(git rev-parse --abbrev-ref HEAD)
|
|
85
|
+
memfork branch dev/<hypothesis-b> --from $(git rev-parse --abbrev-ref HEAD)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Use short kebab-case names (`dev/redis-first`, `dev/bcrypt-cost`).
|
|
89
|
+
|
|
90
|
+
**Step 2 — investigate each path and commit evidence:**
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
memfork commit \
|
|
94
|
+
--branch dev/<hypothesis-a> \
|
|
95
|
+
--message "<what you found>" \
|
|
96
|
+
--facts "<measured fact>" "<result>"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Commit at each meaningful step — hypothesis, measurement, result.
|
|
100
|
+
|
|
101
|
+
**Step 3 — summarise, do not merge.**
|
|
102
|
+
|
|
103
|
+
Report findings side by side and recommend which branch has stronger evidence.
|
|
104
|
+
Never run `memfork merge` as part of a fork investigation — merging is a
|
|
105
|
+
human governance act.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
75
109
|
## Suggesting a merge — proactive but not autonomous
|
|
76
110
|
|
|
77
111
|
You may **suggest** a merge when you notice the current branch has accumulated
|