Claude Code源码系列:5、代码规范与细节
1. 基础配置与规范
1.1 TypeScript 编译配置解析
作为一个反编译的源码项目,Claude Code 的 TypeScript 配置展现了对构建工具适配和容错策略的精心考量。让我们先看完整的 tsconfig.json 配置:
// tsconfig.json 完整配置{"compilerOptions": {// ── 编译目标与模块系统 ─────────────────────────────────────────────────"target": "ES2022", // 目标 ES2022,支持顶层 await、class fields 等"module": "ESNext", // 最新 ES 模块规范,适配 esbuild"moduleResolution": "bundler", // bundler 模式:专为打包工具优化的路径解析// ── 互操作性配置 ───────────────────────────────────────────────────────"esModuleInterop": true, // 允许 CommonJS 模块默认导入"allowSyntheticDefaultImports": true, // 允许合成默认导入// ── 类型检查策略(反编译项目的特殊考量)───────────────────────────────"strict": false, // 非严格模式:容许反编译后的类型不完全匹配"skipLibCheck": true, // 跳过库类型检查:加速编译,避免第三方类型冲突"forceConsistentCasingInFileNames": true, // 强制文件名大小写一致性// ── 输出配置 ───────────────────────────────────────────────────────────"declaration": true, // 生成 .d.ts 类型声明文件"declarationMap": true, // 声明文件映射,支持源码导航"sourceMap": true, // 生成 source map,便于调试"outDir": "dist", // 输出目录"rootDir": "src", // 源码根目录// ── JSX 编译策略 ───────────────────────────────────────────────────────"jsx": "react-jsx", // 自动 JSX 转换,无需 React 全局引入// ── 路径映射与 stub 模块 ────────────────────────────────────────────────"baseUrl": ".", // 相对路径解析基准"paths": {"bun:bundle": ["stubs/bun-bundle.ts"], // Bun 编译时特性 stub 替换"src/*": ["src/*"] // 源码路径别名 },// ── 类型环境 ────────────────────────────────────────────────────────────"types": ["node"], // 仅包含 Node.js 类型"lib": ["ES2022", "DOM"], // 同时支持 ES2022 和 DOM API 类型// ── 其他配置 ────────────────────────────────────────────────────────────"resolveJsonModule": true, // 允许导入 JSON 模块"allowImportingTsExtensions": false, // 禁止导入 .ts 扩展名"noEmit": false// 允许输出文件 },"include": ["src/**/*", "stubs/**/*"], // 包含源码和 stub 目录"exclude": ["node_modules", "dist"] // 排除依赖和输出目录}
下表详细解析每个关键配置项的设计考量:
|
|
|
|
|
|---|---|---|---|
target |
ES2022 |
|
|
moduleResolution |
bundler |
|
|
strict |
false |
反编译源码容错策略
|
|
jsx |
react-jsx |
import React from 'react' |
|
paths |
bun:bundle
|
|
|
lib |
ES2022, DOM |
|
|
1.1.1 bundler 模块解析策略详解
moduleResolution: "bundler" 是 TypeScript 5.0+ 引入的新模式,专为现代打包工具设计:
// bundler 模式下的导入规则import { Tool } from'./Tool'// ✅ 无需扩展名,打包工具会解析import { config } from'./utils/config.js'// ✅ 带 .js 扩展名也能正确解析// 传统 node 模式需要:// import { Tool } from './Tool.js' // ❌ 传统 node 模式要求扩展名// import { Tool } from './Tool.ts' // ❌ 传统模式不支持 .ts 扩展名
为什么选择 bundler 模式?
-
与 esbuild 行为一致:esbuild 本身不要求导入路径包含扩展名 -
支持路径别名:配合 baseUrl和paths实现模块别名 -
条件导入兼容:支持 require()和import混合使用
1.1.2 非严格模式的特殊考量
反编译项目选择 strict: false 的原因:
// 反编译后的代码可能存在类型不匹配// 严格模式下会报错,非严格模式容许通过// 示例:反编译后参数类型可能丢失functionprocessToolResult(result: any) { // any 类型在严格模式不推荐// 反编译可能导致具体类型信息丢失return result.data}// 示例:可选属性可能缺失类型定义type PartialToolDef = { name: string// call?: Function // 反编译后类型可能丢失}
1.2 构建系统设计:feature() 与 MACRO 编译时替换
Claude Code 原始构建使用 Bun bundler,具有独特的编译时特性开关机制。反编译项目通过 scripts/build.mjs 模拟这一机制。
1.2.1 构建流程四阶段详解
┌─────────────────────────────────────────────────────────────────────────┐│ 构建流程四阶段 │├─────────────────────────────────────────────────────────────────────────┤│ ││ Phase 1: Copy source ││ ──────────────────────── ││ src/ ──────────────► build-src/ ││ (原始源码) (转换工作区) ││ ││ Phase 2: Transform source ││ ───────────────────────── ││ feature('X') ─────► false ││ MACRO.VERSION ─────► '2.1.88' ││ bun:bundle ─────► stub 注释 ││ ││ Phase 3: Create entry wrapper ││ ───────────────────────── ││ entry.ts = "#!/usr/bin/env node" + "import './src/entrypoints/cli.tsx'"││ ││ Phase 4: esbuild bundle (迭代 stub 生成) ││ ───────────────────────── ││ [Round 1] esbuild → 收集 missing modules ││ [Round 2] 创建 stubs → 重试 esbuild ││ [Round N] 最多 5 轮 → dist/cli.js ││ │└─────────────────────────────────────────────────────────────────────────┘
1.2.2 feature() 编译时特性开关
Bun bundler 的 feature() 函数在编译时被解析,用于条件编译:
// src/types/permissions.ts - feature gate 条件模式exportconst INTERNAL_PERMISSION_MODES = [ ...EXTERNAL_PERMISSION_MODES, ...(feature('TRANSCRIPT_CLASSIFIER') ? ['auto'] : []), // 编译时决定] asconst// 编译后结果 (feature('TRANSCRIPT_CLASSIFIER') → false):exportconst INTERNAL_PERMISSION_MODES = ['acceptEdits', 'bypassPermissions', 'default', 'dontAsk', 'plan']
build.mjs 中的转换逻辑:
// scripts/build.mjs - feature() 替换逻辑for await (const file of walk(join(BUILD, 'src'))) {if (!file.match(/\.[tj]sx?$/)) continuelet src = await readFile(file, 'utf8')// Step 1: feature('X') → false// 使用正则匹配所有 feature() 调用if (/\bfeature\s*\(\s*['"][A-Z_]+['"]\s*\)/.test(src)) { src = src.replace(/\bfeature\s*\(\s*['"][A-Z_]+['"]\s*\)/g, // 匹配 feature('FLAG_NAME')'false'// 替换为 false ) changed = true }}
1.2.3 MACRO 常量注入机制
MACRO 是 Bun bundler 通过 --define 参数注入的编译时常量:
// scripts/build.mjs - MACRO 替换表const MACROS = {// 版本信息'MACRO.VERSION': `'${VERSION}'`, // → '2.1.88''MACRO.BUILD_TIME': `''`, // → 空字符串// 反馈渠道'MACRO.FEEDBACK_CHANNEL': `'https://github.com/anthropics/claude-code/issues'`,'MACRO.FEEDBACK_CHANNEL_URL': `'https://github.com/anthropics/claude-code/issues'`,'MACRO.ISSUES_EXPLAINER': `'https://github.com/anthropics/claude-code/issues/new/choose'`,'MACRO.ISSUES_EXPLAINER_URL': `'https://github.com/anthropics/claude-code/issues/new/choose'`,// 包定位'MACRO.NATIVE_PACKAGE_URL': `'@anthropic-ai/claude-code'`,'MACRO.PACKAGE_URL': `'@anthropic-ai/claude-code'`,// 其他'MACRO.VERSION_CHANGELOG': `''`, // → 空字符串}
|
|
|
|
|
|---|---|---|---|
MACRO.VERSION |
'2.1.88' |
|
|
MACRO.FEEDBACK_CHANNEL |
|
|
|
MACRO.PACKAGE_URL |
|
|
|
MACRO.BUILD_TIME |
'' |
|
|
1.2.4 迭代式 stub 生成策略
由于反编译项目缺少 108 个 feature-gated 模块,构建采用迭代策略:
// scripts/build.mjs - 迭代 stub 生成const MAX_ROUNDS = 5for (let round = 1; round <= MAX_ROUNDS; round++) {try {// 尝试 esbuild 打包 esbuildOutput = execSync(['npx esbuild',`"${ENTRY}"`,'--bundle','--platform=node','--target=node18','--format=esm',`--outfile="${OUT_FILE}"`,'--packages=external', // 外部化 node_modules'--external:bun:*', // 外部化 bun 模块 ].join(' ')) succeeded = truebreak } catch (e) {// 解析缺失模块const missingRe = /Could not resolve "([^"]+)"/gconst missing = new Set()// 收集所有缺失的模块路径while ((m = missingRe.exec(esbuildOutput)) !== null) { missing.add(m[1]) }// 为缺失模块创建 stubfor (const mod of missing) {// 文本资产 → 空文件if (/\.(txt|md|json)$/.test(mod)) {await writeFile(p, mod.endsWith('.json') ? '{}' : '') }// JS/TS 模块 → 导出空函数if (/\.[tj]sx?$/.test(mod)) {await writeFile(p, `export default function ${safeName}() {}\n`) } } }}
1.3 ESLint 内联规范与 custom-rules
项目未包含 ESLint 配置文件,但源码中大量使用内联 eslint-disable 注释,表明原项目有自定义的 custom-rules 规则集。
1.3.1 条件导入的 lint 处理模式
// src/screens/REPL.tsx - 条件导入示例/* eslint-disable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */// 当 feature gate 关闭时,使用 require() 延迟加载避免循环依赖const useVoiceIntegration = feature('VOICE_MODE') ? require('../hooks/useVoiceIntegration.js').useVoiceIntegration : () => ({ stripTrailing: () =>0 }) // 提供空实现作为 fallback/* eslint-enable custom-rules/no-process-env-top-level, @typescript-eslint/no-require-imports */
custom-rules 规则推测:
|
|
|
|
|---|---|---|
no-top-level-side-effects |
|
|
no-process-env-top-level |
|
|
no-require-imports |
|
|
1.3.2 ESLint 注释的最佳实践模式
// 最佳实践:局部禁用 + 明确启用范围/* eslint-disable custom-rules/no-process-env-top-level */const debugMode = process.env.DEBUG === 'true'// 仅此处允许/* eslint-enable custom-rules/no-process-env-top-level */// 避免:全局禁用(难以追踪)/* eslint-disable */// ❌ 不推荐:影响整个文件
2. 命名规范详解
2.1 文件命名规范
Claude Code 项目遵循清晰的文件命名约定,通过命名即可识别文件类型和用途:
|
|
|
|
|
|---|---|---|---|
| 组件文件 |
|
ListItem.tsx
StatusLine.tsx |
|
| 模块文件 |
|
shellProvider.ts
bashPermissions.ts |
|
| 工具目录 | {ToolName}Tool/ |
BashTool/
GlobTool/ |
|
| 常量文件 |
|
constants.ts
toolLimits.ts |
|
| 类型文件 |
|
permissions.ts
|
|
| Hook 文件 |
|
useCanUseTool.tsx |
|
2.1.1 工具目录结构化命名
每个工具模块遵循统一的目录结构:
src/tools/{ToolName}Tool/├── {ToolName}Tool.tsx # 主实现文件 (buildTool 构建)├── UI.tsx # React 渲染组件 (工具使用时显示)├── UI.ts # UI 工具函数├── toolName.ts # 名称常量 ({TOOL_NAME})├── prompt.js # 模型提示词模板 (AI 提示)├── {toolName}Permissions.ts # 权限检查逻辑├── {toolName}Validation.ts # 输入验证逻辑├── shouldUse{Feature}.ts # 功能开关判断├── commandSemantics.ts # 命令语义解析└── utils.ts # 辅助函数
具体示例:BashTool 目录结构:
src/tools/BashTool/├── BashTool.tsx # 主实现:buildTool() 构建完整工具对象├── BashToolResultMessage.tsx # 结果渲染组件├── UI.tsx # 渲染:工具使用进度、结果展示├── UI.ts # renderToolUseMessage 等渲染函数├── toolName.ts # export const BASH_TOOL_NAME = 'Bash'├── prompt.js # getDefaultTimeoutMs(), getSimplePrompt()├── bashPermissions.ts # bashToolHasPermission(), matchWildcardPattern()├── readOnlyValidation.ts # checkReadOnlyConstraints()├── shouldUseSandbox.ts # shouldUseSandbox() 判断逻辑├── commandSemantics.ts # interpretCommandResult()├── sedEditParser.ts # parseSedEditCommand()└── utils.ts # buildImageToolResult(), isSearchOrReadBashCommand()
2.2 常量与类型命名
2.2.1 常量命名:UPPER_SNAKE_CASE
常量使用全大写蛇形命名,语义清晰:
// src/tools/BashTool/BashTool.tsx - 命令语义分类常量const BASH_SEARCH_COMMANDS = new Set(['find', 'grep', 'rg', 'ag', 'ack', 'locate', 'which', 'whereis'])const BASH_READ_COMMANDS = new Set(['cat', 'head', 'tail', 'less', 'more','wc', 'stat', 'file', 'strings','jq', 'awk', 'cut', 'sort', 'uniq', 'tr'])const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du'])const BASH_SEMANTIC_NEUTRAL_COMMANDS = new Set(['echo', 'printf', 'true', 'false', ':'// bash no-op])const BASH_SILENT_COMMANDS = new Set(['mv', 'cp', 'rm', 'mkdir', 'rmdir','chmod', 'chown', 'chgrp', 'touch', 'ln', 'cd'])// 时间阈值常量const PROGRESS_THRESHOLD_MS = 2000// 2秒后显示进度const ASSISTANT_BLOCKING_BUDGET_MS = 15_000 // 15秒自动后台化const DEFAULT_TIMEOUT = 30 * 60 * 1000// 30分钟默认超时
2.2.2 类型命名:PascalCase + 描述性后缀
类型命名采用语义化后缀,一眼可辨类型用途:
|
|
|
|
|---|---|---|
Props |
|
ListItemProps
SpinnerProps |
Context |
|
ToolPermissionContext
ToolUseContext |
Result |
|
PermissionResult
ExecResult, ValidationResult |
Schema |
|
ModelUsageSchema
BashInputSchema |
Provider |
|
ShellProvider
BashShellProvider |
Decision |
|
PermissionAllowDecision
PermissionDenyDecision |
Config |
|
QueryEngineConfig
ShellConfig |
State |
|
AppState
FileStateCache |
Mode |
|
PermissionMode
SpinnerMode |
Options |
|
BuildExecOptions
SpawnOptions |
Event |
|
StreamEvent
CompactProgressEvent |
类型定义示例:
// src/Tool.ts - Tool 系统核心类型export type ToolPermissionContext = DeepImmutable<{ mode: PermissionMode additionalWorkingDirectories: Map<string, AdditionalWorkingDirectory> alwaysAllowRules: ToolPermissionRulesBySource alwaysDenyRules: ToolPermissionRulesBySource alwaysAskRules: ToolPermissionRulesBySource isBypassPermissionsModeAvailable: boolean isAutoModeAvailable?: boolean strippedDangerousRules?: ToolPermissionRulesBySource shouldAvoidPermissionPrompts?: boolean awaitAutomatedChecksBeforeDialog?: boolean prePlanMode?: PermissionMode}>export type ValidationResult = | { result: true } // 验证通过 | { result: false; message: string; errorCode: number } // 验证失败
3. 注释与文档规范
3.1 JSDoc 文档注释规范
项目采用标准的 JSDoc 格式,注重完整性和示例驱动。以下以 ListItem.tsx 为例进行深度剖析:
3.1.1 类型属性的 JSDoc 注释
// src/components/design-system/ListItem.tsx - 完整类型定义注释type ListItemProps = {/** * Whether this item is currently focused (keyboard selection). * Shows the pointer indicator when true. */ isFocused: boolean;/** * Whether this item is selected (chosen/checked). * Shows the checkmark indicator when true. * @default false */ isSelected?: boolean;/** * The content to display for this item. */ children: ReactNode;/** * Optional description text displayed below the main content. */ description?: string;/** * Show a down arrow indicator instead of pointer (for scroll hints). * Only applies when not focused. */ showScrollDown?: boolean;/** * Show an up arrow indicator instead of pointer (for scroll hints). * Only applies when not focused. */ showScrollUp?: boolean;/** * Whether to apply automatic styling to the children based on focus/selection state. * - When true (default): children are wrapped in Text with state-based colors * - When false: children are rendered as-is, allowing custom styling * @default true */ styled?: boolean;/** * Whether this item is disabled. Disabled items show dimmed text and no indicators. * @default false */ disabled?: boolean;/** * Whether this ListItem should declare the terminal cursor position. * Set false when a child (e.g. BaseTextInput) declares its own cursor. * @default true */ declareCursor?: boolean;};
JSDoc 注释要素解析:
|
|
|
|
|---|---|---|
|
|
/** ... */
|
|
@default
|
@default {value} |
|
@example
|
@example {code} |
|
|
|
- When X: ... |
|
3.1.2 函数的完整 JSDoc 注释
/** * A list item component for selection UIs (dropdowns, multi-selects, menus). * * Handles the common pattern of: * - Pointer indicator for focused items * - Checkmark indicator for selected items * - Scroll indicators for truncated lists * - Color states for focus/selection * * @example * // Basic usage in a selection list * {options.map((option, i) => ( * <ListItem * key={option.id} * isFocused={focusIndex === i} * isSelected={selectedId === option.id} * > * {option.label} * </ListItem> * ))} * * @example * // With scroll indicators for truncated list * <ListItem isFocused={false} showScrollUp>First visible item</ListItem> * <ListItem isFocused={true}>Current selection</ListItem> * <ListItem isFocused={false} showScrollDown>Last visible item</ListItem> * * @example * // With description (secondary text) * <ListItem * isFocused={true} * isSelected={false} * description="Secondary text displayed below main content" * > * Primary text * </ListItem> * * @example * // Custom children styling (styled=false) * <ListItem isFocused={true} styled={false}> * <Text color="claude">Custom styled content</Text> * </ListItem> */exportfunctionListItem(props: ListItemProps): React.ReactNode{ ... }
3.1.3 设计说明注释
核心架构函数使用多行注释解释设计决策:
// src/Tool.ts - buildTool 设计说明/** * Build a complete `Tool` from a partial definition, filling in safe defaults * for the commonly-stubbed methods. All tool exports should go through this so * that defaults live in one place and callers never need `?.() ?? default`. * * Defaults (fail-closed where it matters): * - `isEnabled` → `true` // 默认启用 * - `isConcurrencySafe` → `false` // 默认不并发安全(保守策略) * - `isReadOnly` → `false` // 默认为写操作(保守策略) * - `isDestructive` → `false` // 默认非破坏性 * - `checkPermissions` → `{ behavior: 'allow', updatedInput }` // 依赖通用权限系统 * - `toAutoClassifierInput` → `''` // 跳过分类器(安全相关工具必须覆盖) * - `userFacingName` → `name` // 使用工具名作为显示名 * * Why this design: * - Centralized defaults prevent divergence across 60+ tool implementations * - "Fail-closed" defaults ensure security-sensitive operations are explicitly marked * - Optional params allow both 0-arg and full-arg call sites to type-check */
设计说明注释的要素:
-
Why this design:解释设计动机 -
Defaults 列表:枚举默认值及其含义 -
安全策略标注:明确保守策略的原因
3.2 代码块分隔注释风格
项目使用三种层次的分隔符,用于组织大型文件:
3.2.1 主要段落分隔符:// ═══════════════════════
用于构建脚本中的主要阶段分隔:
// scripts/build.mjs - 主要阶段分隔// ══════════════════════════════════════════════════════════════════════════════// PHASE 1: Copy source// ══════════════════════════════════════════════════════════════════════════════await rm(BUILD, { recursive: true, force: true })await mkdir(BUILD, { recursive: true })await cp(join(ROOT, 'src'), join(BUILD, 'src'), { recursive: true })// ══════════════════════════════════════════════════════════════════════════════// PHASE 2: Transform source// ══════════════════════════════════════════════════════════════════════════════let transformCount = 0// MACRO replacements...
3.2.2 次级段落分隔符:// ────────────────
用于函数组或辅助代码块分隔:
// scripts/build.mjs - 次级分隔// ── Helpers ────────────────────────────────────────────────────────────────async function* walk(dir) {for (const e of await readdir(dir, { withFileTypes: true })) {const p = join(dir, e.name)if (e.isDirectory() && e.name !== 'node_modules') yield* walk(p)else yield p }}async functionexists(p) {try { await stat(p); return true }catch { return false }}
3.2.3 类型定义分隔符:// ============
用于类型定义文件中的逻辑分组:
// src/types/permissions.ts - 类型定义分组// ============================================================================// Permission Modes// ============================================================================export const EXTERNAL_PERMISSION_MODES = ['acceptEdits','bypassPermissions','default','dontAsk','plan',] as const// ============================================================================// Permission Behaviors// ============================================================================export type PermissionBehavior = 'allow' | 'deny' | 'ask'// ============================================================================// Permission Rules// ============================================================================export type PermissionRuleSource = | 'userSettings' | 'projectSettings' | 'localSettings' | 'flagSettings' | 'policySettings'// ============================================================================// Permission Decisions & Results// ============================================================================export type PermissionAllowDecision<Input> = { behavior: 'allow' updatedInput?: Input userModified?: boolean decisionReason?: PermissionDecisionReason}
分隔符层次对照:
|
|
|
|
|
|---|---|---|---|
// ══════ |
|
|
scripts/build.mjs |
// ════ |
|
|
src/types/permissions.ts |
// ────── |
|
|
scripts/build.mjs |
// ─── |
|
|
|
4. 核心架构设计
4.1 Tool 系统架构:buildTool 构建器模式
Tool 系统是 Claude Code 的核心抽象,采用构建器模式集中管理默认值。
4.1.1 Tool 接口完整定义
// src/Tool.ts - Tool 接口核心定义(简化版)export type Tool<Input, Output, P extends ToolProgressData> = {// ── 标识属性 ───────────────────────────────────────────────────────────── name: string// 工具名称,如 'Bash', 'Read' aliases?: string[] // 命令别名,如 ['b', 'run']// ── 输入验证 ───────────────────────────────────────────────────────────── inputSchema: z.ZodType // Zod schema 验证输入格式// ── 核心方法 ───────────────────────────────────────────────────────────── call(args, context, canUseTool, parentMessage, onProgress): Promise<ToolResult<Output>> description(input, options): Promise<string> prompt(options): Promise<string>// ── 权限相关 ───────────────────────────────────────────────────────────── checkPermissions(input, context): Promise<PermissionResult>// ── 状态判断 ───────────────────────────────────────────────────────────── isEnabled(): boolean// 工具是否启用 isConcurrencySafe(input): boolean// 是否可并发执行 isReadOnly(input): boolean// 是否只读操作 isDestructive?(input): boolean// 是否破坏性操作// ── 显示相关 ───────────────────────────────────────────────────────────── userFacingName(input): string// 用户可见名称// ── React 渲染方法 ────────────────────────────────────────────────────── renderToolUseMessage?(...): React.ReactNode renderToolResultMessage?(...): React.ReactNode renderToolUseProgressMessage?(...): React.ReactNode renderToolUseQueuedMessage?(...): React.ReactNode renderToolUseErrorMessage?(...): React.ReactNode}
4.1.2 buildTool 构建器实现
// src/Tool.ts - buildTool 构建器函数(源码级详解)// Step 1: 定义可默认化的方法集合type DefaultableToolKeys = | 'isEnabled' | 'isConcurrencySafe' | 'isReadOnly' | 'isDestructive' | 'checkPermissions' | 'toAutoClassifierInput' | 'userFacingName'// Step 2: 工具定义类型 = 完整 Tool 类型 - 可默认化方法 + 可选的可默认化方法export type ToolDef<Input, Output, P> = Omit<Tool<Input, Output, P>, DefaultableToolKeys> & Partial<Pick<Tool<Input, Output, P>, DefaultableToolKeys>>// Step 3: 默认值配置对象 (fail-closed 安全策略)const TOOL_DEFAULTS = {// 状态判断默认值 isEnabled: () => true, // 默认启用 isConcurrencySafe: (_input?: unknown) => false, // ⚠️ 保守:默认不并发安全 isReadOnly: (_input?: unknown) => false, // ⚠️ 保守:默认为写操作 isDestructive: (_input?: unknown) => false, // 默认非破坏性// 权限检查默认值 checkPermissions: ( input: { [key: string]: unknown }, _ctx?: ToolUseContext, ): Promise<PermissionResult> => Promise.resolve({ behavior: 'allow', updatedInput: input }),// 分类器默认值 toAutoClassifierInput: (_input?: unknown) => '', // 默认跳过分类器// 显示名默认值 userFacingName: (_input?: unknown) => '',}// Step 4: 构建器函数实现export functionbuildTool<DextendsAnyToolDef>(def: D): BuiltTool<D> {// 运行时 spread 合并:// 1. 先展开 TOOL_DEFAULTS(基础默认值)// 2. 覆盖 userFacingName 使用工具名// 3. 最后展开用户定义(覆盖默认值)return { ...TOOL_DEFAULTS, userFacingName: () => def.name, // 特殊处理:默认使用工具名 ...def, // 用户定义覆盖默认值 } as BuiltTool<D>}
安全默认值策略详解:
|
|
|
|
|
|---|---|---|---|
isEnabled() |
true |
|
|
isConcurrencySafe() |
false |
保守策略 |
|
isReadOnly() |
false |
保守策略 |
|
isDestructive() |
false |
|
|
checkPermissions() |
allow |
|
|
toAutoClassifierInput() |
'' |
|
|
4.1.3 BashTool 使用示例
// src/tools/BashTool/BashTool.tsx - buildTool 使用示例import { buildTool, type ToolDef } from '../../Tool.js'// 定义 BashTool 的部分实现const bashToolDef: ToolDef<BashInput, BashOutput, BashProgress> = { name: BASH_TOOL_NAME, aliases: ['b', 'run'],// 输入验证 Zod schema inputSchema: lazySchema(() => z.object({ command: z.string(), timeout: z.number().optional(), sandbox: z.boolean().optional(), })),// 核心调用方法 call: async (args, context, canUseTool, parentMessage, onProgress) => {// ... 执行 bash 命令return { output: execResult.stdout } },// 覆盖默认值的方法 isConcurrencySafe: (input) => {// 并发命令(如 npm install)可安全并发return input.command?.startsWith('npm install') ?? false }, isReadOnly: (input) => {// 使用命令分类判断只读性const { isSearch, isRead, isList } = isSearchOrReadBashCommand(input.command)return isSearch || isRead || isList }, isDestructive: (input) => {// rm、rmdir 等命令是破坏性的return BASH_SILENT_COMMANDS.has(getBaseCommand(input.command)) }, checkPermissions: async (input, context) => {return bashToolHasPermission(input, context) },// 渲染方法 renderToolUseMessage: (args) => <BashToolUseMessage {...args} />, renderToolResultMessage: (result) => <BashToolResultMessage {...result} />,}// 使用 buildTool 构建完整工具对象export const BashTool = buildTool(bashToolDef)
4.2 Shell 抽象层设计
Shell 抽象层实现跨平台兼容,通过 Provider 模式解耦 Shell 实现。
4.2.1 ShellProvider 接口定义
// src/utils/shell/shellProvider.ts - ShellProvider 接口export const SHELL_TYPES = ['bash', 'powershell'] as constexport type ShellType = (typeof SHELL_TYPES)[number]export const DEFAULT_HOOK_SHELL: ShellType = 'bash'export type ShellProvider = {// ── 基本属性 ─────────────────────────────────────────────────────────────type: ShellType // Shell 类型:bash | powershell shellPath: string// Shell 可执行文件路径 detached: boolean// 是否在独立进程中运行// ── 命令构建方法 ──────────────────────────────────────────────────────────/** * Build the full command string including all shell-specific setup. * For bash: source snapshot, session env, disable extglob, eval-wrap, pwd tracking. * For powershell: different setup sequence. */ buildExecCommand( command: string, opts: { id: number | string// 任务 ID sandboxTmpDir?: string// 沙箱临时目录 useSandbox: boolean// 是否使用沙箱 }, ): Promise<{ commandString: string// 完整命令字符串 cwdFilePath: string// cwd 文件路径(用于 pwd tracking) }>// ── spawn 参数方法 ───────────────────────────────────────────────────────/** * Shell args for spawn (e.g., ['-c', '-l', cmd] for bash). */ getSpawnArgs(commandString: string): string[]// ── 环境变量方法 ───────────────────────────────────────────────────────────/** * Extra env vars for this shell type. * May perform async initialization (e.g., tmux socket setup for bash). */ getEnvironmentOverrides(command: string): Promise<Record<string, string>>}
4.2.2 Shell 检测策略:findSuitableShell()
// src/utils/Shell.ts - Shell 智能检测策略export async functionfindSuitableShell(): Promise<string> {// ── 优先级 1: 环境变量覆盖 ────────────────────────────────────────────────const shellOverride = process.env.CLAUDE_CODE_SHELLif (shellOverride) {const isSupported = shellOverride.includes('bash') || shellOverride.includes('zsh')if (isSupported && isExecutable(shellOverride)) { logForDebugging(`Using shell override: ${shellOverride}`)return shellOverride } else { logForDebugging(`CLAUDE_CODE_SHELL="${shellOverride}" is not a valid bash/zsh path, falling back` ) } }// ── 优先级 2: 用户 SHELL 环境偏好 ──────────────────────────────────────────const env_shell = process.env.SHELLconst isEnvShellSupported = env_shell && (env_shell.includes('bash') || env_shell.includes('zsh'))const preferBash = env_shell?.includes('bash')// ── 优先级 3: which 命令检测 ──────────────────────────────────────────────const [zshPath, bashPath] = await Promise.all([which('zsh'), which('bash')])// ── 优先级 4: 预设路径搜索 ────────────────────────────────────────────────const shellPaths = ['/bin','/usr/bin','/usr/local/bin','/opt/homebrew/bin', // macOS Homebrew 安装路径 ]// 根据 Shell 偏好排序const shellOrder = preferBash ? ['bash', 'zsh'] : ['zsh', 'bash']const supportedShells = shellOrder.flatMap(shell => shellPaths.map(path => `${path}/${shell}`), )// 将 which 结果添加到搜索列表头部if (preferBash) {if (bashPath) supportedShells.unshift(bashPath)if (zshPath) supportedShells.push(zshPath) } else {if (zshPath) supportedShells.unshift(zshPath)if (bashPath) supportedShells.push(bashPath) }// SHELL 环境变量优先if (isEnvShellSupported && isExecutable(env_shell)) { supportedShells.unshift(env_shell) }// 遍历查找可执行 Shellconst shellPath = supportedShells.find(shell => shell && isExecutable(shell))if (!shellPath) {throw new Error('No suitable shell found. Claude CLI requires a Posix shell environment.' ) }return shellPath}
Shell 检测优先级:
|
|
|
|
|---|---|---|
|
|
CLAUDE_CODE_SHELL |
|
|
|
SHELL
|
|
|
|
which('zsh/bash') |
|
|
|
|
/bin
/usr/bin 等 |
5. 工具系统设计
5.1 BashTool 常量分组设计模式
BashTool 使用 Set 集合分组命令类型,实现语义分类:
// src/tools/BashTool/BashTool.tsx - 常量分组设计const BASH_SEARCH_COMMANDS = new Set([// 文件搜索命令'find', 'grep', 'rg', 'ag', 'ack', 'locate',// 路径搜索命令'which', 'whereis',])const BASH_READ_COMMANDS = new Set([// 文件读取命令'cat', 'head', 'tail', 'less', 'more',// 分析命令'wc', 'stat', 'file', 'strings',// 数据处理命令(常用于管道解析文件内容)'jq', 'awk', 'cut', 'sort', 'uniq', 'tr',])const BASH_LIST_COMMANDS = new Set(['ls', 'tree', 'du',])const BASH_SEMANTIC_NEUTRAL_COMMANDS = new Set([// 纯输出/状态命令,不影响管道的读/搜索分类'echo', 'printf', 'true', 'false', ':', // bash no-op])const BASH_SILENT_COMMANDS = new Set([// 成功时无 stdout 输出的命令'mv', 'cp', 'rm', 'mkdir', 'rmdir','chmod', 'chown', 'chgrp', 'touch', 'ln','cd', 'export', 'unset', 'wait',])
5.1.1 命令语义判断函数
/** * Checks if a bash command is a search or read operation. * Used to determine if the command should be collapsed in the UI. * Returns an object indicating whether it's a search or read operation. * * For pipelines (e.g., `cat file | jq`), ALL parts must be search/read commands * for the whole command to be considered collapsible. * * Semantic-neutral commands (echo, printf, true, false, :) are skipped in any * position, as they're pure output/status commands that don't affect the * read/search nature of the pipeline. */export functionisSearchOrReadBashCommand(command: string): { isSearch: boolean isRead: boolean isList: boolean} {// Step 1: 使用 AST 解析器分割命令(支持管道、复合命令)let partsWithOperators: string[]try { partsWithOperators = splitCommandWithOperators(command) } catch {// 解析失败(如语法错误),返回 falsereturn { isSearch: false, isRead: false, isList: false } }if (partsWithOperators.length === 0) {return { isSearch: false, isRead: false, isList: false } }// Step 2: 遍历命令部分,判断语义类型let hasSearch = falselet hasRead = falselet hasList = falselet hasNonNeutralCommand = falselet skipNextAsRedirectTarget = falsefor (const part of partsWithOperators) {// 跳过重定向目标(> file 之后的 file)if (skipNextAsRedirectTarget) { skipNextAsRedirectTarget = falsecontinue }// 标记重定向操作符if (part === '>' || part === '>>' || part === '>&') { skipNextAsRedirectTarget = truecontinue }// 跳过操作符本身(|| && | ;)if (part === '||' || part === '&&' || part === '|' || part === ';') {continue }// 提取基础命令名const baseCommand = part.trim().split(/\s+/)[0]if (!baseCommand) continue// 跳过语义中性命令if (BASH_SEMANTIC_NEUTRAL_COMMANDS.has(baseCommand)) {continue } hasNonNeutralCommand = true// 判断命令类型const isPartSearch = BASH_SEARCH_COMMANDS.has(baseCommand)const isPartRead = BASH_READ_COMMANDS.has(baseCommand)const isPartList = BASH_LIST_COMMANDS.has(baseCommand)// 如果有非搜索/读取/列表命令,整体不是可折叠命令if (!isPartSearch && !isPartRead && !isPartList) {return { isSearch: false, isRead: false, isList: false } }// 记录命令类型if (isPartSearch) hasSearch = trueif (isPartRead) hasRead = trueif (isPartList) hasList = true }// 如果全是语义中性命令,返回 falseif (!hasNonNeutralCommand) {return { isSearch: false, isRead: false, isList: false } }return { isSearch: hasSearch, isRead: hasRead, isList: hasList }}
命令分类语义对照:
|
|
|
|
|
|---|---|---|---|
|
|
BASH_SEARCH_COMMANDS |
|
grep
find, rg |
|
|
BASH_READ_COMMANDS |
|
cat
head, jq |
|
|
BASH_LIST_COMMANDS |
|
ls
tree, du |
|
|
BASH_SEMANTIC_NEUTRAL_COMMANDS |
|
echo
printf, true |
|
|
BASH_SILENT_COMMANDS |
|
mv
cp, rm, mkdir |
6. 组件设计规范
6.1 React Compiler 运行时优化
项目使用 React Compiler 自动优化组件,编译产物包含记忆化逻辑:
6.1.1 编译产物分析:ListItem.tsx
// src/components/design-system/ListItem.tsx - React Compiler 编译产物import { c as _c } from "react/compiler-runtime"// React Compiler 运行时export functionListItem(t0) {// Step 1: 声明缓存槽位(32 个)const $ = _c(32) // $ 是缓存数组,_c 是缓存初始化函数// Step 2: 参数解构const { isFocused, isSelected: t1, children, description, showScrollDown, showScrollUp, styled: t2, disabled: t3, declareCursor } = t0// Step 3: 默认值处理const isSelected = t1 === undefined ? false : t1const styled = t2 === undefined ? true : t2const disabled = t3 === undefined ? false : t3// Step 4: 记忆化条件判断 - renderIndicator 函数let t4if ($[0] !== disabled || $[1] !== isFocused || $[2] !== showScrollDown || $[3] !== showScrollUp) {// 依赖变化,重新创建函数 t4 = functionrenderIndicator() {if (disabled) return <Text> </Text> if (isFocused) return <Text color="suggestion">{figures.pointer}</Text>if (showScrollDown) return <Text dimColor>{figures.arrowDown}</Text> if (showScrollUp) return <Text dimColor>{figures.arrowUp}</Text>return <Text> </Text> } // 更新缓存 $[0] = disabled $[1] = isFocused $[2] = showScrollDown $[3] = showScrollUp $[4] = t4 } else { // 依赖未变化,使用缓存 t4 = $[4] } const renderIndicator = t4 // Step 5: 记忆化条件判断 - textColor 计算 let t5 if ($[5] !== disabled || $[6] !== isFocused || $[7] !== isSelected || $[8] !== styled) { const getTextColor = function getTextColor() { if (disabled) return "inactive" if (!styled) return undefined if (isSelected) return "success" if (isFocused) return "suggestion" } t5 = getTextColor() $[5] = disabled $[6] = isFocused $[7] = isSelected $[8] = styled $[9] = t5 } else { t5 = $[9] } const textColor = t5 // ... 继续其他记忆化逻辑 // Step N: 返回渲染结果 return t13 // 最终 JSX 结果}
React Compiler 记忆化原理:
|
|
|
|
|---|---|---|
$[0-3] |
|
disabled
isFocused, showScrollDown, showScrollUp |
$[4] |
|
|
$[5-8] |
|
disabled
isFocused, isSelected, styled |
$[9] |
|
|
6.2 Ink 框架适配模式
Ink 是终端 UI React 框架,提供类似 DOM 的组件:
6.2.1 核心 Ink 组件使用
// src/components/design-system/ListItem.tsx - Ink 组件使用import { Box, Text } from '../../ink.js'import figures from 'figures'// 终端符号库(指针、箭头等)import { useDeclaredCursor } from '../../ink/hooks/use-declared-cursor.js'// ── 终端光标管理 ───────────────────────────────────────────────────────────const cursorRef = useDeclaredCursor({ line: 0, // 相对于 Box 的行位置 column: 0, // 相对于 Box 的列位置 active: isFocused && !disabled && declareCursor !== false, // 光标是否激活})// ── 颜色状态映射 ───────────────────────────────────────────────────────────const textColor = disabled ? 'inactive'// 禁用状态:灰色 : isSelected ? 'success'// 选中状态:绿色 : isFocused ? 'suggestion'// 聚焦状态:黄色 : undefined// 默认:无特殊颜色// ── 渲染结构 ───────────────────────────────────────────────────────────────return ( <Box ref={cursorRef} flexDirection="column"> {/* 主行:指示器 + 内容 + 勾选标记 */} <Box flexDirection="row" gap={1}> {renderIndicator()} {/* 指针/箭头指示器 */} {styled ? ( <Text color={textColor} dimColor={disabled}> {children} </Text> ) : children} {isSelected && !disabled && ( <Text color="success">{figures.tick}</Text> )} </Box> {/* 描述文本(次要内容) */} {description && ( <Box paddingLeft={2}> <Text color="inactive">{description}</Text> </Box> )} </Box>)
Ink 组件对照:
|
|
|
|
|---|---|---|
Box |
<div> |
|
Text |
<span> |
|
useDeclaredCursor |
|
|
7. 权限系统设计
7.1 PermissionMode 多模式架构
权限系统采用分层设计,区分用户可配置模式与内部模式:
// src/types/permissions.ts - PermissionMode 定义// ============================================================================// Permission Modes// ============================================================================// 外部可配置模式:用户可通过 settings.json 或 CLI 设置export const EXTERNAL_PERMISSION_MODES = ['acceptEdits', // 自动接受文件编辑操作'bypassPermissions', // 绕过所有权限检查(危险模式)'default', // 默认交互式确认模式'dontAsk', // 不询问模式,自动拒绝未知操作'plan', // 计划模式,先规划后执行] as constexport type ExternalPermissionMode = (typeof EXTERNAL_PERMISSION_MODES)[number]// 内部模式:包含 feature-gated 模式export type InternalPermissionMode = ExternalPermissionMode | 'auto' | 'bubble'// 运行时有效模式集合export const INTERNAL_PERMISSION_MODES = [ ...EXTERNAL_PERMISSION_MODES, ...(feature('TRANSCRIPT_CLASSIFIER') ? ['auto'] : []), // feature gate 条件] as const satisfies readonly PermissionMode[]export type PermissionMode = InternalPermissionMode
模式语义详解:
|
|
|
|
|
|---|---|---|---|
acceptEdits |
|
|
|
bypassPermissions |
|
|
|
default |
|
|
|
dontAsk |
|
|
|
plan |
|
|
|
auto |
|
|
|
bubble |
|
|
|
7.2 PermissionResult 结构化设计
权限检查返回结构化结果,包含决策原因追踪:
// src/types/permissions.ts - PermissionResult 定义// ============================================================================// Permission Decisions & Results// ============================================================================// 权限允许决策export type PermissionAllowDecision<Input> = { behavior: 'allow' updatedInput?: Input // 可能被修改的输入(如路径规范化) userModified?: boolean// 用户是否手动修改 decisionReason?: PermissionDecisionReason // 决策原因 toolUseID?: string// 工具使用 ID acceptFeedback?: string// 用户反馈 contentBlocks?: ContentBlockParam[] // 附加内容块(如图片)}// 权限询问决策export type PermissionAskDecision<Input> = { behavior: 'ask' message: string// 提示用户的消息 updatedInput?: Input decisionReason?: PermissionDecisionReason suggestions?: PermissionUpdate[] // 建议的权限更新 blockedPath?: string// 被阻止的路径 metadata?: PermissionMetadata pendingClassifierCheck?: PendingClassifierCheck // 待分类器检查 contentBlocks?: ContentBlockParam[]}// 权限拒绝决策export type PermissionDenyDecision = { behavior: 'deny' message: string decisionReason: PermissionDecisionReason toolUseID?: string}// 完整权限结果export type PermissionResult<Input> = | PermissionAllowDecision<Input> | PermissionAskDecision<Input> | PermissionDenyDecision | { behavior: 'passthrough'// 透传模式:交给上层处理 message: string decisionReason?: PermissionDecision<Input>['decisionReason'] suggestions?: PermissionUpdate[] blockedPath?: string pendingClassifierCheck?: PendingClassifierCheck }
7.2.1 决策原因类型
// src/types/permissions.ts - PermissionDecisionReasonexporttype PermissionDecisionReason = | { type: 'rule'; rule: PermissionRule } // 规则匹配 | { type: 'mode'; mode: PermissionMode } // 模式决定 | { type: 'subcommandResults'; reasons: Map<string, PermissionResult> } // 子命令结果 | { type: 'permissionPromptTool'; permissionPromptToolName: string; toolResult: unknown } | { type: 'hook'; hookName: string; hookSource?: string; reason?: string } // Hook 决定 | { type: 'asyncAgent'; reason: string } // 异步代理 | { type: 'sandboxOverride'; reason: 'excludedCommand' | 'dangerouslyDisableSandbox' } | { type: 'classifier'; classifier: string; reason: string } // 分类器决定 | { type: 'workingDir'; reason: string } // 工作目录问题 | { type: 'safetyCheck'; reason: string; classifierApprovable: boolean } // 安全检查 | { type: 'other'; reason: string } // 其他原因
决策原因追踪:
|
|
|
|
|---|---|---|
rule |
rule: PermissionRule |
|
mode |
mode: PermissionMode |
|
hook |
hookName, reason |
|
classifier |
classifier, reason |
|
safetyCheck |
reason, classifierApprovable |
|
8. 总结
核心规范要点汇总
|
|
|
|
|---|---|---|
| 配置 |
|
tsconfig.json
scripts/build.mjs |
| 命名 |
|
|
| 注释 |
// === 分段分隔符,设计决策注释 |
ListItem.tsx
Tool.ts, permissions.ts |
| 架构 |
|
Tool.ts
shellProvider.ts |
| 组件 |
|
ListItem.tsx |
| 权限 |
|
permissions.ts |
可借鉴的设计模式
|
|
|
|
|---|---|---|
| buildTool 构建器模式 |
|
|
| ShellProvider 抽象层 |
|
|
| 常量分组语义分类 |
|
|
| JSDoc 完整文档风格 |
|
|
| 权限多模式架构 |
|
|
| React Compiler 记忆化 |
|
|
反编译源码的特殊考量
|
|
|
|
|---|---|---|
strict: false |
|
|
bun:bundle
|
|
|
|
|
|
|
|
|
|
|
夜雨聆风