@memfork/cli 0.1.26 → 0.1.27
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/branch.d.ts +56 -0
- package/dist/branch.js +74 -0
- package/dist/commands/ops.js +20 -15
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/plugins/codex/skills/memforks-status/SKILL.md +2 -1
- package/plugins/cursor/rules/memforks.mdc +11 -6
package/dist/branch.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch resolution for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Memory in MemForks is namespaced per branch. Every CLI command needs to
|
|
5
|
+
* answer the same question — "which branch am I operating on?" — and they must
|
|
6
|
+
* all answer it the same way. This module is the single source of that answer.
|
|
7
|
+
*
|
|
8
|
+
* Precedence (highest wins):
|
|
9
|
+
* 1. explicit — the `--branch` / `--from` flag passed on the command line
|
|
10
|
+
* 2. env — MEMFORK_BRANCH (CI / headless override)
|
|
11
|
+
* 3. git — the current git branch (the dynamic default for humans)
|
|
12
|
+
* 4. config — `defaultBranch` from .memfork/config.json (non-git fallback)
|
|
13
|
+
* 5. "main" — last resort
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: this lives in the CLI layer ONLY. The core MemForksClient and the
|
|
16
|
+
* framework adapters (@memfork/langgraph, @memfork/vercel-ai) take an explicit
|
|
17
|
+
* `branch` argument and have no git awareness. Keeping git resolution out of
|
|
18
|
+
* the core guarantees those integrations are unaffected by this logic.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Read the current git branch, or undefined when there is no usable answer.
|
|
22
|
+
*
|
|
23
|
+
* Returns undefined when:
|
|
24
|
+
* - not inside a git repo (command fails)
|
|
25
|
+
* - detached HEAD / mid-rebase / mid-bisect (returns the literal "HEAD")
|
|
26
|
+
*
|
|
27
|
+
* In those cases the caller falls through to the next precedence source rather
|
|
28
|
+
* than writing memory into a bogus namespace literally named "HEAD".
|
|
29
|
+
*/
|
|
30
|
+
export declare function gitBranch(cwd?: string): string | undefined;
|
|
31
|
+
export interface BranchSources {
|
|
32
|
+
/** --branch / --from flag (highest priority). */
|
|
33
|
+
explicit?: string;
|
|
34
|
+
/** MEMFORK_BRANCH env var. */
|
|
35
|
+
env?: string;
|
|
36
|
+
/** Current git branch (already guarded against detached HEAD). */
|
|
37
|
+
git?: string;
|
|
38
|
+
/** defaultBranch from project config (non-git fallback). */
|
|
39
|
+
configDefault?: string;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Pure precedence resolver — no I/O. Exposed separately so the precedence
|
|
43
|
+
* rules can be unit-tested exhaustively without a git repo or env mutation.
|
|
44
|
+
*
|
|
45
|
+
* A whitespace-only source is treated as absent.
|
|
46
|
+
*/
|
|
47
|
+
export declare function pickBranch(sources: BranchSources): string;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve the branch a command should operate on, applying the full
|
|
50
|
+
* precedence chain (reads MEMFORK_BRANCH and the current git branch).
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveBranch(opts?: {
|
|
53
|
+
explicit?: string;
|
|
54
|
+
configDefault?: string;
|
|
55
|
+
cwd?: string;
|
|
56
|
+
}): string;
|
package/dist/branch.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Branch resolution for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Memory in MemForks is namespaced per branch. Every CLI command needs to
|
|
5
|
+
* answer the same question — "which branch am I operating on?" — and they must
|
|
6
|
+
* all answer it the same way. This module is the single source of that answer.
|
|
7
|
+
*
|
|
8
|
+
* Precedence (highest wins):
|
|
9
|
+
* 1. explicit — the `--branch` / `--from` flag passed on the command line
|
|
10
|
+
* 2. env — MEMFORK_BRANCH (CI / headless override)
|
|
11
|
+
* 3. git — the current git branch (the dynamic default for humans)
|
|
12
|
+
* 4. config — `defaultBranch` from .memfork/config.json (non-git fallback)
|
|
13
|
+
* 5. "main" — last resort
|
|
14
|
+
*
|
|
15
|
+
* IMPORTANT: this lives in the CLI layer ONLY. The core MemForksClient and the
|
|
16
|
+
* framework adapters (@memfork/langgraph, @memfork/vercel-ai) take an explicit
|
|
17
|
+
* `branch` argument and have no git awareness. Keeping git resolution out of
|
|
18
|
+
* the core guarantees those integrations are unaffected by this logic.
|
|
19
|
+
*/
|
|
20
|
+
import { execSync } from "node:child_process";
|
|
21
|
+
/**
|
|
22
|
+
* Read the current git branch, or undefined when there is no usable answer.
|
|
23
|
+
*
|
|
24
|
+
* Returns undefined when:
|
|
25
|
+
* - not inside a git repo (command fails)
|
|
26
|
+
* - detached HEAD / mid-rebase / mid-bisect (returns the literal "HEAD")
|
|
27
|
+
*
|
|
28
|
+
* In those cases the caller falls through to the next precedence source rather
|
|
29
|
+
* than writing memory into a bogus namespace literally named "HEAD".
|
|
30
|
+
*/
|
|
31
|
+
export function gitBranch(cwd = process.cwd()) {
|
|
32
|
+
try {
|
|
33
|
+
const out = execSync("git rev-parse --abbrev-ref HEAD", {
|
|
34
|
+
encoding: "utf8",
|
|
35
|
+
cwd,
|
|
36
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
37
|
+
}).trim();
|
|
38
|
+
if (!out || out === "HEAD")
|
|
39
|
+
return undefined;
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Pure precedence resolver — no I/O. Exposed separately so the precedence
|
|
48
|
+
* rules can be unit-tested exhaustively without a git repo or env mutation.
|
|
49
|
+
*
|
|
50
|
+
* A whitespace-only source is treated as absent.
|
|
51
|
+
*/
|
|
52
|
+
export function pickBranch(sources) {
|
|
53
|
+
const clean = (s) => {
|
|
54
|
+
const t = s?.trim();
|
|
55
|
+
return t ? t : undefined;
|
|
56
|
+
};
|
|
57
|
+
return (clean(sources.explicit) ??
|
|
58
|
+
clean(sources.env) ??
|
|
59
|
+
clean(sources.git) ??
|
|
60
|
+
clean(sources.configDefault) ??
|
|
61
|
+
"main");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Resolve the branch a command should operate on, applying the full
|
|
65
|
+
* precedence chain (reads MEMFORK_BRANCH and the current git branch).
|
|
66
|
+
*/
|
|
67
|
+
export function resolveBranch(opts = {}) {
|
|
68
|
+
return pickBranch({
|
|
69
|
+
explicit: opts.explicit,
|
|
70
|
+
env: process.env["MEMFORK_BRANCH"],
|
|
71
|
+
git: gitBranch(opts.cwd),
|
|
72
|
+
configDefault: opts.configDefault,
|
|
73
|
+
});
|
|
74
|
+
}
|
package/dist/commands/ops.js
CHANGED
|
@@ -7,6 +7,7 @@ import fs from "node:fs";
|
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import chalk from "chalk";
|
|
9
9
|
import { resolveConfig, toClientConfig, readProjectConfig, writeProjectConfig, MEMWAL_CONSTANTS, } from "../config.js";
|
|
10
|
+
import { resolveBranch } from "../branch.js";
|
|
10
11
|
import { MemForksClient } from "@memfork/core";
|
|
11
12
|
// ─── Shared helpers ───────────────────────────────────────────────────────────
|
|
12
13
|
async function getClient() {
|
|
@@ -14,14 +15,6 @@ async function getClient() {
|
|
|
14
15
|
const client = await MemForksClient.connect(toClientConfig(cfg));
|
|
15
16
|
return { client, cfg };
|
|
16
17
|
}
|
|
17
|
-
function currentGitBranch() {
|
|
18
|
-
try {
|
|
19
|
-
return execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf8" }).trim();
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return "main";
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
18
|
// ─── status ───────────────────────────────────────────────────────────────────
|
|
26
19
|
export async function cmdStatus() {
|
|
27
20
|
const { client, cfg } = await getClient();
|
|
@@ -29,16 +22,18 @@ export async function cmdStatus() {
|
|
|
29
22
|
console.log("");
|
|
30
23
|
console.log(chalk.bold("MemForks status"));
|
|
31
24
|
console.log("");
|
|
25
|
+
const currentBranch = resolveBranch({ configDefault: cfg.defaultBranch });
|
|
32
26
|
console.log(` Tree ${chalk.cyan(cfg.treeId)}`);
|
|
33
27
|
console.log(` Network ${cfg.network}`);
|
|
34
|
-
console.log(` Branch ${chalk.green(
|
|
28
|
+
console.log(` Branch ${chalk.green(currentBranch)}`);
|
|
29
|
+
console.log(` Tree dflt ${chalk.dim(String(tree["default_branch"] ?? cfg.defaultBranch))}`);
|
|
35
30
|
console.log(` Signer ${client.keypair.toSuiAddress()}`);
|
|
36
31
|
console.log("");
|
|
37
32
|
}
|
|
38
33
|
// ─── log ──────────────────────────────────────────────────────────────────────
|
|
39
34
|
export async function cmdLog(opts) {
|
|
40
35
|
const { client, cfg } = await getClient();
|
|
41
|
-
const branch = opts.branch
|
|
36
|
+
const branch = resolveBranch({ explicit: opts.branch, configDefault: cfg.defaultBranch });
|
|
42
37
|
console.log("");
|
|
43
38
|
console.log(`${chalk.bold("memfork log")} ${chalk.dim("branch:")} ${chalk.green(branch)}`);
|
|
44
39
|
console.log("");
|
|
@@ -74,7 +69,7 @@ export async function cmdLog(opts) {
|
|
|
74
69
|
// ─── recall ───────────────────────────────────────────────────────────────────
|
|
75
70
|
export async function cmdRecall(query, opts) {
|
|
76
71
|
const { client, cfg } = await getClient();
|
|
77
|
-
const branch = opts.branch
|
|
72
|
+
const branch = resolveBranch({ explicit: opts.branch, configDefault: cfg.defaultBranch });
|
|
78
73
|
const results = await client.recall(query, { branch, limit: opts.limit ?? 5 });
|
|
79
74
|
if (opts.json) {
|
|
80
75
|
console.log(JSON.stringify(results));
|
|
@@ -98,7 +93,7 @@ export async function cmdRecall(query, opts) {
|
|
|
98
93
|
// ─── commit ───────────────────────────────────────────────────────────────────
|
|
99
94
|
export async function cmdCommit(opts) {
|
|
100
95
|
const { client, cfg } = await getClient();
|
|
101
|
-
const branch = opts.branch
|
|
96
|
+
const branch = resolveBranch({ explicit: opts.branch, configDefault: cfg.defaultBranch });
|
|
102
97
|
let facts = opts.facts ?? [];
|
|
103
98
|
// --from-response + --auto-extract: stub for LLM extraction
|
|
104
99
|
// In production this calls the configured LLM to distil durable facts.
|
|
@@ -288,7 +283,12 @@ export async function cmdPrComment(opts) {
|
|
|
288
283
|
}
|
|
289
284
|
catch { /* non-critical */ }
|
|
290
285
|
// Get the decided fact from the into_branch via recall.
|
|
291
|
-
|
|
286
|
+
// The proposal's into_branch is the most specific target, so it is treated
|
|
287
|
+
// as an explicit selection (ranking above the current git branch).
|
|
288
|
+
const targetBranch = resolveBranch({
|
|
289
|
+
explicit: opts.branch ?? intoBranch,
|
|
290
|
+
configDefault: cfg.defaultBranch,
|
|
291
|
+
});
|
|
292
292
|
let decision = `Use ${fromBranch || "winning branch"} approach.`;
|
|
293
293
|
try {
|
|
294
294
|
const results = await client.recall("decided", { branch: targetBranch, limit: 1 });
|
|
@@ -355,7 +355,12 @@ export async function cmdUi(opts = {}) {
|
|
|
355
355
|
console.log(chalk.dim("Build the app manually: cd apps/visualizer && npm run build"));
|
|
356
356
|
return;
|
|
357
357
|
}
|
|
358
|
-
|
|
358
|
+
// Two layouts: the published bundle (packages/cli/ui/) holds index.html +
|
|
359
|
+
// assets/ directly, while the monorepo source (apps/visualizer/) builds into
|
|
360
|
+
// a dist/ subdir. Detect which one we resolved to.
|
|
361
|
+
const distDir = fs.existsSync(path.join(appDir, "index.html"))
|
|
362
|
+
? appDir
|
|
363
|
+
: path.join(appDir, "dist");
|
|
359
364
|
const indexHtml = path.join(distDir, "index.html");
|
|
360
365
|
// ── Share mode: build → publish to Walrus Site ──────────────────────────
|
|
361
366
|
if (opts.share) {
|
|
@@ -572,7 +577,7 @@ export async function cmdRevoke(address) {
|
|
|
572
577
|
// ─── branch ───────────────────────────────────────────────────────────────────
|
|
573
578
|
export async function cmdBranch(name, opts = {}) {
|
|
574
579
|
const { client, cfg } = await getClient();
|
|
575
|
-
const from = opts.from
|
|
580
|
+
const from = resolveBranch({ explicit: opts.from, configDefault: cfg.defaultBranch });
|
|
576
581
|
process.stdout.write(chalk.dim(`Creating branch ${chalk.green(name)} from ${chalk.green(from)} … `));
|
|
577
582
|
const digest = await client.branch(name, { from });
|
|
578
583
|
console.log(chalk.green("done"));
|
package/dist/index.d.ts
CHANGED
|
@@ -7,3 +7,5 @@
|
|
|
7
7
|
* The CLI binary entry point is src/cli.ts → dist/cli.js
|
|
8
8
|
*/
|
|
9
9
|
export { resolveConfig, toClientConfig, readProjectConfig, writeProjectConfig, readCredentials, writeCredentials, upsertCredential, setDefaultTree, projectConfigPath, credentialsPath, ConfigError, } from "./config.js";
|
|
10
|
+
export { resolveBranch, pickBranch, gitBranch } from "./branch.js";
|
|
11
|
+
export type { BranchSources } from "./branch.js";
|
package/dist/index.js
CHANGED
|
@@ -7,3 +7,4 @@
|
|
|
7
7
|
* The CLI binary entry point is src/cli.ts → dist/cli.js
|
|
8
8
|
*/
|
|
9
9
|
export { resolveConfig, toClientConfig, readProjectConfig, writeProjectConfig, readCredentials, writeCredentials, upsertCredential, setDefaultTree, projectConfigPath, credentialsPath, ConfigError, } from "./config.js";
|
|
10
|
+
export { resolveBranch, pickBranch, gitBranch } from "./branch.js";
|
package/package.json
CHANGED
|
@@ -27,11 +27,12 @@ Use this after significant architectural decisions — not for routine facts.
|
|
|
27
27
|
|
|
28
28
|
```bash
|
|
29
29
|
memfork commit \
|
|
30
|
-
--branch $(git rev-parse --abbrev-ref HEAD) \
|
|
31
30
|
--message "decided: <one-line summary>" \
|
|
32
31
|
--facts "<fact 1>" "<fact 2>"
|
|
33
32
|
```
|
|
34
33
|
|
|
34
|
+
The CLI auto-detects the current Git branch; pass `--branch <name>` only to target a different one.
|
|
35
|
+
|
|
35
36
|
## Merge branches
|
|
36
37
|
|
|
37
38
|
When two branches need to reconcile their memory:
|
|
@@ -54,11 +54,13 @@ a non-trivial problem, also anchor it on-chain for immutable versioning:
|
|
|
54
54
|
|
|
55
55
|
```bash
|
|
56
56
|
memfork commit \
|
|
57
|
-
--branch $(git rev-parse --abbrev-ref HEAD) \
|
|
58
57
|
--message "decided: <one-line summary>" \
|
|
59
58
|
--facts "<fact 1>" "<fact 2>"
|
|
60
59
|
```
|
|
61
60
|
|
|
61
|
+
The CLI auto-detects the current Git branch, so you do not pass `--branch`
|
|
62
|
+
unless you want to target a different branch.
|
|
63
|
+
|
|
62
64
|
This creates a cryptographically verifiable commit on Sui — not just a blob.
|
|
63
65
|
Use it for decisions that matter for audit trail, not routine facts.
|
|
64
66
|
|
|
@@ -66,9 +68,10 @@ Use it for decisions that matter for audit trail, not routine facts.
|
|
|
66
68
|
|
|
67
69
|
## Branch awareness
|
|
68
70
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
71
|
+
- The `memfork` CLI auto-detects the current Git branch; pass `--branch` only to override it.
|
|
72
|
+
- The `memwal_*` MCP tools have no Git context, so pass `namespace="branch/<current-branch>"` explicitly.
|
|
73
|
+
- When the user switches branches, recall from the new branch namespace.
|
|
74
|
+
- Use `memfork status` to see the current branch, on-chain tree state, and open merge proposals.
|
|
72
75
|
|
|
73
76
|
---
|
|
74
77
|
|
|
@@ -81,10 +84,12 @@ stays isolated. Never collapse competing ideas into a single branch.
|
|
|
81
84
|
**Step 1 — announce and create branches:**
|
|
82
85
|
|
|
83
86
|
```bash
|
|
84
|
-
memfork branch dev/<hypothesis-a>
|
|
85
|
-
memfork branch dev/<hypothesis-b>
|
|
87
|
+
memfork branch dev/<hypothesis-a>
|
|
88
|
+
memfork branch dev/<hypothesis-b>
|
|
86
89
|
```
|
|
87
90
|
|
|
91
|
+
Both fork from the current Git branch by default; add `--from <branch>` to fork from another.
|
|
92
|
+
|
|
88
93
|
Use short kebab-case names (`dev/redis-first`, `dev/bcrypt-cost`).
|
|
89
94
|
|
|
90
95
|
**Step 2 — investigate each path and commit evidence:**
|