Skip to content

Latest commit

 

History

History
773 lines (601 loc) · 17.6 KB

File metadata and controls

773 lines (601 loc) · 17.6 KB

Claude Code 内置技能架构深度分析

核心问题:为什么内置技能用 TypeScript 实现?

答案概要

内置技能使用 TypeScript 实现而非纯 Markdown 文件,是因为它们需要动态逻辑、复杂交互和高级功能,这些是静态 Markdown 无法提供的。


架构对比分析

用户技能(Markdown 文件)

---
name: deploy
description: 部署应用到生产环境
when_to_use: 当用户想要部署应用时使用
---

# 部署技能

## 目标
将应用部署到指定的生产环境。

## 步骤

### 1. 运行测试
执行测试套件确保代码质量。

**成功标准**: 所有测试通过

### 2. 构建应用
构建生产版本的应用程序。

**成功标准**: 构建成功,无错误

### 3. 部署到环境
将构建的应用部署到 `$env` 环境。

**成功标准**: 部署成功,应用可访问

特点

  • ✅ 纯静态内容
  • ✅ 易于编辑和维护
  • ✅ 结构清晰,易于理解
  • ❌ 无法包含动态逻辑
  • ❌ 无法与代码库深度集成

内置技能(TypeScript 实现)

// src/skills/bundled/simplify.ts
import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'
import { registerBundledSkill } from '../bundledSkills.js'

const SIMPLIFY_PROMPT = `# Simplify: Code Review and Cleanup

Review all changed files for reuse, quality, and efficiency. Fix any issues found.

## Phase 1: Identify Changes

Run \`git diff\` (or \`git diff HEAD\` if there are staged changes) to see what changed...

## Phase 2: Launch Three Review Agents in Parallel

Use of ${AGENT_TOOL_NAME} tool to launch all three agents concurrently...
`

export function registerSimplifySkill(): void {
  registerBundledSkill({
    name: 'simplify',
    description: 'Review changed code for reuse, quality, and efficiency...',
    userInvocable: true,
    async getPromptForCommand(args) {
      let prompt = SIMPLIFY_PROMPT
      if (args) {
        prompt += `\n\n## Additional Focus\n\n${args}`
      }
      return [{ type: 'text', text: prompt }]
    },
  })
}

特点

  • ✅ 包含动态逻辑
  • ✅ 可以调用其他工具和API
  • ✅ 支持条件渲染
  • ✅ 可以访问系统状态
  • ✅ 支持复杂的交互流程
  • ❌ 需要编译
  • ❌ 修改需要重新构建

详细对比分析

1. 动态提示生成

用户技能(静态)

---
name: deploy
---

# 部署技能

## 目标
部署应用到生产环境。

问题

  • 提示内容固定,无法根据上下文调整
  • 无法动态生成内容
  • 无法处理用户输入参数

内置技能(动态)

async getPromptForCommand(args) {
  let prompt = SIMPLIFY_PROMPT
  if (args) {
    prompt += `\n\n## Additional Focus\n\n${args}`
  }
  return [{ type: 'text', text: prompt }]
}

优势

  • 可以根据参数动态生成提示
  • 支持条件逻辑
  • 可以访问会话上下文

2. 与工具系统集成

用户技能(有限)

---
allowed_tools:
  - Read
  - Write
---

限制

  • 只能声明权限,不能主动调用工具
  • 无法在提示生成时执行逻辑

内置技能(完全集成)

import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'

const SIMPLIFY_PROMPT = `# Simplify

Use ${AGENT_TOOL_NAME} tool to launch all three agents concurrently...
`

export function registerSimplifySkill(): void {
  registerBundledSkill({
    name: 'simplify',
    // ... 其他配置
  })
}

优势

  • 可以直接调用其他工具
  • 可以引用工具常量和配置
  • 支持复杂的多步骤流程

3. 复杂交互逻辑

用户技能(简单)

## 步骤

### 1. 运行测试
执行测试套件...

**成功标准**: 所有测试通过

限制

  • 步骤是线性的,无法有分支逻辑
  • 无法根据条件跳过步骤
  • 无法处理异常情况

内置技能(复杂逻辑)

// src/skills/bundled/skillify.ts
async getPromptForCommand(args, context) {
  const sessionMemory = await getSessionMemoryContent()
  const userMessages = extractUserMessages(
    getMessagesAfterCompactBoundary(context.messages),
  )

  const prompt = SKILLIFY_PROMPT
    .replace('{{sessionMemory}}', sessionMemory)
    .replace('{{userMessages}}', userMessages.join('\n\n'))

  return [{ type: 'text', text: prompt }]
}

