编程 Claude Code Skills 实战:构建生产级 AI 编码 Agent 技能系统

2026-07-05 07:12:43 +0800 CST views 14

Claude Code Skills 实战:构建生产级 AI 编码 Agent 技能系统

2026年6月,前 Tesla AI 总监、OpenAI 创始成员 Andrej Karpathy 开源了一个看似简单的项目——Claude Code Skills 的示范仓库。短短一个月,该项目在 GitHub 斩获 149K Stars,成为 GitHub 历史增长最快的项目之一。这背后折射出的,是 AI 编码 Agent 正在从「玩具」走向「生产工具」的关键转折点。

引言:AI 编码 Agent 的技能危机

如果你在 2025 年用过 Claude Code、Cursor 或任何形式的 AI 编码助手,你可能已经注意到了一个核心矛盾:

模型越来越强,但输出质量越来越不可控。

同样的 Claude Sonnet 4,在 A 项目里能写出优雅的 Rust 异步代码,在 B 项目里却犯下低级的逻辑错误——把 Arc<Mutex<T>> 当成普通引用传递。问题不在模型,而在上下文:AI 不知道你的项目规范、不敢用你的工具链、不了解你的架构约束。

这就是 Skills 系统要解决的核心问题——给 AI Agent 注入可复用、可版本控制、可协作的结构化知识

Karpathy 在他的项目中总结了一句话,值得每个 AI 辅助开发者深思:

"The difference between a junior and senior developer is not knowing the syntax — it's knowing the conventions, the pitfalls, and the 'why' behind every decision. Skills are how we teach that to AI."

本文将深入剖析 Claude Code Skills 的设计哲学、技术架构和工程实践,并通过完整可运行的代码示例,教你从零构建生产级 AI 编码 Agent 技能系统。


一、Karpathy 的 Claude Code Skills 现象解析

1.1 为什么是 Skills 而不是 Prompt?

在深入技术细节之前,我们需要厘清一个根本问题:Skills 和 Prompt 有什么区别?

传统 Prompt 工程的核心范式是「一次性指令注入」——你写一段系统提示,模型读取后生成响应。这种方式有三个致命缺陷:

问题描述Skills 的解法
上下文污染长 Prompt 占用大量 Token,挤压有效上下文按需加载,条件注入
不可维护Prompt 改动影响全局,无法模块化文件级隔离,独立版本控制
无法协作团队成员各自维护 Prompt 副本共享 Skill 仓库,Git 协作

Skills 系统的核心创新在于:将 AI 上下文管理从「黑盒咒语」变成「白盒工程」

1.2 Karpathy 项目的技术亮点

Karpathy 的 Claude Code Skills 仓库(github.com/karpathy/claude-code-skills)虽然代码量不大,但设计极为精妙。其核心贡献可以归纳为三点:

① 标准化的 Skill 文件格式

每个 Skill 是一个独立的 Markdown 文件,遵循统一的元数据结构:

---
name: skill-name
description: What this skill does, when to use it
---

# Skill Content

Specific instructions, code examples, and constraints...

这种格式的优势在于:

  • YAML Front Matter 可被程序解析(自动化加载)
  • Markdown 正文对人类友好(可阅读、可编辑)
  • 文件即技能(无需数据库,Git 原生支持)

② 条件加载机制

Skills 不是无条件全部注入的。Karpathy 的设计中,每个 Skill 的 description 字段实际上充当路由信号——Claude Code 根据当前任务描述,决定是否加载该 Skill。

这就像操作系统的按需分页(Demand Paging),只不过分页的单元是「技能」而不是「内存页」。

③ CLAUDE.md 作为技能编排层

CLAUDE.md 是 Claude Code 的专属配置文件(类似 Cursor 的 .cursorrules),Karpathy 用它来:

  • 定义全局编码规范
  • 声明可用 Skills 列表
  • 设置项目特定的约束条件

这种分层设计使得「全局规范」和「专项技能」解耦,符合软件工程的关注点分离原则。

1.3 数据说话:为什么 149K Stars 不是偶然

让我们看几组数字:

  • Claude Code 的月活开发者:2026年5月突破 200 万
  • Skills 生态的 GitHub 仓库数:截至2026年7月,仅 claude-code-skills 相关仓库超过 2400 个
  • 企业采用率:据 San Francisco AI Engineer Survey 2026,37% 的 AI 编码团队正在评估或已采用 Skills 系统

这背后是一个正在形成的新职业角色:AI Agent 技能工程师(AI Agent Skill Engineer)——专门负责为编码 Agent 设计、测试和维护技能模块。


二、Claude Code Skills 核心架构深度剖析

要真正掌握 Skills 系统,我们需要深入理解其技术架构。这一节将从文件结构、加载机制、注入流程三个维度进行剖析。

2.1 标准项目文件结构

一个生产级 Claude Code Skills 项目的标准结构如下:

your-project/
├── CLAUDE.md                    # 全局配置与技能编排(核心入口)
├── .claude/
│   ├── skills/                  # Skills 目录
│   │   ├── code-review.md      # 代码审查技能
│   │   ├── testing.md          # 测试编写技能
│   │   ├── security-audit.md  # 安全审计技能
│   │   └── _shared/            # 跨技能共享片段
│   │       └── conventions.md
│   ├── commands/               # 自定义斜杠命令
│   │   ├── /review             # 触发代码审查
│   │   └── /test               # 触发测试生成
│   └── settings.json           # Claude Code 本地配置
├── src/                         # 你的业务代码
└── README.md

设计要点

  1. .claude/skills/ 目录是约定俗成的技能存放位置,Claude Code 会自动扫描此目录
  2. CLAUDE.md 必须放在项目根目录,这是 Claude Code 的硬性规定
  3. commands/ 目录定义自定义交互命令,用户可以通过 /review 这样的命令触发特定技能

