Claude Code 源码分析Claude Code 源码分析
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
首页
源码统计
系统架构
UML 图表
工具系统
CodeGraph
  • 概览

    • Claude Code 源码分析
    • 源码统计
    • CodeGraph 图谱
  • 架构

    • 系统架构
    • UML 图表索引
    • 查询引擎
    • 核心流程
    • 消息系统
    • 状态管理
  • 功能模块

    • 工具系统
    • 斜杠命令
    • 服务层
    • MCP 协议
    • Skills 技能
    • 子代理系统
  • 分层深度

    • 入口层
    • UI / Ink 层
    • utils 基础设施
    • 桥接 / 远程
    • 上下文压缩
  • 原理与安全

    • 底层原理
    • 技术难点
    • 权限与安全
    • 内部机制
    • 遥测与分析
  • 深度专题

    • Hooks 系统
    • 插件系统
    • 记忆系统
    • API 通信层
    • Ink 终端 UI
    • 认证系统
    • 构建与发布
    • 术语表
  • 调用分析

    • 调用链分析
    • 核心文件索引
  • 模块详解

    • utils

      • 模块: utils
      • messages · 消息工厂与规范化
      • session-storage · JSONL 会话持久化
      • permissions · 工具权限决策
      • shell-hooks · 用户 Shell Hook 系统
    • components

      • 模块: components
      • REPL · 主屏编排
      • messages · 消息行渲染
      • PermissionRequest · 权限弹窗
      • PromptInput · 底部输入
    • services

      • 模块: services
      • api-claude · Anthropic API 流式与重试
      • mcp-client · MCP 连接与工具调用
      • compact · 上下文压缩与自动触发
      • analytics · GrowthBook、Datadog 与 1P 事件
    • tools

      • 模块: tools
      • tool-interface · Tool 契约与注册表
      • bash-tool · Shell 执行与权限
      • streaming-executor · 流式工具并发调度
      • agent-tool · 子 Agent 委派
    • commands

      • 模块: commands
      • command-registry · commands.ts 注册与分派
      • model-command · /model 模型选择
      • mcp-commands · /mcp 服务器管理
      • compact-memory-commands · /compact 与 /memory
    • ink

      • 模块: ink
      • Ink 渲染管线 · Screen 与终端输出
      • 终端事件 · resize、paste、stdin
      • Ink Hooks · 输入、搜索、终端状态
      • Ink 组件 · Box、Text、ScrollBox 原语
    • hooks

      • 模块: hooks
      • useCanUseTool · 权限 UI 接缝
      • 输入与快捷键 Hook
      • 合并态 Hook(MCP + 本地)
      • notifs 通知 Hook
    • bridge

      • 模块: bridge
      • repl-bridge · REPL 桥初始化与传输
      • bridge-messaging · 桥消息路由与入站处理
      • remote-bridge-core · env-less 核心与守护主循环
      • bridge-permissions-ui · 权限、API 与 TUI
    • cli

      • 模块: cli
      • Structured IO · NDJSON SDK 协议
      • CLI Transports · Session Ingress 传输层
      • CLI Handlers · 子命令懒加载实现
      • Update & Upload · 自更新与串行上传原语
    • screens

      • 模块: screens
      • REPL 屏 · Screen 类型与顶层路由
      • ResumeConversation · 会话恢复选择器
      • Doctor · 安装诊断全屏
    • entrypoints

      • 模块: entrypoints
      • cli-entrypoint · Bootstrap 与快路径
      • sdk-types · core / control / runtime 类型体系
      • mcp-entrypoint · MCP stdio 服务器
      • sandbox-types · 沙箱配置单一真相源
    • skills

      • 模块: skills
      • skills-loading · 磁盘加载与 bundled 注册表
      • bundled-skills · 内置 skill 与 initBundledSkills
      • mcp-skills · MCP prompt 转 skill
      • skill-tool-integration · SkillTool 与命令注册
    • types

      • 模块: types
      • message-types · Message 联合与 content blocks
      • tool-permission-types · Tool、Permission、Command 类型
      • api-sdk-types · API 与 Hooks 协议类型
      • misc-types · ids、plugin、generated 与其余类型
    • tasks

      • 模块: tasks
      • local-agent-task · 本地 Agent 与主会话后台化
      • remote-agent-task · 远程 CCR 与 In-Process Teammate
      • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
      • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybindings

      • 模块: keybindings
      • keybinding-registry · 注册、Provider 与 useKeybinding
      • default-bindings · 默认键位表与平台差异
      • command-bindings · command:* 动态斜杠命令绑定
      • vim-bindings · Vim 模式与 keybindings 边界
    • memdir

      • 模块: memdir
      • memdir-core · 路径、加载与 MEMORY.md
      • memory-extraction · extractMemories 与 SessionMemory
      • memdir-commands · /memory、/remember 与命令集成
    • state

      • 模块: state
      • app-state-core · store、AppState 类型与 Provider
      • app-state-selectors · selectors 与 onChangeAppState
      • teammate-state · 队友视图与 swarm 状态
      • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query

      • 模块: query
      • query config 与 deps · 配置快照与依赖注入
      • query tokenBudget · +500k 自动续跑
      • query transitions · Continue / Terminal 状态机
      • query stopHooks · Stop 事件与 turn 结束编排
  • 模块详解(扩展)

    • messages · 消息工厂与规范化
    • session-storage · JSONL 会话持久化
    • permissions · 工具权限决策
    • shell-hooks · 用户 Shell Hook 系统
    • REPL · 主屏编排
    • messages · 消息行渲染
    • PermissionRequest · 权限弹窗
    • PromptInput · 底部输入
    • api-claude · Anthropic API 流式与重试
    • mcp-client · MCP 连接与工具调用
    • compact · 上下文压缩与自动触发
    • analytics · GrowthBook、Datadog 与 1P 事件
    • tool-interface · Tool 契约与注册表
    • bash-tool · Shell 执行与权限
    • streaming-executor · 流式工具并发调度
    • agent-tool · 子 Agent 委派
    • command-registry · commands.ts 注册与分派
    • model-command · /model 模型选择
    • mcp-commands · /mcp 服务器管理
    • compact-memory-commands · /compact 与 /memory
    • Ink 渲染管线 · Screen 与终端输出
    • 终端事件 · resize、paste、stdin
    • Ink Hooks · 输入、搜索、终端状态
    • Ink 组件 · Box、Text、ScrollBox 原语
    • useCanUseTool · 权限 UI 接缝
    • 输入与快捷键 Hook
    • 合并态 Hook(MCP + 本地)
    • notifs 通知 Hook
    • repl-bridge · REPL 桥初始化与传输
    • bridge-messaging · 桥消息路由与入站处理
    • remote-bridge-core · env-less 核心与守护主循环
    • bridge-permissions-ui · 权限、API 与 TUI
    • Structured IO · NDJSON SDK 协议
    • CLI Transports · Session Ingress 传输层
    • CLI Handlers · 子命令懒加载实现
    • Update & Upload · 自更新与串行上传原语
    • REPL 屏 · Screen 类型与顶层路由
    • ResumeConversation · 会话恢复选择器
    • Doctor · 安装诊断全屏
    • cli-entrypoint · Bootstrap 与快路径
    • sdk-types · core / control / runtime 类型体系
    • mcp-entrypoint · MCP stdio 服务器
    • sandbox-types · 沙箱配置单一真相源
    • skills-loading · 磁盘加载与 bundled 注册表
    • bundled-skills · 内置 skill 与 initBundledSkills
    • mcp-skills · MCP prompt 转 skill
    • skill-tool-integration · SkillTool 与命令注册
    • message-types · Message 联合与 content blocks
    • tool-permission-types · Tool、Permission、Command 类型
    • api-sdk-types · API 与 Hooks 协议类型
    • misc-types · ids、plugin、generated 与其余类型
    • local-agent-task · 本地 Agent 与主会话后台化
    • remote-agent-task · 远程 CCR 与 In-Process Teammate
    • shell-workflow-tasks · Bash 后台、Workflow 与 stopTask
    • dream-monitor-tasks · Dream、Monitor MCP 与 pill 文案
    • keybinding-registry · 注册、Provider 与 useKeybinding
    • default-bindings · 默认键位表与平台差异
    • command-bindings · command:* 动态斜杠命令绑定
    • vim-bindings · Vim 模式与 keybindings 边界
    • memdir-core · 路径、加载与 MEMORY.md
    • memory-extraction · extractMemories 与 SessionMemory
    • memdir-commands · /memory、/remember 与命令集成
    • app-state-core · store、AppState 类型与 Provider
    • app-state-selectors · selectors 与 onChangeAppState
    • teammate-state · 队友视图与 swarm 状态
    • state-boundaries · bootstrap、sessionStorage、FileStateCache
    • query config 与 deps · 配置快照与依赖注入
    • query tokenBudget · +500k 自动续跑
    • query transitions · Continue / Terminal 状态机
    • query stopHooks · Stop 事件与 turn 结束编排
  • 工具详解

    • tool-interface · Tool 契约与注册表
    • tool-permission-types · Tool、Permission、Command 类型
    • 工具: Bash
    • 工具: PowerShell
    • 工具: Agent
    • 工具: LSP
    • 工具: FileEdit
    • 工具: FileRead
    • 工具: Skill
    • 工具: WebFetch
    • 工具: MCP
    • 工具: SendMessage
    • 工具: FileWrite
    • 工具: Config
    • 工具: Grep
    • 工具: Brief
    • 工具: ExitPlanMode
    • 工具: ToolSearch
    • 工具: NotebookEdit
    • 工具: TaskOutput
    • 工具: WebSearch
    • 工具: ScheduleCron