优势

  • 可以访问会话状态和消息历史
  • 支持复杂的模板替换
  • 可以执行异步操作
  • 支持错误处理和重试逻辑

4. 文件系统操作

用户技能(无文件操作)

---
name: deploy
---

# 部署技能

## 步骤
...

限制

  • 无法创建临时文件
  • 无法读取系统文件
  • 无法执行文件系统操作

内置技能(文件操作)

// src/skills/bundled/batch.ts
import { mkdir, open } from 'fs/promises'
import { join } from 'path'

async function extractBundledSkillFiles(
  skillName: string,
  files: Record<string, string>,
): Promise<string | null> {
  const dir = getBundledSkillExtractDir(skillName)
  try {
    await writeSkillFiles(dir, files)
    return dir
  } catch (e) {
    logForDebugging(`Failed to extract bundled skill '${skillName}'...`)
    return null
  }
}

优势

  • 可以创建临时文件和目录
  • 可以提取内置的参考文件
  • 支持文件权限管理
  • 可以清理临时资源

5. 环境检查和条件执行

用户技能(无条件)

---
name: deploy
---

# 部署技能

## 步骤
...

限制

  • 无法检查运行环境
  • 无法根据条件启用/禁用功能
  • 无法访问系统配置

内置技能(条件执行)

// src/skills/bundled/verify.ts
export function registerVerifySkill(): void {
  if (process.env.USER_TYPE !== 'ant') {
    return  // 只在ANT环境中注册
  }

  registerBundledSkill({
    name: 'verify',
    // ... 配置
  })
}

优势

  • 可以检查环境变量
  • 支持功能开关
  • 可以根据条件启用/禁用技能
  • 支持多环境配置

实际案例分析

案例1:simplify 技能

文件位置: src/skills/bundled/simplify.ts

TypeScript 实现的必要性

  1. 多代理协调
import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'

const SIMPLIFY_PROMPT = `# Simplify: Code Review and Cleanup

## Phase 2: Launch Three Review Agents in Parallel

Use ${AGENT_TOOL_NAME} tool to launch all three agents concurrently...
`

为什么需要 TypeScript

  • 需要引用工具常量 AGENT_TOOL_NAME
  • 需要动态生成包含工具调用的提示
  • 需要协调多个代理的并行执行
  1. 动态参数处理
async getPromptForCommand(args) {
  let prompt = SIMPLIFY_PROMPT
  if (args) {
    prompt += `\n\n## Additional Focus\n\n${args}`
  }
  return [{ type: 'text', text: prompt }]
}

为什么需要 TypeScript

  • 需要根据用户输入动态调整提示
  • 需要条件性地添加额外内容

案例2:skillify 技能

文件位置: src/skills/bundled/skillify.ts

TypeScript 实现的复杂性

  1. 会话状态访问
import { getSessionMemoryContent } from '../../services/SessionMemory/sessionMemoryUtils.js'
import { getMessagesAfterCompactBoundary } from '../../utils/messages.js'

function extractUserMessages(messages: Message[]): string[] {
  return messages
    .filter((m): m is Extract<typeof m, { type: 'user' }> => m.type === 'user')
    .map(m => {
      const content = m.message.content
      if (typeof content === 'string') return content
      return content
        .filter((b): b is Extract<typeof b, { type: 'text' }> => b.type === 'text')
        .map(b => b.text)
        .join('\n')
    })
    .filter(text => text.trim().length > 0)
}

为什么需要 TypeScript

  • 需要访问会话内存
  • 需要过滤和处理消息历史
  • 需要复杂的消息类型转换
  1. 模板替换