2.2 CLAUDE.md 的深度配置

CLAUDE.md 是整个技能系统的编排层,它的质量直接决定 AI Agent 的输出质量。以下是一个生产级 CLAUDE.md 的完整示例:

# CLAUDE.md - Project Instructions for Claude Code

## Project Overview
This is a Rust-based microservice for real-time payment processing.
Tech stack: Rust + Axum + SQLx + Redis + Kafka

## Coding Standards (Non-Negotiable)
- All async code MUST use `tokio` with `async/await` (no `std::thread`)
- Error handling MUST use `thiserror` for library code, `anyhow` for application code
- All database queries MUST go through SQLx with compile-time checking
- No `unwrap()` in production code — use `?` or explicit match
- All public functions MUST have doc comments with examples

## Architecture Constraints
- Domain logic goes in `src/domain/`, NEVER in handlers
- All external calls MUST go through `src/adapters/`
- Configuration MUST be loaded via `config.rs` with `dotenvy`
- Feature flags MUST be used for experimental features

## Available Skills
The following skills are available and should be loaded contextually:

1. **code-review** (`/.claude/skills/code-review.md`)
   - Load when: Reviewing PRs, refactoring code, or before committing
   - Contains: Review checklist, common Rust anti-patterns, performance heuristics

2. **testing** (`/.claude/skills/testing.md`)
   - Load when: Writing tests, fixing failing tests, or measuring coverage
   - Contains: Test patterns, mock strategies, property-based testing guide

3. **security-audit** (`/.claude/skills/security-audit.md`)
   - Load when: Handling user input, auth logic, or crypto operations
   - Contains: OWASP checklist, common Rust security pitfalls

## Loading Rules
- Always load `code-review` skill before suggesting any code changes
- Load `testing` skill when test files are modified
- Load `security-audit` skill when dealing with `src/auth/` or `src/payment/`

## Commit Rules
- Commit messages MUST follow Conventional Commits
- NEVER commit secrets, API keys, or `.env` files
- Run `cargo clippy` and `cargo fmt` before every commit

## Known Pitfalls (Project-Specific)
- The `PaymentProcessor` struct in `src/domain/payment.rs` has a known issue 
  with concurrent idempotency keys — always use the `IdempotencyGuard` adapter
- Kafka consumer group rebalancing can cause duplicate processing — 
  see `docs/kafka-idempotency.md` for the workaround

这个 CLAUDE.md 的精妙之处

  1. 架构约束明确:直接告诉 AI 「domain logic 不能进 handler」,从规范层面防止架构腐化
  2. 技能加载规则具体:不是模糊的「可用技能列表」,而是「什么时候加载什么技能」
  3. 已知坑点文档化:把团队的经验教训固化成 AI 可理解的格式,避免重复踩坑

2.3 Skill 文件的内部结构与注入机制

一个高质量的 Skill 文件,其内部结构应当遵循「指令-示例-约束」三段式:

---
name: code-review
description: Load when reviewing Rust code changes. Provides systematic review checklist and common anti-pattern detection.
---

# Code Review Skill

## When to Load This Skill
Load this skill when:
- User asks for code review
- About to commit changes
- Refactoring existing code
- PR title contains "review" or "refactor"

## Review Checklist

### 1. Correctness
- [ ] Is the logic correct for all edge cases?
- [ ] Are error cases handled explicitly?
- [ ] Is there any potential for panic (unwrap, expect)?

**How to check**: Look for `.unwrap()` calls — they should be replaced with `?` or proper error handling.

```rust
// ❌ BAD
let value = hash_map.get(&key).unwrap();

// ✅ GOOD
let value = hash_map.get(&key)
    .ok_or_else(|| anyhow!("Key not found: {:?}", key))?;

2. Performance

  • Unnecessary clones? (Use &str instead of String where possible)
  • Heap allocations in hot paths?
  • O(n²) algorithms on large datasets?

Common Rust Performance Anti-Patterns:

// ❌ BAD: Allocates a new String for every iteration
for s in string_list {
    do_something(s.to_string()); // to_string() allocates
}

// ✅ GOOD: Borrow instead
for s in string_list {
    do_something(s); // s is already &str
}

3. Idiomatic Rust

  • Using clippy suggestions?
  • Proper use of From/Into traits?
  • match instead of chain of if-let?

Project-Specific Rules

  • In this project, all DB types must implement sqlx::FromRow
  • Service structs must be Clone + Send + Sync
  • No panic! in async contexts — use Result and propagate

Output Format

When invoked, produce a review in this format:

Summary

One-line assessment: ✅ LGTM / ⚠️ Needs Changes / ❌ Blocking Issues

Issues Found

For each issue:

  • File: path/to/file.rs:L42
  • Severity: Critical / Major / Minor / Suggestion
  • Category: Correctness / Performance / Idiom / Security
  • Current Code: (code snippet)
  • Suggested Fix: (code snippet)

**注入机制的技术细节**:

Claude Code 在处理用户消息时,会执行以下步骤:

1. **解析 `CLAUDE.md`**:提取技能列表和加载规则
2. **匹配当前上下文**:根据用户消息和当前文件,决定是否加载某个 Skill
3. **注入 Skill 内容**:将匹配的 Skill 文件内容注入到上下文窗口
4. **执行任务**:基于注入的技能指令完成任务

这里的**关键技术挑战**是上下文窗口管理——如果同时加载 10 个 Skill,每个 2000 Token,就占用了 20000 Token,可能挤掉实际的代码上下文。因此,**条件加载不是优化,是必要**。

---

## 三、从零构建生产级 Skills:完整实战

这一节将通过构建一个完整的 Skills 系统,展示从设计到部署的全流程。我们的目标是一个**微服务开发辅助 Skills 套件**,包含代码生成、测试、审查、部署四个技能模块。

### 3.1 项目初始化与目录结构

```bash
# 初始化项目
mkdir -p rust-microservice-skills && cd rust-microservice-skills
git init

