pi-brain 0.1.1
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/LICENSE +21 -0
- package/README.md +109 -0
- package/agents/memory-committer.md +49 -0
- package/package.json +79 -0
- package/skills/brain/SKILL.md +99 -0
- package/skills/brain/scripts/brain-init.sh +87 -0
- package/skills/brain/templates/agents-md.md +44 -0
- package/skills/brain/templates/root-agents-section.md +7 -0
- package/src/branches.ts +125 -0
- package/src/constants.ts +2 -0
- package/src/hash.ts +8 -0
- package/src/index.ts +310 -0
- package/src/memory-branch.ts +28 -0
- package/src/memory-commit.ts +58 -0
- package/src/memory-context.ts +107 -0
- package/src/memory-merge.ts +52 -0
- package/src/memory-switch.ts +29 -0
- package/src/ota-formatter.ts +31 -0
- package/src/ota-logger.ts +132 -0
- package/src/state.ts +143 -0
- package/src/subagent.ts +342 -0
- package/src/types.ts +22 -0
- package/src/yaml.ts +221 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Will Hampson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# pi-brain
|
|
2
|
+
|
|
3
|
+
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
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pi install npm:pi-brain
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Open pi in any project and say "initialize Brain" (or run `/skill:brain`). The agent creates `.memory/` and starts remembering.
|
|
12
|
+
|
|
13
|
+
That's it. The agent decides when to commit, branch, and merge — you don't need to manage anything.
|
|
14
|
+
|
|
15
|
+
## How It Works
|
|
16
|
+
|
|
17
|
+
Brain adds five tools and a few lifecycle hooks to pi. The design is simple: the agent works normally, and Brain records what happens in the background.
|
|
18
|
+
|
|
19
|
+
**Every turn**, Brain appends a structured log entry to `.memory/branches/<branch>/log.md`. This happens automatically via the `turn_end` hook — the agent doesn't call anything.
|
|
20
|
+
|
|
21
|
+
**When the agent reaches a milestone**, it calls `memory_commit` with a short summary. Brain spawns a subagent in a fresh context window that reads the raw log, distills it into a structured commit (decisions, rationale, what was tried and rejected), and appends it to `commits.md`. The log is then cleared.
|
|
22
|
+
|
|
23
|
+
**Each commit is self-contained.** It includes a rolling summary of all prior commits, so the latest commit always tells the full branch story. A new session can read one commit and know everything.
|
|
24
|
+
|
|
25
|
+
**Branching and merging** work like you'd expect. The agent branches to explore alternatives without contaminating the main line, then merges conclusions back with a synthesis.
|
|
26
|
+
|
|
27
|
+
### The Five Tools
|
|
28
|
+
|
|
29
|
+
| Tool | What it does |
|
|
30
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
31
|
+
| `memory_status` | Quick status overview — active branch, latest commit, turn count |
|
|
32
|
+
| `memory_commit` | Checkpoint a milestone (subagent distills the log) |
|
|
33
|
+
| `memory_branch` | Create a branch for exploration |
|
|
34
|
+
| `memory_switch` | Switch between branches |
|
|
35
|
+
| `memory_merge` | Merge insights from one branch into another |
|
|
36
|
+
|
|
37
|
+
For deep retrieval, the agent uses pi's built-in `read` tool on `.memory/` files directly. No special API needed.
|
|
38
|
+
|
|
39
|
+
## Prompt Cache Safety
|
|
40
|
+
|
|
41
|
+
LLM providers cache the prefix of each request. If the prefix changes between turns, the cache misses and you pay full latency and cost. Many memory systems break this by injecting dynamic state into the system prompt.
|
|
42
|
+
|
|
43
|
+
Brain avoids this entirely:
|
|
44
|
+
|
|
45
|
+
- **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 injection** — No `before_agent_start` hook, no changing content before the conversation. The agent retrieves memory on demand via tool calls, which appear as conversation messages appended at the end (outside the cached prefix).
|
|
47
|
+
- **Fixed tool definitions** — All five tools are registered at startup with static schemas. No tools added or removed mid-conversation.
|
|
48
|
+
- **Subagent isolation** — Commit distillation runs in a separate API call with its own cache. The main agent's cache is never touched.
|
|
49
|
+
|
|
50
|
+
The result: Brain adds zero overhead to your prompt cache hit rate.
|
|
51
|
+
|
|
52
|
+
## What Gets Created
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
.memory/
|
|
56
|
+
├── AGENTS.md # Protocol reference
|
|
57
|
+
├── main.md # Project roadmap (agent-authored)
|
|
58
|
+
├── state.yaml # Active branch, session tracking
|
|
59
|
+
└── branches/
|
|
60
|
+
└── main/
|
|
61
|
+
├── commits.md # Distilled milestone snapshots
|
|
62
|
+
├── log.md # Raw turn log (gitignored)
|
|
63
|
+
└── metadata.yaml # Structured context
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Everything in `.memory/` is tracked in git except `log.md` (transient working state). This means memory is shared across machines and team members.
|
|
67
|
+
|
|
68
|
+
## Install Options
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# From npm (recommended)
|
|
72
|
+
pi install npm:pi-brain
|
|
73
|
+
|
|
74
|
+
# From git (latest)
|
|
75
|
+
pi install git:github.com/Whamp/pi-brain
|
|
76
|
+
|
|
77
|
+
# Pinned version (npm)
|
|
78
|
+
pi install npm:pi-brain@0.1.0
|
|
79
|
+
|
|
80
|
+
# Pinned version (git)
|
|
81
|
+
pi install git:github.com/Whamp/pi-brain@v0.1.0
|
|
82
|
+
|
|
83
|
+
# Project-local (shared via .pi/settings.json)
|
|
84
|
+
pi install -l npm:pi-brain
|
|
85
|
+
|
|
86
|
+
# Try without installing
|
|
87
|
+
pi -e npm:pi-brain
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Development
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
git clone https://github.com/Whamp/pi-brain.git
|
|
94
|
+
cd pi-brain
|
|
95
|
+
pnpm install --prod=false
|
|
96
|
+
pnpm run check # lint + typecheck + format + tests + deadcode + secrets
|
|
97
|
+
|
|
98
|
+
pi -e ./src/index.ts # run pi with extension loaded from source
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
| Command | Purpose |
|
|
102
|
+
| ------------------ | --------------- |
|
|
103
|
+
| `pnpm run check` | Full validation |
|
|
104
|
+
| `pnpm run test` | Tests only |
|
|
105
|
+
| `pnpm run release` | Bump, tag, push |
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: memory-committer
|
|
3
|
+
description: Distills OTA logs into structured memory commit entries
|
|
4
|
+
tools: read, grep, find, ls
|
|
5
|
+
model: google-antigravity/gemini-3-flash
|
|
6
|
+
skills: brain
|
|
7
|
+
extensions:
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
You are a commit distiller for Brain (agent memory).
|
|
11
|
+
|
|
12
|
+
Before doing anything else, read `.memory/AGENTS.md` for the full protocol reference.
|
|
13
|
+
|
|
14
|
+
Your job is to read raw OTA logs and previous commits, then produce a structured commit entry.
|
|
15
|
+
|
|
16
|
+
You will receive a task containing:
|
|
17
|
+
|
|
18
|
+
- The branch name
|
|
19
|
+
- The commit summary
|
|
20
|
+
- Paths to the OTA log and commits file
|
|
21
|
+
|
|
22
|
+
Steps:
|
|
23
|
+
|
|
24
|
+
1. Read `.memory/AGENTS.md`
|
|
25
|
+
2. Read the OTA log for the branch
|
|
26
|
+
3. Read the previous commits (if any) for rolling summary context
|
|
27
|
+
4. Respond with EXACTLY three markdown blocks, nothing else
|
|
28
|
+
|
|
29
|
+
### Branch Purpose
|
|
30
|
+
|
|
31
|
+
1-2 sentences restating or refining what this branch is for.
|
|
32
|
+
|
|
33
|
+
### Previous Progress Summary
|
|
34
|
+
|
|
35
|
+
A single self-contained rolling summary that synthesizes ALL prior commits into one narrative. A new reader should understand the full branch history from this section alone. If there is no previous commit, write "Initial commit."
|
|
36
|
+
|
|
37
|
+
### This Commit's Contribution
|
|
38
|
+
|
|
39
|
+
3-7 concise bullets covering what was just learned, decided, or understood. Focus on:
|
|
40
|
+
|
|
41
|
+
- Decisions and their rationale
|
|
42
|
+
- What was tried and rejected (negative results matter)
|
|
43
|
+
- Key findings or conclusions
|
|
44
|
+
|
|
45
|
+
Do NOT include:
|
|
46
|
+
|
|
47
|
+
- Implementation details (the code captures "what")
|
|
48
|
+
- Filler or padding bullets
|
|
49
|
+
- Anything outside those three blocks
|
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pi-brain",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Versioned memory extension for the pi coding agent",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"brain",
|
|
7
|
+
"context",
|
|
8
|
+
"memory",
|
|
9
|
+
"pi",
|
|
10
|
+
"pi-coding-agent",
|
|
11
|
+
"pi-package"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/Whamp/pi-brain#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/Whamp/pi-brain/issues"
|
|
16
|
+
},
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/Whamp/pi-brain.git"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"src/",
|
|
24
|
+
"!src/**/*.test.ts",
|
|
25
|
+
"skills/",
|
|
26
|
+
"agents/",
|
|
27
|
+
"package.json",
|
|
28
|
+
"README.md"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"scripts": {
|
|
32
|
+
"lint": "oxlint -c .oxlintrc.json",
|
|
33
|
+
"lint:fix": "oxlint -c .oxlintrc.json --fix",
|
|
34
|
+
"format": "oxfmt --config .oxfmtrc.jsonc",
|
|
35
|
+
"format:check": "oxfmt --config .oxfmtrc.jsonc --check",
|
|
36
|
+
"typecheck": "tsc --noEmit",
|
|
37
|
+
"test": "vitest run",
|
|
38
|
+
"deadcode": "knip",
|
|
39
|
+
"duplicates": "jscpd src/",
|
|
40
|
+
"secrets": "gitleaks detect --source . --no-git",
|
|
41
|
+
"check": "bash scripts/check.sh",
|
|
42
|
+
"fix": "bash scripts/fix.sh",
|
|
43
|
+
"release": "changelogen --release && git push --follow-tags",
|
|
44
|
+
"prepare": "node -e \"try{require.resolve('husky')}catch(e){process.exit(0)}\" && husky"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@mariozechner/pi-coding-agent": "^0.54.2",
|
|
48
|
+
"@types/node": "^25.3.0",
|
|
49
|
+
"changelogen": "^0.6.2",
|
|
50
|
+
"fast-check": "^4.5.3",
|
|
51
|
+
"gitleaks": "^1.0.0",
|
|
52
|
+
"husky": "^9.1.7",
|
|
53
|
+
"jscpd": "^4.0.8",
|
|
54
|
+
"knip": "^5.85.0",
|
|
55
|
+
"lint-staged": "^16.2.7",
|
|
56
|
+
"oxfmt": "^0.35.0",
|
|
57
|
+
"oxlint": "^1.50.0",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"ultracite": "^7.2.3",
|
|
60
|
+
"vitest": "^4.0.18"
|
|
61
|
+
},
|
|
62
|
+
"peerDependencies": {
|
|
63
|
+
"@mariozechner/pi-coding-agent": "*",
|
|
64
|
+
"@sinclair/typebox": "*"
|
|
65
|
+
},
|
|
66
|
+
"pnpm": {
|
|
67
|
+
"onlyBuiltDependencies": [
|
|
68
|
+
"esbuild"
|
|
69
|
+
]
|
|
70
|
+
},
|
|
71
|
+
"pi": {
|
|
72
|
+
"extensions": [
|
|
73
|
+
"./src/index.ts"
|
|
74
|
+
],
|
|
75
|
+
"skills": [
|
|
76
|
+
"./skills"
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: brain
|
|
3
|
+
description: Use when working on a project with Brain agent memory management. Triggers on memory_status, memory_commit, memory_branch, memory_merge, memory_switch tool usage, or when the project has a .memory/ directory.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Brain — Agent Memory
|
|
7
|
+
|
|
8
|
+
## Initialization
|
|
9
|
+
|
|
10
|
+
When the `<skill>` tag loads this file, it includes a `location` attribute with the
|
|
11
|
+
absolute path to this SKILL.md. Use that to derive the init script path:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bash "/absolute/path/to/skills/brain/scripts/brain-init.sh"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Replace `/absolute/path/to/skills/brain` with the skill directory shown in the
|
|
18
|
+
`<skill>` tag's `location` attribute (strip the `/SKILL.md` suffix).
|
|
19
|
+
|
|
20
|
+
### After Init
|
|
21
|
+
|
|
22
|
+
1. **Tell the user to run `/reload`** — the memory tools won't detect the new `.memory/`
|
|
23
|
+
directory until the extension reloads.
|
|
24
|
+
2. **Write `.memory/main.md`** — the project roadmap (see below).
|
|
25
|
+
3. **Call `memory_status`** to verify Brain is active.
|
|
26
|
+
4. **Make your first commit** when you reach a meaningful milestone.
|
|
27
|
+
|
|
28
|
+
### Writing main.md — Greenfield vs Brownfield
|
|
29
|
+
|
|
30
|
+
**New project (no existing code):** Write goals, intended architecture, and open
|
|
31
|
+
questions as you understand them from conversation with the user.
|
|
32
|
+
|
|
33
|
+
**Existing project:** Orient yourself first:
|
|
34
|
+
|
|
35
|
+
- Read `AGENTS.md`, `README.md`, `package.json` (or equivalent)
|
|
36
|
+
- Scan recent git history (`git log --oneline -20`)
|
|
37
|
+
- Read any specs or plans in `docs/`
|
|
38
|
+
|
|
39
|
+
Then write the roadmap covering: project purpose, current state, key decisions
|
|
40
|
+
already made, completed milestones, and planned work.
|
|
41
|
+
|
|
42
|
+
## When to Commit
|
|
43
|
+
|
|
44
|
+
- You've reached a stable understanding or decision
|
|
45
|
+
- You've completed an exploration and have a conclusion
|
|
46
|
+
- You're about to change direction significantly
|
|
47
|
+
- A meaningful amount of work has accumulated (use judgment, not a fixed interval)
|
|
48
|
+
- Before ending a session if significant progress was made
|
|
49
|
+
- **When the extension warns that log.md is large** — even mundane activity is
|
|
50
|
+
worth distilling. A commit that records "routine maintenance, no significant
|
|
51
|
+
decisions" tells future agents what was already explored.
|
|
52
|
+
|
|
53
|
+
## How to Write Good Commits
|
|
54
|
+
|
|
55
|
+
- Focus on decisions and rationale, not implementation details
|
|
56
|
+
- Capture "why" more than "what" — the code captures "what"
|
|
57
|
+
- Be specific: "Chose PostgreSQL over MongoDB because ACID compliance is required
|
|
58
|
+
for financial transactions" not "Chose database"
|
|
59
|
+
|
|
60
|
+
A subagent handles commit distillation — it reads your `log.md` and prior commits,
|
|
61
|
+
then produces the structured commit entry. You just provide a good `summary` string.
|
|
62
|
+
|
|
63
|
+
## When to Branch
|
|
64
|
+
|
|
65
|
+
- You want to explore an alternative approach without contaminating current thinking
|
|
66
|
+
- You're prototyping something uncertain
|
|
67
|
+
- You want to compare two design hypotheses
|
|
68
|
+
|
|
69
|
+
## When to Merge
|
|
70
|
+
|
|
71
|
+
- A branch has reached a conclusion (positive or negative)
|
|
72
|
+
- The branch's findings should inform the main line of thinking
|
|
73
|
+
- Include what was learned even if the approach was abandoned
|
|
74
|
+
|
|
75
|
+
**Important:** Always review the source branch history BEFORE calling `memory_merge`.
|
|
76
|
+
Use:
|
|
77
|
+
|
|
78
|
+
- `memory_status` for high-level status
|
|
79
|
+
- `read .memory/branches/<target>/commits.md` for full branch history
|
|
80
|
+
|
|
81
|
+
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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# brain-init.sh — One-time Brain memory initialization
|
|
3
|
+
# Creates .memory/ directory structure and appends Brain section to root AGENTS.md
|
|
4
|
+
# Idempotent: safe to run multiple times without clobbering existing content.
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
TEMPLATES_DIR="$SCRIPT_DIR/../templates"
|
|
10
|
+
|
|
11
|
+
MEMORY_DIR=".memory"
|
|
12
|
+
BRANCHES_DIR="$MEMORY_DIR/branches/main"
|
|
13
|
+
STATE_FILE="$MEMORY_DIR/state.yaml"
|
|
14
|
+
MEMORY_AGENTS_FILE="$MEMORY_DIR/AGENTS.md"
|
|
15
|
+
MAIN_MD_FILE="$MEMORY_DIR/main.md"
|
|
16
|
+
ROOT_AGENTS_FILE="AGENTS.md"
|
|
17
|
+
GITIGNORE_FILE=".gitignore"
|
|
18
|
+
LOG_IGNORE_PATTERN=".memory/branches/*/log.md"
|
|
19
|
+
|
|
20
|
+
# --- Create .memory directory structure (skip if already exists) ---
|
|
21
|
+
|
|
22
|
+
if [ ! -d "$BRANCHES_DIR" ]; then
|
|
23
|
+
mkdir -p "$BRANCHES_DIR"
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
if [ ! -f "$BRANCHES_DIR/log.md" ]; then
|
|
27
|
+
touch "$BRANCHES_DIR/log.md"
|
|
28
|
+
fi
|
|
29
|
+
|
|
30
|
+
if [ ! -f "$BRANCHES_DIR/commits.md" ]; then
|
|
31
|
+
cat > "$BRANCHES_DIR/commits.md" <<'EOF'
|
|
32
|
+
# main
|
|
33
|
+
|
|
34
|
+
**Purpose:** Main project memory branch
|
|
35
|
+
EOF
|
|
36
|
+
fi
|
|
37
|
+
|
|
38
|
+
if [ ! -f "$BRANCHES_DIR/metadata.yaml" ]; then
|
|
39
|
+
touch "$BRANCHES_DIR/metadata.yaml"
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [ ! -f "$MAIN_MD_FILE" ]; then
|
|
43
|
+
touch "$MAIN_MD_FILE"
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# --- Write state.yaml (skip if already exists) ---
|
|
47
|
+
|
|
48
|
+
if [ ! -f "$STATE_FILE" ]; then
|
|
49
|
+
cat > "$STATE_FILE" <<EOF
|
|
50
|
+
active_branch: main
|
|
51
|
+
initialized: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
52
|
+
EOF
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# --- Write .memory/AGENTS.md from template (always overwrite — it's a reference doc) ---
|
|
56
|
+
|
|
57
|
+
if [ -f "$TEMPLATES_DIR/agents-md.md" ]; then
|
|
58
|
+
cp "$TEMPLATES_DIR/agents-md.md" "$MEMORY_AGENTS_FILE"
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# --- Append Brain section to root AGENTS.md (idempotent) ---
|
|
62
|
+
|
|
63
|
+
if [ ! -f "$ROOT_AGENTS_FILE" ]; then
|
|
64
|
+
touch "$ROOT_AGENTS_FILE"
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
if ! grep -q "## Brain" "$ROOT_AGENTS_FILE" 2>/dev/null; then
|
|
68
|
+
if [ -s "$ROOT_AGENTS_FILE" ]; then
|
|
69
|
+
echo "" >> "$ROOT_AGENTS_FILE"
|
|
70
|
+
fi
|
|
71
|
+
cat "$TEMPLATES_DIR/root-agents-section.md" >> "$ROOT_AGENTS_FILE"
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# --- Ignore transient branch logs in git (idempotent) ---
|
|
75
|
+
|
|
76
|
+
if [ ! -f "$GITIGNORE_FILE" ]; then
|
|
77
|
+
touch "$GITIGNORE_FILE"
|
|
78
|
+
fi
|
|
79
|
+
|
|
80
|
+
if ! grep -Fxq "$LOG_IGNORE_PATTERN" "$GITIGNORE_FILE"; then
|
|
81
|
+
if [ -s "$GITIGNORE_FILE" ]; then
|
|
82
|
+
echo "" >> "$GITIGNORE_FILE"
|
|
83
|
+
fi
|
|
84
|
+
echo "$LOG_IGNORE_PATTERN" >> "$GITIGNORE_FILE"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
echo "Brain memory initialized successfully."
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Brain — Agent Memory
|
|
2
|
+
|
|
3
|
+
This directory contains your project's agent memory, managed by the Brain extension.
|
|
4
|
+
|
|
5
|
+
## Tools
|
|
6
|
+
|
|
7
|
+
| Tool | Purpose |
|
|
8
|
+
| --------------- | --------------------------------------- |
|
|
9
|
+
| `memory_commit` | Checkpoint a milestone in understanding |
|
|
10
|
+
| `memory_branch` | Create a memory branch for exploration |
|
|
11
|
+
| `memory_merge` | Synthesize branch conclusions |
|
|
12
|
+
| `memory_status` | Multi-resolution retrieval of memory |
|
|
13
|
+
| `memory_switch` | Switch active memory branch |
|
|
14
|
+
|
|
15
|
+
## File Structure
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
.memory/
|
|
19
|
+
├── AGENTS.md # This file — protocol reference
|
|
20
|
+
├── main.md # Project roadmap (agent-authored)
|
|
21
|
+
└── branches/
|
|
22
|
+
└── <branch-name>/
|
|
23
|
+
├── commits.md # Milestone memory snapshots
|
|
24
|
+
├── log.md # OTA trace since last commit (auto)
|
|
25
|
+
└── metadata.yaml # Structured context
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Commit Format
|
|
29
|
+
|
|
30
|
+
Each commit in `commits.md` has three blocks:
|
|
31
|
+
|
|
32
|
+
- **Branch Purpose** — Why this branch exists
|
|
33
|
+
- **Previous Progress Summary** — Rolling compression of all prior commits
|
|
34
|
+
- **This Commit's Contribution** — What was just learned or decided
|
|
35
|
+
|
|
36
|
+
The latest commit always contains a self-contained summary of the full branch history.
|
|
37
|
+
|
|
38
|
+
## Conventions
|
|
39
|
+
|
|
40
|
+
- **Agent-driven**: You decide when to commit, branch, and merge
|
|
41
|
+
- **Decisions over details**: Capture "why", not "what" — git tracks file changes
|
|
42
|
+
- **Rolling summaries**: Each commit re-synthesizes all prior progress
|
|
43
|
+
- **No direct log.md writes**: The extension maintains log.md automatically
|
|
44
|
+
- **Call `memory_status` first**: Always review context before merging or starting new work
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## Brain — Agent Memory
|
|
2
|
+
|
|
3
|
+
This project uses Brain for agent memory management.
|
|
4
|
+
|
|
5
|
+
**Start here when orienting:** Read `.memory/main.md` for the project roadmap, key decisions, and open problems.
|
|
6
|
+
Read `.memory/AGENTS.md` for the full Brain protocol reference.
|
|
7
|
+
Tools: memory_commit, memory_branch, memory_merge, memory_switch, memory_status
|
package/src/branches.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Manages `.memory/branches/` directory operations.
|
|
6
|
+
* Each branch has: log.md, commits.md, metadata.yaml.
|
|
7
|
+
*/
|
|
8
|
+
export class BranchManager {
|
|
9
|
+
private readonly branchesDir: string;
|
|
10
|
+
|
|
11
|
+
constructor(projectDir: string) {
|
|
12
|
+
this.branchesDir = path.join(projectDir, ".memory", "branches");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
createBranch(name: string, purpose: string): void {
|
|
16
|
+
const branchDir = path.join(this.branchesDir, name);
|
|
17
|
+
fs.mkdirSync(branchDir, { recursive: true });
|
|
18
|
+
fs.writeFileSync(path.join(branchDir, "log.md"), "");
|
|
19
|
+
fs.writeFileSync(
|
|
20
|
+
path.join(branchDir, "commits.md"),
|
|
21
|
+
`# ${name}\n\n**Purpose:** ${purpose}\n`
|
|
22
|
+
);
|
|
23
|
+
fs.writeFileSync(path.join(branchDir, "metadata.yaml"), "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
appendLog(branch: string, content: string): void {
|
|
27
|
+
const logPath = this.logPath(branch);
|
|
28
|
+
fs.appendFileSync(logPath, content);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
readLog(branch: string): string {
|
|
32
|
+
const logPath = this.logPath(branch);
|
|
33
|
+
if (!fs.existsSync(logPath)) {
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
return fs.readFileSync(logPath, "utf8");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
clearLog(branch: string): void {
|
|
40
|
+
const logPath = this.logPath(branch);
|
|
41
|
+
if (fs.existsSync(logPath)) {
|
|
42
|
+
fs.writeFileSync(logPath, "");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
appendCommit(branch: string, entry: string): void {
|
|
47
|
+
const commitsPath = this.commitsPath(branch);
|
|
48
|
+
fs.appendFileSync(commitsPath, entry);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
readCommits(branch: string): string {
|
|
52
|
+
const commitsPath = this.commitsPath(branch);
|
|
53
|
+
if (!fs.existsSync(commitsPath)) {
|
|
54
|
+
return "";
|
|
55
|
+
}
|
|
56
|
+
return fs.readFileSync(commitsPath, "utf8");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
readMetadata(branch: string): string {
|
|
60
|
+
const metaPath = path.join(this.branchesDir, branch, "metadata.yaml");
|
|
61
|
+
if (!fs.existsSync(metaPath)) {
|
|
62
|
+
return "";
|
|
63
|
+
}
|
|
64
|
+
return fs.readFileSync(metaPath, "utf8");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
listBranches(): string[] {
|
|
68
|
+
if (!fs.existsSync(this.branchesDir)) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return fs.readdirSync(this.branchesDir).filter((entry) => {
|
|
73
|
+
const fullPath = path.join(this.branchesDir, entry);
|
|
74
|
+
return fs.statSync(fullPath).isDirectory();
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
branchExists(name: string): boolean {
|
|
79
|
+
const branchDir = path.join(this.branchesDir, name);
|
|
80
|
+
return fs.existsSync(branchDir) && fs.statSync(branchDir).isDirectory();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getLogSizeBytes(branch: string): number {
|
|
84
|
+
const lp = this.logPath(branch);
|
|
85
|
+
if (!fs.existsSync(lp)) {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
return fs.statSync(lp).size;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
getLogTurnCount(branch: string): number {
|
|
92
|
+
const log = this.readLog(branch);
|
|
93
|
+
if (log === "") {
|
|
94
|
+
return 0;
|
|
95
|
+
}
|
|
96
|
+
const matches = log.match(/^## Turn /gm);
|
|
97
|
+
return matches ? matches.length : 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
getLatestCommit(branch: string): string | null {
|
|
101
|
+
const commits = this.readCommits(branch);
|
|
102
|
+
if (commits === "") {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Split on commit separator (--- followed by ## Commit)
|
|
107
|
+
const parts = commits.split(/\n---\n/);
|
|
108
|
+
// Find the last part that contains a commit header
|
|
109
|
+
for (let i = parts.length - 1; i >= 0; i--) {
|
|
110
|
+
if (parts[i].includes("## Commit ")) {
|
|
111
|
+
return parts[i].trim();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private logPath(branch: string): string {
|
|
119
|
+
return path.join(this.branchesDir, branch, "log.md");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private commitsPath(branch: string): string {
|
|
123
|
+
return path.join(this.branchesDir, branch, "commits.md");
|
|
124
|
+
}
|
|
125
|
+
}
|
package/src/constants.ts
ADDED