本章总览

先看用户入口:memdir-commands 讲的是“用户如何显式触达记忆系统”,核心路径是 /memory UI 编辑、remember 技能辅助与 command:memory 快捷键桥接。它不负责后台提取本身,而是把命令层与 memdir 文件系统安全地接起来。

学完本章你应该能

  • 描述 /memory 命令 load/call 懒加载模式
  • 说明 MemoryFileSelector 与 getMemoryFiles 数据源
  • 理解 remember skill 与 isAutoMemoryEnabled 门控
  • 知道 editFileInEditor 与 VISUAL/EDITOR 提示
  • 区分 /memory UI 编辑与 agent Write 自动保存
  • 能在 commands/registry 找到 memory 注册

核心概念(先读懂这些)

/memory 是 LocalJSXCommand

commands/memory/index.ts type local-jsx,load() dynamic import memory.tsx。call 返回 MemoryCommand Dialog,onDone display system 展示相对路径与编辑器 hint。

getMemoryFiles 缓存

call 前 clearMemoryFileCaches + await getMemoryFiles() 避免 Suspense flash。getMemoryFiles 合并 project memory、auto mem、CLAUDE.md 等路径供 selector 展示。

remember skill vs /memory

remember skill 引导模型通过 AskUserQuestion 等收集要记住的内容并 Write;/memory 是人类直接编辑磁盘文件。两者都受 isAutoMemoryEnabled 影响;--bare 时 paths.ts 注释一并关闭。

建议学习步骤

  1. 阅读 commands/memory/index.ts
  2. 阅读 memory.tsx handleSelectMemoryFile
  3. 打开 MemoryFileSelector 数据源
  4. 阅读 skills/bundled/remember.ts
  5. 在 utils/claudemd getMemoryFiles 看路径解析
  6. 搜索 command:memory keybinding 示例

常见误区

注意

无 /memdir 命令——文档 memdir 指模块名

注意

wx flag 创建空文件,EEXIST 保留原内容

注意

memoryPath 含 config home 时才 mkdir getClaudeConfigHomeDir

注意

remember skill 需 EXTRACT_MEMORIES 无直接关系

/memory 命令注册

commands/memory/index.ts:

const memory: Command = {
  name: 'memory',
  description: 'Edit Claude memory files',
  load: () => import('./memory.js'),
}

