.maestro/playbooks/2026-02-25-CM-Issues-PRs/2026-02-25-Branch-Memory/BRANCH-MEMORY-01.md
This phase establishes the foundation for branch memory by adding database columns, creating a branch detection utility, and threading branch metadata through the entire observation pipeline — from the hook layer through the worker to the database. By the end, every new observation will be stored with its git branch name and commit SHA. This is the critical foundation that all subsequent phases build on.
Add schema migration and update observation types:
src/services/sqlite/migrations/runner.ts, add a new private method addObservationBranchColumns() as migration 24 following the exact pattern of addSessionCustomTitleColumn() (migration 23): check schema_versions for version 24, use PRAGMA table_info(observations) to guard each column, ALTER TABLE observations ADD COLUMN branch TEXT, ALTER TABLE observations ADD COLUMN commit_sha TEXT, insert version 24 into schema_versionsthis.addObservationBranchColumns(); as the last call in runAllMigrations()src/services/sqlite/observations/types.ts, add branch?: string and commit_sha?: string to ObservationInputbranch?: string | string[] and commit_sha?: string | string[] to GetObservationsByIdsOptions (array support enables ancestry-based IN clause filtering later)branch?: string | null and commit_sha?: string | null to AllRecentObservationRowCreate git branch detection utility:
src/services/integrations/git-branch.tsBranchInfo { branch: string | null; commitSha: string | null; }async function detectCurrentBranch(cwd: string): Promise<BranchInfo>Bun.spawn (check existing codebase for spawn patterns first — search for Bun.spawn or child_process usage to match the project's convention) to run git rev-parse --abbrev-ref HEAD for branch name and git rev-parse HEAD for commit SHA, both with { cwd } option{ branch: null, commitSha: null } on any failure (no git repo, git not installed, etc.). This function runs inside hooks where stderr is suppressed and errors must never crash the processgit rev-parse --abbrev-ref HEAD returns "HEAD", set branch to null but still capture the commit SHAThread branch metadata through hook layer to worker POST body:
NormalizedHookInput type definition (search for interface NormalizedHookInput across the codebase) and add optional branch?: string and commitSha?: string fieldssrc/cli/hook-command.ts, after the input.platform = platform; line, add branch detection: import detectCurrentBranch from the new utility, call it with input.cwd (only if input.cwd is truthy), assign results to input.branch and input.commitShasrc/cli/handlers/observation.ts, add branch: input.branch ?? null and commit_sha: input.commitSha ?? null to the JSON body of the fetch() POST to /api/sessions/observations (the body currently contains contentSessionId, tool_name, tool_input, tool_response, cwd)Thread branch metadata through worker internals to database storage:
src/services/worker/http/routes/SessionRoutes.ts, in the handleObservationsByClaudeId method, extract branch and commit_sha from req.body. Pass them through to sessionManager.queueObservation() — add them to whatever data object is passed as the second argumentsessionManager.queueObservation through to where storeObservation() is actually called. Read each file in the chain (SessionManager.ts → likely SDKAgent.ts or an observation processor). Add branch and commit_sha to each intermediate data structure/interface so they reach the store call. Branch and commit_sha are metadata — they should be passed alongside the observation content, not into the SDK agent's promptsrc/services/sqlite/observations/store.ts:
branch?: string and commitSha?: string parameters to storeObservation() after the discoveryTokens parameterbranch, commit_sha after created_at_epochbranch ?? null and commitSha ?? null to the VALUES bind array (add two more ? placeholders too)VALUES (?, ?, ...) matches the column countcomputeObservationContentHash(): change the hash input from (memorySessionId || '') + (title || '') + (narrative || '') to (memorySessionId || '') + (branch || '') + (title || '') + (narrative || '') — this prevents cross-branch deduplication where identical observations on different branches would be silently dropped within the 30-second dedup windowBuild the project and verify the migration works:
npm run build-and-sync to compile all TypeScript and sync the pluginsqlite3 ~/.claude-mem/claude-mem.db "PRAGMA table_info(observations);" and confirm branch and commit_sha columns appear