const SKILLIFY_PROMPT = `# Skillify {{userDescriptionBlock}}

Here is session memory summary:
<session_memory>
{{sessionMemory}}
</session_memory>

Here are user's messages:
<user_messages>
{{userMessages}}
</user_messages>

为什么需要 TypeScript

  • 需要动态替换模板变量
  • 需要处理复杂的嵌套结构
  • 需要格式化和清理数据
  1. 异步操作
async getPromptForCommand(args, context) {
  const sessionMemory = await getSessionMemoryContent()
  const userMessages = extractUserMessages(
    getMessagesAfterCompactBoundary(context.messages),
  )

  const prompt = SKILLIFY_PROMPT
    .replace('{{sessionMemory}}', sessionMemory)
    .replace('{{userMessages}}', userMessages.join('\n\n'))

  return [{ type: 'text', text: prompt }]
}

为什么需要 TypeScript

  • 需要等待异步操作完成
  • 需要处理Promise和错误
  • 需要访问上下文对象

案例3:batch 技能

文件位置: src/skills/bundled/batch.ts

TypeScript 实现的高级功能

  1. 环境检查
import { getIsGit } from '../../utils/git.js'

export function registerBatchSkill(): void {
  registerBundledSkill({
    name: 'batch',
    async getPromptForCommand(args) {
      const isGit = await getIsGit()
      if (!isGit) {
        return [{ type: 'text', text: NOT_A_GIT_REPO_MESSAGE }]
      }
      // ...
    },
  })
}

为什么需要 TypeScript

  • 需要检查是否在Git仓库中
  • 需要根据环境条件返回不同消息
  • 需要提供有用的错误信息
  1. 字符串模板构建
function buildPrompt(instruction: string): string {
  return `# Batch: Parallel Work Orchestration

You are orchestrating a large, parallelizable change across this codebase.

## User Instruction

${instruction}

## Phase 1: Research and Plan (Plan Mode)

Call of \`${ENTER_PLAN_MODE_TOOL_NAME}\` tool now to enter plan mode, then:
...
`
}

为什么需要 TypeScript

  • 需要动态构建复杂的提示模板
  • 需要嵌入多个工具常量
  • 需要保持代码的可维护性

案例4:debug 技能

文件位置: src/skills/bundled/debug.ts

TypeScript 实现的系统集成

  1. 文件系统操作
import { open, stat } from 'fs/promises'
import { enableDebugLogging, getDebugLogPath } from '../../utils/debug.js'

export function registerDebugSkill(): void {
  registerBundledSkill({
    name: 'debug',
    async getPromptForCommand(args) {
      const wasAlreadyLogging = enableDebugLogging()
      const debugLogPath = getDebugLogPath()

      let logInfo: string
      try {
        const stats = await stat(debugLogPath)
        const readSize = Math.min(stats.size, TAIL_READ_BYTES)
        const fd = await open(debugLogPath, 'r')
        try {
          const { buffer, bytesRead } = await fd.read({
            buffer: Buffer.alloc(readSize),
            position: startOffset,
          })
          const tail = buffer.toString('utf-8', 0, bytesRead)
            // ... 处理日志内容
        } finally {
          await fd.close()
        }
      } catch (e) {
        logInfo = isENOENT(e)
          ? 'No debug log exists yet...'
          : `Failed to read debug log: ${errorMessage(e)}`
      }
      // ...
    },
  })
}

为什么需要 TypeScript

  • 需要读取文件系统
  • 需要处理文件权限和错误
  • 需要管理文件描述符
  • 需要解析和格式化日志内容
  1. 系统状态管理
const wasAlreadyLogging = enableDebugLogging()

为什么需要 TypeScript

  • 需要调用系统API启用功能
  • 需要跟踪状态变化
  • 需要提供条件性反馈

架构优势总结

TypeScript 实现的优势

1. 类型安全

import type { ToolUseContext } from '../Tool.js'
import type { ContentBlockParam } from '@anthropic-ai/sdk/resources/index.mjs'

async getPromptForCommand(
  args: string,
  context: ToolUseContext,
): Promise<ContentBlockParam[]>

优势

  • 编译时类型检查
  • IDE智能提示
  • 重构安全性
  • 接口一致性

2. 代码复用

import { AGENT_TOOL_NAME } from '../../tools/AgentTool/constants.js'
import { getSessionMemoryContent } from '../../services/SessionMemory/sessionMemoryUtils.js'

优势

  • 可以导入和复用现有模块
  • 避免代码重复
  • 统一的API接口

3. 测试能力

// 可以编写单元测试
describe('simplify skill', () => {
  it('should generate prompt with additional focus', () => {
    const result = await getPromptForCommand('focus on performance')
    expect(result[0].text).toContain('Additional Focus')
  })
})

优势

  • 可以进行自动化测试
  • 可以模拟各种场景
  • 可以验证边界情况

4. 性能优化

// 缓存和记忆化
const SIMPLIFY_PROMPT = `# Simplify...` // 编译时常量

// 懒加载
if (process.env.USER_TYPE !== 'ant') {
  return
}

优势

  • 编译时优化
  • 死代码消除
  • 条件编译

5. 维护性

// 清晰的模块边界
export function registerSimplifySkill(): void {
  registerBundledSkill({
    name: 'simplify',
    // ... 配置
  })
}