# 创建目录结构
mkdir -p .claude/skills .claude/commands
mkdir -p src/{handlers,domain,adapters,config}
mkdir -p tests docs

# 创建基础文件
touch CLAUDE.md .claude/settings.json
touch src/lib.rs src/main.rs

3.2 编写第一个 Skill:api-design.md

我们先从最常用的场景入手——设计符合 Rust 惯用法的 Axum 异步 API

---
name: api-design
description: Load when creating or modifying Axum HTTP handlers. Provides Rust async API design patterns, error handling strategies, and Axum best practices.
---

# API Design Skill for Axum + Rust

## Design Principles

### 1. Handler 函数签名规范
所有 Handler 必须满足:
- 输入:Extractors(不能手动反序列化)
- 输出:`Result<Response, AppError>`(统一错误类型)
- 异步:`async fn` with `tokio`

```rust
// ✅ CANONICAL HANDLER PATTERN
use axum::{
    extract::{Path, Json, State},
    http::StatusCode,
    response::Json as JsonResponse,
};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
pub struct CreateUserRequest {
    pub email: String,
    pub name: String,
}

#[derive(Serialize)]
pub struct CreateUserResponse {
    pub id: uuid::Uuid,
    pub email: String,
}

#[derive(Clone)]
pub struct AppState {
    pub db: sqlx::PgPool,
    pub redis: redis::Client,
}

/// Create a new user
/// 
/// # Errors
/// - 409: Email already exists
/// - 400: Invalid email format
/// - 500: Database error
pub async fn create_user_handler(
    State(state): State<AppState>,
    Json(req): Json<CreateUserRequest>,
) -> Result<JsonResponse<CreateUserResponse>, AppError> {
    // Validate input
    if !is_valid_email(&req.email) {
        return Err(AppError::BadRequest("Invalid email format".into()));
    }
    
    // Check duplicate
    let existing = sqlx::query!(
        "SELECT id FROM users WHERE email = $1",
        req.email
    )
    .fetch_optional(&state.db)
    .await?;
    
    if existing.is_some() {
        return Err(AppError::Conflict("Email already registered".into()));
    }
    
    // Create user
    let user_id = uuid::Uuid::new_v4();
    sqlx::query!(
        "INSERT INTO users (id, email, name) VALUES ($1, $2, $3)",
        user_id,
        req.email,
        req.name
    )
    .execute(&state.db)
    .await?;
    
    Ok(JsonResponse(CreateUserResponse {
        id: user_id,
        email: req.email,
    }))
}

2. 统一错误类型设计

// src/error.rs

use axum::response::{Response, IntoResponse};
use axum::http::StatusCode;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum AppError {
    #[error("Bad request: {0}")]
    BadRequest(String),
    
    #[error("Not found: {0}")]
    NotFound(String),
    
    #[error("Conflict: {0}")]
    Conflict(String),
    
    #[error("Internal error: {0}")]
    Internal(#[from] anyhow::Error),
}

impl IntoResponse for AppError {
    fn into_response(self) -> Response {
        let (status, message) = match self {
            AppError::BadRequest(msg) => (StatusCode::BAD_REQUEST, msg),
            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
            AppError::Conflict(msg) => (StatusCode::CONFLICT, msg),
            AppError::Internal(err) => {
                tracing::error!("Internal error: {:?}", err);
                (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error".into())
            }
        };
        
        (status, axum::Json(serde_json::json!({
            "error": message,
            "code": status.as_u16()
        }))).into_response()
    }
}

// Implement From<sqlx::Error> for AppError
impl From<sqlx::Error> for AppError {
    fn from(err: sqlx::Error) -> Self {
        match err {
            sqlx::Error::RowNotFound => AppError::NotFound("Resource not found".into()),
            _ => AppError::Internal(anyhow::anyhow!("Database error: {}", err)),
        }
    }
}

3. 中间件模式

// src/middleware.rs

use axum::{
    extract::Request,
    middleware::Next,
    response::Response,
};
use tower_http::trace::TraceLayer;
use tracing::Span;

/// Request ID middleware — adds unique ID to every request for tracing
pub async fn request_id_middleware(
    mut request: Request,
    next: Next,
) -> Response {
    let request_id = uuid::Uuid::new_v4().to_string();
    request.extensions_mut().insert(RequestId(request_id.clone()));
    
    // Add request_id to tracing span
    let span = tracing::info_span!("request", %request_id);
    let _guard = span.enter();
    
    let response = next.run(request).await;
    response
}

#[derive(Clone)]
struct RequestId(String);

/// Tracing layer with custom span for Axum
pub fn create_trace_layer() -> TraceLayer {
    TraceLayer::new_for_http()
        .make_span_with(|request: &Request<_>| {
            let method = request.method();
            let uri = request.uri();
            tracing::info_span!("http_request", %method, %uri)
        })
        .on_response(|response: &Response<_>, latency: std::time::Duration, _span: &Span| {
            let status = response.status().as_u16();
            tracing::info!(%status, ?latency, "response sent");
        })
}

4. 路由组织模式

// src/routes.rs

use axum::{Router, routing::{get, post, put, delete}};

pub fn create_router(state: AppState) -> Router {
    Router::new()
        // Health check (no auth required)
        .route("/health", get(health_check))
        
        // User routes
        .route("/users", post(create_user_handler))
        .route("/users/:id", get(get_user_handler))
        .route("/users/:id", put(update_user_handler))
        .route("/users/:id", delete(deactivate_user_handler))
        
        // Nested API v2
        .nest("/api/v2", api_v2_routes())
        
        // With state
        .with_state(state)
}

fn api_v2_routes() -> Router<AppState> {
    Router::new()
        .route("/users", post(v2::create_user))
        .route("/analytics", get(v2::get_analytics))
}

Common Pitfalls to Avoid

Pitfall 1: Blocking in async context

// ❌ BAD: Blocking the tokio runtime
pub async fn bad_handler() -> String {
    // This blocks the entire runtime!
    std::thread::sleep(std::time::Duration::from_secs(1));
    "done".into()
}

// ✅ GOOD: Use tokio::time::sleep
pub async fn good_handler() -> String {
    tokio::time::sleep(std::time::Duration::from_secs(1)).await;
    "done".into()
}

Pitfall 2: Not using typed SQLx queries

// ❌ BAD: Runtime query building (no compile-time check)
sqlx::query(&format!("SELECT * FROM users WHERE id = {}", user_id))
    .fetch_one(&pool)
    .await?;

// ✅ GOOD: Compile-time checked queries
sqlx::query!(
    "SELECT id, email, name FROM users WHERE id = $1",
    user_id
)
.fetch_one(&pool)
.await?;

Pitfall 3: Forgetting to handle connection pool exhaustion

// Add to AppState creation
pub async fn create_app_state(config: &Config) -> Result<AppState, AppError> {
    let db = sqlx::postgres::PgPoolOptions::new()
        .max_connections(20)  // ← Don't forget this!
        .connect_timeout(std::time::Duration::from_secs(5))
        .connect(&config.database_url)
        .await?;
    
    Ok(AppState { db, .. })
}

### 3.3 编写测试技能:`testing.md`

测试是 Rust 项目的生命线,但 AI 生成的测试往往质量低下——缺少边界条件、不测试并发、忽略属性测试。这个 Skill 解决这些问题。

```markdown
---
name: testing
description: Load when writing or fixing tests. Provides Rust testing patterns including unit tests, integration tests, property-based testing with proptest, and mock strategies.
---