类型 LocalJSXCommand,supportsNonInteractive 通常 false——需要 Ink Dialog。

源码引用: src/commands/memory/index.ts · 第 1–10 行(共 11 行)

   1| import type { Command } from '../../commands.js'
   2| 
   3| const memory: Command = {
   4|   type: 'local-jsx',
   5|   name: 'memory',
   6|   description: 'Edit Claude memory files',
   7|   load: () => import('./memory.js'),
   8| }
   9| 
  10| export default memory

MemoryCommand UI 流程

memory.tsx MemoryCommand 组件:

  1. MemoryFileSelector onSelect → handleSelectMemoryFile
  2. 若路径在 config home 下 mkdir recursive
  3. writeFile wx 创建空文件(EEXIST 忽略)
  4. editFileInEditor(memoryPath)
  5. onDone 相对路径 + VISUAL/EDITOR hint,display: system

Dialog color="remember",文档 Link code.claude.com/docs/en/memory。

源码引用: src/commands/memory/memory.tsx · 第 14–82 行(共 103 行)

  14| 
  15| function MemoryCommand({
  16|   onDone,
  17| }: {
  18|   onDone: (
  19|     result?: string,
  20|     options?: { display?: CommandResultDisplay },
  21|   ) => void
  22| }): React.ReactNode {
  23|   const handleSelectMemoryFile = async (memoryPath: string) => {
  24|     try {
  25|       // Create claude directory if it doesn't exist (idempotent with recursive)
  26|       if (memoryPath.includes(getClaudeConfigHomeDir())) {
  27|         await mkdir(getClaudeConfigHomeDir(), { recursive: true })
  28|       }
  29| 
  30|       // Create file if it doesn't exist (wx flag fails if file exists,
  31|       // which we catch to preserve existing content)
  32|       try {
  33|         await writeFile(memoryPath, '', { encoding: 'utf8', flag: 'wx' })
  34|       } catch (e: unknown) {
  35|         if (getErrnoCode(e) !== 'EEXIST') {
  36|           throw e
  37|         }
  38|       }
  39| 
  40|       await editFileInEditor(memoryPath)
  41| 
  42|       // Determine which environment variable controls the editor
  43|       let editorSource = 'default'
  44|       let editorValue = ''
  45|       if (process.env.VISUAL) {
  46|         editorSource = '$VISUAL'
  47|         editorValue = process.env.VISUAL
  48|       } else if (process.env.EDITOR) {
  49|         editorSource = '$EDITOR'
  50|         editorValue = process.env.EDITOR
  51|       }
  52| 
  53|       const editorInfo =
  54|         editorSource !== 'default'
  55|           ? `Using ${editorSource}="${editorValue}".`
  56|           : ''
  57| 
  58|       const editorHint = editorInfo
  59|         ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.`
  60|         : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`
  61| 
  62|       onDone(
  63|         `Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`,
  64|         { display: 'system' },
  65|       )
  66|     } catch (error) {
  67|       logError(error)
  68|       onDone(`Error opening memory file: ${error}`)
  69|     }
  70|   }
  71| 
  72|   const handleCancel = () => {
  73|     onDone('Cancelled memory editing', { display: 'system' })
  74|   }
  75| 
  76|   return (
  77|     <Dialog title="Memory" onCancel={handleCancel} color="remember">
  78|       <Box flexDirection="column">
  79|         <React.Suspense fallback={null}>
  80|           <MemoryFileSelector
  81|             onSelect={handleSelectMemoryFile}
  82|             onCancel={handleCancel}

源码引用: src/commands/memory/memory.tsx · 第 83–89 行(共 103 行)

  83|           />
  84|         </React.Suspense>
  85| 
  86|         <Box marginTop={1}>
  87|           <Text dimColor>
  88|             Learn more: <Link url="https://code.claude.com/docs/en/memory" />
  89|           </Text>

MemoryFileSelector 与 claudemd

components/memory/MemoryFileSelector.tsx 列出可选 memory 文件,Suspense 加载 getMemoryFiles() 结果。

utils/claudemd.ts getMemoryFiles 聚合:

  • Project CLAUDE.md、.claude/rules
  • Auto memory dir topic files + MEMORY.md
  • 缓存供多处 UI 共享

clearMemoryFileCaches 在 /memory 打开前调用保证 fresh 列表。

源码引用: src/components/memory/MemoryFileSelector.tsx · 第 1–60 行(共 325 行)

   1| import { feature } from 'bun:bundle'
   2| import chalk from 'chalk'
   3| import { mkdir } from 'fs/promises'
   4| import { join } from 'path'
   5| import * as React from 'react'
   6| import { use, useEffect, useState } from 'react'
   7| import { getOriginalCwd } from '../../bootstrap/state.js'
   8| import { useExitOnCtrlCDWithKeybindings } from '../../hooks/useExitOnCtrlCDWithKeybindings.js'
   9| import { Box, Text } from '../../ink.js'
  10| import { useKeybinding } from '../../keybindings/useKeybinding.js'
  11| import { getAutoMemPath, isAutoMemoryEnabled } from '../../memdir/paths.js'
  12| import { logEvent } from '../../services/analytics/index.js'
  13| import { isAutoDreamEnabled } from '../../services/autoDream/config.js'
  14| import { readLastConsolidatedAt } from '../../services/autoDream/consolidationLock.js'
  15| import { useAppState } from '../../state/AppState.js'
  16| import { getAgentMemoryDir } from '../../tools/AgentTool/agentMemory.js'
  17| import { openPath } from '../../utils/browser.js'
  18| import { getMemoryFiles, type MemoryFileInfo } from '../../utils/claudemd.js'
  19| import { getClaudeConfigHomeDir } from '../../utils/envUtils.js'
  20| import { getDisplayPath } from '../../utils/file.js'
  21| import { formatRelativeTimeAgo } from '../../utils/format.js'
  22| import { projectIsInGitRepo } from '../../utils/memory/versions.js'
  23| import { updateSettingsForSource } from '../../utils/settings/settings.js'
  24| import { Select } from '../CustomSelect/index.js'
  25| import { ListItem } from '../design-system/ListItem.js'
  26| 
  27| /* eslint-disable @typescript-eslint/no-require-imports */
  28| const teamMemPaths = feature('TEAMMEM')
  29|   ? (require('../../memdir/teamMemPaths.js') as typeof import('../../memdir/teamMemPaths.js'))
  30|   : null
  31| /* eslint-enable @typescript-eslint/no-require-imports */
  32| 
  33| interface ExtendedMemoryFileInfo extends MemoryFileInfo {
  34|   isNested?: boolean
  35|   exists: boolean
  36| }
  37| 
  38| // Remember last selected path
  39| let lastSelectedPath: string | undefined
  40| 
  41| const OPEN_FOLDER_PREFIX = '__open_folder__'
  42| 
  43| type Props = {
  44|   onSelect: (path: string) => void
  45|   onCancel: () => void
  46| }
  47| 
  48| export function MemoryFileSelector({
  49|   onSelect,
  50|   onCancel,
  51| }: Props): React.ReactNode {
  52|   const existingMemoryFiles = use(getMemoryFiles())
  53| 
  54|   // Create entries for User and Project CLAUDE.md even if they don't exist
  55|   const userMemoryPath = join(getClaudeConfigHomeDir(), 'CLAUDE.md')
  56|   const projectMemoryPath = join(getOriginalCwd(), 'CLAUDE.md')
  57| 
  58|   // Check if these are already in the existing files
  59|   const hasUserMemory = existingMemoryFiles.some(f => f.path === userMemoryPath)
  60|   const hasProjectMemory = existingMemoryFiles.some(

源码引用: src/utils/claudemd.ts · 第 1–80 行(共 1480 行)

   1| /**
   2|  * Files are loaded in the following order:
   3|  *
   4|  * 1. Managed memory (eg. /etc/claude-code/CLAUDE.md) - Global instructions for all users
   5|  * 2. User memory (~/.claude/CLAUDE.md) - Private global instructions for all projects
   6|  * 3. Project memory (CLAUDE.md, .claude/CLAUDE.md, and .claude/rules/*.md in project roots) - Instructions checked into the codebase
   7|  * 4. Local memory (CLAUDE.local.md in project roots) - Private project-specific instructions
   8|  *
   9|  * Files are loaded in reverse order of priority, i.e. the latest files are highest priority
  10|  * with the model paying more attention to them.
  11|  *
  12|  * File discovery:
  13|  * - User memory is loaded from the user's home directory
  14|  * - Project and Local files are discovered by traversing from the current directory up to root
  15|  * - Files closer to the current directory have higher priority (loaded later)
  16|  * - CLAUDE.md, .claude/CLAUDE.md, and all .md files in .claude/rules/ are checked in each directory for Project memory
  17|  *
  18|  * Memory @include directive:
  19|  * - Memory files can include other files using @ notation
  20|  * - Syntax: @path, @./relative/path, @~/home/path, or @/absolute/path
  21|  * - @path (without prefix) is treated as a relative path (same as @./path)
  22|  * - Works in leaf text nodes only (not inside code blocks or code strings)
  23|  * - Included files are added as separate entries before the including file
  24|  * - Circular references are prevented by tracking processed files
  25|  * - Non-existent files are silently ignored
  26|  */
  27| 
  28| import { feature } from 'bun:bundle'
  29| import ignore from 'ignore'
  30| import memoize from 'lodash-es/memoize.js'
  31| import { Lexer } from 'marked'
  32| import {
  33|   basename,
  34|   dirname,
  35|   extname,
  36|   isAbsolute,
  37|   join,
  38|   parse,
  39|   relative,
  40|   sep,
  41| } from 'path'
  42| import picomatch from 'picomatch'
  43| import { logEvent } from 'src/services/analytics/index.js'
  44| import {
  45|   getAdditionalDirectoriesForClaudeMd,
  46|   getOriginalCwd,
  47| } from '../bootstrap/state.js'
  48| import { truncateEntrypointContent } from '../memdir/memdir.js'
  49| import { getAutoMemEntrypoint, isAutoMemoryEnabled } from '../memdir/paths.js'
  50| import { getFeatureValue_CACHED_MAY_BE_STALE } from '../services/analytics/growthbook.js'
  51| import {
  52|   getCurrentProjectConfig,
  53|   getManagedClaudeRulesDir,
  54|   getMemoryPath,
  55|   getUserClaudeRulesDir,
  56| } from './config.js'
  57| import { logForDebugging } from './debug.js'
  58| import { logForDiagnosticsNoPII } from './diagLogs.js'
  59| import { getClaudeConfigHomeDir, isEnvTruthy } from './envUtils.js'
  60| import { getErrnoCode } from './errors.js'
  61| import { normalizePathForComparison } from './file.js'
  62| import { cacheKeys, type FileStateCache } from './fileStateCache.js'
  63| import {
  64|   parseFrontmatter,
  65|   splitPathInFrontmatter,
  66| } from './frontmatterParser.js'
  67| import { getFsImplementation, safeResolvePath } from './fsOperations.js'
  68| import { findCanonicalGitRoot, findGitRoot } from './git.js'
  69| import {
  70|   executeInstructionsLoadedHooks,
  71|   hasInstructionsLoadedHook,
  72|   type InstructionsLoadReason,
  73|   type InstructionsMemoryType,
  74| } from './hooks.js'
  75| import type { MemoryType } from './memory/types.js'
  76| import { expandPath } from './path.js'
  77| import { pathInWorkingPath } from './permissions/filesystem.js'
  78| import { isSettingSourceEnabled } from './settings/constants.js'
  79| import { getInitialSettings } from './settings/settings.js'
  80| 

源码引用: src/components/memory/MemoryUpdateNotification.tsx · 第 1–40 行(共 43 行)

   1| import { homedir } from 'os'
   2| import { relative } from 'path'
   3| import React from 'react'
   4| import { Box, Text } from '../../ink.js'
   5| import { getCwd } from '../../utils/cwd.js'
   6| 
   7| export function getRelativeMemoryPath(path: string): string {
   8|   const homeDir = homedir()
   9|   const cwd = getCwd()
  10| 
  11|   // Calculate relative paths
  12|   const relativeToHome = path.startsWith(homeDir)
  13|     ? '~' + path.slice(homeDir.length)
  14|     : null
  15| 
  16|   const relativeToCwd = path.startsWith(cwd) ? './' + relative(cwd, path) : null
  17| 
  18|   // Return the shorter path, or absolute if neither is applicable
  19|   if (relativeToHome && relativeToCwd) {
  20|     return relativeToHome.length <= relativeToCwd.length
  21|       ? relativeToHome
  22|       : relativeToCwd
  23|   }
  24| 
  25|   return relativeToHome || relativeToCwd || path
  26| }
  27| 
  28| export function MemoryUpdateNotification({
  29|   memoryPath,
  30| }: {
  31|   memoryPath: string
  32| }): React.ReactNode {
  33|   const displayPath = getRelativeMemoryPath(memoryPath)
  34| 
  35|   return (
  36|     <Box flexDirection="column" flexGrow={1}>
  37|       <Text color="text">
  38|         Memory updated in {displayPath} · /memory to edit
  39|       </Text>
  40|     </Box>

remember bundled skill

skills/bundled/remember.ts registerRememberSkill:

  • isAutoMemoryEnabled() gate
  • name: 'remember'
  • 引导用户确认要记住的内容,Write 到 memdir
  • AskUserQuestionTool source: 'remember' analytics

与 extractMemories 互补:skill 即时;extract 回合末批量。paths.ts 注释 SIMPLE 模式关闭 remember。

源码引用: src/skills/bundled/remember.ts · 第 1–80 行(共 83 行)

   1| import { isAutoMemoryEnabled } from '../../memdir/paths.js'
   2| import { registerBundledSkill } from '../bundledSkills.js'
   3| 
   4| export function registerRememberSkill(): void {
   5|   if (process.env.USER_TYPE !== 'ant') {
   6|     return
   7|   }
   8| 
   9|   const SKILL_PROMPT = `# Memory Review
  10| 
  11| ## Goal
  12| Review the user's memory landscape and produce a clear report of proposed changes, grouped by action type. Do NOT apply changes — present proposals for user approval.
  13| 
  14| ## Steps
  15| 
  16| ### 1. Gather all memory layers
  17| Read CLAUDE.md and CLAUDE.local.md from the project root (if they exist). Your auto-memory content is already in your system prompt — review it there. Note which team memory sections exist, if any.
  18| 
  19| **Success criteria**: You have the contents of all memory layers and can compare them.
  20| 
  21| ### 2. Classify each auto-memory entry
  22| For each substantive entry in auto-memory, determine the best destination:
  23| 
  24| | Destination | What belongs there | Examples |
  25| |---|---|---|
  26| | **CLAUDE.md** | Project conventions and instructions for Claude that all contributors should follow | "use bun not npm", "API routes use kebab-case", "test command is bun test", "prefer functional style" |
  27| | **CLAUDE.local.md** | Personal instructions for Claude specific to this user, not applicable to other contributors | "I prefer concise responses", "always explain trade-offs", "don't auto-commit", "run tests before committing" |
  28| | **Team memory** | Org-wide knowledge that applies across repositories (only if team memory is configured) | "deploy PRs go through #deploy-queue", "staging is at staging.internal", "platform team owns infra" |
  29| | **Stay in auto-memory** | Working notes, temporary context, or entries that don't clearly fit elsewhere | Session-specific observations, uncertain patterns |
  30| 
  31| **Important distinctions:**
  32| - CLAUDE.md and CLAUDE.local.md contain instructions for Claude, not user preferences for external tools (editor theme, IDE keybindings, etc. don't belong in either)
  33| - Workflow practices (PR conventions, merge strategies, branch naming) are ambiguous — ask the user whether they're personal or team-wide
  34| - When unsure, ask rather than guess
  35| 
  36| **Success criteria**: Each entry has a proposed destination or is flagged as ambiguous.
  37| 
  38| ### 3. Identify cleanup opportunities
  39| Scan across all layers for:
  40| - **Duplicates**: Auto-memory entries already captured in CLAUDE.md or CLAUDE.local.md → propose removing from auto-memory
  41| - **Outdated**: CLAUDE.md or CLAUDE.local.md entries contradicted by newer auto-memory entries → propose updating the older layer
  42| - **Conflicts**: Contradictions between any two layers → propose resolution, noting which is more recent
  43| 
  44| **Success criteria**: All cross-layer issues identified.
  45| 
  46| ### 4. Present the report
  47| Output a structured report grouped by action type:
  48| 1. **Promotions** — entries to move, with destination and rationale
  49| 2. **Cleanup** — duplicates, outdated entries, conflicts to resolve
  50| 3. **Ambiguous** — entries where you need the user's input on destination
  51| 4. **No action needed** — brief note on entries that should stay put
  52| 
  53| If auto-memory is empty, say so and offer to review CLAUDE.md for cleanup.
  54| 
  55| **Success criteria**: User can review and approve/reject each proposal individually.
  56| 
  57| ## Rules
  58| - Present ALL proposals before making any changes
  59| - Do NOT modify files without explicit user approval
  60| - Do NOT create new files unless the target doesn't exist yet
  61| - Ask about ambiguous entries — don't guess
  62| `
  63| 
  64|   registerBundledSkill({
  65|     name: 'remember',
  66|     description:
  67|       'Review auto-memory entries and propose promotions to CLAUDE.md, CLAUDE.local.md, or shared memory. Also detects outdated, conflicting, and duplicate entries across memory layers.',
  68|     whenToUse:
  69|       'Use when the user wants to review, organize, or promote their auto-memory entries. Also useful for cleaning up outdated or conflicting entries across CLAUDE.md, CLAUDE.local.md, and auto-memory.',
  70|     userInvocable: true,
  71|     isEnabled: () => isAutoMemoryEnabled(),
  72|     async getPromptForCommand(args) {
  73|       let prompt = SKILL_PROMPT
  74| 
  75|       if (args) {
  76|         prompt += `\n## Additional context from user\n\n${args}`
  77|       }
  78| 
  79|       return [{ type: 'text', text: prompt }]
  80|     },

源码引用: src/memdir/paths.ts · 第 38–42 行(共 279 行)

  38|   // --bare / SIMPLE: prompts.ts already drops the memory section from the
  39|   // system prompt via its SIMPLE early-return; this gate stops the other half
  40|   // (extractMemories turn-end fork, autoDream, /remember, /dream, team sync).
  41|   if (isEnvTruthy(process.env.CLAUDE_CODE_SIMPLE)) {
  42|     return false

keybinding command:memory

用户可在 keybindings.json 配置:

{ "context": "Chat", "bindings": { "alt+m": "command:memory" } }

CommandKeybindingHandlers 提交 /memory,fromKeybinding: true,走 handlePromptSubmit local-jsx 分支,打开同一 MemoryCommand Dialog。

见 keybindings/command-bindings 子章节。

源码引用: src/hooks/useCommandKeybindings.tsx · 第 77–83 行(共 83 行)

  77|     context: 'Chat',
  78|     isActive: isActive && !isModalOverlayActive,
  79|   })
  80| 
  81|   return null
  82| }
  83| 

源码引用: src/types/command.ts · 第 144–155 行(共 217 行)

 144| type LocalJSXCommand = {
 145|   type: 'local-jsx'
 146|   /**
 147|    * Lazy-load the command implementation.
 148|    * Returns a module with a call() function.
 149|    * This defers loading heavy dependencies until the command is invoked.
 150|    */
 151|   load: () => Promise<LocalJSXCommandModule>
 152| }
 153| 
 154| /**
 155|  * Declares which auth/provider environments a command is available in.

compact-memory 与相邻命令

commands/compact-memory-commands(module-content 已有)处理 /compact 与 memory 边界——compact 可能引用 MEMORY_TYPE_VALUES from utils/memory/types。

/dream(若启用)与 autoDream 服务共用 memdir 写入路径,gate 在 isAutoMemoryEnabled。

无 /memdir slash——运维调试 memdir 用 ls ~/.claude/projects/*/memory/ 或 /memory UI。

源码缺失

File not found: c:\Users\123\Desktop\jd\Claude code源码\claude-code-complete\claude-code-complete\src-readable\commands\compact-memory-commands.mjs

源码引用: src/services/compact/compact.ts · 第 59–59 行(共 1706 行)

  59| import { MEMORY_TYPE_VALUES } from '../../utils/memory/types.js'

端到端用户旅程

自动:session start loadMemoryPrompt → 模型见 MEMORY.md + 指南
        turn end extractMemories → 可能新增 topic.md
手动:/memory → 选人编辑 → editFileInEditor
        /remember skill → 对话式保存
Keybinding:command:memory → 同 /memory

memdir/ 提供路径与 prompt;commands/skills 提供入口;extractMemories 提供后台同步。

命令入口与自动写入的分层

/memory、command:memory、remember skill 与自动 extractMemories 都能接触“记忆”,但用户意图不同。/memory 是 local-jsx 命令,commands/memory/index.ts 只注册 name、description、load,真正 UI 在 memory.tsx 中打开 MemoryFileSelector,让用户选文件后用 editFileInEditor 编辑。command:memory 只是 keybindings 的动态入口:CommandKeybindingHandlers 把它转成 /memory,并通过 fromKeybinding 保留当前 prompt 草稿。remember skill 则是模型侧的用户可调用技能,先检查 isAutoMemoryEnabled,再给模型一份“审阅、分类、提出修改建议”的 prompt。

自动写入不从 commands/ 开始,而是 stopHooks 或 print drain 触发 extractMemories。它扫描 memory 目录、构建 manifest、启动 forked agent、受 hasMemoryWritesSince 互斥保护,最后可能追加 memory_saved 系统消息。也就是说,commands/memory 是人类编辑文件的门,skills/bundled/remember 是对话式整理入口,services/extractMemories 是后台维护管线。文档里不要写“/memdir 命令”,因为源码 registry 里没有这个用户入口;如果用户想手动看或改文件,应引导到 /memory 或配置 command:memory;如果想让模型长期记住,则依赖 memdir prompt、Write 工具或自动提取。

/memory 的 local-jsx 形态还意味着它不能被当作普通 prompt 命令看待。它需要 Ink Dialog、MemoryFileSelector、编辑器启动和 onDone 回调,所以非交互环境或 modal 已打开时行为会受限制。command:memory 触发同一路径,但 fromKeybinding 保留草稿;手输 /memory 则会进入正常 slash 命令历史。remember skill 更像“整理建议生成器”,它要求先审阅各层记忆并提出变更,不应擅自写文件。把这三种入口分清,用户文档才不会把人工编辑、快捷键打开和模型自动保存混成一个动作。

维护命令侧内容时,优先从 commands.ts registry、commands/memory/index.ts 和 memory.tsx 的 call/onDone 读起,再去看 utils/claudemd 的文件列表来源。若要新增记忆相关命令,应明确它是人类 UI、模型 prompt skill,还是后台服务开关;不同入口需要的权限、交互模式和 transcript 展示完全不同。

源码引用: src/commands/memory/index.ts · 第 1–10 行(共 11 行)

   1| import type { Command } from '../../commands.js'
   2| 
   3| const memory: Command = {
   4|   type: 'local-jsx',
   5|   name: 'memory',
   6|   description: 'Edit Claude memory files',
   7|   load: () => import('./memory.js'),
   8| }
   9| 
  10| export default memory

源码引用: src/hooks/useCommandKeybindings.tsx · 第 77–83 行(共 83 行)

  77|     context: 'Chat',
  78|     isActive: isActive && !isModalOverlayActive,
  79|   })
  80| 
  81|   return null
  82| }
  83| 

源码引用: src/skills/bundled/remember.ts · 第 64–81 行(共 83 行)

  64|   registerBundledSkill({
  65|     name: 'remember',
  66|     description:
  67|       'Review auto-memory entries and propose promotions to CLAUDE.md, CLAUDE.local.md, or shared memory. Also detects outdated, conflicting, and duplicate entries across memory layers.',
  68|     whenToUse:
  69|       'Use when the user wants to review, organize, or promote their auto-memory entries. Also useful for cleaning up outdated or conflicting entries across CLAUDE.md, CLAUDE.local.md, and auto-memory.',
  70|     userInvocable: true,
  71|     isEnabled: () => isAutoMemoryEnabled(),
  72|     async getPromptForCommand(args) {
  73|       let prompt = SKILL_PROMPT
  74| 
  75|       if (args) {
  76|         prompt += `\n## Additional context from user\n\n${args}`
  77|       }
  78| 
  79|       return [{ type: 'text', text: prompt }]
  80|     },
  81|   })

源码引用: src/services/extractMemories/extractMemories.ts · 第 395–427 行(共 616 行)

 395|       // Pre-inject the memory directory manifest so the agent doesn't spend
 396|       // a turn on `ls`. Reuses findRelevantMemories' frontmatter scan.
 397|       // Placed after the throttle gate so skipped turns don't pay the scan cost.
 398|       const existingMemories = formatMemoryManifest(
 399|         await scanMemoryFiles(memoryDir, createAbortController().signal),
 400|       )
 401| 
 402|       const userPrompt =
 403|         feature('TEAMMEM') && teamMemoryEnabled
 404|           ? buildExtractCombinedPrompt(
 405|               newMessageCount,
 406|               existingMemories,
 407|               skipIndex,
 408|             )
 409|           : buildExtractAutoOnlyPrompt(
 410|               newMessageCount,
 411|               existingMemories,
 412|               skipIndex,
 413|             )
 414| 
 415|       const result = await runForkedAgent({
 416|         promptMessages: [createUserMessage({ content: userPrompt })],
 417|         cacheSafeParams,
 418|         canUseTool,
 419|         querySource: 'extract_memories',
 420|         forkLabel: 'extract_memories',
 421|         // The extractMemories subagent does not need to record to transcript.
 422|         // Doing so can create race conditions with the main thread.
 423|         skipTranscript: true,
 424|         // Well-behaved extractions complete in 2-4 turns (read → write).
 425|         // A hard cap prevents verification rabbit-holes from burning turns.
 426|         maxTurns: 5,
 427|       })

命令层改动时的兼容检查

记忆相关命令改动通常会跨三层:命令注册(commands/registry 与 index.ts)、交互组件(MemoryFileSelector 与 Dialog)、文件系统接入(claudemd 缓存与 memdir 路径)。因此变更评审时应同时检查:命令是否仍可在 Chat context 触发、选择器是否拿到最新文件列表、非交互或 modal 场景是否正确降级、onDone 文案是否与实际写盘路径一致。

如果只改了命令入口而忽略文件列表缓存,用户会看到“命令执行成功但列表没刷新”;如果只改了 UI 而忽略 fromKeybinding 语义,快捷键触发会污染草稿输入。这些都是 memdir-commands 层最常见回归。

源码引用: src/commands/memory/memory.tsx · 第 29–74 行(共 103 行)

  29| 
  30|       // Create file if it doesn't exist (wx flag fails if file exists,
  31|       // which we catch to preserve existing content)
  32|       try {
  33|         await writeFile(memoryPath, '', { encoding: 'utf8', flag: 'wx' })
  34|       } catch (e: unknown) {
  35|         if (getErrnoCode(e) !== 'EEXIST') {
  36|           throw e
  37|         }
  38|       }
  39| 
  40|       await editFileInEditor(memoryPath)
  41| 
  42|       // Determine which environment variable controls the editor
  43|       let editorSource = 'default'
  44|       let editorValue = ''
  45|       if (process.env.VISUAL) {
  46|         editorSource = '$VISUAL'
  47|         editorValue = process.env.VISUAL
  48|       } else if (process.env.EDITOR) {
  49|         editorSource = '$EDITOR'
  50|         editorValue = process.env.EDITOR
  51|       }
  52| 
  53|       const editorInfo =
  54|         editorSource !== 'default'
  55|           ? `Using ${editorSource}="${editorValue}".`
  56|           : ''
  57| 
  58|       const editorHint = editorInfo
  59|         ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.`
  60|         : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`
  61| 
  62|       onDone(
  63|         `Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`,
  64|         { display: 'system' },
  65|       )
  66|     } catch (error) {
  67|       logError(error)
  68|       onDone(`Error opening memory file: ${error}`)
  69|     }
  70|   }
  71| 
  72|   const handleCancel = () => {
  73|     onDone('Cancelled memory editing', { display: 'system' })
  74|   }

源码引用: src/utils/claudemd.ts · 第 541–580 行(共 1480 行)

 541|  * Only applies to User, Project, and Local memory types.
 542|  * Managed, AutoMem, and TeamMem types are never excluded.
 543|  *
 544|  * Matches both the original path and the realpath-resolved path to handle symlinks
 545|  * (e.g., /tmp -> /private/tmp on macOS).
 546|  */
 547| function isClaudeMdExcluded(filePath: string, type: MemoryType): boolean {
 548|   if (type !== 'User' && type !== 'Project' && type !== 'Local') {
 549|     return false
 550|   }
 551| 
 552|   const patterns = getInitialSettings().claudeMdExcludes
 553|   if (!patterns || patterns.length === 0) {
 554|     return false
 555|   }
 556| 
 557|   const matchOpts = { dot: true }
 558|   const normalizedPath = filePath.replaceAll('\\', '/')
 559| 
 560|   // Build an expanded pattern list that includes realpath-resolved versions of
 561|   // absolute patterns. This handles symlinks like /tmp -> /private/tmp on macOS:
 562|   // the user writes "/tmp/project/CLAUDE.md" in their exclude, but the system
 563|   // resolves the CWD to "/private/tmp/project/...", so the file path uses the
 564|   // real path. By resolving the patterns too, both sides match.
 565|   const expandedPatterns = resolveExcludePatterns(patterns).filter(
 566|     p => p.length > 0,
 567|   )
 568|   if (expandedPatterns.length === 0) {
 569|     return false
 570|   }
 571| 
 572|   return picomatch.isMatch(normalizedPath, expandedPatterns, matchOpts)
 573| }
 574| 
 575| /**
 576|  * Expands exclude patterns by resolving symlinks in absolute path prefixes.
 577|  * For each absolute pattern (starting with /), tries to resolve the longest
 578|  * existing directory prefix via realpathSync and adds the resolved version.
 579|  * Glob patterns (containing *) have their static prefix resolved.
 580|  */

源码引用: src/hooks/useCommandKeybindings.tsx · 第 77–83 行(共 83 行)

  77|     context: 'Chat',
  78|     isActive: isActive && !isModalOverlayActive,
  79|   })
  80| 
  81|   return null
  82| }
  83| 

