pi-brain 0.1.2 → 0.1.4
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/README.md +7 -2
- package/banner.png +0 -0
- package/package.json +3 -2
- package/skills/brain/SKILL.md +24 -30
- package/skills/brain/templates/agents-md.md +13 -8
- package/skills/brain/templates/root-agents-section.md +1 -1
- package/src/branches.ts +26 -1
- package/src/index.ts +111 -94
- package/src/memory-branch.ts +154 -9
- package/src/memory-commit.ts +9 -3
- package/src/memory-context.ts +88 -47
- package/src/types.ts +0 -7
- package/src/memory-merge.ts +0 -52
- package/src/memory-switch.ts +0 -29
package/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./banner.png" alt="pi-brain banner" width="720" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# pi-brain
|
|
2
6
|
|
|
3
7
|
Versioned memory for the [pi coding agent](https://github.com/badlogic/pi-mono). Agents commit decisions and reasoning to a `.memory/` directory, preserving context across sessions, compactions, and model switches.
|
|
@@ -43,9 +47,10 @@ LLM providers cache the prefix of each request. If the prefix changes between tu
|
|
|
43
47
|
Brain avoids this entirely:
|
|
44
48
|
|
|
45
49
|
- **Static AGENTS.md** — Written once at init, never updated. No branch names, no commit counts, no dynamic state. The system prompt prefix stays identical across every turn and session.
|
|
46
|
-
- **No per-turn
|
|
47
|
-
- **Fixed tool definitions** —
|
|
50
|
+
- **No per-turn prompt mutation** — Brain does not rewrite `systemPrompt` between turns. Status context is appended as message content, keeping the cached prefix stable.
|
|
51
|
+
- **Fixed tool definitions** — Tool schemas are static at startup. No tools are added or removed mid-conversation.
|
|
48
52
|
- **Subagent isolation** — Commit distillation runs in a separate API call with its own cache. The main agent's cache is never touched.
|
|
53
|
+
- **Regression-tested safety** — Prompt-cache safety invariants are covered in `src/cache-safety.test.ts` (property tests for append-only prompt behavior, lifecycle-gated status injection, and deterministic status rendering).
|
|
49
54
|
|
|
50
55
|
The result: Brain adds zero overhead to your prompt cache hit rate.
|
|
51
56
|
|
package/banner.png
ADDED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-brain",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Versioned memory extension for the pi coding agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent-memory",
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"skills/",
|
|
28
28
|
"agents/",
|
|
29
29
|
"package.json",
|
|
30
|
-
"README.md"
|
|
30
|
+
"README.md",
|
|
31
|
+
"banner.png"
|
|
31
32
|
],
|
|
32
33
|
"type": "module",
|
|
33
34
|
"scripts": {
|
package/skills/brain/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: brain
|
|
3
|
-
description: Use when working on a project with Brain agent memory management. Triggers on
|
|
3
|
+
description: Use when working on a project with Brain agent memory management. Triggers on memory_commit, memory_branch tool usage, or when the project has a .memory/ directory.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Brain — Agent Memory
|
|
@@ -19,11 +19,11 @@ Replace `/absolute/path/to/skills/brain` with the skill directory shown in the
|
|
|
19
19
|
|
|
20
20
|
### After Init
|
|
21
21
|
|
|
22
|
-
1. **
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
1. **Write `.memory/main.md`** — the project roadmap (see below).
|
|
23
|
+
2. **Make your first commit** when you reach a meaningful milestone.
|
|
24
|
+
|
|
25
|
+
> **Note:** No `/reload` is needed. The memory tools lazily detect `.memory/`
|
|
26
|
+
> on every call via `tryLoad()`.
|
|
27
27
|
|
|
28
28
|
### Writing main.md — Greenfield vs Brownfield
|
|
29
29
|
|
|
@@ -39,6 +39,22 @@ questions as you understand them from conversation with the user.
|
|
|
39
39
|
Then write the roadmap covering: project purpose, current state, key decisions
|
|
40
40
|
already made, completed milestones, and planned work.
|
|
41
41
|
|
|
42
|
+
## Context Retrieval
|
|
43
|
+
|
|
44
|
+
Memory status is **automatically injected** at session start (via the
|
|
45
|
+
`before_agent_start` hook) and appended to every successful `memory_branch` and
|
|
46
|
+
`memory_commit` result. Automatic status is compact and may truncate long
|
|
47
|
+
roadmaps, so keep the newest critical context near the top of `.memory/main.md`.
|
|
48
|
+
You do not need to call a separate tool to see status.
|
|
49
|
+
|
|
50
|
+
For deep retrieval, use `read` directly:
|
|
51
|
+
|
|
52
|
+
- `read .memory/branches/<name>/commits.md` — full branch history
|
|
53
|
+
- `read .memory/branches/<name>/log.md` — OTA trace since last commit
|
|
54
|
+
- `read .memory/branches/<name>/metadata.yaml` — structured metadata
|
|
55
|
+
- `read .memory/main.md` — project roadmap
|
|
56
|
+
- `read .memory/AGENTS.md` — full protocol reference
|
|
57
|
+
|
|
42
58
|
## When to Commit
|
|
43
59
|
|
|
44
60
|
- You've reached a stable understanding or decision
|
|
@@ -72,28 +88,6 @@ then produces the structured commit entry. You just provide a good `summary` str
|
|
|
72
88
|
- The branch's findings should inform the main line of thinking
|
|
73
89
|
- Include what was learned even if the approach was abandoned
|
|
74
90
|
|
|
75
|
-
**Important:** Always review the source branch history BEFORE calling
|
|
76
|
-
Use
|
|
77
|
-
|
|
78
|
-
- `memory_status` for high-level status
|
|
79
|
-
- `read .memory/branches/<target>/commits.md` for full branch history
|
|
80
|
-
|
|
91
|
+
**Important:** Always review the source branch history BEFORE calling merge.
|
|
92
|
+
Use `read .memory/branches/<target>/commits.md` for full branch history.
|
|
81
93
|
You need the full context to write a good synthesis.
|
|
82
|
-
|
|
83
|
-
## When to Use Context Retrieval
|
|
84
|
-
|
|
85
|
-
- Starting a new session on an existing project — call `memory_status` first
|
|
86
|
-
- Before making a decision that might conflict with earlier reasoning
|
|
87
|
-
- When you need to recall the rationale behind a previous decision
|
|
88
|
-
|
|
89
|
-
## Context Retrieval
|
|
90
|
-
|
|
91
|
-
Use `memory_status` for high-level status only.
|
|
92
|
-
|
|
93
|
-
For deep retrieval, use `read` directly:
|
|
94
|
-
|
|
95
|
-
- `read .memory/branches/<name>/commits.md` — full branch history
|
|
96
|
-
- `read .memory/branches/<name>/log.md` — OTA trace since last commit
|
|
97
|
-
- `read .memory/branches/<name>/metadata.yaml` — structured metadata
|
|
98
|
-
- `read .memory/main.md` — project roadmap
|
|
99
|
-
- `read .memory/AGENTS.md` — full protocol reference
|
|
@@ -4,13 +4,18 @@ This directory contains your project's agent memory, managed by the Brain extens
|
|
|
4
4
|
|
|
5
5
|
## Tools
|
|
6
6
|
|
|
7
|
-
| Tool | Purpose
|
|
8
|
-
| --------------- |
|
|
9
|
-
| `memory_commit` | Checkpoint a milestone in understanding
|
|
10
|
-
| `memory_branch` |
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
| Tool | Purpose |
|
|
8
|
+
| --------------- | ----------------------------------------- |
|
|
9
|
+
| `memory_commit` | Checkpoint a milestone in understanding |
|
|
10
|
+
| `memory_branch` | Manage branches: create, switch, or merge |
|
|
11
|
+
|
|
12
|
+
### memory_branch Actions
|
|
13
|
+
|
|
14
|
+
| Action | Required Params | Description |
|
|
15
|
+
| -------- | --------------------- | ------------------------------------------- |
|
|
16
|
+
| `create` | `name`, `purpose` | Create a new branch and switch to it |
|
|
17
|
+
| `switch` | `branch` | Switch active memory branch |
|
|
18
|
+
| `merge` | `branch`, `synthesis` | Synthesize a branch's insights into current |
|
|
14
19
|
|
|
15
20
|
## File Structure
|
|
16
21
|
|
|
@@ -41,4 +46,4 @@ The latest commit always contains a self-contained summary of the full branch hi
|
|
|
41
46
|
- **Decisions over details**: Capture "why", not "what" — git tracks file changes
|
|
42
47
|
- **Rolling summaries**: Each commit re-synthesizes all prior progress
|
|
43
48
|
- **No direct log.md writes**: The extension maintains log.md automatically
|
|
44
|
-
- **
|
|
49
|
+
- **Status is automatic**: Memory status is injected at session start and appended to tool results (compact/truncated when large; use `read .memory/main.md` for full roadmap)
|
|
@@ -4,4 +4,4 @@ This project uses Brain for agent memory management.
|
|
|
4
4
|
|
|
5
5
|
**Start here when orienting:** Read `.memory/main.md` for the project roadmap, key decisions, and open problems.
|
|
6
6
|
Read `.memory/AGENTS.md` for the full Brain protocol reference.
|
|
7
|
-
Tools: memory_commit, memory_branch
|
|
7
|
+
Tools: memory_commit, memory_branch (create/switch/merge)
|
package/src/branches.ts
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
|
|
4
|
+
function sortBranchNames(names: readonly string[]): string[] {
|
|
5
|
+
const sorted: string[] = [];
|
|
6
|
+
|
|
7
|
+
for (const name of names) {
|
|
8
|
+
const insertIndex = sorted.findIndex(
|
|
9
|
+
(existing) => existing.localeCompare(name) > 0
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
if (insertIndex === -1) {
|
|
13
|
+
sorted.push(name);
|
|
14
|
+
continue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
sorted.splice(insertIndex, 0, name);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return sorted;
|
|
21
|
+
}
|
|
22
|
+
|
|
4
23
|
/**
|
|
5
24
|
* Manages `.memory/branches/` directory operations.
|
|
6
25
|
* Each branch has: log.md, commits.md, metadata.yaml.
|
|
@@ -64,15 +83,21 @@ export class BranchManager {
|
|
|
64
83
|
return fs.readFileSync(metaPath, "utf8");
|
|
65
84
|
}
|
|
66
85
|
|
|
86
|
+
protected readBranchEntries(): string[] {
|
|
87
|
+
return fs.readdirSync(this.branchesDir);
|
|
88
|
+
}
|
|
89
|
+
|
|
67
90
|
listBranches(): string[] {
|
|
68
91
|
if (!fs.existsSync(this.branchesDir)) {
|
|
69
92
|
return [];
|
|
70
93
|
}
|
|
71
94
|
|
|
72
|
-
|
|
95
|
+
const branchNames = this.readBranchEntries().filter((entry) => {
|
|
73
96
|
const fullPath = path.join(this.branchesDir, entry);
|
|
74
97
|
return fs.statSync(fullPath).isDirectory();
|
|
75
98
|
});
|
|
99
|
+
|
|
100
|
+
return sortBranchNames(branchNames);
|
|
76
101
|
}
|
|
77
102
|
|
|
78
103
|
branchExists(name: string): boolean {
|
package/src/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { fileURLToPath } from "node:url";
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
AgentToolResult,
|
|
6
|
+
BeforeAgentStartEvent,
|
|
6
7
|
ExtensionAPI,
|
|
7
8
|
ExtensionContext,
|
|
8
9
|
SessionBeforeCompactEvent,
|
|
@@ -13,9 +14,7 @@ import { BranchManager } from "./branches.js";
|
|
|
13
14
|
import { LOG_SIZE_WARNING_BYTES } from "./constants.js";
|
|
14
15
|
import { executeMemoryBranch } from "./memory-branch.js";
|
|
15
16
|
import { executeMemoryCommit, finalizeMemoryCommit } from "./memory-commit.js";
|
|
16
|
-
import {
|
|
17
|
-
import { executeMemoryMerge } from "./memory-merge.js";
|
|
18
|
-
import { executeMemorySwitch } from "./memory-switch.js";
|
|
17
|
+
import { buildStatusView } from "./memory-context.js";
|
|
19
18
|
import { formatOtaEntry } from "./ota-formatter.js";
|
|
20
19
|
import { extractOtaInput } from "./ota-logger.js";
|
|
21
20
|
import { MemoryState } from "./state.js";
|
|
@@ -23,6 +22,8 @@ import { extractCommitBlocks, spawnCommitter } from "./subagent.js";
|
|
|
23
22
|
|
|
24
23
|
const MEMORY_NOT_INITIALIZED_MESSAGE =
|
|
25
24
|
"Brain not initialized. Run brain-init.sh first.";
|
|
25
|
+
const BEFORE_AGENT_START_ROADMAP_CHAR_LIMIT = 1200;
|
|
26
|
+
const BEFORE_AGENT_START_BRANCH_LIMIT = 8;
|
|
26
27
|
|
|
27
28
|
function createTextResult(text: string): AgentToolResult<unknown> {
|
|
28
29
|
return {
|
|
@@ -52,6 +53,21 @@ function upsertCurrentSession(state: MemoryState, ctx: ExtensionContext): void {
|
|
|
52
53
|
state.save();
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
function setBrainFooterStatus(
|
|
57
|
+
ctx: ExtensionContext,
|
|
58
|
+
state: MemoryState | null,
|
|
59
|
+
branchManager: BranchManager | null
|
|
60
|
+
): void {
|
|
61
|
+
if (!state || !branchManager || !state.isInitialized) {
|
|
62
|
+
ctx.ui.setStatus("brain", undefined);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const turnCount = branchManager.getLogTurnCount(state.activeBranch);
|
|
67
|
+
const turnLabel = `${turnCount} uncommitted turn${turnCount === 1 ? "" : "s"}`;
|
|
68
|
+
ctx.ui.setStatus("brain", `Brain: ${state.activeBranch} (${turnLabel})`);
|
|
69
|
+
}
|
|
70
|
+
|
|
55
71
|
function buildCompactionReminder(
|
|
56
72
|
state: MemoryState,
|
|
57
73
|
branchManager: BranchManager
|
|
@@ -85,6 +101,7 @@ function resolveSkillPath(): string {
|
|
|
85
101
|
export default function activate(pi: ExtensionAPI) {
|
|
86
102
|
let state: MemoryState | null = null;
|
|
87
103
|
let branchManager: BranchManager | null = null;
|
|
104
|
+
let statusInjected = false;
|
|
88
105
|
|
|
89
106
|
function tryLoad(ctx: ExtensionContext): boolean {
|
|
90
107
|
if (isMemoryReady(state, branchManager)) {
|
|
@@ -104,107 +121,56 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
104
121
|
return true;
|
|
105
122
|
}
|
|
106
123
|
|
|
107
|
-
pi.registerTool({
|
|
108
|
-
name: "memory_status",
|
|
109
|
-
label: "Memory Status",
|
|
110
|
-
description: "Retrieve agent memory status overview.",
|
|
111
|
-
parameters: Type.Object({
|
|
112
|
-
level: Type.Optional(Type.String()),
|
|
113
|
-
branch: Type.Optional(Type.String()),
|
|
114
|
-
commit: Type.Optional(Type.String()),
|
|
115
|
-
segment: Type.Optional(Type.String()),
|
|
116
|
-
}),
|
|
117
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
118
|
-
if (
|
|
119
|
-
!tryLoad(ctx) ||
|
|
120
|
-
!isMemoryReady(state, branchManager) ||
|
|
121
|
-
!branchManager
|
|
122
|
-
) {
|
|
123
|
-
return createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return createTextResult(
|
|
127
|
-
executeMemoryStatus(params, state, branchManager, ctx.cwd)
|
|
128
|
-
);
|
|
129
|
-
},
|
|
130
|
-
});
|
|
131
|
-
|
|
132
124
|
pi.registerTool({
|
|
133
125
|
name: "memory_branch",
|
|
134
126
|
label: "Memory Branch",
|
|
135
|
-
description:
|
|
136
|
-
|
|
137
|
-
name: Type.String({ description: "Branch name" }),
|
|
138
|
-
purpose: Type.String({ description: "Why this branch exists" }),
|
|
139
|
-
}),
|
|
140
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
141
|
-
if (
|
|
142
|
-
!tryLoad(ctx) ||
|
|
143
|
-
!isMemoryReady(state, branchManager) ||
|
|
144
|
-
!branchManager
|
|
145
|
-
) {
|
|
146
|
-
return createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const previousBranch = state.activeBranch;
|
|
150
|
-
const result = executeMemoryBranch(params, state, branchManager);
|
|
151
|
-
|
|
152
|
-
if (state.activeBranch !== previousBranch) {
|
|
153
|
-
upsertCurrentSession(state, ctx);
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
return createTextResult(result);
|
|
157
|
-
},
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
pi.registerTool({
|
|
161
|
-
name: "memory_switch",
|
|
162
|
-
label: "Memory Switch",
|
|
163
|
-
description: "Switch to another memory branch.",
|
|
127
|
+
description:
|
|
128
|
+
"Manage memory branches. Actions: create (new branch), switch (change active branch), merge (synthesize branch into current).",
|
|
164
129
|
parameters: Type.Object({
|
|
165
|
-
|
|
130
|
+
action: Type.Union(
|
|
131
|
+
[Type.Literal("create"), Type.Literal("switch"), Type.Literal("merge")],
|
|
132
|
+
{
|
|
133
|
+
description: 'Action to perform: "create", "switch", or "merge"',
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
name: Type.Optional(
|
|
137
|
+
Type.String({ description: "Branch name (required for create)" })
|
|
138
|
+
),
|
|
139
|
+
purpose: Type.Optional(
|
|
140
|
+
Type.String({
|
|
141
|
+
description: "Why this branch exists (required for create)",
|
|
142
|
+
})
|
|
143
|
+
),
|
|
144
|
+
branch: Type.Optional(
|
|
145
|
+
Type.String({
|
|
146
|
+
description: "Target branch (required for switch and merge)",
|
|
147
|
+
})
|
|
148
|
+
),
|
|
149
|
+
synthesis: Type.Optional(
|
|
150
|
+
Type.String({ description: "Synthesized insight (required for merge)" })
|
|
151
|
+
),
|
|
166
152
|
}),
|
|
167
|
-
|
|
153
|
+
execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
168
154
|
if (
|
|
169
155
|
!tryLoad(ctx) ||
|
|
170
156
|
!isMemoryReady(state, branchManager) ||
|
|
171
157
|
!branchManager
|
|
172
158
|
) {
|
|
173
|
-
|
|
159
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
160
|
+
return Promise.resolve(
|
|
161
|
+
createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE)
|
|
162
|
+
);
|
|
174
163
|
}
|
|
175
164
|
|
|
176
165
|
const previousBranch = state.activeBranch;
|
|
177
|
-
const result =
|
|
166
|
+
const result = executeMemoryBranch(params, state, branchManager, ctx.cwd);
|
|
178
167
|
|
|
179
168
|
if (state.activeBranch !== previousBranch) {
|
|
180
169
|
upsertCurrentSession(state, ctx);
|
|
181
170
|
}
|
|
182
171
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
pi.registerTool({
|
|
188
|
-
name: "memory_merge",
|
|
189
|
-
label: "Memory Merge",
|
|
190
|
-
description:
|
|
191
|
-
"Merge insights from one memory branch into the active branch.",
|
|
192
|
-
parameters: Type.Object({
|
|
193
|
-
branch: Type.String({ description: "Source branch to merge from" }),
|
|
194
|
-
synthesis: Type.String({
|
|
195
|
-
description: "Synthesized insight from source branch",
|
|
196
|
-
}),
|
|
197
|
-
}),
|
|
198
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
199
|
-
if (
|
|
200
|
-
!tryLoad(ctx) ||
|
|
201
|
-
!isMemoryReady(state, branchManager) ||
|
|
202
|
-
!branchManager
|
|
203
|
-
) {
|
|
204
|
-
return createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return createTextResult(executeMemoryMerge(params, state, branchManager));
|
|
172
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
173
|
+
return Promise.resolve(createTextResult(result));
|
|
208
174
|
},
|
|
209
175
|
});
|
|
210
176
|
|
|
@@ -222,6 +188,7 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
222
188
|
!isMemoryReady(state, branchManager) ||
|
|
223
189
|
!branchManager
|
|
224
190
|
) {
|
|
191
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
225
192
|
return createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE);
|
|
226
193
|
}
|
|
227
194
|
|
|
@@ -246,9 +213,11 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
246
213
|
params.summary,
|
|
247
214
|
commitContent,
|
|
248
215
|
state,
|
|
249
|
-
branchManager
|
|
216
|
+
branchManager,
|
|
217
|
+
ctx.cwd
|
|
250
218
|
);
|
|
251
219
|
|
|
220
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
252
221
|
return createTextResult(message);
|
|
253
222
|
},
|
|
254
223
|
});
|
|
@@ -257,14 +226,15 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
257
226
|
state = new MemoryState(ctx.cwd);
|
|
258
227
|
state.load();
|
|
259
228
|
branchManager = new BranchManager(ctx.cwd);
|
|
229
|
+
statusInjected = false;
|
|
260
230
|
|
|
261
231
|
if (!state.isInitialized) {
|
|
232
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
262
233
|
return;
|
|
263
234
|
}
|
|
264
235
|
|
|
265
236
|
upsertCurrentSession(state, ctx);
|
|
266
237
|
|
|
267
|
-
const turnCount = branchManager.getLogTurnCount(state.activeBranch);
|
|
268
238
|
const logSizeBytes = branchManager.getLogSizeBytes(state.activeBranch);
|
|
269
239
|
|
|
270
240
|
if (logSizeBytes >= LOG_SIZE_WARNING_BYTES) {
|
|
@@ -273,20 +243,66 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
273
243
|
`Brain: log.md is large (${sizeKB} KB). You should commit to distill this into structured memory.`,
|
|
274
244
|
"warning"
|
|
275
245
|
);
|
|
276
|
-
} else {
|
|
277
|
-
ctx.ui.notify(
|
|
278
|
-
`Brain active: branch "${state.activeBranch}" (${turnCount} uncommitted turn${turnCount === 1 ? "" : "s"}).`,
|
|
279
|
-
"info"
|
|
280
|
-
);
|
|
281
246
|
}
|
|
247
|
+
|
|
248
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
pi.on("session_switch", (_event, ctx) => {
|
|
252
|
+
statusInjected = false;
|
|
253
|
+
|
|
254
|
+
state = new MemoryState(ctx.cwd);
|
|
255
|
+
state.load();
|
|
256
|
+
branchManager = new BranchManager(ctx.cwd);
|
|
257
|
+
|
|
258
|
+
if (state.isInitialized) {
|
|
259
|
+
upsertCurrentSession(state, ctx);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
pi.on("before_agent_start", (_event: BeforeAgentStartEvent, ctx) => {
|
|
266
|
+
if (statusInjected) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (
|
|
271
|
+
!tryLoad(ctx) ||
|
|
272
|
+
!isMemoryReady(state, branchManager) ||
|
|
273
|
+
!branchManager
|
|
274
|
+
) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
statusInjected = true;
|
|
279
|
+
|
|
280
|
+
const status = buildStatusView(state, branchManager, ctx.cwd, {
|
|
281
|
+
compact: true,
|
|
282
|
+
roadmapCharLimit: BEFORE_AGENT_START_ROADMAP_CHAR_LIMIT,
|
|
283
|
+
branchLimit: BEFORE_AGENT_START_BRANCH_LIMIT,
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
message: {
|
|
287
|
+
customType: "brain-status",
|
|
288
|
+
content: status,
|
|
289
|
+
display: true,
|
|
290
|
+
details: {},
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
pi.on("session_compact", () => {
|
|
296
|
+
statusInjected = false;
|
|
282
297
|
});
|
|
283
298
|
|
|
284
299
|
pi.on("resources_discover", () => ({
|
|
285
300
|
skillPaths: [resolveSkillPath()],
|
|
286
301
|
}));
|
|
287
302
|
|
|
288
|
-
pi.on("turn_end", (event) => {
|
|
303
|
+
pi.on("turn_end", (event, ctx) => {
|
|
289
304
|
if (!isMemoryReady(state, branchManager) || !branchManager) {
|
|
305
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
290
306
|
return;
|
|
291
307
|
}
|
|
292
308
|
|
|
@@ -297,6 +313,7 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
297
313
|
|
|
298
314
|
const entry = formatOtaEntry(input);
|
|
299
315
|
branchManager.appendLog(state.activeBranch, entry);
|
|
316
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
300
317
|
});
|
|
301
318
|
|
|
302
319
|
pi.on("session_before_compact", (event) => {
|
package/src/memory-branch.ts
CHANGED
|
@@ -1,28 +1,173 @@
|
|
|
1
1
|
import type { BranchManager } from "./branches.js";
|
|
2
|
+
import { generateHash } from "./hash.js";
|
|
3
|
+
import { buildStatusView } from "./memory-context.js";
|
|
2
4
|
import type { MemoryState } from "./state.js";
|
|
3
5
|
|
|
4
6
|
interface MemoryBranchParams {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
action: string;
|
|
8
|
+
name?: string;
|
|
9
|
+
purpose?: string;
|
|
10
|
+
branch?: string;
|
|
11
|
+
synthesis?: string;
|
|
7
12
|
}
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
14
|
+
interface ActionResult {
|
|
15
|
+
text: string;
|
|
16
|
+
ok: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function executeCreate(
|
|
13
20
|
params: MemoryBranchParams,
|
|
14
21
|
state: MemoryState,
|
|
15
22
|
branches: BranchManager
|
|
16
|
-
):
|
|
23
|
+
): ActionResult {
|
|
17
24
|
const { name, purpose } = params;
|
|
18
25
|
|
|
26
|
+
if (!name || !purpose) {
|
|
27
|
+
return {
|
|
28
|
+
text: '"name" and "purpose" are required for the create action.',
|
|
29
|
+
ok: false,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
if (branches.branchExists(name)) {
|
|
20
|
-
return
|
|
34
|
+
return {
|
|
35
|
+
text: `Branch "${name}" already exists. Use action "switch" to switch to it.`,
|
|
36
|
+
ok: false,
|
|
37
|
+
};
|
|
21
38
|
}
|
|
22
39
|
|
|
23
40
|
branches.createBranch(name, purpose);
|
|
24
41
|
state.setActiveBranch(name);
|
|
25
42
|
state.save();
|
|
26
43
|
|
|
27
|
-
return
|
|
44
|
+
return {
|
|
45
|
+
text: `Created branch "${name}" and switched to it.\nPurpose: ${purpose}`,
|
|
46
|
+
ok: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function executeSwitch(
|
|
51
|
+
params: MemoryBranchParams,
|
|
52
|
+
state: MemoryState,
|
|
53
|
+
branches: BranchManager
|
|
54
|
+
): ActionResult {
|
|
55
|
+
const { branch } = params;
|
|
56
|
+
|
|
57
|
+
if (!branch) {
|
|
58
|
+
return { text: '"branch" is required for the switch action.', ok: false };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!branches.branchExists(branch)) {
|
|
62
|
+
return {
|
|
63
|
+
text: `Branch "${branch}" not found. Available branches: ${branches.listBranches().join(", ")}`,
|
|
64
|
+
ok: false,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
state.setActiveBranch(branch);
|
|
69
|
+
state.save();
|
|
70
|
+
|
|
71
|
+
const latest = branches.getLatestCommit(branch);
|
|
72
|
+
const summary = latest ?? "No commits yet.";
|
|
73
|
+
|
|
74
|
+
return { text: `Switched to branch "${branch}".\n\n${summary}`, ok: true };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function executeMerge(
|
|
78
|
+
params: MemoryBranchParams,
|
|
79
|
+
state: MemoryState,
|
|
80
|
+
branches: BranchManager
|
|
81
|
+
): ActionResult {
|
|
82
|
+
const { branch: sourceBranch, synthesis } = params;
|
|
83
|
+
|
|
84
|
+
if (!sourceBranch || !synthesis) {
|
|
85
|
+
return {
|
|
86
|
+
text: '"branch" and "synthesis" are required for the merge action.',
|
|
87
|
+
ok: false,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const targetBranch = state.activeBranch;
|
|
92
|
+
|
|
93
|
+
if (sourceBranch === targetBranch) {
|
|
94
|
+
return {
|
|
95
|
+
text: `Cannot merge branch "${sourceBranch}" into itself.`,
|
|
96
|
+
ok: false,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (!branches.branchExists(sourceBranch)) {
|
|
101
|
+
return {
|
|
102
|
+
text: `Branch "${sourceBranch}" not found. Available branches: ${branches.listBranches().join(", ")}`,
|
|
103
|
+
ok: false,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const hash = generateHash();
|
|
108
|
+
const timestamp = new Date().toISOString();
|
|
109
|
+
const summary = `Merge from ${sourceBranch}`;
|
|
110
|
+
|
|
111
|
+
const entry = [
|
|
112
|
+
"",
|
|
113
|
+
"---",
|
|
114
|
+
"",
|
|
115
|
+
`## Commit ${hash} | ${timestamp}`,
|
|
116
|
+
"",
|
|
117
|
+
`### Merge from ${sourceBranch}`,
|
|
118
|
+
"",
|
|
119
|
+
synthesis,
|
|
120
|
+
"",
|
|
121
|
+
].join("\n");
|
|
122
|
+
|
|
123
|
+
branches.appendCommit(targetBranch, entry);
|
|
124
|
+
|
|
125
|
+
state.setLastCommit(targetBranch, hash, timestamp, summary);
|
|
126
|
+
state.save();
|
|
127
|
+
|
|
128
|
+
return {
|
|
129
|
+
text: `Merge commit ${hash} written to branch "${targetBranch}" (merged from "${sourceBranch}").`,
|
|
130
|
+
ok: true,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Execute the unified memory_branch tool.
|
|
136
|
+
* Actions: create, switch, merge.
|
|
137
|
+
* On success, appends the current memory status view.
|
|
138
|
+
*/
|
|
139
|
+
export function executeMemoryBranch(
|
|
140
|
+
params: MemoryBranchParams,
|
|
141
|
+
state: MemoryState,
|
|
142
|
+
branches: BranchManager,
|
|
143
|
+
projectDir: string
|
|
144
|
+
): string {
|
|
145
|
+
let result: ActionResult;
|
|
146
|
+
|
|
147
|
+
switch (params.action) {
|
|
148
|
+
case "create": {
|
|
149
|
+
result = executeCreate(params, state, branches);
|
|
150
|
+
break;
|
|
151
|
+
}
|
|
152
|
+
case "switch": {
|
|
153
|
+
result = executeSwitch(params, state, branches);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
case "merge": {
|
|
157
|
+
result = executeMerge(params, state, branches);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
default: {
|
|
161
|
+
return `Unknown action "${params.action}". Valid actions: create, switch, merge.`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!result.ok) {
|
|
166
|
+
return result.text;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const status = buildStatusView(state, branches, projectDir, {
|
|
170
|
+
compact: true,
|
|
171
|
+
});
|
|
172
|
+
return `${result.text}\n\n${status}`;
|
|
28
173
|
}
|
package/src/memory-commit.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BranchManager } from "./branches.js";
|
|
2
2
|
import { generateHash } from "./hash.js";
|
|
3
|
+
import { buildStatusView } from "./memory-context.js";
|
|
3
4
|
import type { MemoryState } from "./state.js";
|
|
4
5
|
import { buildCommitterTask } from "./subagent.js";
|
|
5
6
|
|
|
@@ -26,13 +27,14 @@ export function executeMemoryCommit(
|
|
|
26
27
|
|
|
27
28
|
/**
|
|
28
29
|
* Step 2: Write the agent's commit content to commits.md,
|
|
29
|
-
* clear log.md, and
|
|
30
|
+
* clear log.md, update state, and return result with status.
|
|
30
31
|
*/
|
|
31
32
|
export function finalizeMemoryCommit(
|
|
32
33
|
summary: string,
|
|
33
34
|
commitContent: string,
|
|
34
35
|
state: MemoryState,
|
|
35
|
-
branches: BranchManager
|
|
36
|
+
branches: BranchManager,
|
|
37
|
+
projectDir: string
|
|
36
38
|
): string {
|
|
37
39
|
const branch = state.activeBranch;
|
|
38
40
|
const hash = generateHash();
|
|
@@ -54,5 +56,9 @@ export function finalizeMemoryCommit(
|
|
|
54
56
|
state.setLastCommit(branch, hash, timestamp, summary);
|
|
55
57
|
state.save();
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
const resultText = `Commit ${hash} written to branch "${branch}".`;
|
|
60
|
+
const status = buildStatusView(state, branches, projectDir, {
|
|
61
|
+
compact: true,
|
|
62
|
+
});
|
|
63
|
+
return `${resultText}\n\n${status}`;
|
|
58
64
|
}
|
package/src/memory-context.ts
CHANGED
|
@@ -4,7 +4,15 @@ import * as path from "node:path";
|
|
|
4
4
|
import type { BranchManager } from "./branches.js";
|
|
5
5
|
import { LOG_SIZE_WARNING_BYTES } from "./constants.js";
|
|
6
6
|
import type { MemoryState } from "./state.js";
|
|
7
|
-
|
|
7
|
+
|
|
8
|
+
interface StatusViewOptions {
|
|
9
|
+
compact?: boolean;
|
|
10
|
+
roadmapCharLimit?: number;
|
|
11
|
+
branchLimit?: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_COMPACT_ROADMAP_CHAR_LIMIT = 1200;
|
|
15
|
+
const DEFAULT_COMPACT_BRANCH_LIMIT = 8;
|
|
8
16
|
|
|
9
17
|
function extractCommitSummaryLine(commitEntry: string): string {
|
|
10
18
|
const marker = "### This Commit's Contribution";
|
|
@@ -30,31 +38,91 @@ function extractCommitSummaryLine(commitEntry: string): string {
|
|
|
30
38
|
return "(unknown)";
|
|
31
39
|
}
|
|
32
40
|
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
function buildRoadmapSection(
|
|
42
|
+
lines: string[],
|
|
43
|
+
projectDir: string,
|
|
44
|
+
compact: boolean,
|
|
45
|
+
roadmapCharLimit: number
|
|
46
|
+
): void {
|
|
40
47
|
const mainMdPath = path.join(projectDir, ".memory", "main.md");
|
|
41
|
-
if (fs.existsSync(mainMdPath)) {
|
|
42
|
-
const roadmap = fs.readFileSync(mainMdPath, "utf8").trim();
|
|
43
|
-
if (roadmap) {
|
|
44
|
-
lines.push(roadmap, "");
|
|
45
|
-
} else {
|
|
46
|
-
lines.push(
|
|
47
|
-
"Roadmap is empty. Update `.memory/main.md` with project goals and current state.",
|
|
48
|
-
""
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
} else {
|
|
48
|
+
if (!fs.existsSync(mainMdPath)) {
|
|
52
49
|
lines.push(
|
|
53
50
|
"No roadmap found. Create `.memory/main.md` to set project goals.",
|
|
54
51
|
""
|
|
55
52
|
);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const roadmap = fs.readFileSync(mainMdPath, "utf8").trim();
|
|
57
|
+
if (!roadmap) {
|
|
58
|
+
lines.push(
|
|
59
|
+
"Roadmap is empty. Update `.memory/main.md` with project goals and current state.",
|
|
60
|
+
""
|
|
61
|
+
);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!compact || roadmap.length <= roadmapCharLimit) {
|
|
66
|
+
lines.push(roadmap, "");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const excerpt = roadmap.slice(0, roadmapCharLimit).trimEnd();
|
|
71
|
+
lines.push(excerpt, "");
|
|
72
|
+
lines.push(
|
|
73
|
+
`_Roadmap truncated for automatic status output. Use \`read .memory/main.md\` for full roadmap._`,
|
|
74
|
+
""
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function buildBranchesSection(
|
|
79
|
+
lines: string[],
|
|
80
|
+
state: MemoryState,
|
|
81
|
+
branches: BranchManager,
|
|
82
|
+
compact: boolean,
|
|
83
|
+
branchLimit: number
|
|
84
|
+
): void {
|
|
85
|
+
const branchList = branches.listBranches();
|
|
86
|
+
if (branchList.length === 0) {
|
|
87
|
+
return;
|
|
56
88
|
}
|
|
57
89
|
|
|
90
|
+
lines.push("## Branches", "");
|
|
91
|
+
|
|
92
|
+
const visibleBranches = compact
|
|
93
|
+
? branchList.slice(0, branchLimit)
|
|
94
|
+
: branchList;
|
|
95
|
+
for (const name of visibleBranches) {
|
|
96
|
+
const latest = branches.getLatestCommit(name);
|
|
97
|
+
const summary = latest ? extractCommitSummaryLine(latest) : "(no commits)";
|
|
98
|
+
const marker = name === state.activeBranch ? " (active)" : "";
|
|
99
|
+
lines.push(`- **${name}**${marker}: ${summary}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (compact && branchList.length > visibleBranches.length) {
|
|
103
|
+
const remaining = branchList.length - visibleBranches.length;
|
|
104
|
+
const branchLabel = remaining === 1 ? "branch" : "branches";
|
|
105
|
+
lines.push(`- ... ${remaining} more ${branchLabel} not shown.`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
lines.push("");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function buildStatusView(
|
|
112
|
+
state: MemoryState,
|
|
113
|
+
branches: BranchManager,
|
|
114
|
+
projectDir: string,
|
|
115
|
+
options: StatusViewOptions = {}
|
|
116
|
+
): string {
|
|
117
|
+
const compact = options.compact ?? false;
|
|
118
|
+
const roadmapCharLimit =
|
|
119
|
+
options.roadmapCharLimit ?? DEFAULT_COMPACT_ROADMAP_CHAR_LIMIT;
|
|
120
|
+
const branchLimit = options.branchLimit ?? DEFAULT_COMPACT_BRANCH_LIMIT;
|
|
121
|
+
|
|
122
|
+
const lines = ["# Memory Status", ""];
|
|
123
|
+
|
|
124
|
+
buildRoadmapSection(lines, projectDir, compact, roadmapCharLimit);
|
|
125
|
+
|
|
58
126
|
lines.push(`Active branch: ${state.activeBranch}`, "");
|
|
59
127
|
|
|
60
128
|
const logSizeBytes = branches.getLogSizeBytes(state.activeBranch);
|
|
@@ -67,21 +135,7 @@ function buildStatusView(
|
|
|
67
135
|
);
|
|
68
136
|
}
|
|
69
137
|
|
|
70
|
-
|
|
71
|
-
if (branchList.length > 0) {
|
|
72
|
-
lines.push("## Branches", "");
|
|
73
|
-
|
|
74
|
-
for (const name of branchList) {
|
|
75
|
-
const latest = branches.getLatestCommit(name);
|
|
76
|
-
const summary = latest
|
|
77
|
-
? extractCommitSummaryLine(latest)
|
|
78
|
-
: "(no commits)";
|
|
79
|
-
const marker = name === state.activeBranch ? " (active)" : "";
|
|
80
|
-
lines.push(`- **${name}**${marker}: ${summary}`);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
lines.push("");
|
|
84
|
-
}
|
|
138
|
+
buildBranchesSection(lines, state, branches, compact, branchLimit);
|
|
85
139
|
|
|
86
140
|
lines.push("## Deep Retrieval", "");
|
|
87
141
|
lines.push("Use `read .memory/branches/<name>/commits.md` for full history.");
|
|
@@ -92,16 +146,3 @@ function buildStatusView(
|
|
|
92
146
|
|
|
93
147
|
return lines.join("\n");
|
|
94
148
|
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Execute the memory_status tool — status overview.
|
|
98
|
-
* Additional parameters are accepted for backward compatibility but ignored.
|
|
99
|
-
*/
|
|
100
|
-
export function executeMemoryStatus(
|
|
101
|
-
_params: MemoryStatusParams,
|
|
102
|
-
state: MemoryState,
|
|
103
|
-
branches: BranchManager,
|
|
104
|
-
projectDir: string
|
|
105
|
-
): string {
|
|
106
|
-
return buildStatusView(state, branches, projectDir);
|
|
107
|
-
}
|
package/src/types.ts
CHANGED
|
@@ -8,13 +8,6 @@ export interface OtaEntryInput {
|
|
|
8
8
|
observations: string[];
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export interface MemoryStatusParams {
|
|
12
|
-
level?: string;
|
|
13
|
-
branch?: string;
|
|
14
|
-
commit?: string;
|
|
15
|
-
segment?: string;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
11
|
export interface SubagentResult {
|
|
19
12
|
text: string;
|
|
20
13
|
exitCode: number;
|
package/src/memory-merge.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { BranchManager } from "./branches.js";
|
|
2
|
-
import { generateHash } from "./hash.js";
|
|
3
|
-
import type { MemoryState } from "./state.js";
|
|
4
|
-
|
|
5
|
-
interface MemoryMergeParams {
|
|
6
|
-
branch: string;
|
|
7
|
-
synthesis: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Execute the memory_merge tool — synthesize a branch back into the current branch.
|
|
12
|
-
* The agent should call memory_status BEFORE calling this.
|
|
13
|
-
*/
|
|
14
|
-
export function executeMemoryMerge(
|
|
15
|
-
params: MemoryMergeParams,
|
|
16
|
-
state: MemoryState,
|
|
17
|
-
branches: BranchManager
|
|
18
|
-
): string {
|
|
19
|
-
const { branch: sourceBranch, synthesis } = params;
|
|
20
|
-
const targetBranch = state.activeBranch;
|
|
21
|
-
|
|
22
|
-
if (sourceBranch === targetBranch) {
|
|
23
|
-
return `Cannot merge branch "${sourceBranch}" into itself.`;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!branches.branchExists(sourceBranch)) {
|
|
27
|
-
return `Branch "${sourceBranch}" not found. Available branches: ${branches.listBranches().join(", ")}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const hash = generateHash();
|
|
31
|
-
const timestamp = new Date().toISOString();
|
|
32
|
-
const summary = `Merge from ${sourceBranch}`;
|
|
33
|
-
|
|
34
|
-
const entry = [
|
|
35
|
-
"",
|
|
36
|
-
"---",
|
|
37
|
-
"",
|
|
38
|
-
`## Commit ${hash} | ${timestamp}`,
|
|
39
|
-
"",
|
|
40
|
-
`### Merge from ${sourceBranch}`,
|
|
41
|
-
"",
|
|
42
|
-
synthesis,
|
|
43
|
-
"",
|
|
44
|
-
].join("\n");
|
|
45
|
-
|
|
46
|
-
branches.appendCommit(targetBranch, entry);
|
|
47
|
-
|
|
48
|
-
state.setLastCommit(targetBranch, hash, timestamp, summary);
|
|
49
|
-
state.save();
|
|
50
|
-
|
|
51
|
-
return `Merge commit ${hash} written to branch "${targetBranch}" (merged from "${sourceBranch}").`;
|
|
52
|
-
}
|
package/src/memory-switch.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import type { BranchManager } from "./branches.js";
|
|
2
|
-
import type { MemoryState } from "./state.js";
|
|
3
|
-
|
|
4
|
-
interface MemorySwitchParams {
|
|
5
|
-
branch: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Execute the memory_switch tool — switch the active memory branch.
|
|
10
|
-
*/
|
|
11
|
-
export function executeMemorySwitch(
|
|
12
|
-
params: MemorySwitchParams,
|
|
13
|
-
state: MemoryState,
|
|
14
|
-
branches: BranchManager
|
|
15
|
-
): string {
|
|
16
|
-
const { branch } = params;
|
|
17
|
-
|
|
18
|
-
if (!branches.branchExists(branch)) {
|
|
19
|
-
return `Branch "${branch}" not found. Available branches: ${branches.listBranches().join(", ")}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
state.setActiveBranch(branch);
|
|
23
|
-
state.save();
|
|
24
|
-
|
|
25
|
-
const latest = branches.getLatestCommit(branch);
|
|
26
|
-
const summary = latest ?? "No commits yet.";
|
|
27
|
-
|
|
28
|
-
return `Switched to branch "${branch}".\n\n${summary}`;
|
|
29
|
-
}
|