/** * Claude Code auto-memory — the MEMORY.md Claude Code loads at the start of a * session: `/projects//memory/MEMORY.md`. * * Moved here from claude-faf-mcp (its tri-sync writer and path resolver) so * every FAF tool shares one copy, with what the audit found fixed. * * WHERE the file is — Claude Code's own rule (mirrored from Claude Code 2.1.x): * - Project root: the canonical git root of the folder — the first folder at * or above it that has a `.git` (a folder or a file). A linked worktree * resolves to its main checkout, so every worktree shares one memory. A * folder that is not in git is its own root. * - Project id: that path with every character outside [a-zA-Z0-9] replaced * by `-` (so `/home/me/my_app` → `-home-me-my-app`). An id longer than * 200 characters is cut to 200 and gets `-` plus a base-36 hash of the * full path. * - Base: CLAUDE_CODE_REMOTE_MEMORY_DIR when set, else Claude's config * folder: CLAUDE_CONFIG_DIR, else ~/.claude. * Not read: Claude Code's `autoMemoryDirectory` setting (or any other * override of the whole memory folder). When one is set, pass `memoryDir`. * * WHAT faf writes — one managed block (the same markers and injector as * CLAUDE.md). Everything else in the file is Claude's and is kept byte for * byte, CRLF line ends and a BOM included: * - no MEMORY.md → the file is created with the block * - faf's block is there → only the text between its marker lines changes * - claude-faf-mcp's earlier section (its `# Project Context (from * project.faf)` line through its `*This section is managed by tri-sync.` * line, both whole lines) → replaced in place by the block, once * - anything else → the block goes on top; nothing is removed * Markers match whole lines only (faf's own exactly), never substrings, and * only outside fenced code, raw HTML blocks and multi-line HTML comments, as * both CommonMark and a plain column-0 reading see them (a note that quotes * the block or the old section is an example, not a marker; see inject.ts). * When the block goes on top of a file whose older faf block sits in such a * region, the result's warnings say so in one line. Only a missing file reads * as "no file": any other read error is thrown and nothing is written, and a * file that is not UTF-8 is refused. The write is atomic, is refused if the * file changed on disk after faf read it, and a run that changes nothing * writes nothing. */ import type { FafData } from '../core/types.js'; export interface ClaudeMemoryOptions { /** Claude Code's config folder. Default: CLAUDE_CODE_REMOTE_MEMORY_DIR, * else CLAUDE_CONFIG_DIR, else ~/.claude. */ configDir?: string; /** The memory folder itself, when you know better than the rule (Claude * Code's `autoMemoryDirectory` setting, say). Skips the resolver. */ memoryDir?: string; } /** What `writeClaudeMemory` did. */ export type ClaudeMemoryAction = 'created' | 'updated' | 'migrated' | 'added' | 'unchanged'; export interface ClaudeMemoryResult { /** The MEMORY.md (its real path). */ path: string; /** * - `created` there was no MEMORY.md; it now holds the block * - `updated` faf's block was replaced in place * - `migrated` claude-faf-mcp's earlier section was replaced by the block * - `added` the file had neither; the block went on top * - `unchanged` the block was already exactly this; nothing was written */ action: ClaudeMemoryAction; /** False when nothing was written (`unchanged`). */ written: boolean; /** Every byte outside faf's block is still in the file, in place — checked * by reading the file back after the write. */ preserved: boolean; /** Lines in the file now. */ lines: number; /** Plain-words notes (e.g. lines past the 200 Claude Code loads). */ warnings: string[]; } /** What is in the MEMORY.md Claude Code loads for a project (read only). */ export interface ClaudeMemoryStatus { /** The MEMORY.md path (whether or not it exists). */ path: string; exists: boolean; /** Lines in the file. */ lines: number; /** faf's block is in the file. */ hasBlock: boolean; /** Lines in faf's block, its marker lines included (0 when there is none). */ blockLines: number; /** claude-faf-mcp's earlier tri-sync section is in the file (the next write replaces it). */ hasLegacySection: boolean; /** Non-blank lines outside faf's block: Claude's own notes. */ otherLines: number; /** Plain-words notes (e.g. lines past the 200 Claude Code loads). */ warnings: string[]; } /** * Claude Code's folder name for a project path: every character outside * [a-zA-Z0-9] becomes `-`; past 200 characters the id is cut to 200 and gets * `-` plus a base-36 hash of the full path. `/home/me/my_app` → `-home-me-my-app`. * Pass the project root (see {@link claudeProjectRoot}), not any folder. */ export declare function claudeProjectId(projectRoot: string): string; /** * The project root Claude Code keys a folder's memory by: the canonical git * root (a linked worktree → its main checkout), or the folder itself when it * is not in git. The folder is resolved on disk first (links followed), as * Claude Code sees its working folder; the result is NFC-normalized. */ export declare function claudeProjectRoot(dir: string): string; /** `/projects//memory` for the project `dir` is in. */ export declare function resolveClaudeMemoryDir(dir: string, opts?: ClaudeMemoryOptions): string; /** The MEMORY.md Claude Code loads for the project `dir` is in. */ export declare function resolveClaudeMemoryPath(dir: string, opts?: ClaudeMemoryOptions): string; /** * The block body faf keeps in MEMORY.md, from .faf data: a short project * summary (name, goal, language, type), the filled stack slots, the 6 Ws, * and up to 12 commands and key files. Kept short on purpose — Claude Code * loads only the first 200 lines, and the rest of the file is Claude's. */ export declare function renderClaudeMemory(data: FafData): string; /** * Read what is in the MEMORY.md Claude Code loads for the project `dir` is in: * whether faf's block (or claude-faf-mcp's earlier section) is there, and how * many lines are Claude's own. Reads only; never creates or writes anything. * A file that exists but cannot be read throws. */ export declare function claudeMemoryStatus(dir: string, opts?: ClaudeMemoryOptions): ClaudeMemoryStatus; /** * Write faf's block into the MEMORY.md Claude Code loads for the project `dir` * is in (see the file header for the path rule and what is kept). Creates the * memory folder when it is not there yet. Throws — having written nothing — * when the file cannot be read (other than not existing), is not UTF-8, is a * link that leaves the memory folder, changed on disk while faf was writing, * cannot be written (the original is kept), or is one where faf cannot place * its block where its next run finds it again (SafePathError `unplaceable`, * the same one-line refusal as injectFafBlock). */ export declare function writeClaudeMemory(dir: string, data: FafData, opts?: ClaudeMemoryOptions): ClaudeMemoryResult;