配置与迁移系统
五层配置层级、Zod Schema 验证、自动迁移、输出样式与快捷键系统
职责概述
解决的问题:每个用户、每个项目对 CC 的行为偏好都不同——允许哪些工具、用哪个模型、什么格式输出、哪些快捷键。需要一个配置系统记住这些偏好,并且在不同版本升级时自动迁移旧配置,不会因为格式变了就挂掉。
应用场景:① 用户设置"总是允许读文件但写文件要确认" ② 项目级配置 .claude/settings.json 定义团队共享的工具权限 ③ CC 升级后自动把旧格式配置迁移到新格式 ④ 用户自定义快捷键映射 ⑤ 组织管理员下发统一策略覆盖个人配置。
一句话理解:就像 VS Code 的 settings.json——个人设置、项目设置、工作区设置分层叠加,升级时自动迁移。
架构设计
配置层级图
子系统架构
核心数据流
配置加载流程
getSettingsForSource(source) 按源类型读取对应文件。策略层优先级:remote > MDM > file > HKCUSettingsSchema().safeParse(data) 验证 JSON 结构。无效权限规则被 filterInvalidPermissionRules() 过滤,不阻塞整文件settingsCache.ts 缓存解析结果,clone() 防止调用者污染缓存getInitialSettings() 按优先级 mergeWith 各层。策略层 > 标志层 > 本地层 > 项目层 > 用户层配置写入流程
undefined 值触发删除resetSettingsCache() 清除所有缓存,确保下次读取反映新值迁移流程
关键类型与接口
SettingSource — 配置来源类型
// utils/settings/constants.ts:7-22
export const SETTING_SOURCES = [
'userSettings', // ~/.claude/settings.json
'projectSettings', // .claude/settings.json
'localSettings', // .claude/settings.local.json
'flagSettings', // --settings 指定的文件
'policySettings', // managed-settings.json / MDM / remote
] as const
export type SettingSource = (typeof SETTING_SOURCES)[number]
SettingsSchema — Zod 验证
// utils/settings/types.ts — 核心验证 Schema
export const PermissionsSchema = lazySchema(() =>
z.object({
allow: z.array(PermissionRuleSchema()).optional(),
deny: z.array(PermissionRuleSchema()).optional(),
ask: z.array(PermissionRuleSchema()).optional(),
defaultMode: z.enum(PERMISSION_MODES).optional(),
disableBypassPermissionsMode: z.enum(['disable']).optional(),
additionalDirectories: z.array(z.string()).optional(),
}).passthrough()
)
配置文件路径映射
// utils/settings/settings.ts:274-296
export function getSettingsFilePathForSource(source: SettingSource): string {
switch (source) {
case 'userSettings':
return join(getClaudeConfigHomeDir(), getUserSettingsFilePath())
case 'projectSettings':
return join(getOriginalCwd(), '.claude', 'settings.json')
case 'localSettings':
return join(getOriginalCwd(), '.claude', 'settings.local.json')
case 'policySettings':
return getManagedSettingsFilePath() // managed-settings.json
case 'flagSettings':
return getFlagSettingsPath()
}
}
OutputStyleConfig — 输出样式
// constants/outputStyles.ts:11-23
export type OutputStyleConfig = {
name: string
description: string
prompt: string // 注入到系统提示词的文本
source: SettingSource | 'built-in' | 'plugin'
keepCodingInstructions?: boolean
forceForPlugin?: boolean // 插件自动激活此样式
}
Keybinding Schema
// keybindings/schema.ts:12-32
export const KEYBINDING_CONTEXTS = [
'Global', 'Chat', 'Autocomplete', 'Confirmation',
'Help', 'Transcript', 'HistorySearch', 'Task',
'ThemePicker', 'Settings', 'Tabs',
'Attachments', 'Footer', 'MessageSelector',
'DiffDialog', 'ModelPicker', 'Select', 'Plugin',
] as const
export const KEYBINDING_ACTIONS = [
'app:interrupt', 'app:exit', 'chat:submit',
'chat:cycleMode', 'chat:modelPicker',
'autocomplete:accept', 'confirm:yes', 'confirm:no',
// ... 80+ 个动作标识符
] as const
迁移函数示例
// migrations/migrateSonnet45ToSonnet46.ts:29-67
export function migrateSonnet45ToSonnet46(): void {
if (getAPIProvider() !== 'firstParty') return
if (!isProSubscriber() && !isMaxSubscriber()) return
const model = getSettingsForSource('userSettings')?.model
if (model !== 'claude-sonnet-4-5-20250929' &&
model !== 'claude-sonnet-4-5-20250929[1m]') return
const has1m = model.endsWith('[1m]')
updateSettingsForSource('userSettings', {
model: has1m ? 'sonnet[1m]' : 'sonnet'
})
// 记录迁移时间戳(非首次启动才通知)
if (getGlobalConfig().numStartups > 1) {
saveGlobalConfig(current => ({
...current,
sonnet45To46MigrationTimestamp: Date.now()
}))
}
}
设计模式与亮点
1. First-Source-Wins 策略(策略层)
策略层(policySettings)使用"首个非空源获胜"而非"合并覆盖":
// utils/settings/settings.ts:322-345
if (source === 'policySettings') {
const remote = getRemoteManagedSettingsSyncFromCache()
if (remote && Object.keys(remote).length > 0) return remote // 最高
const mdm = getMdmSettings()
if (Object.keys(mdm.settings).length > 0) return mdm.settings
const { settings: file } = loadManagedFileSettings()
if (file) return file
const hkcu = getHkcuSettings()
if (Object.keys(hkcu.settings).length > 0) return hkcu.settings
return null
}
这确保了企业策略源的互斥性——远程 API 策略 > 本地 MDM > 文件策略,不会混搭。
2. Drop-in 配置目录
managed-settings.d/ 目录支持多个 JSON 文件独立管理策略片段(类似 systemd/sudoers 的 drop-in 约定)。 文件按字母序排序,后面的覆盖前面的。这让不同团队可以独立管理策略(如 10-otel.json、20-security.json)。
3. 幂等迁移
所有迁移函数都是幂等的——读取特定值、条件匹配才写入。不需要"已完成"标记。
例如 migrateFennecToOpus 只检查 userSettings.model 是否以 'fennec' 开头,已迁移的配置自然不匹配。
4. 输出样式的 Markdown-as-Config
自定义输出样式用 Markdown 文件定义,放在 .claude/output-styles/ 或 ~/.claude/output-styles/。
文件名即样式名,frontmatter 提供 name/description,正文成为注入系统提示词的 prompt 文本。
这让非技术用户也能创建定制样式。
5. 快捷键的上下文感知解析
快捷键系统按 context(如 'Chat'、'Autocomplete'、'Confirmation')组织。
解析器在匹配时查找所有活跃上下文的绑定,最后一个匹配获胜(用户覆盖默认)。
支持 chord 序列(如 ctrl+x ctrl+k)和 null 解绑定。
6. 缓存隔离
配置系统的缓存策略精心设计:
- 文件级缓存:
getCachedParsedFile(path)按文件路径缓存解析结果 - 源级缓存:
getCachedSettingsForSource(source)按源类型缓存合并后的设置 - 会话缓存:
getSessionSettingsCache()缓存最终合并结果 - 写入时全量清除:
resetSettingsCache()确保一致性
开发者实践指南
添加新配置项
步骤 1:在 utils/settings/types.ts 的 Schema 中添加字段:
// 在 SettingsSchema 中添加新字段
myNewFeature: z.boolean().optional()
.describe('Enable the new feature')
步骤 2:在代码中通过 getSettings_DEPRECATED() 或 getSettingsForSource() 读取
const settings = getSettings_DEPRECATED()
const enabled = settings?.myNewFeature ?? false
步骤 3(可选):通过 /config 面板暴露给用户
添加配置迁移
在 migrations/ 目录创建新文件:
// migrations/migrateOldToNew.ts
export function migrateOldToNew(): void {
const settings = getSettingsForSource('userSettings')
if (settings?.oldKey === undefined) return // 已迁移或未设置
updateSettingsForSource('userSettings', {
oldKey: undefined, // 删除旧键
newKey: transform(settings.oldKey),
})
}
添加自定义输出样式
创建 .claude/output-styles/my-style.md:
---
name: My Style
description: A custom output style for my team
keep-coding-instructions: true
---
You are an assistant that always provides concise bullet-point answers.
Focus on actionable steps, avoid lengthy explanations.
自定义快捷键
编辑 ~/.claude/keybindings.json:
{
"$schema": "https://claude.ai/keybindings.schema.json",
"bindings": [
{
"context": "Chat",
"bindings": {
"ctrl+shift+s": "chat:stash",
"ctrl+j": null
}
}
]
}
架构师决策指南
五层层级的设计意图
配置层级的设计遵循"最小权限"原则:
- userSettings(~/.claude/):个人偏好,跨项目生效
- projectSettings(.claude/settings.json):团队共享,提交到 Git,确保团队一致性
- localSettings(.claude/settings.local.json):个人本地偏好,不提交 Git
- flagSettings(--settings):CI/CD 和脚本场景的一次性配置
- policySettings(managed-settings.json):企业管理员强制策略,不可被用户覆盖
策略层的"首个源获胜"(而非合并)是企业场景的关键需求——IT 管理员需要确定性地知道哪条策略生效。
迁移系统的幂等性设计
迁移函数没有版本号或完成标记。它们依赖条件守卫实现幂等: 读取特定值,不匹配则跳过。这意味着迁移可以安全地重复执行(如多次启动),也意味着旧迁移函数可以在新版本中删除—— 当目标值不再存在时,守卫条件自然不满足。
输出样式作为提示词工程
prompt 字段在查询时被追加到系统提示词末尾。
keepCodingInstructions 控制是否保留默认的编码指令。这种设计让输出定制成为一种"提示词模板"机制,
而非代码层面的配置。
快捷键系统的扩展性
快捷键系统使用上下文(context)隔离,新增 UI 场景只需:
- 在
schema.ts的KEYBINDING_CONTEXTS添加新上下文名称 - 在
defaultBindings.ts添加默认绑定 - 在组件中通过
useKeybinding('action', handler)注册
用户通过 keybindings.json 覆盖任何默认绑定,null 值解绑。
reservedShortcuts.ts 阻止用户重新绑定 ctrl+c/ctrl+d 等关键快捷键。
性能考量
- Schema 编译:
lazySchema(() => ...)延迟 Zod schema 编译到首次使用,减少启动时间 - 文件读取:缓存 + clone 策略避免重复 I/O 和缓存污染
- mergeWith:使用 lodash 的自定义合并器处理数组替换(而非深度合并)
- 内部写入标记:
markInternalWrite()区分用户操作和程序更新,避免触发不必要的 watcher
◈ 可视化处理拓扑图
Phase 1 — 五层配置加载与深合并
配置系统从最低优先级到最高优先级逐层叠加。loadSettingsFromDisk() 是核心入口,遍历所有配置源并深合并。
Phase 2 — 配置写入与迁移
写入流程绕过缓存直接读取文件,迁移函数按序执行且幂等。
Phase 3 — 策略互斥 vs 文件叠加
组织管控确定性
用户灵活定制
⇉ 核心处理流程详解
配置系统的核心是 五层合并 流程——从最低优先级的插件设置到最高优先级的 CLI 标志,逐层叠加并深合并。整个流程围绕 loadSettingsFromDisk()(settings.ts:645)展开,每次调用都会遍历所有启用的配置源。
loadSettingsFromDisk() 在 L660-668 以插件设置为最低优先级基座。插件设置只包含 allowlisted 的 key(如 agent),通过 getPluginSettingsBase() 从 plugin 配置中提取 — settings.ts:660-668SettingsSchema().safeParse() 验证(L684-686)— settings.ts:675-739parseSettingsFile()(L749)读取并解析 JSON 文件。解析结果通过 lazySchema(() => SettingsSchema) 延迟编译的 Zod schema 做运行时验证。验证错误被收集到 allErrors 数组,不影响有效配置的加载 — settings.ts:741-790lodash mergeWith 深合并。settingsMergeCustomizer()(L538-547)定义了自定义合并策略:数组执行替换而非拼接——这确保了 permissions.allow 等数组字段的语义是"覆盖"而非"追加" — settings.ts:538-547settingsCache.ts:7-13),后续调用直接返回缓存。配置变更需要重启 CLI 才能生效。当需要确保磁盘最新状态时(如 getSettingsWithSources()),先调用 resetSettingsCache() 清空缓存再重新加载 — settingsCache.ts:7-13、settings.ts:836-848updateSettingsForSource()(L416-524)负责写入:先读取目标源当前配置 → 用 mergeWith 合并新设置 → 通过 Zod schema 验证合并结果 → 原子写入 JSON 文件。写入后通过 markInternalWrite() 标记,区分用户手动编辑和程序更新,避免触发不必要的 watcher — settings.ts:416-524migrations/ 目录下的迁移函数按序执行。每个迁移函数检测旧配置格式并自动转换:如 migrateFennecToOpus.ts 将旧模型名映射到新模型名,migrateAutoUpdatesToSettings.ts 将环境变量设置迁移到 settings.json。迁移是幂等的——重复执行不会产生副作用 — migrations/SettingsWithErrors 返回给上层。UI 层可以显示具体哪个文件的哪个字段有什么问题,帮助用户快速定位配置错误 — settings.ts:730-735★ 设计精华
1. 延迟 Schema 编译——lazySchema 模式
SettingsSchema 使用 lazySchema(() => ...)(types.ts)将 Zod schema 的编译延迟到首次使用。这是因为 SettingsSchema 非常庞大(1149 行类型定义),包含大量嵌套的 permission、hook、MCP server 子 schema。如果在模块加载时编译,会显著拖慢 CLI 启动时间。
// utils/settings/types.ts — 延迟 Zod schema 编译
export const SettingsSchema = lazySchema(() =>
z.object({
permissions: z.union([...]).optional(),
hooks: HooksSchema.optional(),
mcpServers: z.record(...).optional(),
// ... 50+ 字段,嵌套 5+ 层
})
)
2. 策略源互斥 vs 文件源叠加——两种合并哲学
配置系统同时使用了两种截然不同的合并策略:策略设置(policySettings)采用"首源获胜"——远程管理 > MDM > managed-settings.json > HKCU,一旦找到有内容的源就停止查找;文件设置(user/project/local/flag)采用"深合并叠加"——所有源都参与合并,高优先级源的字段覆盖低优先级源的同名字段。
// settings.ts:675-739 — 策略源互斥(first source wins)
if (source === 'policySettings') {
// 1. Remote (highest)
const remoteSettings = getRemoteManagedSettingsSyncFromCache()
if (remoteSettings && Object.keys(remoteSettings).length > 0) {
policySettings = SettingsSchema().safeParse(remoteSettings).data
}
// 2. MDM — 仅在 remote 无内容时检查
if (!policySettings) { policySettings = getMdmSettings().settings }
// 3. managed-settings.json — 仅在 MDM 无内容时检查
if (!policySettings) { policySettings = loadManagedFileSettings().settings }
}
// settings.ts:673-674 — 文件源叠加(deep merge all)
for (const source of getEnabledSettingSources()) {
mergedSettings = mergeWith(mergedSettings, settings, settingsMergeCustomizer)
}
3. 文件路径去重——跨源同一文件防御
某些场景下不同配置源可能指向同一个文件(如 projectSettings 和 localSettings 在非项目目录时路径相同)。seenFiles Set(L746-747)确保每个物理文件只被解析一次,避免重复读取和验证。
// settings.ts:741-749
const filePath = getSettingsFilePathForSource(source)
if (filePath) {
const resolvedPath = resolve(filePath)
if (!seenFiles.has(resolvedPath)) { // 路径去重
seenFiles.add(resolvedPath)
const { settings, errors } = parseSettingsFile(filePath)
// ...merge
}
}
4. 迁移幂等性——版本无关的配置兼容
每个迁移函数都是幂等的——重复执行不会改变配置状态。例如 migrateFennecToOpus.ts 只在检测到旧模型名时才替换,如果配置已经是新模型名则直接跳过。这使得迁移可以安全地在每次启动时运行,不需要记录"已执行的迁移"状态。
// migrations/migrateFennecToOpus.ts — 典型幂等迁移
// 仅当 model 字段包含旧值时才更新
if (settings.model === 'fennec') {
updateSettingsForSource('userSettings', { model: 'opus' })
}
◈ Agent 实践借鉴 — 客服 Agent 配置管理设计
一、场景映射:客服 agent 的配置管理挑战
SaaS 客服平台是典型的多租户系统:A 公司用自建 LLM(私有化部署),B 公司用 OpenAI API;A 公司的电话客服要求回复风格正式严谨,B 公司的在线客服偏好活泼亲切;坐席小王想自定义问候语,但不能改系统的 API 地址。CC 的三层配置(全局 → 项目 → 本地)完美映射到客服的三层:平台全局 → 租户 → 坐席个人。CC 的策略层互斥设计则对应平台方对租户的管控边界。
二、借鉴 CC + 客服改造
实践 1:三层配置层级(借鉴 CC 全局/项目/本地分层)
CC 用五个 SettingSource 实现了"全局默认 → 项目覆盖 → 本地定制"的分层体系。客服场景同样需要三层:Platform(平台默认配置)→ Tenant(租户覆盖)→ Agent(坐席个人配置),高优先级覆盖低优先级的同名字段。
// 借鉴 settings.ts 的 SettingSource 分层设计
type ConfigSource = 'platform' | 'tenant' | 'agent'
interface CustomerServiceConfig {
model: string // 使用哪个 LLM
replyStyle: 'formal' | 'casual' | 'professional'
greeting: string // 问候语
maxRetries: number // 最大重试次数
apiEndpoint: string // LLM API 地址
allowedChannels: string[] // 该租户开通的渠道
transferRules: TransferRule[] // 转接人工的规则
systemPrompt: string // 系统提示词
}
// 三层配置加载:platform → tenant → agent
function loadEffectiveConfig(
tenantId: string, agentId: string,
): CustomerServiceConfig {
// 第一层:平台默认配置(最低优先级)
const platformDefaults = loadPlatformConfig()
// 第二层:租户配置(覆盖平台默认)
const tenantConfig = loadTenantConfig(tenantId)
// 第三层:坐席个人配置(覆盖租户配置)
const agentConfig = loadAgentConfig(tenantId, agentId)
// 深合并:tenant 覆盖 platform,agent 覆盖 tenant
return deepMergeConfig(
deepMergeConfig(platformDefaults, tenantConfig),
agentConfig,
)
}
// 示例:平台默认用 sonnet,A 租户用自建 LLM,坐席小王不改模型
// Platform: { model: 'sonnet', apiEndpoint: 'https://api.openai.com' }
// Tenant A: { model: 'custom-llm', apiEndpoint: 'https://llm.company-a.com' }
// Agent 小王: { greeting: '您好,我是小王' } // 只改问候语
// 最终结果: { model: 'custom-llm', apiEndpoint: 'https://llm.company-a.com',
// greeting: '您好,我是小王', ... }
实践 2:深层合并策略(借鉴 CC 的 mergeWith + 数组替换)
CC 的 settingsMergeCustomizer 确保数组执行替换而非拼接。客服场景也有同样需求——transferRules(转接规则)在租户层配置了就应该完全替换平台默认规则,而不是追加。
// 借鉴 settings.ts:538-547 的 settingsMergeCustomizer
function deepMergeConfig<T>(base: T, override: Partial<T>): T {
return mergeWith({}, base, override, (baseVal, overVal) => {
// 数组执行替换,不拼接——转接规则的语义是"覆盖"而非"追加"
if (Array.isArray(baseVal) && Array.isArray(overVal)) {
return overVal // 租户的 transferRules 完全替换平台默认
}
// 字符串/数字等基本类型直接覆盖
if (typeof baseVal !== 'object' || baseVal === null) return overVal
// 对象递归合并
return undefined // 让 mergeWith 默认递归
})
}
// 反例(错误做法):用 Object.assign 或浅拷贝
// Object.assign(platformDefaults, tenantConfig)
// 问题:嵌套对象会被整体替换而非合并
// 平台: { systemPrompt: { prefix: '你是客服', suffix: '请保持礼貌' } }
// 租户: { systemPrompt: { prefix: '你是 A 公司的客服' } }
// Object.assign 结果: { prefix: '你是 A 公司的客服' } // suffix 丢了!
// 深合并结果: { prefix: '你是 A 公司的客服', suffix: '请保持礼貌' } // 正确
实践 3:配置热更新(借鉴 CC 的文件监听 + 缓存失效)
CC 通过 resetSettingsCache() 在写入后清除缓存。客服场景更严格——10 万个在线会话的配置变更必须实时生效,不能重启 agent。
// 借鉴 settingsCache.ts 的缓存失效 + 广播模式
class TenantConfigManager {
private configCache = new Map<string, { config: CustomerServiceConfig; version: number }>()
private subscribers = new Map<string, Set<(config: CustomerServiceConfig) => void>>()
// 管理后台修改租户配置 → 触发热更新
async updateTenantConfig(tenantId: string, patch: Partial<CustomerServiceConfig>) {
// 1. 验证配置合法性
const validation = validateConfig(patch)
if (!validation.success) {
throw new Error(`配置校验失败:${validation.errors.map(e => e.message).join('; ')}`)
}
// 2. 写入数据库
await db.tenantConfigs.update(tenantId, patch)
// 3. 清除缓存(借鉴 resetSettingsCache)
const newConfig = this.reloadFromDB(tenantId)
// 4. 广播给该租户所有正在运行的 agent 实例
this.broadcastToAgents(tenantId, newConfig)
}
// 每个 agent 实例订阅自己租户的配置变更
subscribe(tenantId: string, callback: (config: CustomerServiceConfig) => void) {
if (!this.subscribers.has(tenantId)) {
this.subscribers.set(tenantId, new Set())
}
this.subscribers.get(tenantId)!.add(callback)
// 返回取消订阅函数
return () => this.subscribers.get(tenantId)?.delete(callback)
}
private broadcastToAgents(tenantId: string, config: CustomerServiceConfig) {
const agents = this.subscribers.get(tenantId)
agents?.forEach(cb => {
try { cb(config) } catch (e) { console.error('配置热更新回调失败', e) }
})
}
}
实践 4:配置校验与友好错误提示(借鉴 CC 的 Zod 验证 + 错误收集)
// 借鉴 settings.ts 的 parseSettingsFile + 错误收集模式
interface ConfigError {
field: string // 'transferRules[2].condition'
message: string // '条件表达式语法错误'
suggestion: string // '请参考文档:https://docs.example.com/rules'
}
function validateConfig(config: unknown): { success: boolean; errors: ConfigError[] } {
const result = CustomerServiceConfigSchema.safeParse(config)
if (result.success) return { success: true, errors: [] }
const errors: ConfigError[] = result.error.issues.map(issue => ({
field: issue.path.join('.'),
message: issue.message,
suggestion: getSuggestion(issue.path.join('.'), issue.message),
}))
return { success: false, errors }
}
// 友好提示,不是 "schema validation failed"
function getSuggestion(field: string, _msg: string): string {
if (field === 'apiEndpoint') return '请输入完整的 HTTPS 地址,如 https://api.openai.com'
if (field.startsWith('transferRules')) return '转接规则格式:{ condition: "关键词", target: "技能组ID" }'
if (field === 'replyStyle') return '可选值:formal / casual / professional'
return '请参考配置文档'
}
三、落地清单
- 分层配置 + 深层合并——Platform/Tenant/Agent 三层,用深合并而非浅拷贝,数组必须替换不拼接
- 配置校验——每个字段验证 + 友好错误提示,不是 "schema validation failed"
- 配置热更新——管理后台修改后广播给运行中的 agent 实例,不能要求重启
- lazySchema——客服场景 agent 启动不频繁(不像 CLI 每次命令都启动),可以启动时编译 schema
- .claude/settings.local.json 概念——客服配置在管理后台数据库,不在本地文件系统
- 模型名迁移——CC 的 migrateFennecToOpus 是因为 API 模型名变了,客服场景的配置迁移需求不同
四、常见坑
- 用 Object.assign 做配置合并——嵌套对象被整体替换而非合并,租户只想改
systemPrompt.prefix却把systemPrompt.suffix丢了。必须用深层合并。 - 配置热更新丢失进行中的对话状态——配置变更后广播给 agent,但正在执行的对话可能用的是旧配置。必须做"新会话用新配置,旧会话保持旧配置"的灰度切换。
- 坐席个人配置覆盖了系统安全字段——坐席不应修改
apiEndpoint或transferRules等系统配置。借鉴 CC 的策略层只读保护,用白名单控制坐席可修改的字段。 - 校验错误消息对运营人员不友好——运营人员不是开发者,"Expected string, received number" 毫无意义。必须为每个字段提供中文友好提示和修复建议。
代码索引
| 文件 | 行数 | 说明 |
|---|---|---|
utils/settings/settings.ts | ~500 | 配置读取、合并、写入核心逻辑 |
utils/settings/types.ts | ~200 | SettingsJson 类型、Zod Schema 定义 |
utils/settings/constants.ts | ~100 | SETTING_SOURCES 定义、源名称映射 |
utils/settings/validation.ts | ~200 | Zod 错误格式化、权限规则过滤 |
utils/settings/settingsCache.ts | ~150 | 多级缓存(文件/源/会话) |
utils/settings/managedPath.ts | ~50 | managed-settings.json 路径管理 |
utils/settings/changeDetector.ts | ~80 | 配置变更检测(文件 watcher) |
utils/settings/applySettingsChange.ts | ~100 | 配置变更应用逻辑 |
utils/config.ts | ~400 | 全局配置管理(globalConfig) |
migrations/migrateAutoUpdatesToSettings.ts | ~62 | 自动更新配置迁移 |
migrations/migrateFennecToOpus.ts | ~46 | Fennec → Opus 模型名迁移 |
migrations/migrateSonnet45ToSonnet46.ts | ~68 | Sonnet 4.5 → 4.6 别名迁移 |
migrations/migrateOpusToOpus1m.ts | ~40 | Opus → Opus 1m 迁移 |
migrations/migrateLegacyOpusToCurrent.ts | ~40 | 旧版 Opus 到当前版迁移 |
migrations/migrateReplBridgeEnabledToRemoteControlAtStartup.ts | ~30 | Bridge → RemoteControl 迁移 |
constants/outputStyles.ts | ~217 | 内置输出样式定义(default/Explanatory/Learning) |
outputStyles/loadOutputStylesDir.ts | ~99 | 从 .claude/output-styles/ 加载自定义样式 |
keybindings/defaultBindings.ts | ~341 | 默认快捷键绑定(按上下文组织) |
keybindings/schema.ts | ~237 | Zod Schema、上下文/动作枚举 |
keybindings/resolver.ts | ~120 | 快捷键解析器(匹配 + chord 支持) |
keybindings/parser.ts | ~100 | 快捷键字符串解析 |
keybindings/useKeybinding.ts | ~80 | React hook:注册快捷键处理器 |