# Testing Skill for Rust Projects

## Testing Strategy Overview

Our testing pyramid:
- **Unit tests** (70%): Fast, isolated, test pure logic
- **Integration tests** (20%): Test API contracts and DB interactions
- **Property tests** (10%): Find edge cases via randomized inputs

## 1. Unit Test Patterns

### Basic Pattern
```rust
// In src/domain/payment.rs

pub struct PaymentProcessor {
    idempotency_store: Arc<dyn IdempotencyStore>,
}

impl PaymentProcessor {
    /// Process a payment with idempotency guarantee
    pub async fn process(
        &self,
        payment: Payment,
        idempotency_key: &str,
    ) -> Result<PaymentResult, PaymentError> {
        // Check idempotency
        if let Some(cached) = self.idempotency_store
            .get(idempotency_key)
            .await?
        {
            return Ok(cached);
        }
        
        // Process payment...
        let result = self.do_process(payment).await?;
        
        // Cache result
        self.idempotency_store
            .set(idempotency_key, &result)
            .await?;
        
        Ok(result)
    }
}

// ✅ UNIT TESTS in the same file (Rust convention)
#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::Arc;
    
    // Mock store for testing
    struct MockIdempotencyStore {
        store: std::sync::Mutex<HashMap<String, PaymentResult>>,
    }
    
    impl MockIdempotencyStore {
        fn new() -> Self {
            Self { store: HashMap::new() }
        }
    }
    
    #[async_trait]
    impl IdempotencyStore for MockIdempotencyStore {
        async fn get(&self, key: &str) -> Result<Option<PaymentResult>, StoreError> {
            Ok(self.store.lock().unwrap().get(key).cloned())
        }
        
        async fn set(&self, key: &str, result: &PaymentResult) -> Result<(), StoreError> {
            self.store.lock().unwrap().insert(key.to_string(), result.clone());
            Ok(())
        }
    }
    
    #[tokio::test]
    async fn test_idempotent_payment() {
        // Arrange
        let store = Arc::new(MockIdempotencyStore::new());
        let processor = PaymentProcessor { idempotency_store: store.clone() };
        let payment = Payment::new(100, "USD");
        let key = "test-key-123";
        
        // Act: First call
        let result1 = processor.process(payment.clone(), key).await.unwrap();
        
        // Act: Second call with same key
        let result2 = processor.process(payment.clone(), key).await.unwrap();
        
        // Assert: Results are identical (idempotent)
        assert_eq!(result1.transaction_id, result2.transaction_id);
    }
    
    #[tokio::test]
    async fn test_payment_validation_rejects_negative_amount() {
        let processor = create_test_processor();
        let payment = Payment::new(-100, "USD"); // Invalid!
        
        let result = processor.process(payment, "key-1").await;
        
        assert!(matches!(result, Err(PaymentError::InvalidAmount(_))));
    }
}

2. Integration Test Pattern

// In tests/api_test.rs (separate file for integration tests)

use axum::body::Body;
use axum::http::{Request, StatusCode};
use tower::ServiceExt; // for `oneshot`

#[sqlx::test]
async fn test_create_user_api(pool: sqlx::PgPool) {
    // Arrange: Setup app with test pool
    let state = AppState { db: pool };
    let app = create_router(state);
    
    let payload = serde_json::json!({
        "email": "test@example.com",
        "name": "Test User"
    });
    
    // Act: Send request to the router (no server needed!)
    let response = app
        .oneshot(
            Request::builder()
                .method("POST")
                .uri("/users")
                .header("content-type", "application/json")
                .body(Body::from(payload.to_string()))
                .unwrap()
        )
        .await
        .unwrap();
    
    // Assert
    assert_eq!(response.status(), StatusCode::CREATED);
    
    let body = axum::body::to_bytes(response.into_body(), usize::MAX)
        .await
        .unwrap();
    let json: serde_json::Value = serde_json::from_slice(&body).unwrap();
    
    assert_eq!(json["email"], "test@example.com");
    assert!(json["id"].is_string()); // UUID present
}

#[sqlx::test]
async fn test_create_user_duplicate_email(pool: sqlx::PgPool) {
    let state = AppState { db: pool };
    let app = create_router(state);
    
    // Create first user
    let payload = serde_json::json!({
        "email": "dup@example.com",
        "name": "User One"
    });
    
    app.clone()
        .oneshot(create_post_request("/users", &payload))
        .await
        .unwrap();
    
    // Try to create second user with same email
    let response = app
        .oneshot(create_post_request("/users", &payload))
        .await
        .unwrap();
    
    assert_eq!(response.status(), StatusCode::CONFLICT);
}

3. Property-Based Testing with Proptest

// In src/domain/amount.rs

/// A verified non-negative payment amount
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PaymentAmount(f64);

impl PaymentAmount {
    pub fn new(amount: f64) -> Result<Self, PaymentError> {
        if amount < 0.0 {
            return Err(PaymentError::InvalidAmount("Amount cannot be negative".into()));
        }
        if amount > 1_000_000.0 {
            return Err(PaymentError::InvalidAmount("Amount exceeds maximum".into()));
        }
        // Check for NaN/Infinity
        if !amount.is_finite() {
            return Err(PaymentError::InvalidAmount("Invalid amount value".into()));
        }
        Ok(PaymentAmount(amount))
    }
    
    pub fn value(self) -> f64 {
        self.0
    }
}

// ✅ PROPERTY TEST: Test invariants hold for ALL valid inputs
#[cfg(test)]
mod prop_tests {
    use super::*;
    use proptest::prelude::*;
    
    proptest! {
        #[test]
        fn amount_always_non_negative(amount in 0.0..1_000_000.0f64) {
            let result = PaymentAmount::new(amount);
            prop_assert!(result.is_ok());
            prop_assert!(result.unwrap().value() >= 0.0);
        }
        
        #[test]
        fn negative_amount_always_rejected(
            amount in f64::NEG_INFINITY..0.0  // All negative numbers
        ) {
            let result = PaymentAmount::new(amount);
            prop_assert!(result.is_err());
        }
        
        #[test]
        fn large_amount_always_rejected(
            amount in 1_000_000.01..f64::INFINITY
        ) {
            let result = PaymentAmount::new(amount);
            prop_assert!(result.is_err());
        }
        
        // Invariant: Creating amount, then calling value(), gives original
        #[test]
        fn value_roundtrip(amount in 0.0..1_000_000.0f64) {
            let amt = PaymentAmount::new(amount).unwrap();
            prop_assert!((amt.value() - amount).abs() < f64::EPSILON);
        }
    }
}

4. Test Coverage Configuration

# In Cargo.toml
[dev-dependencies]
tarpaulin = "0.27"  # For coverage (CI only)

# Run coverage
# cargo tarpaulin --out Html --output-dir coverage/

Checklist Before Committing Tests

  • Every pub fn has at least one unit test
  • Error paths are tested (not just happy path)
  • Concurrent code is tested with tokio::join! or tokio::spawn
  • Property tests cover boundary values (0, MAX, NaN, Infinity)
  • Mocks implement the same traits as production code

### 3.4 技能协同:让多个 Skills 一起工作

单个 Skill 的价值有限,**技能协同**才是生产力爆发点。在 `CLAUDE.md` 中定义技能之间的协作规则:

```markdown
# In CLAUDE.md

