📌 专题 重建源码 → 思想

从逆向材料里
抠实现细节与设计思想

本站仓库中的 ccsource/claude-code-main/ 来自 npm 包误带的 Source Map 所还原的 TypeScript 树(规模约 1900+ 文件量级,与事件复盘一致)。
下面不是「抄代码教程」,而是:用真实路径当锚点,把权限、压缩、MCP、主查询链路等机制读薄,提炼成可迁移的工程思想。

材料边界(必读)

事件背景与规模数据:长文复盘(MD)↗ · cli.js.map 说明 ↗

六条可迁移的「实现哲学」

以下从重建树中反复出现的结构归纳——适用于任何「LLM + 工具 + 终端」类产品,而非背诵某个函数名。

🎯

编排中心化

QueryEngine 持有 cwd、tools、commands、MCP、agents、canUseTool、会话状态等一整张依赖图;主循环周边功能(压缩、选择器、coordinator、snip)用 Feature 与懒加载挂上去,避免圆形依赖炸包。

🛡️

权限=策略 + 可解释性

除规则表格外,还有面向 shell 的 风险分级解释器(结构化 tool 输出 LOW/MEDIUM/HIGH),以及 auto-mode 下用外部模板 + 标签替换段让用户改 allow/deny/environment 规则——把「黑箱拒绝」变成可编辑策略。

📉

上下文是预算问题

压缩链路里显式出现 token 统计、compact 边界消息、前后 Hook、与 transcript / session 存储的衔接;思想是:压缩不是删聊天记录,是重新立边界并保留可恢复信号

🔌

MCP 一等公民

客户端直接基于官方 @modelcontextprotocol/sdk 多传输(stdio / SSE / Streamable HTTP),工具侧有 MCPTool、资源列出/读取、OAuth 刷新等——外部能力不是「字符串拼进 prompt」,而是协议状态机

🧱

工具是强类型契约

Tool.ts 集中 Progress、Permission、MCP Elicit 等交叉类型,避免每工具各写一套;执行链与权限结果类型在单点导入,降低「工具作者」心智负担。

🪶

实用主义工程

例如挫败感检测可用关键词/正则而非上情感模型(见站内 map README 与 useFrustrationDetection 一类模块);能省算力与延迟的就不堆 ML——这是产品代码里常见的「克制」。

模块 ↔ 思想(真实路径速查)

路径相对于仓库 ccsource/claude-code-main/

路径读什么细节提炼什么思想
src/QueryEngine.ts QueryEngineConfig 字段;对 query、SDK 消息、权限孤儿、压缩 snip 的编排入口 单一编排核 + Feature 切片扩展
src/Tool.ts Tool JSON Schema 形状、CanUseToolFn、各类 Progress 联合类型 工具层协议先于具体工具实现
src/utils/permissions/yoloClassifier.ts auto-mode 规则模板、<user_*_to_replace> 标签解析、内外部权限模板分支 策略文本化 + 可导出默认项
src/utils/permissions/permissionExplainer.ts explain_command 强制 schema、LOW/MEDIUM/HIGH 枚举 用结构化模型输出做 UX,而不是裸生成段落
src/services/compact/compact.ts compact 前后 Hook、边界消息、token 估计、与 session transcript 的衔接 压缩 = 带审计边界的上下文重写
src/services/mcp/client.ts 多 Transport、ListTools / CallTool schema、OAuth 与错误类型 与 MCP 规范对齐,少造轮子

硬读导航(带锚点)

下列摘录来自重建树 ccsource/claude-code-main/,仅作教学引用(少量连续行),便于你对照类型名与函数名;完整版权仍属原作者。

权限硬读

AutoModeRulesgetDefaultExternalAutoModeRulesEXPLAIN_COMMAND_TOOL

标签模板 · 解释器 schema · 走读清单

Compact 硬读

compactConversation、PreCompact Hook、token 估计入口

入口签名 · Hook 与状态 · 走读清单

其他速览

QueryEngine:依赖注入壳,见上文模块表。

MCPListToolsResultSchema / CallToolResultSchema,见 services/mcp/client.ts 首部 import。

权限硬读 · yoloClassifier + permissionExplainer

1. 策略如何落进 JSON 设置:AutoModeRules

三段式 allow / soft_deny / environment 与模板标签 <user_*_to_replace> 对齐,getDefaultExternalAutoModeRules 负责把模板「默认值」解析成数组——CLI 导出默认规则时不必硬编码在 TS 里。