从用户心智看三种入口

给用户解释记忆功能时,建议直接用“三入口心智”避免混淆:/memory 是“我想手动改文件”;remember 是“请你帮我整理该记什么”;自动提取是“回合结束后你自己补记”。三者最终都可能影响同一 memory 目录,但触发者、时机和可见反馈不同。这个区分能明显减少“为什么我刚编辑了还被自动改动”的误解。

命令设计也应围绕这个心智:/memory 偏可控与可审阅,remember 偏交互引导,extract 偏后台兜底。只要文案和 UI 保持这三种角色一致,用户就能预测行为。

在培训或文档里也建议把三入口放在同一页对比说明,并标注“何时触发、是否会改草稿、是否显示系统提示”,这样新用户更容易建立稳定预期。

源码引用: src/commands/memory/memory.tsx · 第 47–74 行(共 103 行)

  47|         editorValue = process.env.VISUAL
  48|       } else if (process.env.EDITOR) {
  49|         editorSource = '$EDITOR'
  50|         editorValue = process.env.EDITOR
  51|       }
  52| 
  53|       const editorInfo =
  54|         editorSource !== 'default'
  55|           ? `Using ${editorSource}="${editorValue}".`
  56|           : ''
  57| 
  58|       const editorHint = editorInfo
  59|         ? `> ${editorInfo} To change editor, set $EDITOR or $VISUAL environment variable.`
  60|         : `> To use a different editor, set the $EDITOR or $VISUAL environment variable.`
  61| 
  62|       onDone(
  63|         `Opened memory file at ${getRelativeMemoryPath(memoryPath)}\n\n${editorHint}`,
  64|         { display: 'system' },
  65|       )
  66|     } catch (error) {
  67|       logError(error)
  68|       onDone(`Error opening memory file: ${error}`)
  69|     }
  70|   }
  71| 
  72|   const handleCancel = () => {
  73|     onDone('Cancelled memory editing', { display: 'system' })
  74|   }