优势

  • 模块化设计
  • 清晰的依赖关系
  • 易于调试和更新

混合架构设计

为什么两种方式都存在?

用户技能(Markdown)的优势

  • 用户友好:易于编辑和理解
  • 快速迭代:无需重新编译
  • 透明性:内容一目了然
  • 灵活性:支持任意复杂度的工作流

内置技能(TypeScript)的优势

  • 功能强大:支持复杂逻辑和系统集成
  • 性能优化:编译时优化和缓存
  • 类型安全:减少运行时错误
  • 可测试:支持自动化测试
  • 版本控制:代码级别的版本管理

最佳实践

何时使用用户技能(Markdown)

  • 简单的工作流
  • 主要包含文本内容
  • 需要频繁修改
  • 用户自定义的工作流

何时使用内置技能(TypeScript)

  • 复杂的多步骤流程
  • 需要与系统深度集成
  • 需要性能优化
  • 需要类型安全和测试
  • 官方提供的高质量技能

技术实现细节

注册机制

// src/skills/bundledSkills.ts
export function registerBundledSkill(definition: BundledSkillDefinition): void {
  const { files } = definition

  // 处理内置文件
  if (files && Object.keys(files).length > 0) {
    skillRoot = getBundledSkillExtractDir(definition.name)
    // 懒提取文件到磁盘
    getPromptForCommand = async (args, ctx) => {
      extractionPromise ??= extractBundledSkillFiles(definition.name, files)
      const extractedDir = await extractionPromise
      const blocks = await inner(args, ctx)
      if (extractedDir === null) return blocks
      return prependBaseDir(blocks, extractedDir)
    }
  }

  // 创建命令对象
  const command: Command = {
    type: 'prompt',
    name: definition.name,
    description: definition.description,
    // ... 其他属性
    getPromptForCommand,
  }

  bundledSkills.push(command)
}

文件提取机制

async function extractBundledSkillFiles(
  skillName: string,
  files: Record<string, string>,
): Promise<string | null> {
  const dir = getBundledSkillExtractDir(skillName)
  try {
    await writeSkillFiles(dir, files)
    return dir
  } catch (e) {
    logForDebugging(`Failed to extract bundled skill '${skillName}'...`)
    return null
  }
}

async function writeSkillFiles(
  dir: string,
  files: Record<string, string>,
): Promise<void> {
  // 按父目录分组以优化创建
  const byParent = new Map<string, [string, string][]>()
  for (const [relPath, content] of Object.entries(files)) {
    const target = resolveSkillFilePath(dir, relPath)
    const parent = dirname(target)
    const entry: [string, string] = [target, content]
    const group = byParent.get(parent)
    if (group) group.push(entry)
    else byParent.set(parent, [entry])
  }

  // 并行创建目录和文件
  await Promise.all(
    [...byParent].map(async ([parent, entries]) => {
      await mkdir(parent, { recursive: true, mode: 0o700 })
      await Promise.all(entries.map(([p, c]) => safeWriteFile(p, c)))
    }),
  )
}

总结

核心答案

内置技能使用 TypeScript 实现是因为它们需要:

  1. 动态逻辑:根据上下文和输入生成不同的提示
  2. 系统集成:调用其他工具、访问系统状态、操作文件系统
  3. 复杂交互:多步骤流程、条件分支、异步操作
  4. 类型安全:编译时检查、IDE支持、重构安全
  5. 性能优化:编译时优化、缓存、死代码消除
  6. 可测试性:单元测试、集成测试、场景模拟
  7. 维护性:模块化、清晰的依赖、版本控制

设计哲学

Claude Code 采用混合架构

  • 用户技能:使用Markdown,提供简单、灵活的工作流定义
  • 内置技能:使用TypeScript,提供强大、可靠、高性能的核心功能

这种设计既保证了易用性(用户可以轻松创建技能),又确保了功能强大性(内置技能可以处理复杂场景)。

未来扩展

如果需要创建类似内置技能的复杂技能:

  1. 评估复杂度:是否需要动态逻辑、系统集成、性能优化
  2. 选择实现方式
    • 简单场景 → Markdown 用户技能
    • 复杂场景 → TypeScript 内置技能
  3. 考虑贡献:对于通用技能,可以考虑贡献到官方仓库

结论:TypeScript 实现的内置技能是 Claude Code 架构设计的必然选择,它们提供了Markdown技能无法实现的复杂功能、系统集成和性能优化,同时保持了类型安全和可维护性。