## Skill Orchestration Rules

### When user says "implement [feature]":
1. Load `api-design` skill → Generate the handler
2. Load `testing` skill → Generate tests for the handler  
3. Run tests → If fail, load `debugging` skill
4. Load `code-review` skill → Final review before commit

### When user says "refactor [module]":
1. Load `code-review` skill → Understand current code
2. Load `testing` skill → Ensure tests exist before refactoring
3. Refactor + run tests → Ensure no regression
4. Load `performance` skill → Check for optimization opportunities

### Skill Conflict Resolution
- If `api-design` and `performance` skills give conflicting advice:
  - Prioritize correctness (api-design) over performance
  - Only optimize if benchmark proves it's necessary
- If `testing` and `code-review` disagree on test strategy:
  - Follow `testing` skill for test implementation
  - Follow `code-review` skill for test coverage assessment

四、高级技能设计模式

当你掌握了基础 Skills 编写后,可以进一步探索高级模式。这些模式是生产环境中经过验证的最佳实践。

4.1 条件注入模式(Conditional Injection Pattern)

问题:有些技能内容只在特定条件下有用,但无条件注入会浪费 Token。

解法:在 Skill 文件中使用「触发条件」标记,让 Claude Code 根据条件决定是否注入完整内容。

---
name: kubernetes-deploy
description: Load when deploying to K8s. Contains K8s manifests, Helm values, and deployment runbooks.
conditions:
  - "user mentions deploy/staging/production"
  - "file modified matches *.k8s.yaml or helm/**"
  - "git branch is develop or main"
---

# Kubernetes Deployment Skill

> **NOTE TO CLAUDE**: Only load the sections below that match the current task.
> If user is only asking about staging deploy, skip the Production section.

## Staging Deployment

### Pre-deployment Checks
1. Run `cargo audit` — no known vulnerabilities
2. Run `cargo clippy -- -D warnings` — no lint warnings
3. Ensure `STAGING_DB_URL` is set in GitHub Secrets