源码引用: src/skills/bundled/remember.ts · 第 64–81 行(共 83 行)

  64|   registerBundledSkill({
  65|     name: 'remember',
  66|     description:
  67|       'Review auto-memory entries and propose promotions to CLAUDE.md, CLAUDE.local.md, or shared memory. Also detects outdated, conflicting, and duplicate entries across memory layers.',
  68|     whenToUse:
  69|       'Use when the user wants to review, organize, or promote their auto-memory entries. Also useful for cleaning up outdated or conflicting entries across CLAUDE.md, CLAUDE.local.md, and auto-memory.',
  70|     userInvocable: true,
  71|     isEnabled: () => isAutoMemoryEnabled(),
  72|     async getPromptForCommand(args) {
  73|       let prompt = SKILL_PROMPT
  74| 
  75|       if (args) {
  76|         prompt += `\n## Additional context from user\n\n${args}`
  77|       }
  78| 
  79|       return [{ type: 'text', text: prompt }]
  80|     },
  81|   })

源码引用: src/services/extractMemories/extractMemories.ts · 第 395–427 行(共 616 行)

 395|       // Pre-inject the memory directory manifest so the agent doesn't spend
 396|       // a turn on `ls`. Reuses findRelevantMemories' frontmatter scan.
 397|       // Placed after the throttle gate so skipped turns don't pay the scan cost.
 398|       const existingMemories = formatMemoryManifest(
 399|         await scanMemoryFiles(memoryDir, createAbortController().signal),
 400|       )
 401| 
 402|       const userPrompt =
 403|         feature('TEAMMEM') && teamMemoryEnabled
 404|           ? buildExtractCombinedPrompt(
 405|               newMessageCount,
 406|               existingMemories,
 407|               skipIndex,
 408|             )
 409|           : buildExtractAutoOnlyPrompt(
 410|               newMessageCount,
 411|               existingMemories,
 412|               skipIndex,
 413|             )
 414| 
 415|       const result = await runForkedAgent({
 416|         promptMessages: [createUserMessage({ content: userPrompt })],
 417|         cacheSafeParams,
 418|         canUseTool,
 419|         querySource: 'extract_memories',
 420|         forkLabel: 'extract_memories',
 421|         // The extractMemories subagent does not need to record to transcript.
 422|         // Doing so can create race conditions with the main thread.
 423|         skipTranscript: true,
 424|         // Well-behaved extractions complete in 2-4 turns (read → write).
 425|         // A hard cap prevents verification rabbit-holes from burning turns.
 426|         maxTurns: 5,
 427|       })

本章小结与延伸

memdir-commands 连接 REPL UI 与磁盘 memory 文件。回到 memdir 总览串联 9 文件全貌。 继续学习:

  • memdir 模块总览
  • memory-extraction
Prev
memory-extraction · extractMemories 与 SessionMemory
Next
app-state-core · store、AppState 类型与 Provider