Skip to content

内存目录系统 (memdir)

src/memdir/ 包含 8 个文件,实现了 Claude Code 的持久记忆系统。

核心概念

记忆系统基于 MEMORY.md 文件:

~/.claude/
├── MEMORY.md              # 全局记忆入口(最多 200 行 / 25KB)
├── memories/              # 自动记忆子目录
│   ├── user-preference.md
│   ├── project-setup.md
│   └── ...
project/
├── CLAUDE.md              # 项目级记忆(自动加载)
├── CLAUDE.local.md        # 本地记忆(gitignore)
└── .claude/
    └── memories/          # 项目级自动记忆

文件架构

memdir.ts — 核心模块

typescript
// 常量
const ENTRYPOINT_NAME = 'MEMORY.md'
const MAX_ENTRYPOINT_LINES = 200
const MAX_ENTRYPOINT_BYTES = 25000

// 类型
interface EntrypointTruncation {
  content: string
  lineCount: number
  byteCount: number
  wasLineTruncated: boolean
  wasByteTruncated: boolean
}

// 双重截断(行数 + 字节数)
function truncateEntrypointContent(raw: string): EntrypointTruncation {
  let content = raw
  let wasLineTruncated = false
  let wasByteTruncated = false

  // 1. 行截断
  const lines = content.split('\n')
  if (lines.length > MAX_ENTRYPOINT_LINES) {
    content = lines.slice(0, MAX_ENTRYPOINT_LINES).join('\n')
    wasLineTruncated = true
  }

  // 2. 字节截断
  if (Buffer.byteLength(content) > MAX_ENTRYPOINT_BYTES) {
    content = content.slice(0, MAX_ENTRYPOINT_BYTES)
    wasByteTruncated = true
  }

  return { content, lineCount: lines.length, byteCount: Buffer.byteLength(content), wasLineTruncated, wasByteTruncated }
}

// 四类记忆分类指导
function buildMemoryLines(): string[] {
  // 生成记忆类型分类提示
  // user / feedback / project / reference
}

paths.ts — 路径解析

typescript
// 5 级优先级的自动记忆开关
function isAutoMemoryEnabled(): boolean {
  // 1. 环境变量覆盖
  // 2. bare 模式关闭
  // 3. CCR 模式关闭
  // 4. settings.json 配置
  // 5. 默认启用
}

// 记忆基础目录
function getMemoryBaseDir(): string {
  // CLAUDE_CODE_REMOTE_MEMORY_DIR > ~/.claude
}

// 自动记忆路径(带安全校验)
function getAutoMemPath(): string | null {
  // 项目级记忆目录
  // 校验:拒绝相对路径、根目录、遍历攻击
}

memoryTypes.ts — 四类记忆

typescript
const MEMORY_TYPES = ['user', 'feedback', 'project', 'reference'] as const
type MemoryType = typeof MEMORY_TYPES[number]
类型范围用途
user全局用户偏好、习惯
feedback全局用户反馈、纠正
project项目项目配置、约定
reference项目技术参考、API 记录

每种类型都有 XML 格式的定义:

xml
<scope>全局/项目</scope>
<description>记忆类型描述</description>
<when_to_save>何时保存</when_to_save>
<how_to_use>如何使用</how_to_use>
<body_structure>内容结构</body_structure>
<examples>示例</examples>

memoryScan.ts — 记忆扫描

typescript
interface MemoryHeader {
  filename: string
  filePath: string
  mtimeMs: number
  description: string
  type: MemoryType
}

const MAX_MEMORY_FILES = 200
const FRONTMATTER_MAX_LINES = 30

// 递归扫描 .md 文件
async function scanMemoryFiles(
  memoryDir: string,
  signal?: AbortSignal
): Promise<MemoryHeader[]> {
  // 1. 递归扫描所有 .md 文件
  // 2. 解析 frontmatter(前 30 行)
  // 3. 按修改时间倒序排列(最新优先)
  // 4. 截断到 200 个文件
}

// 格式化为一行一文件的清单
function formatMemoryManifest(memories: MemoryHeader[]): string

findRelevantMemories.ts — AI 检索

typescript
interface RelevantMemory {
  path: string
  mtimeMs: number
}

// Sonnet 驱动的记忆选择(最多 5 条)
async function findRelevantMemories(
  query: string,
  memoryDir: string,
  signal?: AbortSignal,
  recentTools?: string[],
  alreadySurfaced?: string[]
): Promise<RelevantMemory[]> {
  // 1. 扫描记忆目录获取清单
  // 2. 排除已展示的记忆
  // 3. 发送侧查询到 Sonnet,附带最近工具上下文
  // 4. 模型选择最相关的 ≤5 条记忆
}

memoryAge.ts — 新鲜度计算

typescript
function memoryAge(mtimeMs: number): string {
  // "today" / "yesterday" / "N days ago"
}

function memoryFreshnessNote(mtimeMs: number): string {
  // >1 天的记忆包裹在 <system-reminder> 标签中
  // 提醒模型注意时效性
}

teamMemPaths.ts — 团队记忆

typescript
class PathTraversalError extends Error {}

// 路径消毒(防止遍历攻击)
function sanitizePathKey(key: string): string {
  // 拒绝:null 字节、URL 编码遍历、Unicode 规范化攻击
  // 拒绝:反斜杠、绝对路径
}

function isTeamMemoryEnabled(): boolean {
  // 需要 autoMemory + 特性门控
}

teamMemPrompts.ts — 团队记忆提示

typescript
function buildCombinedMemoryPrompt(
  extraGuidelines?: string,
  skipIndex?: boolean
): string {
  // 合并私有记忆 + 团队记忆的提示
  // 两步保存流程:
  //   1. 写入文件(含 frontmatter)
  //   2. 在 MEMORY.md 索引中添加指针
}

安全防护

  • 路径遍历防护: sanitizePathKey() 拒绝 ../、null 字节、Unicode 攻击
  • 符号链接安全: PathTraversalError 阻止符号链接跟踪
  • 大小限制: 200 行 / 25KB 入口点截断,200 文件上限
  • 特性门控: 团队记忆需要独立的特性门控