### Deploy Command
```bash
# Deploy to staging
helm upgrade --install payment-svc ./helm \
  --namespace staging \
  --set image.tag=${GITHUB_SHA::7} \
  --set environment=staging \
  --dry-run  # Always do dry-run first!

Production Deployment (Load ONLY when explicitly asked)

Additional Requirements

  • PR must be approved by 2 reviewers
  • Must include database migration rollback plan
  • Deploy during low-traffic window (02:00-04:00 UTC)

Production Helm Values (Override)

# helm/values-production.yaml
replicaCount: 5  # Higher availability
resources:
  limits:
    memory: 2Gi
    cpu: 2000m

Rollback Procedure

If deployment fails health check:

helm rollback payment-svc --namespace production

### 4.2 工具链编排模式(Toolchain Orchestration Pattern)

**问题**:AI 知道该做什么,但不知道如何用你的具体工具链做。

**解法**:在 Skill 中嵌入完整的工具调用序列,包括 `cargo` 命令、`curl` 请求、甚至 `kubectl` 操作。

```markdown
---
name: local-dev-setup
description: Load when setting up local dev environment or debugging local issues.
---

# Local Development Setup Skill

## First-Time Setup (Run once)

```bash
# 1. Install Rust toolchain (specific version)
rustup install 1.82.0
rustup default 1.82.0

# 2. Install required tools
cargo install cargo-watch   # Auto-restart on file change
cargo install cargo-audit   # Security vulnerability scanner
cargo install sqlx-cli      # Database migration tool

# 3. Setup pre-commit hook
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
cargo fmt --check && cargo clippy -- -D warnings && cargo test
EOF
chmod +x .git/hooks/pre-commit

# 4. Start local infrastructure (Docker)
docker run -d --name local-postgres \
  -e POSTGRES_PASSWORD=localdev \
  -e POSTGRES_DB=payment_local \
  -p 5432:5432 \
  postgres:16-alpine

docker run -d --name local-redis \
  -p 6379:6379 \
  redis:7-alpine

# 5. Run migrations
sqlx migrate run --database-url postgres://postgres:localdev@localhost:5432/payment_local

# 6. Load test data
psql postgres://postgres:localdev@localhost:5432/payment_local < tests/fixtures/test_data.sql

Development Workflow

Start development server (with auto-reload)

cargo watch -x 'run --bin payment-service'

Run specific test file

cargo test --test api_test -- --nocapture

Check what the AI changed

# Good practice: Always review AI-generated code
git diff --staged

Debugging Common Issues

Issue: "connection refused" to database

# Check if Postgres is running
docker ps | grep local-postgres

# If not, start it
docker start local-postgres

# Check logs
docker logs local-postgres

Issue: sqlx compile error "database URL not set"

# Ensure .env file exists
cat > .env << EOF
DATABASE_URL=postgres://postgres:localdev@localhost:5432/payment_local
REDIS_URL=redis://localhost:6379
RUST_LOG=debug
EOF

# Source it
export $(cat .env | xargs)

### 4.3 上下文压缩模式(Context Compression Pattern)

**问题**:当项目变大,完整的 `CLAUDE.md` + 所有 Skills 会超出上下文窗口。

**解法**:使用「分层注入」——先注入索引,再按需注入细节。

```markdown
---
name: project-map
description: Always load. Provides a compressed project overview for navigation.
---

# Project Map (Compressed Context)

## Directory Structure (One-Line Descriptions)
- `src/domain/` — Pure business logic, no framework dependencies
- `src/adapters/` — External service integrations (DB, Redis, Kafka)
- `src/handlers/` — Axum HTTP handlers (thin, delegate to domain)
- `src/config.rs` — Configuration loading from env + .env
- `migrations/` — SQLx database migrations
- `helm/` — Kubernetes Helm charts
- `tests/` — Integration tests

## Key Types (For Quick Reference)
- `PaymentId` (uuid::Uuid) — Unique payment identifier
- `PaymentAmount` (f64 wrapper) — Validated non-negative amount
- `IdempotencyKey` (String) — Prevents duplicate processing
- `AppError` (enum) — All possible errors, implements IntoResponse

## Critical Constraints (Must Remember)
1. All handlers must use `State<AppState>` extractor
2. No raw SQL — always use `sqlx::query!` macro
3. All async functions must be `Send + Sync`
4. Kafka messages must be idempotent (check `IdempotencyKey`)

## When You Need More Details
- For domain logic details: Read `src/domain/README.md`
- For API contract details: Read `docs/api-contract.md`
- For deployment details: Load `kubernetes-deploy` skill
- For testing strategy: Load `testing` skill

这个 Skill 只有约 30 行,但提供了足够的「导航信息」,让 AI 知道去哪里找更详细的信息——就像一个微型索引


五、与 Cursor / Windsurf / Devin 的技能系统对比

Skills 系统不是 Claude Code 独有的概念。让我们对比主流 AI 编码工具的技能/上下文管理机制:

工具技能系统名称核心机制优势劣势
Claude CodeCLAUDE.md + Skills文件级 Markdown 注入简单、透明、Git 原生无官方 Skill 市场
Cursor.cursorrules + Rules全局/项目级规则注入支持通配符文件匹配规则是黑盒,不可调试
WindsurfCascade Memories自动记忆重要上下文低维护成本记忆质量不稳定
DevinKnowledge Base云端知识库团队协作友好闭源,不可本地编辑
GitHub CopilotPrompt File (Preview)类似 Skills 的文件注入与 GitHub 深度集成功能尚不完善

Claude Code Skills 的独特优势

  1. 完全本地化:所有 Skill 文件在本地 Git 仓库中,可以 code review、可以 PR、可以 CI/CD
  2. 可调试:你可以直接编辑 CLAUDE.md,下次对话立即生效——不需要重新训练模型
  3. 可组合:Skills 是纯文本文件,可以用任何模板引擎生成(比如根据 OpenAPI spec 自动生成 API 调用 Skill)

六、性能优化:让 AI 编码 Agent 快起来的技巧

