pi-brain 0.1.2 → 0.1.5
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 +10 -3
- package/banner.png +0 -0
- package/package.json +3 -2
- package/skills/brain/SKILL.md +38 -30
- package/skills/brain/templates/agents-md.md +25 -8
- package/skills/brain/templates/root-agents-section.md +1 -1
- package/src/branches.ts +26 -1
- package/src/index.ts +116 -95
- package/src/memory-branch.ts +154 -9
- package/src/memory-commit.ts +16 -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,7 +1,13 @@
|
|
|
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.
|
|
4
8
|
|
|
9
|
+
Credit: This project is my implementation for Pi of this paper [Git Context Controller: Manage the Context of LLM-based Agents like Git](https://arxiv.org/html/2508.00031v1)
|
|
10
|
+
|
|
5
11
|
## Getting Started
|
|
6
12
|
|
|
7
13
|
```bash
|
|
@@ -10,7 +16,7 @@ pi install npm:pi-brain
|
|
|
10
16
|
|
|
11
17
|
Open pi in any project and say "initialize Brain" (or run `/skill:brain`). The agent creates `.memory/` and starts remembering.
|
|
12
18
|
|
|
13
|
-
That's it. The agent decides when to commit, branch, and merge — you don't need to manage anything.
|
|
19
|
+
That's it. The agent decides when to commit, branch, and merge — you don't **need** to manage anything. However, you can always prompt the agent to remember something specific if you'd like.
|
|
14
20
|
|
|
15
21
|
## How It Works
|
|
16
22
|
|
|
@@ -43,9 +49,10 @@ LLM providers cache the prefix of each request. If the prefix changes between tu
|
|
|
43
49
|
Brain avoids this entirely:
|
|
44
50
|
|
|
45
51
|
- **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** —
|
|
52
|
+
- **No per-turn prompt mutation** — Brain does not rewrite `systemPrompt` between turns. Status context is appended as message content, keeping the cached prefix stable.
|
|
53
|
+
- **Fixed tool definitions** — Tool schemas are static at startup. No tools are added or removed mid-conversation.
|
|
48
54
|
- **Subagent isolation** — Commit distillation runs in a separate API call with its own cache. The main agent's cache is never touched.
|
|
55
|
+
- **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
56
|
|
|
50
57
|
The result: Brain adds zero overhead to your prompt cache hit rate.
|
|
51
58
|
|
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.5",
|
|
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
|
|
@@ -60,6 +76,20 @@ already made, completed milestones, and planned work.
|
|
|
60
76
|
A subagent handles commit distillation — it reads your `log.md` and prior commits,
|
|
61
77
|
then produces the structured commit entry. You just provide a good `summary` string.
|
|
62
78
|
|
|
79
|
+
## After Every Commit
|
|
80
|
+
|
|
81
|
+
**Update `.memory/main.md` to reflect the current state.** The roadmap is the first
|
|
82
|
+
thing a new session reads — if it's stale, every future session starts with a wrong
|
|
83
|
+
picture. After `memory_commit` returns, review and update:
|
|
84
|
+
|
|
85
|
+
- **Current State** section — reflect what's actually true now
|
|
86
|
+
- **Key Decisions Made** — add any new decisions from this commit
|
|
87
|
+
- **Milestones** — move completed items, add new planned ones
|
|
88
|
+
|
|
89
|
+
For trivial commits that don't change the project's state, decisions, or milestones
|
|
90
|
+
(e.g., minor refactors, typo fixes), you can pass `update_roadmap: false` to skip
|
|
91
|
+
the reminder. Most commits should update the roadmap.
|
|
92
|
+
|
|
63
93
|
## When to Branch
|
|
64
94
|
|
|
65
95
|
- You want to explore an alternative approach without contaminating current thinking
|
|
@@ -72,28 +102,6 @@ then produces the structured commit entry. You just provide a good `summary` str
|
|
|
72
102
|
- The branch's findings should inform the main line of thinking
|
|
73
103
|
- Include what was learned even if the approach was abandoned
|
|
74
104
|
|
|
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
|
-
|
|
105
|
+
**Important:** Always review the source branch history BEFORE calling merge.
|
|
106
|
+
Use `read .memory/branches/<target>/commits.md` for full branch history.
|
|
81
107
|
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
|
|
|
@@ -35,10 +40,22 @@ Each commit in `commits.md` has three blocks:
|
|
|
35
40
|
|
|
36
41
|
The latest commit always contains a self-contained summary of the full branch history.
|
|
37
42
|
|
|
43
|
+
## When to Commit
|
|
44
|
+
|
|
45
|
+
Call `memory_commit` when one of these is true:
|
|
46
|
+
|
|
47
|
+
- You reached a stable decision or understanding worth preserving.
|
|
48
|
+
- You finished an exploration branch with a clear conclusion.
|
|
49
|
+
- You are about to change direction significantly.
|
|
50
|
+
- You completed meaningful progress and are about to end the session.
|
|
51
|
+
- The extension warns that `log.md` is getting large.
|
|
52
|
+
- You are about to claim the task is complete or hand off to another agent.
|
|
53
|
+
|
|
38
54
|
## Conventions
|
|
39
55
|
|
|
40
56
|
- **Agent-driven**: You decide when to commit, branch, and merge
|
|
41
57
|
- **Decisions over details**: Capture "why", not "what" — git tracks file changes
|
|
42
58
|
- **Rolling summaries**: Each commit re-synthesizes all prior progress
|
|
43
59
|
- **No direct log.md writes**: The extension maintains log.md automatically
|
|
44
|
-
- **
|
|
60
|
+
- **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)
|
|
61
|
+
- **Keep main.md current**: After every commit, update `.memory/main.md` to reflect the new state — current state, decisions, and milestones. The roadmap is the first thing new sessions read; stale roadmaps cause wrong orientation. For trivial commits that don't change project state (e.g., minor refactors), pass `update_roadmap: false` to skip the reminder.
|
|
@@ -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,54 @@ 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.String({
|
|
131
|
+
enum: ["create", "switch", "merge"],
|
|
132
|
+
description: 'Action to perform: "create", "switch", or "merge"',
|
|
133
|
+
}),
|
|
134
|
+
name: Type.Optional(
|
|
135
|
+
Type.String({ description: "Branch name (required for create)" })
|
|
136
|
+
),
|
|
137
|
+
purpose: Type.Optional(
|
|
138
|
+
Type.String({
|
|
139
|
+
description: "Why this branch exists (required for create)",
|
|
140
|
+
})
|
|
141
|
+
),
|
|
142
|
+
branch: Type.Optional(
|
|
143
|
+
Type.String({
|
|
144
|
+
description: "Target branch (required for switch and merge)",
|
|
145
|
+
})
|
|
146
|
+
),
|
|
147
|
+
synthesis: Type.Optional(
|
|
148
|
+
Type.String({ description: "Synthesized insight (required for merge)" })
|
|
149
|
+
),
|
|
166
150
|
}),
|
|
167
|
-
|
|
151
|
+
execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
168
152
|
if (
|
|
169
153
|
!tryLoad(ctx) ||
|
|
170
154
|
!isMemoryReady(state, branchManager) ||
|
|
171
155
|
!branchManager
|
|
172
156
|
) {
|
|
173
|
-
|
|
157
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
158
|
+
return Promise.resolve(
|
|
159
|
+
createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE)
|
|
160
|
+
);
|
|
174
161
|
}
|
|
175
162
|
|
|
176
163
|
const previousBranch = state.activeBranch;
|
|
177
|
-
const result =
|
|
164
|
+
const result = executeMemoryBranch(params, state, branchManager, ctx.cwd);
|
|
178
165
|
|
|
179
166
|
if (state.activeBranch !== previousBranch) {
|
|
180
167
|
upsertCurrentSession(state, ctx);
|
|
181
168
|
}
|
|
182
169
|
|
|
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));
|
|
170
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
171
|
+
return Promise.resolve(createTextResult(result));
|
|
208
172
|
},
|
|
209
173
|
});
|
|
210
174
|
|
|
@@ -214,7 +178,12 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
214
178
|
description: "Checkpoint a milestone in agent memory.",
|
|
215
179
|
parameters: Type.Object({
|
|
216
180
|
summary: Type.String({ description: "Short summary of this checkpoint" }),
|
|
217
|
-
update_roadmap: Type.Optional(
|
|
181
|
+
update_roadmap: Type.Optional(
|
|
182
|
+
Type.Boolean({
|
|
183
|
+
description:
|
|
184
|
+
"Update .memory/main.md after commit. Defaults to true — set false to skip for trivial commits.",
|
|
185
|
+
})
|
|
186
|
+
),
|
|
218
187
|
}),
|
|
219
188
|
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
220
189
|
if (
|
|
@@ -222,6 +191,7 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
222
191
|
!isMemoryReady(state, branchManager) ||
|
|
223
192
|
!branchManager
|
|
224
193
|
) {
|
|
194
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
225
195
|
return createTextResult(MEMORY_NOT_INITIALIZED_MESSAGE);
|
|
226
196
|
}
|
|
227
197
|
|
|
@@ -246,9 +216,12 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
246
216
|
params.summary,
|
|
247
217
|
commitContent,
|
|
248
218
|
state,
|
|
249
|
-
branchManager
|
|
219
|
+
branchManager,
|
|
220
|
+
ctx.cwd,
|
|
221
|
+
params.update_roadmap
|
|
250
222
|
);
|
|
251
223
|
|
|
224
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
252
225
|
return createTextResult(message);
|
|
253
226
|
},
|
|
254
227
|
});
|
|
@@ -257,14 +230,15 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
257
230
|
state = new MemoryState(ctx.cwd);
|
|
258
231
|
state.load();
|
|
259
232
|
branchManager = new BranchManager(ctx.cwd);
|
|
233
|
+
statusInjected = false;
|
|
260
234
|
|
|
261
235
|
if (!state.isInitialized) {
|
|
236
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
262
237
|
return;
|
|
263
238
|
}
|
|
264
239
|
|
|
265
240
|
upsertCurrentSession(state, ctx);
|
|
266
241
|
|
|
267
|
-
const turnCount = branchManager.getLogTurnCount(state.activeBranch);
|
|
268
242
|
const logSizeBytes = branchManager.getLogSizeBytes(state.activeBranch);
|
|
269
243
|
|
|
270
244
|
if (logSizeBytes >= LOG_SIZE_WARNING_BYTES) {
|
|
@@ -273,20 +247,66 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
273
247
|
`Brain: log.md is large (${sizeKB} KB). You should commit to distill this into structured memory.`,
|
|
274
248
|
"warning"
|
|
275
249
|
);
|
|
276
|
-
} else {
|
|
277
|
-
ctx.ui.notify(
|
|
278
|
-
`Brain active: branch "${state.activeBranch}" (${turnCount} uncommitted turn${turnCount === 1 ? "" : "s"}).`,
|
|
279
|
-
"info"
|
|
280
|
-
);
|
|
281
250
|
}
|
|
251
|
+
|
|
252
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
pi.on("session_switch", (_event, ctx) => {
|
|
256
|
+
statusInjected = false;
|
|
257
|
+
|
|
258
|
+
state = new MemoryState(ctx.cwd);
|
|
259
|
+
state.load();
|
|
260
|
+
branchManager = new BranchManager(ctx.cwd);
|
|
261
|
+
|
|
262
|
+
if (state.isInitialized) {
|
|
263
|
+
upsertCurrentSession(state, ctx);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
pi.on("before_agent_start", (_event: BeforeAgentStartEvent, ctx) => {
|
|
270
|
+
if (statusInjected) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (
|
|
275
|
+
!tryLoad(ctx) ||
|
|
276
|
+
!isMemoryReady(state, branchManager) ||
|
|
277
|
+
!branchManager
|
|
278
|
+
) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
statusInjected = true;
|
|
283
|
+
|
|
284
|
+
const status = buildStatusView(state, branchManager, ctx.cwd, {
|
|
285
|
+
compact: true,
|
|
286
|
+
roadmapCharLimit: BEFORE_AGENT_START_ROADMAP_CHAR_LIMIT,
|
|
287
|
+
branchLimit: BEFORE_AGENT_START_BRANCH_LIMIT,
|
|
288
|
+
});
|
|
289
|
+
return {
|
|
290
|
+
message: {
|
|
291
|
+
customType: "brain-status",
|
|
292
|
+
content: status,
|
|
293
|
+
display: true,
|
|
294
|
+
details: {},
|
|
295
|
+
},
|
|
296
|
+
};
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
pi.on("session_compact", () => {
|
|
300
|
+
statusInjected = false;
|
|
282
301
|
});
|
|
283
302
|
|
|
284
303
|
pi.on("resources_discover", () => ({
|
|
285
304
|
skillPaths: [resolveSkillPath()],
|
|
286
305
|
}));
|
|
287
306
|
|
|
288
|
-
pi.on("turn_end", (event) => {
|
|
307
|
+
pi.on("turn_end", (event, ctx) => {
|
|
289
308
|
if (!isMemoryReady(state, branchManager) || !branchManager) {
|
|
309
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
290
310
|
return;
|
|
291
311
|
}
|
|
292
312
|
|
|
@@ -297,6 +317,7 @@ export default function activate(pi: ExtensionAPI) {
|
|
|
297
317
|
|
|
298
318
|
const entry = formatOtaEntry(input);
|
|
299
319
|
branchManager.appendLog(state.activeBranch, entry);
|
|
320
|
+
setBrainFooterStatus(ctx, state, branchManager);
|
|
300
321
|
});
|
|
301
322
|
|
|
302
323
|
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,15 @@ 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,
|
|
38
|
+
updateRoadmap?: boolean
|
|
36
39
|
): string {
|
|
37
40
|
const branch = state.activeBranch;
|
|
38
41
|
const hash = generateHash();
|
|
@@ -54,5 +57,15 @@ export function finalizeMemoryCommit(
|
|
|
54
57
|
state.setLastCommit(branch, hash, timestamp, summary);
|
|
55
58
|
state.save();
|
|
56
59
|
|
|
57
|
-
|
|
60
|
+
const resultText = `Commit ${hash} written to branch "${branch}".`;
|
|
61
|
+
const status = buildStatusView(state, branches, projectDir, {
|
|
62
|
+
compact: true,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const roadmapReminder =
|
|
66
|
+
updateRoadmap === false
|
|
67
|
+
? ""
|
|
68
|
+
: "\n\n**Action required:** Update `.memory/main.md` to reflect this commit — current state, decisions, and milestones. The roadmap is the first thing new sessions read.";
|
|
69
|
+
|
|
70
|
+
return `${resultText}\n\n${status}${roadmapReminder}`;
|
|
58
71
|
}
|
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
|
-
}
|