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 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
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ /** 600 KB — approximately 150k-175k tokens of mixed markdown content. */
2
+ export const LOG_SIZE_WARNING_BYTES = 600 * 1024;
package/src/hash.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { randomBytes } from "node:crypto";
2
+
3
+ /**
4
+ * Generate an 8-character lowercase hex hash for memory commits.
5
+ */
6
+ export function generateHash(): string {
7
+ return randomBytes(4).toString("hex");
8
+ }