用了 Skills 之后,你可能会发现 AI 变慢了——因为它要处理更多上下文。这一节介绍优化技巧。

6.1 Token usage 分析

先测量,再优化。Claude Code 的 Token 消耗主要来自:

Total Token Budget (200k for Claude Sonnet 4):
├── System Prompt (固定)              ~3,000
├── CLAUDE.md                         ~2,000
├── Loaded Skills                     ~5,000-15,000
├── Conversation History              ~10,000-50,000
├── Current File(s)                   ~2,000-10,000
└── Remaining for Output              ~20,000-50,000

优化目标:把 Loaded Skills 控制在 5,000 Token 以内。

6.2 技能精简技巧

技巧1:删除「显而易见」的内容

<!-- ❌ BAD: Wasting tokens on obvious things -->
## What is Rust?
Rust is a systems programming language that is fast and safe...

<!-- ✅ GOOD: Assume the reader (AI) knows Rust -->
## Rust Version Requirements
- Edition 2021 or later
- rustc >= 1.82.0 (for `impl Trait` in return position)

技巧2:用代码代替文字描述

<!-- ❌ BAD: 100 words to describe a pattern -->
When handling errors in Rust, you should never use unwrap() because 
it can cause panics in production. Instead, you should use the ? operator
to propagate errors to the caller...

<!-- ✅ GOOD: 10 lines of code say more -->
```rust
// ❌ BAD
let value = hash_map.get(&key).unwrap();

// ✅ GOOD  
let value = hash_map.get(&key)
    .ok_or_else(|| AppError::NotFound("Key not found".into()))?;

**技巧3:使用「参见」模式**

```markdown
## Database Patterns

For complete database patterns, see:
- `src/domain/README.md#database-patterns` (detailed examples)
- `migrations/README.md` (migration conventions)

Quick reference:
- Always use `sqlx::query!` (compile-time check)
- Always wrap in transaction for multi-table updates

6.3 上下文窗口管理策略

对于大型项目,即使精简了 Skills,上下文仍然可能溢出。此时需要主动上下文管理

# In CLAUDE.md

## Context Management Strategy

### High-Priority Context (Always Load)
- This section (CLAUDE.md project overview)
- `src/domain/types.rs` (core type definitions)
- Current file being edited

### Medium-Priority Context (Load on Demand)
- Related test files
- Related adapter implementations
- Skill files matching current task

### Low-Priority Context (Reference Only)
- Full project history
- Deprecated modules
- Third-party integration details

### Context Eviction Rules
When context window is >70% full:
1. Remove conversation history older than 10 turns
2. Keep only the current file + immediate dependencies
3. Summarize (don't delete) low-priority context

七、真实案例:用 Skills 重构一个微服务

理论讲完了,这一节展示一个完整的实战案例:用 Skills 辅助重构一个支付微服务

7.1 初始状态(问题代码)

假设我们有一个运行了 2 年的支付服务,代码已经部分腐化:

// src/handlers/payment.rs (BEFORE - The "bad" version)
use actix_web::{web, HttpResponse, Responder};

pub async fn process_payment(
    req: web::Json<serde_json::Value>,
    db: web::Data<sqlx::PgPool>,
) -> impl Responder {
    let amount = req.get("amount").unwrap().as_f64().unwrap();
    let user_id = req.get("user_id").unwrap().as_str().unwrap();
    
    // Direct SQL — no compile-time check!
    let result = sqlx::query("INSERT INTO payments (user_id, amount) VALUES ($1, $2)")
        .bind(user_id)
        .bind(amount)
        .execute(db.get_ref())
        .await;
    
    match result {
        Ok(_) => HttpResponse::Ok().json(serde_json::json!({"status": "ok"})),
        Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
    }
}

问题清单

  • 手动 JSON 解析(不安全)
  • unwrap() 会导致 panic
  • 字符串拼接 SQL(虽然用了参数化,但没有编译时检查)
  • 错误处理粗糙(直接返回 error string)
  • 没有输入验证
  • 没有幂等性保证

7.2 用 Skills 辅助重构(AFTER)

在配置了完整 Skills 的 Claude Code 中,我们只需要说:

"Refactor src/handlers/payment.rs to follow our project conventions. Load the api-design and testing skills."

Claude Code(在 Skills 指导下)会生成:

// src/handlers/payment.rs (AFTER - The "good" version)
use axum::{
    extract::{State, Json},
    http::StatusCode,
    response::Json as JsonResponse,
};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Deserialize)]
pub struct ProcessPaymentRequest {
    pub user_id: Uuid,
    pub amount: f64,
    pub idempotency_key: String,  // ← New: idempotency support
}

#[derive(Serialize)]
pub struct ProcessPaymentResponse {
    pub payment_id: Uuid,
    pub status: String,
    pub amount: f64,
}

#[derive(Clone)]
pub struct AppState {
    pub db: sqlx::PgPool,
    pub idempotency_store: RedisPool,
}

