从Claude Code泄露源码看工程架构:项目架构总览与分层设计哲学
发布时间:2026/9/26 15:49:33来源:尧图网络
1. 从一次真实踩坑说起为什么我要拆这套架构Claude Code 泄露源码这件事圈子里讨论最多的是功能清单但我更关心的是它的项目架构和分层设计哲学。原因很直接我手上有个自研的终端 AI 编程助手代码写到 3 万行左右就开始失控——工具逻辑和 UI 状态搅在一起加一个新工具要改五六个文件改完还容易把会话状态搞崩。所以当我看到 Claude Code 的源码结构时第一反应不是功能真多而是这分层是怎么做到不互相污染的。Claude Code 表面上是个命令行交互工具但它的源码规模已经远超传统 CLI 的范畴约 1884 个 TypeScript 文件144 个 UI 组件85 个 React Hooks43 组工具实现329 个 utils 文件主入口 main.tsx 约 785 KBREPL 界面文件约 875 KB。这个量级下还能保持可维护性靠的不是某个技巧而是一整套分层边界约束。这篇文章面向想理解大型 AI 编程工具工程化落地的开发者。我会交付可复制的目录结构骨架、分层依赖配置片段以及对照源码验证分层边界的检查动作。你可以把这套思路直接搬进自己的项目哪怕你的项目只有它的十分之一大。2. 前置准备用 TaoToken 搭一个可验证的模型调用环境要验证分层设计是否真的解耦光看代码不够你得能实际跑起来、改一处看影响面。我建议先准备一个稳定的模型调用入口这样你在做架构实验时不会被网络和鉴权问题打断。TaoToken 在这里的作用是提供一个统一的 API 入口让你在验证查询编排层、工具执行层时能真实拿到模型响应。它兼容常见的 Anthropic 风格接口接入成本低。第一步去控制台创建 API Keyhttps://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite第二步在 API Keys 页面生成密钥并保存https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite第三步如果你只是想先验证模型对话链路是否通可以直接用模型对话页面测试https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite接入文档在这里里面有完整的请求格式和参数说明https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteAPI 基础地址统一用https://taotoken.net/api注意API 地址不要加 UTM 参数只有页面链接才需要带。这个细节在配置环境变量时容易搞混我踩过一次请求一直 404排查了半小时才发现是 URL 拼错了。如果你打算长期做编码类 Agent 的架构实验Coding Plan 会更划算适合高频调用场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite3. 可复制的目录结构骨架与分层依赖配置3.1 七层架构的目录映射Claude Code 的顶层目录如果按架构层次重新分组可以归纳为七层。我把这套结构抽象成一个可复制的骨架你可以直接拿去改your-ai-cli/ ├── entrypoints/ # 第一层入口层 │ ├── cli.tsx # 命令解析 快速路径分流 │ ├── init.ts # 全局初始化 │ └── mcp.ts # 作为 MCP Server 暴露 ├── main.tsx # 总装配入口依赖注入 ├── replLauncher.tsx # 第二层界面壳层装配 ├── screens/ # 页面级组件 ├── components/ # 终端 UI 组件库 ├── hooks/ # React Hooks ├── state/ # 第三层状态管理层 ├── context/ # 上下文通知、弹窗、统计 ├── QueryEngine.ts # 第四层查询编排层 ├── query.ts # 主循环采样 → 工具执行 → 结果回流 ├── query/ # Token 预算、停止条件 ├── Tool.ts # 第五层能力层抽象 ├── tools.ts # 工具注册与装配 ├── tools/ # 工具实现 ├── tasks/ # 任务执行层 ├── services/ # 第六层外部集成层 │ ├── api/ # 模型 API 客户端 │ ├── mcp/ # MCP 协议集成 │ ├── lsp/ # 语言服务器协议 │ └── policyLimits/ # 策略与限额 └── utils/ # 第七层基础设施层这个骨架的关键不在于目录名字而在于依赖方向必须单向。上层可以依赖下层下层绝不能反向依赖上层。3.2 分层依赖约束配置光靠约定不够得用工具强制约束。我推荐用dependency-cruiser来做分层依赖检查配置片段如下// .dependency-cruiser.js module.exports { forbidden: [ { name: no-entry-to-query, comment: 入口层不得直接依赖查询编排层内部实现, severity: error, from: { path: ^entrypoints }, to: { path: ^query/ } }, { name: no-ui-to-services, comment: UI 层不得直接调用外部服务必须经过查询编排层, severity: error, from: { path: ^(components|screens|hooks) }, to: { path: ^services/ } }, { name: no-utils-to-upper, comment: 基础设施层不得依赖任何上层模块, severity: error, from: { path: ^utils/ }, to: { path: ^(entrypoints|components|screens|services|tools)/ } }, { name: no-circular, comment: 禁止循环依赖, severity: error, from: {}, to: { circular: true } } ], options: { doNotFollow: { path: node_modules }, tsConfig: { fileName: tsconfig.json } } };装好之后跑一次npx depcruise --config .dependency-cruiser.js src/如果输出里有 error 级别的违规说明你的分层边界已经被打破了。我在自己项目里第一次跑这个检查时发现utils/里有个文件偷偷 import 了services/api这就是典型的反向依赖会导致基础设施层无法独立测试。3.3 入口极瘦原则的代码落地Claude Code 的entrypoints/cli.tsx体现了一个重要原则入口只做参数解析和路由分发不做业务逻辑。它的快速路径设计是这样的async function main(): Promisevoid { const args process.argv.slice(2); // 快速路径--version 零模块加载 if (args.length 1 (args[0] --version || args[0] -v)) { console.log(${MACRO.VERSION} (Your CLI)); return; // 零成本返回 } // 动态导入性能监控避免拖慢启动 const { profileCheckpoint } await import(../utils/startupProfiler.js); profileCheckpoint(cli_entry); // 进入完整装配流程 const { launch } await import(../main.js); await launch(args); }这个设计的价值在于轻量级命令不需要加载 React Ink REPL 全量依赖冷启动延迟能降一个数量级。你可以用console.time实测一下加了快速路径之后--version的响应时间通常在 50ms 以内而不加的话可能要 800ms 以上。4. 验证请求确认分层边界真的生效4.1 用一次完整请求追踪数据流配置好之后你需要验证分层是否真的按预期工作。方法是在关键层打日志追踪一次请求的完整流转// entrypoints/cli.tsx 入口层 console.log([L1-Entry] 命令解析完成, { args }); // main.tsx 装配层 console.log([L2-Assembly] 依赖注入完成, { hasPolicy: !!policyLimits, hasTools: !!toolPool, hasRemoteConfig: !!remoteSettings }); // QueryEngine.ts 查询编排层 console.log([L4-Query] 提交查询, { messageId, tokenBudget }); // query.ts 主循环 console.log([L4-Loop] 进入自循环, { turn: 1 }); // Tool.ts 能力层 console.log([L5-Tool] 工具调用, { toolName, input }); // services/api 外部集成层 console.log([L6-Service] 模型采样请求, { model, stream: true });跑一次请求你应该看到日志按 L1 → L2 → L4 → L5 → L6 的顺序输出。如果出现 L6 在 L4 之前或者 L5 直接跳到 L1说明分层边界有问题。4.2 成功结果对照一次健康的分层请求日志应该长这样[L1-Entry] 命令解析完成 { args: [fix, bug.ts] } [L2-Assembly] 依赖注入完成 { hasPolicy: true, hasTools: true, hasRemoteConfig: true } [L4-Query] 提交查询 { messageId: msg_001, tokenBudget: 128000 } [L4-Loop] 进入自循环 { turn: 1 } [L5-Tool] 工具调用 { toolName: ReadFile, input: { path: bug.ts } } [L6-Service] 模型采样请求 { model: claude-sonnet, stream: true } [L4-Loop] 工具结果回流 { turn: 1, toolResult: ... } [L4-Loop] 进入自循环 { turn: 2 } [L5-Tool] 工具调用 { toolName: EditFile, input: { path: bug.ts } } [L4-Loop] 任务完成 { totalTurns: 2 }关键观察点工具执行结果会回流到主循环触发下一轮推理。这就是自循环机制也是 Claude Code 能完成多步任务的核心。4.3 用依赖图可视化验证除了日志你还可以生成依赖图直观检查npx depcruise --config .dependency-cruiser.js --output-type dot src/ | dot -T svg deps.svg打开生成的 SVG健康的依赖图应该是从上往下的漏斗形入口层在最上面基础设施层在最下面中间层逐级收敛。如果出现横向箭头或者向上的箭头就是分层被打破了。5. 本篇常见错排查5.1 循环依赖导致启动卡死现象项目启动时卡在某个 import 上或者运行时报Cannot access X before initialization。原因两个模块互相 import比如Tool.ts引了tools.tstools.ts又引了Tool.ts。排查npx madge --circular --extensions ts,tsx src/修复把共享类型抽到独立的types/目录两边都只依赖类型文件不依赖具体实现。5.2 工具执行结果没有回流到主循环现象工具调用成功但模型没有基于结果继续推理直接返回了空响应。原因query.ts的主循环里工具结果没有正确 push 回消息队列。排查在query.ts的 while 循环里加日志确认toolResult事件是否被消费for await (const event of stream) { if (event.type tool_result) { console.log([Loop] 收到工具结果, event.toolUseId); messages.push({ role: user, content: event.content }); } }修复确保工具结果以user角色回注而不是assistant角色。这个细节很容易搞错我踩过一次模型一直以为工具没执行。5.3 状态管理混乱导致 UI 不更新现象工具执行完了但终端界面还显示执行中。原因UI 状态和运行时状态混在同一个 store 里工具执行层直接改了 UI 状态绕过了状态管理层。排查检查state/目录下是否有跨层引用。用依赖检查工具跑一遍npx depcruise --config .dependency-cruiser.js src/state/修复UI 态放state/运行时态放bootstrap/会话态放context/。三层各管各的通过事件总线通信不要直接互相引用。5.4 快速路径失效导致冷启动慢现象--version命令响应超过 500ms。原因快速路径判断之前就加载了重依赖比如在文件顶部 import 了 React。排查检查entrypoints/cli.tsx的顶部 import确保快速路径分支之前没有任何重依赖的静态 import。修复把所有非必要的 import 改成动态await import()只在真正需要时才加载。5.5 协议细节泄漏到 UI 层现象新增一种 MCP 传输方式时需要改screens/REPL.tsx。原因MCP 协议判断逻辑写在了 UI 层没有收敛到services/mcp/。排查在 UI 层搜索sse、websocket、stdio等协议关键词如果出现就是泄漏了。修复在services/mcp/client.ts里统一抽象成MCPServerConnection接口UI 层只消费这个接口不关心底层传输方式。6. 把架构思路用起来从验证到落地分层设计这件事看别人代码觉得理所当然自己动手就容易走样。我的建议是先用dependency-cruiser把约束立起来再写代码。约束先行的好处是你在写第一行代码时就知道哪些 import 是不允许的比写完再重构成本低得多。如果你在验证过程中需要频繁调用模型来测试查询编排层和工具执行层可以用 TaoToken 的模型对话页面快速验证链路https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite长期做编码类 Agent 实验的话Coding Plan 更适合高频场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite接入文档里有完整的请求示例和参数说明配置环境变量时对照着看https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite最后说一个我自己的经验分层架构的价值不在于层数多而在于每层的边界是否可验证。Claude Code 的七层模型之所以有效是因为每层都有明确的输入输出契约你可以单独测试任何一层而不需要启动整个系统。你在自己的项目里落地时先问自己一个问题——我能不能在不启动 UI 的情况下测试查询编排层如果答案是否定的说明你的分层还没做到位。
网站建设高端定制企业官网