// ccsource/claude-code-main/src/utils/permissions/yoloClassifier.ts(摘录)
export type AutoModeRules = {
  allow: string[]
  soft_deny: string[]
  environment: string[]
}

export function getDefaultExternalAutoModeRules(): AutoModeRules {
  return {
    allow: extractTaggedBullets('user_allow_rules_to_replace'),
    soft_deny: extractTaggedBullets('user_deny_rules_to_replace'),
    environment: extractTaggedBullets('user_environment_to_replace'),
  }
}

2. 面向 UX 的结构化解释:explain_command

不是让模型自由写段落,而是强制 tool 形状输出 riskLevel(三档枚举)+ 短 risk,下游 UI 可稳定做颜色/按钮,而不是解析自然语言。

// ccsource/claude-code-main/src/utils/permissions/permissionExplainer.ts(摘录)
const EXPLAIN_COMMAND_TOOL = {
  name: 'explain_command',
  description: 'Provide an explanation of a shell command',
  input_schema: {
    type: 'object' as const,
    properties: {
      explanation: { type: 'string', ... },
      reasoning: { type: 'string', ... },
      risk: { type: 'string', description: 'What could go wrong, under 15 words' },
      riskLevel: {
        type: 'string',
        enum: ['LOW', 'MEDIUM', 'HIGH'],
        ...
      },
    },
    required: ['explanation', 'reasoning', 'risk', 'riskLevel'],
  },
}

解析侧用 RiskAssessmentSchema(Zod)与 tool 输出对齐,避免「模型多写一句就 JSON 失效」时无处兜底。

走读清单(权限)

  1. yoloClassifier.tsisUsingExternalPermissions,理清 USER_TYPE 与 GrowthBook 配置如何切换内外模板。
  2. permissionExplainer.ts 跟一次「从 toolInput 格式化 → sideQuery → 解析 RiskAssessmentSchema」的调用链。
  3. 自问:若要把 riskLevel 映射到「自动放行 / 弹窗 / 硬拒绝」,映射表应放在权限层还是 bash 工具层?

Compact 硬读 · compactConversation

1. 入口即立边界:token 计数与参数面

compactConversation 接收完整 messagesToolUseContext,第一步就做 tokenCountWithEstimation(messages),随后进入 compact 专用 SDK 状态(如 setSDKStatus('compacting'))。思想:压缩是和普通对话并列的一等流程,要有独立进度与可取消信号。

// ccsource/claude-code-main/src/services/compact/compact.ts(摘录)
export async function compactConversation(
  messages: Message[],
  context: ToolUseContext,
  cacheSafeParams: CacheSafeParams,
  suppressFollowUpQuestions: boolean,
  customInstructions?: string,
  isAutoCompact: boolean = false,
  recompactionInfo?: RecompactionInfo,
): Promise<CompactionResult> {
  ...
  const preCompactTokenCount = tokenCountWithEstimation(messages)
  ...
  context.onCompactProgress?.({ type: 'hooks_start', hookType: 'pre_compact' })
  context.setSDKStatus?.('compacting')
  const hookResult = await executePreCompactHooks(
    { trigger: isAutoCompact ? 'auto' : 'manual', customInstructions: customInstructions ?? null },
    context.abortController.signal,
  )
  customInstructions = mergeHookInstructions(customInstructions, hookResult.newCustomInstructions)
  ...
}

2. PreCompact Hook 与指令合并

executePreCompactHooks 可把额外指令写回 customInstructions(经 mergeHookInstructions),等于给「自动/手动压缩」加了一层用户可插桩策略;与 D 课里「压缩不是悄悄 slice」一致。

走读清单(Compact)

  1. compactConversation 往下跟 streamCompactSummary,看摘要请求如何插入消息列表。
  2. createCompactBoundaryMessage / SystemCompactBoundaryMessage,理解压缩后历史如何被「切段」以便复盘与重放。
  3. 对照 D05 思考题:消息累积与 compact 触发条件如何闭环?

与 S / D 课程怎么配合?

主线课讲概念与伪代码;本专题给你「打开哪几个 ts 文件」。建议顺序:S01 / D01(含 最小 Loop 练习思考题答案)→ 对照 QueryEngine.ts;S03/D03 → 权限硬读;S05/D05 → Compact 硬读;S07/D07 → services/mcp/client.ts

去课程表 Awesome 镜像列表 ↗