/// Process a payment with idempotency guarantee
pub async fn process_payment_handler(
    State(state): State<AppState>,
    Json(req): Json<ProcessPaymentRequest>,
) -> Result<JsonResponse<ProcessPaymentResponse>, AppError> {
    // Input validation (from api-design skill)
    if req.amount <= 0.0 {
        return Err(AppError::BadRequest("Amount must be positive".into()));
    }
    
    if req.amount > 1_000_000.0 {
        return Err(AppError::BadRequest("Amount exceeds maximum".into()));
    }
    
    // Idempotency check (from business logic skill)
    if let Some(cached) = state.idempotency_store
        .get(&format!("payment:{}", req.idempotency_key))
        .await?
    {
        let response: ProcessPaymentResponse = serde_json::from_str(&cached)?;
        return Ok(JsonResponse(response));
    }
    
    // Process payment in transaction
    let payment_id = Uuid::new_v4();
    let result = sqlx::transaction(&state.db, |tx| {
        Box::pin(async move {
            // Compile-time checked query (from api-design skill)
            sqlx::query!(
                "INSERT INTO payments (id, user_id, amount, status) 
                 VALUES ($1, $2, $3, $4)",
                payment_id,
                req.user_id,
                req.amount,
                "pending"
            )
            .execute(&mut *tx)
            .await?;
            
            Ok(())
        })
    })
    .await?;
    
    let response = ProcessPaymentResponse {
        payment_id,
        status: "pending".into(),
        amount: req.amount,
    };
    
    // Cache idempotency result
    state.idempotency_store
        .set(
            &format!("payment:{}", req.idempotency_key),
            &serde_json::to_string(&response)?,
        )
        .await?;
    
    Ok(JsonResponse(response))
}

// Corresponding unit tests (generated by testing skill)
#[cfg(test)]
mod tests {
    use super::*;
    
    #[tokio::test]
    async fn test_process_payment_success() {
        let state = create_test_state().await;
        let req = ProcessPaymentRequest {
            user_id: Uuid::new_v4(),
            amount: 99.99,
            idempotency_key: "test-key".into(),
        };
        
        let result = process_payment_handler(State(state), Json(req)).await;
        
        assert!(result.is_ok());
        let response = result.unwrap();
        assert_eq!(response.amount, 99.99);
    }
    
    #[tokio::test]
    async fn test_process_payment_idempotent() {
        let state = create_test_state().await;
        let req = ProcessPaymentRequest {
            user_id: Uuid::new_v4(),
            amount: 50.0,
            idempotency_key: "idem-key".into(),
        };
        
        // First call
        let r1 = process_payment_handler(State(state.clone()), Json(req.clone())).await.unwrap();
        
        // Second call — should return same result
        let r2 = process_payment_handler(State(state), Json(req)).await.unwrap();
        
        assert_eq!(r1.0.payment_id, r2.0.payment_id);
    }
}

重构差异对比

维度BEFOREAFTER
输入验证类型安全 + 业务规则
错误处理unwrap() + 裸字符串AppError 枚举 + IntoResponse
SQL 安全运行时拼 SQL编译时检查 sqlx::query!
幂等性IdempotencyKey + Redis 缓存
测试单元测试 + 幂等性测试
代码行数25 行120 行(但质量天差地别)

八、开源生态与 Skill 市场

随着 Skills 系统的普及,开源社区正在形成一个技能生态。以下是值得关注的项目和趋势:

8.1 优质开源 Skills 仓库

  1. karpathy/claude-code-skills
    Karpathy 的原作,包含通用编程技能模板

  2. anthropics/claude-code-skills-official
    Anthropic 官方的 Skills 示例集合(2026年6月发布)

  3. rust-skills/axum-patterns
    专注于 Axum Web 开发的 Skill 集合

  4. go-skills/gin-best-practices
    Go + Gin 框架的 Skills 套件

8.2 Skill 描述语言标准化尝试

社区正在推动 Skill Description Language (SDL) 的标准化——一种 YAML/JSON 格式的 Skill 描述规范,使得 Skills 可以在不同 AI 编码工具之间移植。

# skill-manifest.yaml (proposed standard)
name: api-design
version: 1.2.0
description: Axum API design patterns for Rust
authors:
  - "Your Name <you@example.com>"
compatible_tools:
  - claude-code
  - cursor
  - windsurf
tags:
  - rust
  - axum
  - api-design
dependencies:
  - name: rust-conventions
    version: ">=1.0.0"
loading_rules:
  - condition: "file_modified matches **/*handler*.rs"
    priority: high
  - condition: "user_mentions contains 'api' or 'endpoint'"
    priority: medium

九、总结与展望

关键要点回顾

  1. Skills 不是 Prompt,是结构化的、可版本控制的知识模块
  2. CLAUDE.md 是编排层,定义何时加载哪些 Skills
  3. 条件加载是核心机制,避免 Token 浪费
  4. 代码即文档,Skill 中的代码示例比文字描述更有价值
  5. 技能协同产生复利,1 + 1 > 2

未来展望

短期(2026年下半年)

  • Claude Code 官方 Skill 市场上线
  • Skill 描述语言(SDL)成为行业标准
  • 主流 IDE 插件支持 Skills 导入

中期(2027年)

  • Skills 自动生成:从代码库自动提取「项目特定 Skills」
  • 跨项目 Skill 迁移:将一个项目的 Skills 适配到另一个项目
  • Skill 质量评估:自动检测低质量 Skills(过时、冲突、冗余)

长期愿景

  • Skill 作为一种服务(Skill as a Service):团队内部 Skill 仓库,新人入职自动加载团队 Skills
  • AI Agent 技能图谱:类似依赖树,可视化 AI Agent 的知识结构
  • Skills 驱动的代码生成:不是「生成代码」,而是「生成符合 Skills 约束的代码」

参考资源


如果你正在构建 AI 编码 Agent 的技能系统,欢迎在评论区分享你的经验和 Skill 文件——让我们一起推动这个领域向前发展。

最后,记住 Karpathy 的话:

"The goal is not to make the AI smarter. The goal is to give it the same context that a senior developer would have on their first day."

复制全文 生成海报 Claude Code Skills AI编码 Rust Axum GitHub Trending

推荐文章

robots.txt 的写法及用法
2024-11-19 01:44:21 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
JavaScript中设置器和获取器
2024-11-17 19:54:27 +0800 CST
如何在 Vue 3 中使用 TypeScript?
2024-11-18 22:30:18 +0800 CST
Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
使用 sync.Pool 优化 Go 程序性能
2024-11-19 05:56:51 +0800 CST
程序员茄子在线接单