Claude Code | Skills 最佳配置案例(中文)
本文主要记录 Claude Code Skills 模块的最佳实践,来自 Anthropic 黑客马拉松获胜者的完整 Claude Code 配置集合。
·
引言
本文来自 Anthropic 黑客马拉松获胜者的完整 Claude Code 配置集合,该仓库包含:生产级代理、技能、钩子、命令、规则和 MCP 配置,经过 10 多个月构建真实产品的密集日常使用而演化,本文主要记录 Skills 模块。

本文不讲废话,直接上示例,大家可以直接根据目录导航来根据需要查看实际的生产配置,相关概念目录如下:
- 后端模式(backend-patterns)
- ClickHouse IO(clickhouse-io)
- 编码标准(coding-standards)
- 持续学习(continuous-learning)
- 持续学习v2(continuous-learning-v2)
- 评估框架(eval-harness)
- 前端模式(frontend-patterns)
- Golang模式(golang-patterns)
- Golang测试(golang-testing)
- 迭代检索(iterative-retrieval)
- Postgres模式(postgres-patterns)
- 项目指南示例(project-guidelines-example)
- 云基础设施安全(cloud-infrastructure-security)
- 安全性审查技能(SKILL)
- 策略性压缩(strategic-compact)
- TDD工作流(tdd-workflow)
- 验证循环(verification-loop)
后端模式(backend-patterns)
---
name: backend-patterns
description: Backend architecture patterns, API design, database optimization, and server-side best practices for Node.js, Express, and Next.js API routes.
---
# 后端开发模式
用于可扩展伺服器端应用程式的后端架构模式和最佳实务。
## API 设计模式
### RESTful API 结构
```typescript
// ✅ 基于资源的 URL
GET /api/markets # 列出资源
GET /api/markets/:id # 取得单一资源
POST /api/markets # 建立资源
PUT /api/markets/:id # 替换资源
PATCH /api/markets/:id # 更新资源
DELETE /api/markets/:id # 删除资源
// ✅ 用于过滤、排序、分页的查询参数
GET /api/markets?status=active&sort=volume&limit=20&offset=0
```
### Repository 模式
```typescript
// 抽象资料存取逻辑
interface MarketRepository {
findAll(filters?: MarketFilters): Promise
ClickHouse IO(clickhouse-io)
---
name: clickhouse-io
description: ClickHouse database patterns, query optimization, analytics, and data engineering best practices for high-performance analytical workloads.
---
# ClickHouse 分析模式
用于高效能分析和资料工程的 ClickHouse 特定模式。
## 概述
ClickHouse 是一个列式资料库管理系统(DBMS),用于线上分析处理(OLAP)。它针对大型资料集的快速分析查询进行了优化。
**关键特性:**
- 列式储存
- 资料压缩
- 平行查询执行
- 分散式查询
- 即时分析
## 表格设计模式
### MergeTree 引擎(最常见)
```sql
CREATE TABLE markets_analytics (
date Date,
market_id String,
market_name String,
volume UInt64,
trades UInt32,
unique_traders UInt32,
avg_trade_size Float64,
created_at DateTime
) ENGINE = MergeTree()
PARTITION BY toYYYYMM(date)
ORDER BY (date, market_id)
SETTINGS index_granularity = 8192;
```
### ReplacingMergeTree(去重)
```sql
-- 用于可能有重复的资料(例如来自多个来源)
CREATE TABLE user_events (
event_id String,
user_id String,
event_type String,
timestamp DateTime,
properties String
) ENGINE = ReplacingMergeTree()
PARTITION BY toYYYYMM(timestamp)
ORDER BY (user_id, event_id, timestamp)
PRIMARY KEY (user_id, event_id);
```
### AggregatingMergeTree(预聚合)
```sql
-- 用于维护聚合指标
CREATE TABLE market_stats_hourly (
hour DateTime,
market_id String,
total_volume AggregateFunction(sum, UInt64),
total_trades AggregateFunction(count, UInt32),
unique_users AggregateFunction(uniq, String)
) ENGINE = AggregatingMergeTree()
PARTITION BY toYYYYMM(hour)
ORDER BY (hour, market_id);
-- 查询聚合资料
SELECT
hour,
market_id,
sumMerge(total_volume) AS volume,
countMerge(total_trades) AS trades,
uniqMerge(unique_users) AS users
FROM market_stats_hourly
WHERE hour >= toStartOfHour(now() - INTERVAL 24 HOUR)
GROUP BY hour, market_id
ORDER BY hour DESC;
```
## 查询优化模式
### 高效过滤
```sql
-- ✅ 良好:先使用索引栏位
SELECT *
FROM markets_analytics
WHERE date >= '2025-01-01'
AND market_id = 'market-123'
AND volume > 1000
ORDER BY date DESC
LIMIT 100;
-- ❌ 不良:先过滤非索引栏位
SELECT *
FROM markets_analytics
WHERE volume > 1000
AND market_name LIKE '%election%'
AND date >= '2025-01-01';
```
### 聚合
```sql
-- ✅ 良好:使用 ClickHouse 特定聚合函式
SELECT
toStartOfDay(created_at) AS day,
market_id,
sum(volume) AS total_volume,
count() AS total_trades,
uniq(trader_id) AS unique_traders,
avg(trade_size) AS avg_size
FROM trades
WHERE created_at >= today() - INTERVAL 7 DAY
GROUP BY day, market_id
ORDER BY day DESC, total_volume DESC;
-- ✅ 使用 quantile 计算百分位数(比 percentile 更高效)
SELECT
quantile(0.50)(trade_size) AS median,
quantile(0.95)(trade_size) AS p95,
quantile(0.99)(trade_size) AS p99
FROM trades
WHERE created_at >= now() - INTERVAL 1 HOUR;
```
### 视窗函式
```sql
-- 计算累计总和
SELECT
date,
market_id,
volume,
sum(volume) OVER (
PARTITION BY market_id
ORDER BY date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_volume
FROM markets_analytics
WHERE date >= today() - INTERVAL 30 DAY
ORDER BY market_id, date;
```
## 资料插入模式
### 批量插入(推荐)
```typescript
import { ClickHouse } from 'clickhouse'
const clickhouse = new ClickHouse({
url: process.env.CLICKHOUSE_URL,
port: 8123,
basicAuth: {
username: process.env.CLICKHOUSE_USER,
password: process.env.CLICKHOUSE_PASSWORD
}
})
// ✅ 批量插入(高效)
async function bulkInsertTrades(trades: Trade[]) {
const values = trades.map(trade => `(
'${trade.id}',
'${trade.market_id}',
'${trade.user_id}',
${trade.amount},
'${trade.timestamp.toISOString()}'
)`).join(',')
await clickhouse.query(`
INSERT INTO trades (id, market_id, user_id, amount, timestamp)
VALUES ${values}
`).toPromise()
}
// ❌ 个别插入(慢)
async function insertTrade(trade: Trade) {
// 不要在回圈中这样做!
await clickhouse.query(`
INSERT INTO trades VALUES ('${trade.id}', ...)
`).toPromise()
}
```
### 串流插入
```typescript
// 用于持续资料摄取
import { createWriteStream } from 'fs'
import { pipeline } from 'stream/promises'
async function streamInserts() {
const stream = clickhouse.insert('trades').stream()
for await (const batch of dataSource) {
stream.write(batch)
}
await stream.end()
}
```
## 物化视图
### 即时聚合
```sql
-- 建立每小时统计的物化视图
CREATE MATERIALIZED VIEW market_stats_hourly_mv
TO market_stats_hourly
AS SELECT
toStartOfHour(timestamp) AS hour,
market_id,
sumState(amount) AS total_volume,
countState() AS total_trades,
uniqState(user_id) AS unique_users
FROM trades
GROUP BY hour, market_id;
-- 查询物化视图
SELECT
hour,
market_id,
sumMerge(total_volume) AS volume,
countMerge(total_trades) AS trades,
uniqMerge(unique_users) AS users
FROM market_stats_hourly
WHERE hour >= now() - INTERVAL 24 HOUR
GROUP BY hour, market_id;
```
## 效能监控
### 查询效能
```sql
-- 检查慢查询
SELECT
query_id,
user,
query,
query_duration_ms,
read_rows,
read_bytes,
memory_usage
FROM system.query_log
WHERE type = 'QueryFinish'
AND query_duration_ms > 1000
AND event_time >= now() - INTERVAL 1 HOUR
ORDER BY query_duration_ms DESC
LIMIT 10;
```
### 表格统计
```sql
-- 检查表格大小
SELECT
database,
table,
formatReadableSize(sum(bytes)) AS size,
sum(rows) AS rows,
max(modification_time) AS latest_modification
FROM system.parts
WHERE active
GROUP BY database, table
ORDER BY sum(bytes) DESC;
```
## 常见分析查询
### 时间序列分析
```sql
-- 每日活跃使用者
SELECT
toDate(timestamp) AS date,
uniq(user_id) AS daily_active_users
FROM events
WHERE timestamp >= today() - INTERVAL 30 DAY
GROUP BY date
ORDER BY date;
-- 留存分析
SELECT
signup_date,
countIf(days_since_signup = 0) AS day_0,
countIf(days_since_signup = 1) AS day_1,
countIf(days_since_signup = 7) AS day_7,
countIf(days_since_signup = 30) AS day_30
FROM (
SELECT
user_id,
min(toDate(timestamp)) AS signup_date,
toDate(timestamp) AS activity_date,
dateDiff('day', signup_date, activity_date) AS days_since_signup
FROM events
GROUP BY user_id, activity_date
)
GROUP BY signup_date
ORDER BY signup_date DESC;
```
### 漏斗分析
```sql
-- 转换漏斗
SELECT
countIf(step = 'viewed_market') AS viewed,
countIf(step = 'clicked_trade') AS clicked,
countIf(step = 'completed_trade') AS completed,
round(clicked / viewed * 100, 2) AS view_to_click_rate,
round(completed / clicked * 100, 2) AS click_to_completion_rate
FROM (
SELECT
user_id,
session_id,
event_type AS step
FROM events
WHERE event_date = today()
)
GROUP BY session_id;
```
### 世代分析
```sql
-- 按注册月份的使用者世代
SELECT
toStartOfMonth(signup_date) AS cohort,
toStartOfMonth(activity_date) AS month,
dateDiff('month', cohort, month) AS months_since_signup,
count(DISTINCT user_id) AS active_users
FROM (
SELECT
user_id,
min(toDate(timestamp)) OVER (PARTITION BY user_id) AS signup_date,
toDate(timestamp) AS activity_date
FROM events
)
GROUP BY cohort, month, months_since_signup
ORDER BY cohort, months_since_signup;
```
## 资料管线模式
### ETL 模式
```typescript
// 提取、转换、载入
async function etlPipeline() {
// 1. 从来源提取
const rawData = await extractFromPostgres()
// 2. 转换
const transformed = rawData.map(row => ({
date: new Date(row.created_at).toISOString().split('T')[0],
market_id: row.market_slug,
volume: parseFloat(row.total_volume),
trades: parseInt(row.trade_count)
}))
// 3. 载入到 ClickHouse
await bulkInsertToClickHouse(transformed)
}
// 定期执行
setInterval(etlPipeline, 60 * 60 * 1000) // 每小时
```
### 变更资料捕获(CDC)
```typescript
// 监听 PostgreSQL 变更并同步到 ClickHouse
import { Client } from 'pg'
const pgClient = new Client({ connectionString: process.env.DATABASE_URL })
pgClient.query('LISTEN market_updates')
pgClient.on('notification', async (msg) => {
const update = JSON.parse(msg.payload)
await clickhouse.insert('market_updates', [
{
market_id: update.id,
event_type: update.operation, // INSERT, UPDATE, DELETE
timestamp: new Date(),
data: JSON.stringify(update.new_data)
}
])
})
```
## 最佳实务
### 1. 分区策略
- 按时间分区(通常按月或日)
- 避免太多分区(效能影响)
- 分区键使用 DATE 类型
### 2. 排序键
- 最常过滤的栏位放在最前面
- 考虑基数(高基数优先)
- 排序影响压缩
### 3. 资料类型
- 使用最小的适当类型(UInt32 vs UInt64)
- 重复字串使用 LowCardinality
- 分类资料使用 Enum
### 4. 避免
- SELECT *(指定栏位)
- FINAL(改为在查询前合并资料)
- 太多 JOINs(为分析反正规化)
- 小量频繁插入(改用批量)
### 5. 监控
- 追踪查询效能
- 监控磁碟使用
- 检查合并操作
- 审查慢查询日志
**记住**:ClickHouse 擅长分析工作负载。为你的查询模式设计表格,批量插入,并利用物化视图进行即时聚合。
编码标准(coding-standards)
---
name: coding-standards
description: Universal coding standards, best practices, and patterns for TypeScript, JavaScript, React, and Node.js development.
---
# 程式码标准与最佳实务
适用于所有专案的通用程式码标准。
## 程式码品质原则
### 1. 可读性优先
- 程式码被阅读的次数远多于被撰写的次数
- 使用清晰的变数和函式名称
- 优先使用自文件化的程式码而非注解
- 保持一致的格式化
### 2. KISS(保持简单)
- 使用最简单的解决方案
- 避免过度工程
- 不做过早优化
- 易于理解 > 聪明的程式码
### 3. DRY(不重复自己)
- 将共用逻辑提取为函式
- 建立可重用的元件
- 在模组间共享工具函式
- 避免复制贴上程式设计
### 4. YAGNI(你不会需要它)
- 在需要之前不要建置功能
- 避免推测性的通用化
- 只在需要时增加复杂度
- 从简单开始,需要时再重构
## TypeScript/JavaScript 标准
### 变数命名
```typescript
// ✅ 良好:描述性名称
const marketSearchQuery = 'election'
const isUserAuthenticated = true
const totalRevenue = 1000
// ❌ 不良:不清楚的名称
const q = 'election'
const flag = true
const x = 1000
```
### 函式命名
```typescript
// ✅ 良好:动词-名词模式
async function fetchMarketData(marketId: string) { }
function calculateSimilarity(a: number[], b: number[]) { }
function isValidEmail(email: string): boolean { }
// ❌ 不良:不清楚或只有名词
async function market(id: string) { }
function similarity(a, b) { }
function email(e) { }
```
### 不可变性模式(关键)
```typescript
// ✅ 总是使用展开运算符
const updatedUser = {
...user,
name: 'New Name'
}
const updatedArray = [...items, newItem]
// ❌ 永远不要直接修改
user.name = 'New Name' // 不良
items.push(newItem) // 不良
```
### 错误处理
```typescript
// ✅ 良好:完整的错误处理
async function fetchData(url: string) {
try {
const response = await fetch(url)
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return await response.json()
} catch (error) {
console.error('Fetch failed:', error)
throw new Error('Failed to fetch data')
}
}
// ❌ 不良:无错误处理
async function fetchData(url) {
const response = await fetch(url)
return response.json()
}
```
### Async/Await 最佳实务
```typescript
// ✅ 良好:可能时并行执行
const [users, markets, stats] = await Promise.all([
fetchUsers(),
fetchMarkets(),
fetchStats()
])
// ❌ 不良:不必要的顺序执行
const users = await fetchUsers()
const markets = await fetchMarkets()
const stats = await fetchStats()
```
### 型别安全
```typescript
// ✅ 良好:正确的型别
interface Market {
id: string
name: string
status: 'active' | 'resolved' | 'closed'
created_at: Date
}
function getMarket(id: string): Promise {
// 实作
}
// ❌ 不良:使用 'any'
function getMarket(id: any): Promise {
// 实作
}
```
## React 最佳实务
### 元件结构
```typescript
// ✅ 良好:具有型别的函式元件
interface ButtonProps {
children: React.ReactNode
onClick: () => void
disabled?: boolean
variant?: 'primary' | 'secondary'
}
export function Button({
children,
onClick,
disabled = false,
variant = 'primary'
}: ButtonProps) {
return (
持续学习(continuous-learning)
---
name: continuous-learning
description: Automatically extract reusable patterns from Claude Code sessions and save them as learned skills for future use.
---
# 持续学习技能
自动评估 Claude Code 工作阶段结束时的内容,提取可重用模式并储存为学习技能。
## 运作方式
此技能作为 **Stop hook** 在每个工作阶段结束时执行:
1. **工作阶段评估**:检查工作阶段是否有足够讯息(预设:10+ 则)
2. **模式侦测**:从工作阶段识别可提取的模式
3. **技能提取**:将有用模式储存到 `~/.claude/skills/learned/`
## 设定
编辑 `config.json` 以自订:
```json
{
"min_session_length": 10,
"extraction_threshold": "medium",
"auto_approve": false,
"learned_skills_path": "~/.claude/skills/learned/",
"patterns_to_detect": [
"error_resolution",
"user_corrections",
"workarounds",
"debugging_techniques",
"project_specific"
],
"ignore_patterns": [
"simple_typos",
"one_time_fixes",
"external_api_issues"
]
}
```
## 模式类型
| 模式 | 描述 |
|------|------|
| `error_resolution` | 特定错误如何被解决 |
| `user_corrections` | 来自使用者修正的模式 |
| `workarounds` | 框架/函式库怪异问题的解决方案 |
| `debugging_techniques` | 有效的除错方法 |
| `project_specific` | 专案特定惯例 |
## Hook 设定
新增到你的 `~/.claude/settings.json`:
```json
{
"hooks": {
"Stop": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning/evaluate-session.sh"
}]
}]
}
}
```
## 为什么用 Stop Hook?
- **轻量**:工作阶段结束时只执行一次
- **非阻塞**:不会为每则讯息增加延迟
- **完整上下文**:可存取完整工作阶段记录
## 相关
- [Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - 持续学习章节
- `/learn` 指令 - 工作阶段中手动提取模式
---
## 比较笔记(研究:2025 年 1 月)
### vs Homunculus (github.com/humanplane/homunculus)
Homunculus v2 采用更复杂的方法:
| 功能 | 我们的方法 | Homunculus v2 |
|------|----------|---------------|
| 观察 | Stop hook(工作阶段结束) | PreToolUse/PostToolUse hooks(100% 可靠) |
| 分析 | 主要上下文 | 背景 agent(Haiku) |
| 粒度 | 完整技能 | 原子「本能」 |
| 信心 | 无 | 0.3-0.9 加权 |
| 演化 | 直接到技能 | 本能 → 聚类 → 技能/指令/agent |
| 分享 | 无 | 汇出/汇入本能 |
**来自 homunculus 的关键见解:**
> "v1 依赖技能进行观察。技能是机率性的——它们触发约 50-80% 的时间。v2 使用 hooks 进行观察(100% 可靠),并以本能作为学习行为的原子单位。"
### 潜在 v2 增强
1. **基于本能的学习** - 较小的原子行为,带信心评分
2. **背景观察者** - Haiku agent 并行分析
3. **信心衰减** - 如果被矛盾则本能失去信心
4. **领域标记** - code-style、testing、git、debugging 等
5. **演化路径** - 将相关本能聚类为技能/指令
参见:`/Users/affoon/Documents/tasks/12-continuous-learning-v2.md` 完整规格。
持续学习v2(continuous-learning-v2)
---
name: continuous-learning-v2
description: Instinct-based learning system that observes sessions via hooks, creates atomic instincts with confidence scoring, and evolves them into skills/commands/agents.
version: 2.0.0
---
# 持续学习 v2 - 基于本能的架构
进阶学习系统,透过原子「本能」(带信心评分的小型学习行为)将你的 Claude Code 工作阶段转化为可重用知识。
## v2 的新功能
| 功能 | v1 | v2 |
|------|----|----|
| 观察 | Stop hook(工作阶段结束) | PreToolUse/PostToolUse(100% 可靠) |
| 分析 | 主要上下文 | 背景 agent(Haiku) |
| 粒度 | 完整技能 | 原子「本能」 |
| 信心 | 无 | 0.3-0.9 加权 |
| 演化 | 直接到技能 | 本能 → 聚类 → 技能/指令/agent |
| 分享 | 无 | 汇出/汇入本能 |
## 本能模型
本能是一个小型学习行为:
```yaml
---
id: prefer-functional-style
trigger: "when writing new functions"
confidence: 0.7
domain: "code-style"
source: "session-observation"
---
# 偏好函式风格
## 动作
适当时使用函式模式而非类别。
## 证据
- 观察到 5 次函式模式偏好
- 使用者在 2025-01-15 将基于类别的方法修正为函式
```
**属性:**
- **原子性** — 一个触发器,一个动作
- **信心加权** — 0.3 = 试探性,0.9 = 近乎确定
- **领域标记** — code-style、testing、git、debugging、workflow 等
- **证据支持** — 追踪建立它的观察
## 运作方式
```
工作阶段活动
│
│ Hooks 捕获提示 + 工具使用(100% 可靠)
▼
┌─────────────────────────────────────────┐
│ observations.jsonl │
│ (提示、工具呼叫、结果) │
└─────────────────────────────────────────┘
│
│ Observer agent 读取(背景、Haiku)
▼
┌─────────────────────────────────────────┐
│ 模式侦测 │
│ • 使用者修正 → 本能 │
│ • 错误解决 → 本能 │
│ • 重复工作流程 → 本能 │
└─────────────────────────────────────────┘
│
│ 建立/更新
▼
┌─────────────────────────────────────────┐
│ instincts/personal/ │
│ • prefer-functional.md (0.7) │
│ • always-test-first.md (0.9) │
│ • use-zod-validation.md (0.6) │
└─────────────────────────────────────────┘
│
│ /evolve 聚类
▼
┌─────────────────────────────────────────┐
│ evolved/ │
│ • commands/new-feature.md │
│ • skills/testing-workflow.md │
│ • agents/refactor-specialist.md │
└─────────────────────────────────────────┘
```
## 快速开始
### 1. 启用观察 Hooks
新增到你的 `~/.claude/settings.json`:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh pre"
}]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/continuous-learning-v2/hooks/observe.sh post"
}]
}]
}
}
```
### 2. 初始化目录结构
```bash
mkdir -p ~/.claude/homunculus/{instincts/{personal,inherited},evolved/{agents,skills,commands}}
touch ~/.claude/homunculus/observations.jsonl
```
### 3. 执行 Observer Agent(可选)
观察者可以在背景执行并分析观察:
```bash
# 启动背景观察者
~/.claude/skills/continuous-learning-v2/agents/start-observer.sh
```
## 指令
| 指令 | 描述 |
|------|------|
| `/instinct-status` | 显示所有学习本能及其信心 |
| `/evolve` | 将相关本能聚类为技能/指令 |
| `/instinct-export` | 汇出本能以分享 |
| `/instinct-import ` | 从他人汇入本能 |
## 设定
编辑 `config.json`:
```json
{
"version": "2.0",
"observation": {
"enabled": true,
"store_path": "~/.claude/homunculus/observations.jsonl",
"max_file_size_mb": 10,
"archive_after_days": 7
},
"instincts": {
"personal_path": "~/.claude/homunculus/instincts/personal/",
"inherited_path": "~/.claude/homunculus/instincts/inherited/",
"min_confidence": 0.3,
"auto_approve_threshold": 0.7,
"confidence_decay_rate": 0.05
},
"observer": {
"enabled": true,
"model": "haiku",
"run_interval_minutes": 5,
"patterns_to_detect": [
"user_corrections",
"error_resolutions",
"repeated_workflows",
"tool_preferences"
]
},
"evolution": {
"cluster_threshold": 3,
"evolved_path": "~/.claude/homunculus/evolved/"
}
}
```
## 档案结构
```
~/.claude/homunculus/
├── identity.json # 你的个人资料、技术水平
├── observations.jsonl # 当前工作阶段观察
├── observations.archive/ # 已处理观察
├── instincts/
│ ├── personal/ # 自动学习本能
│ └── inherited/ # 从他人汇入
└── evolved/
├── agents/ # 产生的专业 agents
├── skills/ # 产生的技能
└── commands/ # 产生的指令
```
## 与 Skill Creator 整合
当你使用 [Skill Creator GitHub App](https://skill-creator.app) 时,它现在产生**两者**:
- 传统 SKILL.md 档案(用于向后相容)
- 本能集合(用于 v2 学习系统)
从仓库分析的本能有 `source: "repo-analysis"` 并包含来源仓库 URL。
## 信心评分
信心随时间演化:
| 分数 | 意义 | 行为 |
|------|------|------|
| 0.3 | 试探性 | 建议但不强制 |
| 0.5 | 中等 | 相关时应用 |
| 0.7 | 强烈 | 自动批准应用 |
| 0.9 | 近乎确定 | 核心行为 |
**信心增加**当:
- 重复观察到模式
- 使用者不修正建议行为
- 来自其他来源的类似本能同意
**信心减少**当:
- 使用者明确修正行为
- 长期未观察到模式
- 出现矛盾证据
## 为何 Hooks vs Skills 用于观察?
> "v1 依赖技能进行观察。技能是机率性的——它们根据 Claude 的判断触发约 50-80% 的时间。"
Hooks **100% 的时间**确定性地触发。这意味着:
- 每个工具呼叫都被观察
- 无模式被遗漏
- 学习是全面的
## 向后相容性
v2 完全相容 v1:
- 现有 `~/.claude/skills/learned/` 技能仍可运作
- Stop hook 仍执行(但现在也喂入 v2)
- 渐进迁移路径:两者并行执行
## 隐私
- 观察保持在你的机器**本机**
- 只有**本能**(模式)可被汇出
- 不会分享实际程式码或对话内容
- 你控制汇出内容
## 相关
- [Skill Creator](https://skill-creator.app) - 从仓库历史产生本能
- [Homunculus](https://github.com/humanplane/homunculus) - v2 架构灵感
- [Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - 持续学习章节
---
*基于本能的学习:一次一个观察,教导 Claude 你的模式。*
评估框架(eval-harness)
---
name: eval-harness
description: Formal evaluation framework for Claude Code sessions implementing eval-driven development (EDD) principles
tools: Read, Write, Edit, Bash, Grep, Glob
---
# Eval Harness 技能
Claude Code 工作阶段的正式评估框架,实作 eval 驱动开发(EDD)原则。
## 理念
Eval 驱动开发将 evals 视为「AI 开发的单元测试」:
- 在实作前定义预期行为
- 开发期间持续执行 evals
- 每次变更追踪回归
- 使用 pass@k 指标进行可靠性测量
## Eval 类型
### 能力 Evals
测试 Claude 是否能做到以前做不到的事:
```markdown
[CAPABILITY EVAL: feature-name]
任务:Claude 应完成什么的描述
成功标准:
- [ ] 标准 1
- [ ] 标准 2
- [ ] 标准 3
预期输出:预期结果描述
```
### 回归 Evals
确保变更不会破坏现有功能:
```markdown
[REGRESSION EVAL: feature-name]
基准:SHA 或检查点名称
测试:
- existing-test-1: PASS/FAIL
- existing-test-2: PASS/FAIL
- existing-test-3: PASS/FAIL
结果:X/Y 通过(先前为 Y/Y)
```
## 评分器类型
### 1. 基于程式码的评分器
使用程式码的确定性检查:
```bash
# 检查档案是否包含预期模式
grep -q "export function handleAuth" src/auth.ts && echo "PASS" || echo "FAIL"
# 检查测试是否通过
npm test -- --testPathPattern="auth" && echo "PASS" || echo "FAIL"
# 检查建置是否成功
npm run build && echo "PASS" || echo "FAIL"
```
### 2. 基于模型的评分器
使用 Claude 评估开放式输出:
```markdown
[MODEL GRADER PROMPT]
评估以下程式码变更:
1. 它是否解决了陈述的问题?
2. 结构是否良好?
3. 边界案例是否被处理?
4. 错误处理是否适当?
分数:1-5(1=差,5=优秀)
理由:[解释]
```
### 3. 人工评分器
标记为手动审查:
```markdown
[HUMAN REVIEW REQUIRED]
变更:变更内容的描述
理由:为何需要人工审查
风险等级:LOW/MEDIUM/HIGH
```
## 指标
### pass@k
「k 次尝试中至少一次成功」
- pass@1:第一次尝试成功率
- pass@3:3 次尝试内成功
- 典型目标:pass@3 > 90%
### pass^k
「所有 k 次试验都成功」
- 更高的可靠性标准
- pass^3:连续 3 次成功
- 用于关键路径
## Eval 工作流程
### 1. 定义(编码前)
```markdown
## EVAL 定义:feature-xyz
### 能力 Evals
1. 可以建立新使用者帐户
2. 可以验证电子邮件格式
3. 可以安全地杂凑密码
### 回归 Evals
1. 现有登入仍可运作
2. 工作阶段管理未变更
3. 登出流程完整
### 成功指标
- 能力 evals 的 pass@3 > 90%
- 回归 evals 的 pass^3 = 100%
```
### 2. 实作
撰写程式码以通过定义的 evals。
### 3. 评估
```bash
# 执行能力 evals
[执行每个能力 eval,记录 PASS/FAIL]
# 执行回归 evals
npm test -- --testPathPattern="existing"
# 产生报告
```
### 4. 报告
```markdown
EVAL 报告:feature-xyz
========================
能力 Evals:
create-user: PASS (pass@1)
validate-email: PASS (pass@2)
hash-password: PASS (pass@1)
整体: 3/3 通过
回归 Evals:
login-flow: PASS
session-mgmt: PASS
logout-flow: PASS
整体: 3/3 通过
指标:
pass@1: 67% (2/3)
pass@3: 100% (3/3)
状态:准备审查
```
## 整合模式
### 实作前
```
/eval define feature-name
```
在 `.claude/evals/feature-name.md` 建立 eval 定义档案
### 实作期间
```
/eval check feature-name
```
执行当前 evals 并报告状态
### 实作后
```
/eval report feature-name
```
产生完整 eval 报告
## Eval 储存
在专案中储存 evals:
```
.claude/
evals/
feature-xyz.md # Eval 定义
feature-xyz.log # Eval 执行历史
baseline.json # 回归基准
```
## 最佳实务
1. **编码前定义 evals** - 强制清楚思考成功标准
2. **频繁执行 evals** - 及早捕捉回归
3. **随时间追踪 pass@k** - 监控可靠性趋势
4. **可能时使用程式码评分器** - 确定性 > 机率性
5. **安全性需人工审查** - 永远不要完全自动化安全检查
6. **保持 evals 快速** - 慢 evals 不会被执行
7. **与程式码一起版本化 evals** - Evals 是一等工件
## 范例:新增认证
```markdown
## EVAL:add-authentication
### 阶段 1:定义(10 分钟)
能力 Evals:
- [ ] 使用者可以用电子邮件/密码注册
- [ ] 使用者可以用有效凭证登入
- [ ] 无效凭证被拒绝并显示适当错误
- [ ] 工作阶段在页面重新载入后持续
- [ ] 登出清除工作阶段
回归 Evals:
- [ ] 公开路由仍可存取
- [ ] API 回应未变更
- [ ] 资料库 schema 相容
### 阶段 2:实作(视情况而定)
[撰写程式码]
### 阶段 3:评估
执行:/eval check add-authentication
### 阶段 4:报告
EVAL 报告:add-authentication
==============================
能力:5/5 通过(pass@3:100%)
回归:3/3 通过(pass^3:100%)
状态:准备发布
```
前端模式(frontend-patterns)
---
name: frontend-patterns
description: Frontend development patterns for React, Next.js, state management, performance optimization, and UI best practices.
---
# 前端开发模式
用于 React、Next.js 和高效能使用者介面的现代前端模式。
## 元件模式
### 组合优于继承
```typescript
// ✅ 良好:元件组合
interface CardProps {
children: React.ReactNode
variant?: 'default' | 'outlined'
}
export function Card({ children, variant = 'default' }: CardProps) {
return
Golang模式(golang-patterns)
---
name: golang-patterns
description: Idiomatic Go patterns, best practices, and conventions for building robust, efficient, and maintainable Go applications.
---
# Go 开发模式
用于建构稳健、高效且可维护应用程式的惯用 Go 模式和最佳实务。
## 何时启用
- 撰写新的 Go 程式码
- 审查 Go 程式码
- 重构现有 Go 程式码
- 设计 Go 套件/模组
## 核心原则
### 1. 简单与清晰
Go 偏好简单而非聪明。程式码应该明显且易读。
```go
// 良好:清晰直接
func GetUser(id string) (*User, error) {
user, err := db.FindUser(id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
// 不良:过于聪明
func GetUser(id string) (*User, error) {
return func() (*User, error) {
if u, e := db.FindUser(id); e == nil {
return u, nil
} else {
return nil, e
}
}()
}
```
### 2. 让零值有用
设计类型使其零值无需初始化即可立即使用。
```go
// 良好:零值有用
type Counter struct {
mu sync.Mutex
count int // 零值为 0,可直接使用
}
func (c *Counter) Inc() {
c.mu.Lock()
c.count++
c.mu.Unlock()
}
// 良好:bytes.Buffer 零值可用
var buf bytes.Buffer
buf.WriteString("hello")
// 不良:需要初始化
type BadCounter struct {
counts map[string]int // nil map 会 panic
}
```
### 3. 接受介面,回传结构
函式应接受介面参数并回传具体类型。
```go
// 良好:接受介面,回传具体类型
func ProcessData(r io.Reader) (*Result, error) {
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
return &Result{Data: data}, nil
}
// 不良:回传介面(不必要地隐藏实作细节)
func ProcessData(r io.Reader) (io.Reader, error) {
// ...
}
```
## 错误处理模式
### 带上下文的错误包装
```go
// 良好:包装错误并加上上下文
func LoadConfig(path string) (*Config, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("load config %s: %w", path, err)
}
var cfg Config
if err := json.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
return &cfg, nil
}
```
### 自订错误类型
```go
// 定义领域特定错误
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string {
return fmt.Sprintf("validation failed on %s: %s", e.Field, e.Message)
}
// 常见情况的哨兵错误
var (
ErrNotFound = errors.New("resource not found")
ErrUnauthorized = errors.New("unauthorized")
ErrInvalidInput = errors.New("invalid input")
)
```
### 使用 errors.Is 和 errors.As 检查错误
```go
func HandleError(err error) {
// 检查特定错误
if errors.Is(err, sql.ErrNoRows) {
log.Println("No records found")
return
}
// 检查错误类型
var validationErr *ValidationError
if errors.As(err, &validationErr) {
log.Printf("Validation error on field %s: %s",
validationErr.Field, validationErr.Message)
return
}
// 未知错误
log.Printf("Unexpected error: %v", err)
}
```
### 绝不忽略错误
```go
// 不良:用空白识别符忽略错误
result, _ := doSomething()
// 良好:处理或明确说明为何安全忽略
result, err := doSomething()
if err != nil {
return err
}
// 可接受:当错误真的不重要时(罕见)
_ = writer.Close() // 尽力清理,错误在其他地方记录
```
## 并行模式
### Worker Pool
```go
func WorkerPool(jobs <-chan Job, results chan<- Result, numWorkers int) {
var wg sync.WaitGroup
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for job := range jobs {
results <- process(job)
}
}()
}
wg.Wait()
close(results)
}
```
### 取消和逾时的 Context
```go
func FetchWithTimeout(ctx context.Context, url string) ([]byte, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
```
### 优雅关闭
```go
func GracefulShutdown(server *http.Server) {
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}
```
### 协调 Goroutines 的 errgroup
```go
import "golang.org/x/sync/errgroup"
func FetchAll(ctx context.Context, urls []string) ([][]byte, error) {
g, ctx := errgroup.WithContext(ctx)
results := make([][]byte, len(urls))
for i, url := range urls {
i, url := i, url // 捕获回圈变数
g.Go(func() error {
data, err := FetchWithTimeout(ctx, url)
if err != nil {
return err
}
results[i] = data
return nil
})
}
if err := g.Wait(); err != nil {
return nil, err
}
return results, nil
}
```
### 避免 Goroutine 泄漏
```go
// 不良:如果 context 被取消会泄漏 goroutine
func leakyFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte)
go func() {
data, _ := fetch(url)
ch <- data // 如果无接收者会永远阻塞
}()
return ch
}
// 良好:正确处理取消
func safeFetch(ctx context.Context, url string) <-chan []byte {
ch := make(chan []byte, 1) // 带缓冲的 channel
go func() {
data, err := fetch(url)
if err != nil {
return
}
select {
case ch <- data:
case <-ctx.Done():
}
}()
return ch
}
```
## 介面设计
### 小而专注的介面
```go
// 良好:单一方法介面
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
type Closer interface {
Close() error
}
// 依需要组合介面
type ReadWriteCloser interface {
Reader
Writer
Closer
}
```
### 在使用处定义介面
```go
// 在消费者套件中,而非提供者
package service
// UserStore 定义此服务需要的内容
type UserStore interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
type Service struct {
store UserStore
}
// 具体实作可以在另一个套件
// 它不需要知道这个介面
```
### 使用型别断言的可选行为
```go
type Flusher interface {
Flush() error
}
func WriteAndFlush(w io.Writer, data []byte) error {
if _, err := w.Write(data); err != nil {
return err
}
// 如果支援则 Flush
if f, ok := w.(Flusher); ok {
return f.Flush()
}
return nil
}
```
## 套件组织
### 标准专案结构
```text
myproject/
├── cmd/
│ └── myapp/
│ └── main.go # 进入点
├── internal/
│ ├── handler/ # HTTP handlers
│ ├── service/ # 业务逻辑
│ ├── repository/ # 资料存取
│ └── config/ # 设定
├── pkg/
│ └── client/ # 公开 API 客户端
├── api/
│ └── v1/ # API 定义(proto、OpenAPI)
├── testdata/ # 测试 fixtures
├── go.mod
├── go.sum
└── Makefile
```
### 套件命名
```go
// 良好:简短、小写、无底线
package http
package json
package user
// 不良:冗长、混合大小写或冗余
package httpHandler
package json_parser
package userService // 冗余的 'Service' 后缀
```
### 避免套件层级状态
```go
// 不良:全域可变状态
var db *sql.DB
func init() {
db, _ = sql.Open("postgres", os.Getenv("DATABASE_URL"))
}
// 良好:依赖注入
type Server struct {
db *sql.DB
}
func NewServer(db *sql.DB) *Server {
return &Server{db: db}
}
```
## 结构设计
### Functional Options 模式
```go
type Server struct {
addr string
timeout time.Duration
logger *log.Logger
}
type Option func(*Server)
func WithTimeout(d time.Duration) Option {
return func(s *Server) {
s.timeout = d
}
}
func WithLogger(l *log.Logger) Option {
return func(s *Server) {
s.logger = l
}
}
func NewServer(addr string, opts ...Option) *Server {
s := &Server{
addr: addr,
timeout: 30 * time.Second, // 预设值
logger: log.Default(), // 预设值
}
for _, opt := range opts {
opt(s)
}
return s
}
// 使用方式
server := NewServer(":8080",
WithTimeout(60*time.Second),
WithLogger(customLogger),
)
```
### 嵌入用于组合
```go
type Logger struct {
prefix string
}
func (l *Logger) Log(msg string) {
fmt.Printf("[%s] %s\n", l.prefix, msg)
}
type Server struct {
*Logger // 嵌入 - Server 获得 Log 方法
addr string
}
func NewServer(addr string) *Server {
return &Server{
Logger: &Logger{prefix: "SERVER"},
addr: addr,
}
}
// 使用方式
s := NewServer(":8080")
s.Log("Starting...") // 呼叫嵌入的 Logger.Log
```
## 记忆体与效能
### 已知大小时预分配 Slice
```go
// 不良:多次扩展 slice
func processItems(items []Item) []Result {
var results []Result
for _, item := range items {
results = append(results, process(item))
}
return results
}
// 良好:单次分配
func processItems(items []Item) []Result {
results := make([]Result, 0, len(items))
for _, item := range items {
results = append(results, process(item))
}
return results
}
```
### 频繁分配使用 sync.Pool
```go
var bufferPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func ProcessRequest(data []byte) []byte {
buf := bufferPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufferPool.Put(buf)
}()
buf.Write(data)
// 处理...
return buf.Bytes()
}
```
### 避免回圈中的字串串接
```go
// 不良:产生多次字串分配
func join(parts []string) string {
var result string
for _, p := range parts {
result += p + ","
}
return result
}
// 良好:使用 strings.Builder 单次分配
func join(parts []string) string {
var sb strings.Builder
for i, p := range parts {
if i > 0 {
sb.WriteString(",")
}
sb.WriteString(p)
}
return sb.String()
}
// 最佳:使用标准函式库
func join(parts []string) string {
return strings.Join(parts, ",")
}
```
## Go 工具整合
### 基本指令
```bash
# 建置和执行
go build ./...
go run ./cmd/myapp
# 测试
go test ./...
go test -race ./...
go test -cover ./...
# 静态分析
go vet ./...
staticcheck ./...
golangci-lint run
# 模组管理
go mod tidy
go mod verify
# 格式化
gofmt -w .
goimports -w .
```
### 建议的 Linter 设定(.golangci.yml)
```yaml
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- gofmt
- goimports
- misspell
- unconvert
- unparam
linters-settings:
errcheck:
check-type-assertions: true
govet:
check-shadowing: true
issues:
exclude-use-default: false
```
## 快速参考:Go 惯用语
| 惯用语 | 描述 |
|-------|------|
| 接受介面,回传结构 | 函式接受介面参数,回传具体类型 |
| 错误是值 | 将错误视为一等值,而非例外 |
| 不要透过共享记忆体通讯 | 使用 channel 在 goroutine 间协调 |
| 让零值有用 | 类型应无需明确初始化即可工作 |
| 一点复制比一点依赖好 | 避免不必要的外部依赖 |
| 清晰优于聪明 | 优先考虑可读性而非聪明 |
| gofmt 不是任何人的最爱但是所有人的朋友 | 总是用 gofmt/goimports 格式化 |
| 提早返回 | 先处理错误,保持快乐路径不缩排 |
## 要避免的反模式
```go
// 不良:长函式中的裸返回
func process() (result int, err error) {
// ... 50 行 ...
return // 返回什么?
}
// 不良:使用 panic 作为控制流程
func GetUser(id string) *User {
user, err := db.Find(id)
if err != nil {
panic(err) // 不要这样做
}
return user
}
// 不良:在结构中传递 context
type Request struct {
ctx context.Context // Context 应该是第一个参数
ID string
}
// 良好:Context 作为第一个参数
func ProcessRequest(ctx context.Context, id string) error {
// ...
}
// 不良:混合值和指标接收器
type Counter struct{ n int }
func (c Counter) Value() int { return c.n } // 值接收器
func (c *Counter) Increment() { c.n++ } // 指标接收器
// 选择一种风格并保持一致
```
**记住**:Go 程式码应该以最好的方式无聊 - 可预测、一致且易于理解。有疑虑时,保持简单。
Golang测试(golang-testing)
---
name: golang-testing
description: Go testing patterns including table-driven tests, subtests, benchmarks, fuzzing, and test coverage. Follows TDD methodology with idiomatic Go practices.
---
# Go 测试模式
用于撰写可靠、可维护测试的完整 Go 测试模式,遵循 TDD 方法论。
## 何时启用
- 撰写新的 Go 函式或方法
- 为现有程式码增加测试覆盖率
- 为效能关键程式码建立基准测试
- 实作输入验证的模糊测试
- 在 Go 专案中遵循 TDD 工作流程
## Go 的 TDD 工作流程
### RED-GREEN-REFACTOR 循环
```
RED → 先写失败的测试
GREEN → 撰写最少程式码使测试通过
REFACTOR → 在保持测试绿色的同时改善程式码
REPEAT → 继续下一个需求
```
### Go 中的逐步 TDD
```go
// 步骤 1:定义介面/签章
// calculator.go
package calculator
func Add(a, b int) int {
panic("not implemented") // 占位符
}
// 步骤 2:撰写失败测试(RED)
// calculator_test.go
package calculator
import "testing"
func TestAdd(t *testing.T) {
got := Add(2, 3)
want := 5
if got != want {
t.Errorf("Add(2, 3) = %d; want %d", got, want)
}
}
// 步骤 3:执行测试 - 验证失败
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: not implemented
// 步骤 4:实作最少程式码(GREEN)
func Add(a, b int) int {
return a + b
}
// 步骤 5:执行测试 - 验证通过
// $ go test
// PASS
// 步骤 6:如需要则重构,验证测试仍然通过
```
## 表格驱动测试
Go 测试的标准模式。以最少程式码达到完整覆盖。
```go
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"positive numbers", 2, 3, 5},
{"negative numbers", -1, -2, -3},
{"zero values", 0, 0, 0},
{"mixed signs", -1, 1, 0},
{"large numbers", 1000000, 2000000, 3000000},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d; want %d",
tt.a, tt.b, got, tt.expected)
}
})
}
}
```
### 带错误案例的表格驱动测试
```go
func TestParseConfig(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{
name: "valid config",
input: `{"host": "localhost", "port": 8080}`,
want: &Config{Host: "localhost", Port: 8080},
},
{
name: "invalid JSON",
input: `{invalid}`,
wantErr: true,
},
{
name: "empty input",
input: "",
wantErr: true,
},
{
name: "minimal config",
input: `{}`,
want: &Config{}, // 零值 config
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("got %+v; want %+v", got, tt.want)
}
})
}
}
```
## 子测试
### 组织相关测试
```go
func TestUser(t *testing.T) {
// 所有子测试共享的设置
db := setupTestDB(t)
t.Run("Create", func(t *testing.T) {
user := &User{Name: "Alice"}
err := db.CreateUser(user)
if err != nil {
t.Fatalf("CreateUser failed: %v", err)
}
if user.ID == "" {
t.Error("expected user ID to be set")
}
})
t.Run("Get", func(t *testing.T) {
user, err := db.GetUser("alice-id")
if err != nil {
t.Fatalf("GetUser failed: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
})
t.Run("Update", func(t *testing.T) {
// ...
})
t.Run("Delete", func(t *testing.T) {
// ...
})
}
```
### 并行子测试
```go
func TestParallel(t *testing.T) {
tests := []struct {
name string
input string
}{
{"case1", "input1"},
{"case2", "input2"},
{"case3", "input3"},
}
for _, tt := range tests {
tt := tt // 捕获范围变数
t.Run(tt.name, func(t *testing.T) {
t.Parallel() // 并行执行子测试
result := Process(tt.input)
// 断言...
_ = result
})
}
}
```
## 测试辅助函式
### 辅助函式
```go
func setupTestDB(t *testing.T) *sql.DB {
t.Helper() // 标记为辅助函式
db, err := sql.Open("sqlite3", ":memory:")
if err != nil {
t.Fatalf("failed to open database: %v", err)
}
// 测试结束时清理
t.Cleanup(func() {
db.Close()
})
// 执行 migrations
if _, err := db.Exec(schema); err != nil {
t.Fatalf("failed to create schema: %v", err)
}
return db
}
func assertNoError(t *testing.T, err error) {
t.Helper()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func assertEqual[T comparable](t *testing.T, got, want T) {
t.Helper()
if got != want {
t.Errorf("got %v; want %v", got, want)
}
}
```
### 临时档案和目录
```go
func TestFileProcessing(t *testing.T) {
// 建立临时目录 - 自动清理
tmpDir := t.TempDir()
// 建立测试档案
testFile := filepath.Join(tmpDir, "test.txt")
err := os.WriteFile(testFile, []byte("test content"), 0644)
if err != nil {
t.Fatalf("failed to create test file: %v", err)
}
// 执行测试
result, err := ProcessFile(testFile)
if err != nil {
t.Fatalf("ProcessFile failed: %v", err)
}
// 断言...
_ = result
}
```
## Golden 档案
使用储存在 `testdata/` 中的预期输出档案进行测试。
```go
var update = flag.Bool("update", false, "update golden files")
func TestRender(t *testing.T) {
tests := []struct {
name string
input Template
}{
{"simple", Template{Name: "test"}},
{"complex", Template{Name: "test", Items: []string{"a", "b"}}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Render(tt.input)
golden := filepath.Join("testdata", tt.name+".golden")
if *update {
// 更新 golden 档案:go test -update
err := os.WriteFile(golden, got, 0644)
if err != nil {
t.Fatalf("failed to update golden file: %v", err)
}
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("failed to read golden file: %v", err)
}
if !bytes.Equal(got, want) {
t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
}
})
}
}
```
## 使用介面 Mock
### 基于介面的 Mock
```go
// 定义依赖的介面
type UserRepository interface {
GetUser(id string) (*User, error)
SaveUser(user *User) error
}
// 生产实作
type PostgresUserRepository struct {
db *sql.DB
}
func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
// 实际资料库查询
}
// 测试用 Mock 实作
type MockUserRepository struct {
GetUserFunc func(id string) (*User, error)
SaveUserFunc func(user *User) error
}
func (m *MockUserRepository) GetUser(id string) (*User, error) {
return m.GetUserFunc(id)
}
func (m *MockUserRepository) SaveUser(user *User) error {
return m.SaveUserFunc(user)
}
// 使用 mock 的测试
func TestUserService(t *testing.T) {
mock := &MockUserRepository{
GetUserFunc: func(id string) (*User, error) {
if id == "123" {
return &User{ID: "123", Name: "Alice"}, nil
}
return nil, ErrNotFound
},
}
service := NewUserService(mock)
user, err := service.GetUserProfile("123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if user.Name != "Alice" {
t.Errorf("got name %q; want %q", user.Name, "Alice")
}
}
```
## 基准测试
### 基本基准测试
```go
func BenchmarkProcess(b *testing.B) {
data := generateTestData(1000)
b.ResetTimer() // 不计算设置时间
for i := 0; i < b.N; i++ {
Process(data)
}
}
// 执行:go test -bench=BenchmarkProcess -benchmem
// 输出:BenchmarkProcess-8 10000 105234 ns/op 4096 B/op 10 allocs/op
```
### 不同大小的基准测试
```go
func BenchmarkSort(b *testing.B) {
sizes := []int{100, 1000, 10000, 100000}
for _, size := range sizes {
b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
data := generateRandomSlice(size)
b.ResetTimer()
for i := 0; i < b.N; i++ {
// 复制以避免排序已排序的资料
tmp := make([]int, len(data))
copy(tmp, data)
sort.Ints(tmp)
}
})
}
}
```
### 记忆体分配基准测试
```go
func BenchmarkStringConcat(b *testing.B) {
parts := []string{"hello", "world", "foo", "bar", "baz"}
b.Run("plus", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var s string
for _, p := range parts {
s += p
}
_ = s
}
})
b.Run("builder", func(b *testing.B) {
for i := 0; i < b.N; i++ {
var sb strings.Builder
for _, p := range parts {
sb.WriteString(p)
}
_ = sb.String()
}
})
b.Run("join", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = strings.Join(parts, "")
}
})
}
```
## 模糊测试(Go 1.18+)
### 基本模糊测试
```go
func FuzzParseJSON(f *testing.F) {
// 新增种子语料库
f.Add(`{"name": "test"}`)
f.Add(`{"count": 123}`)
f.Add(`[]`)
f.Add(`""`)
f.Fuzz(func(t *testing.T, input string) {
var result map[string]interface{}
err := json.Unmarshal([]byte(input), &result)
if err != nil {
// 随机输入预期会有无效 JSON
return
}
// 如果解析成功,重新编码应该可行
_, err = json.Marshal(result)
if err != nil {
t.Errorf("Marshal failed after successful Unmarshal: %v", err)
}
})
}
// 执行:go test -fuzz=FuzzParseJSON -fuzztime=30s
```
### 多输入模糊测试
```go
func FuzzCompare(f *testing.F) {
f.Add("hello", "world")
f.Add("", "")
f.Add("abc", "abc")
f.Fuzz(func(t *testing.T, a, b string) {
result := Compare(a, b)
// 属性:Compare(a, a) 应该总是等于 0
if a == b && result != 0 {
t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
}
// 属性:Compare(a, b) 和 Compare(b, a) 应该有相反符号
reverse := Compare(b, a)
if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
if result != 0 || reverse != 0 {
t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
a, b, result, b, a, reverse)
}
}
})
}
```
## 测试覆盖率
### 执行覆盖率
```bash
# 基本覆盖率
go test -cover ./...
# 产生覆盖率 profile
go test -coverprofile=coverage.out ./...
# 在浏览器查看覆盖率
go tool cover -html=coverage.out
# 按函式查看覆盖率
go tool cover -func=coverage.out
# 含竞态侦测的覆盖率
go test -race -coverprofile=coverage.out ./...
```
### 覆盖率目标
| 程式码类型 | 目标 |
|-----------|------|
| 关键业务逻辑 | 100% |
| 公开 API | 90%+ |
| 一般程式码 | 80%+ |
| 产生的程式码 | 排除 |
## HTTP Handler 测试
```go
func TestHealthHandler(t *testing.T) {
// 建立请求
req := httptest.NewRequest(http.MethodGet, "/health", nil)
w := httptest.NewRecorder()
// 呼叫 handler
HealthHandler(w, req)
// 检查回应
resp := w.Result()
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
}
body, _ := io.ReadAll(resp.Body)
if string(body) != "OK" {
t.Errorf("got body %q; want %q", body, "OK")
}
}
func TestAPIHandler(t *testing.T) {
tests := []struct {
name string
method string
path string
body string
wantStatus int
wantBody string
}{
{
name: "get user",
method: http.MethodGet,
path: "/users/123",
wantStatus: http.StatusOK,
wantBody: `{"id":"123","name":"Alice"}`,
},
{
name: "not found",
method: http.MethodGet,
path: "/users/999",
wantStatus: http.StatusNotFound,
},
{
name: "create user",
method: http.MethodPost,
path: "/users",
body: `{"name":"Bob"}`,
wantStatus: http.StatusCreated,
},
}
handler := NewAPIHandler()
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var body io.Reader
if tt.body != "" {
body = strings.NewReader(tt.body)
}
req := httptest.NewRequest(tt.method, tt.path, body)
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
handler.ServeHTTP(w, req)
if w.Code != tt.wantStatus {
t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
}
if tt.wantBody != "" && w.Body.String() != tt.wantBody {
t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
}
})
}
}
```
## 测试指令
```bash
# 执行所有测试
go test ./...
# 执行详细输出的测试
go test -v ./...
# 执行特定测试
go test -run TestAdd ./...
# 执行匹配模式的测试
go test -run "TestUser/Create" ./...
# 执行带竞态侦测器的测试
go test -race ./...
# 执行带覆盖率的测试
go test -cover -coverprofile=coverage.out ./...
# 只执行短测试
go test -short ./...
# 执行带逾时的测试
go test -timeout 30s ./...
# 执行基准测试
go test -bench=. -benchmem ./...
# 执行模糊测试
go test -fuzz=FuzzParse -fuzztime=30s ./...
# 计算测试执行次数(用于侦测不稳定测试)
go test -count=10 ./...
```
## 最佳实务
**应该做的:**
- 先写测试(TDD)
- 使用表格驱动测试以获得完整覆盖
- 测试行为,而非实作
- 在辅助函式中使用 `t.Helper()`
- 对独立测试使用 `t.Parallel()`
- 用 `t.Cleanup()` 清理资源
- 使用描述情境的有意义测试名称
**不应该做的:**
- 不要直接测试私有函式(透过公开 API 测试)
- 不要在测试中使用 `time.Sleep()`(使用 channels 或条件)
- 不要忽略不稳定测试(修复或移除它们)
- 不要 mock 所有东西(可能时偏好整合测试)
- 不要跳过错误路径测试
## CI/CD 整合
```yaml
# GitHub Actions 范例
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Run tests
run: go test -race -coverprofile=coverage.out ./...
- name: Check coverage
run: |
go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
awk -F'%' '{if ($1 < 80) exit 1}'
```
**记住**:测试是文件。它们展示你的程式码应该如何使用。清楚地撰写并保持更新。
迭代检索(iterative-retrieval)
---
name: iterative-retrieval
description: Pattern for progressively refining context retrieval to solve the subagent context problem
---
# 迭代检索模式
解决多 agent 工作流程中的「上下文问题」,其中子 agents 在开始工作之前不知道需要什么上下文。
## 问题
子 agents 以有限上下文产生。它们不知道:
- 哪些档案包含相关程式码
- 程式码库中存在什么模式
- 专案使用什么术语
标准方法失败:
- **传送所有内容**:超过上下文限制
- **不传送内容**:Agent 缺乏关键资讯
- **猜测需要什么**:经常错误
## 解决方案:迭代检索
一个渐进精炼上下文的 4 阶段循环:
```
┌─────────────────────────────────────────────┐
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ DISPATCH │─────▶│ EVALUATE │ │
│ └──────────┘ └──────────┘ │
│ ▲ │ │
│ │ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ LOOP │◀─────│ REFINE │ │
│ └──────────┘ └──────────┘ │
│ │
│ 最多 3 个循环,然后继续 │
└─────────────────────────────────────────────┘
```
### 阶段 1:DISPATCH
初始广泛查询以收集候选档案:
```javascript
// 从高层意图开始
const initialQuery = {
patterns: ['src/**/*.ts', 'lib/**/*.ts'],
keywords: ['authentication', 'user', 'session'],
excludes: ['*.test.ts', '*.spec.ts']
};
// 派遣到检索 agent
const candidates = await retrieveFiles(initialQuery);
```
### 阶段 2:EVALUATE
评估检索内容的相关性:
```javascript
function evaluateRelevance(files, task) {
return files.map(file => ({
path: file.path,
relevance: scoreRelevance(file.content, task),
reason: explainRelevance(file.content, task),
missingContext: identifyGaps(file.content, task)
}));
}
```
评分标准:
- **高(0.8-1.0)**:直接实作目标功能
- **中(0.5-0.7)**:包含相关模式或类型
- **低(0.2-0.4)**:间接相关
- **无(0-0.2)**:不相关,排除
### 阶段 3:REFINE
基于评估更新搜寻标准:
```javascript
function refineQuery(evaluation, previousQuery) {
return {
// 新增在高相关性档案中发现的新模式
patterns: [...previousQuery.patterns, ...extractPatterns(evaluation)],
// 新增在程式码库中找到的术语
keywords: [...previousQuery.keywords, ...extractKeywords(evaluation)],
// 排除确认不相关的路径
excludes: [...previousQuery.excludes, ...evaluation
.filter(e => e.relevance < 0.2)
.map(e => e.path)
],
// 针对特定缺口
focusAreas: evaluation
.flatMap(e => e.missingContext)
.filter(unique)
};
}
```
### 阶段 4:LOOP
以精炼标准重复(最多 3 个循环):
```javascript
async function iterativeRetrieve(task, maxCycles = 3) {
let query = createInitialQuery(task);
let bestContext = [];
for (let cycle = 0; cycle < maxCycles; cycle++) {
const candidates = await retrieveFiles(query);
const evaluation = evaluateRelevance(candidates, task);
// 检查是否有足够上下文
const highRelevance = evaluation.filter(e => e.relevance >= 0.7);
if (highRelevance.length >= 3 && !hasCriticalGaps(evaluation)) {
return highRelevance;
}
// 精炼并继续
query = refineQuery(evaluation, query);
bestContext = mergeContext(bestContext, highRelevance);
}
return bestContext;
}
```
## 实际范例
### 范例 1:Bug 修复上下文
```
任务:「修复认证 token 过期 bug」
循环 1:
DISPATCH:在 src/** 搜寻 "token"、"auth"、"expiry"
EVALUATE:找到 auth.ts (0.9)、tokens.ts (0.8)、user.ts (0.3)
REFINE:新增 "refresh"、"jwt" 关键字;排除 user.ts
循环 2:
DISPATCH:搜寻精炼术语
EVALUATE:找到 session-manager.ts (0.95)、jwt-utils.ts (0.85)
REFINE:足够上下文(2 个高相关性档案)
结果:auth.ts、tokens.ts、session-manager.ts、jwt-utils.ts
```
### 范例 2:功能实作
```
任务:「为 API 端点增加速率限制」
循环 1:
DISPATCH:在 routes/** 搜寻 "rate"、"limit"、"api"
EVALUATE:无匹配 - 程式码库使用 "throttle" 术语
REFINE:新增 "throttle"、"middleware" 关键字
循环 2:
DISPATCH:搜寻精炼术语
EVALUATE:找到 throttle.ts (0.9)、middleware/index.ts (0.7)
REFINE:需要路由器模式
循环 3:
DISPATCH:搜寻 "router"、"express" 模式
EVALUATE:找到 router-setup.ts (0.8)
REFINE:足够上下文
结果:throttle.ts、middleware/index.ts、router-setup.ts
```
## 与 Agents 整合
在 agent 提示中使用:
```markdown
为此任务检索上下文时:
1. 从广泛关键字搜寻开始
2. 评估每个档案的相关性(0-1 尺度)
3. 识别仍缺少的上下文
4. 精炼搜寻标准并重复(最多 3 个循环)
5. 回传相关性 >= 0.7 的档案
```
## 最佳实务
1. **从广泛开始,逐渐缩小** - 不要过度指定初始查询
2. **学习程式码库术语** - 第一个循环通常会揭示命名惯例
3. **追踪缺失内容** - 明确的缺口识别驱动精炼
4. **在「足够好」时停止** - 3 个高相关性档案胜过 10 个普通档案
5. **自信地排除** - 低相关性档案不会变得相关
## 相关
- [Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - 子 agent 协调章节
- `continuous-learning` 技能 - 用于随时间改进的模式
- `~/.claude/agents/` 中的 Agent 定义
Postgres模式(postgres-patterns)
---
name: postgres-patterns
description: PostgreSQL database patterns for query optimization, schema design, indexing, and security. Based on Supabase best practices.
---
# PostgreSQL 模式
PostgreSQL 最佳实务快速参考。详细指南请使用 `database-reviewer` agent。
## 何时启用
- 撰写 SQL 查询或 migrations
- 设计资料库 schema
- 疑难排解慢查询
- 实作 Row Level Security
- 设定连线池
## 快速参考
### 索引速查表
| 查询模式 | 索引类型 | 范例 |
|---------|---------|------|
| `WHERE col = value` | B-tree(预设) | `CREATE INDEX idx ON t (col)` |
| `WHERE col > value` | B-tree | `CREATE INDEX idx ON t (col)` |
| `WHERE a = x AND b > y` | 复合 | `CREATE INDEX idx ON t (a, b)` |
| `WHERE jsonb @> '{}'` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| `WHERE tsv @@ query` | GIN | `CREATE INDEX idx ON t USING gin (col)` |
| 时间序列范围 | BRIN | `CREATE INDEX idx ON t USING brin (col)` |
### 资料类型快速参考
| 使用情况 | 正确类型 | 避免 |
|---------|---------|------|
| IDs | `bigint` | `int`、随机 UUID |
| 字串 | `text` | `varchar(255)` |
| 时间戳 | `timestamptz` | `timestamp` |
| 金额 | `numeric(10,2)` | `float` |
| 旗标 | `boolean` | `varchar`、`int` |
### 常见模式
**复合索引顺序:**
```sql
-- 等值栏位优先,然后是范围栏位
CREATE INDEX idx ON orders (status, created_at);
-- 适用于:WHERE status = 'pending' AND created_at > '2024-01-01'
```
**覆盖索引:**
```sql
CREATE INDEX idx ON users (email) INCLUDE (name, created_at);
-- 避免 SELECT email, name, created_at 时的表格查询
```
**部分索引:**
```sql
CREATE INDEX idx ON users (email) WHERE deleted_at IS NULL;
-- 更小的索引,只包含活跃使用者
```
**RLS 政策(优化):**
```sql
CREATE POLICY policy ON orders
USING ((SELECT auth.uid()) = user_id); -- 用 SELECT 包装!
```
**UPSERT:**
```sql
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value;
```
**游标分页:**
```sql
SELECT * FROM products WHERE id > $last_id ORDER BY id LIMIT 20;
-- O(1) vs OFFSET 是 O(n)
```
**伫列处理:**
```sql
UPDATE jobs SET status = 'processing'
WHERE id = (
SELECT id FROM jobs WHERE status = 'pending'
ORDER BY created_at LIMIT 1
FOR UPDATE SKIP LOCKED
) RETURNING *;
```
### 反模式侦测
```sql
-- 找出未建索引的外键
SELECT conrelid::regclass, a.attname
FROM pg_constraint c
JOIN pg_attribute a ON a.attrelid = c.conrelid AND a.attnum = ANY(c.conkey)
WHERE c.contype = 'f'
AND NOT EXISTS (
SELECT 1 FROM pg_index i
WHERE i.indrelid = c.conrelid AND a.attnum = ANY(i.indkey)
);
-- 找出慢查询
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
WHERE mean_exec_time > 100
ORDER BY mean_exec_time DESC;
-- 检查表格膨胀
SELECT relname, n_dead_tup, last_vacuum
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;
```
### 设定范本
```sql
-- 连线限制(依 RAM 调整)
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB';
-- 逾时
ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET statement_timeout = '30s';
-- 监控
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 安全预设值
REVOKE ALL ON SCHEMA public FROM public;
SELECT pg_reload_conf();
```
## 相关
- Agent:`database-reviewer` - 完整资料库审查工作流程
- Skill:`clickhouse-io` - ClickHouse 分析模式
- Skill:`backend-patterns` - API 和后端模式
---
*基于 [Supabase Agent Skills](https://github.com/supabase/agent-skills)(MIT 授权)*
项目指南示例(project-guidelines-example)
# 专案指南技能(范例)
这是专案特定技能的范例。使用此作为你自己专案的范本。
基于真实生产应用程式:[Zenith](https://zenith.chat) - AI 驱动的客户探索平台。
---
## 何时使用
在处理专案特定设计时参考此技能。专案技能包含:
- 架构概览
- 档案结构
- 程式码模式
- 测试要求
- 部署工作流程
---
## 架构概览
**技术堆叠:**
- **前端**:Next.js 15(App Router)、TypeScript、React
- **后端**:FastAPI(Python)、Pydantic 模型
- **资料库**:Supabase(PostgreSQL)
- **AI**:Claude API 带工具呼叫和结构化输出
- **部署**:Google Cloud Run
- **测试**:Playwright(E2E)、pytest(后端)、React Testing Library
**服务:**
```
┌─────────────────────────────────────────────────────────────┐
│ 前端 │
│ Next.js 15 + TypeScript + TailwindCSS │
│ 部署:Vercel / Cloud Run │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 后端 │
│ FastAPI + Python 3.11 + Pydantic │
│ 部署:Cloud Run │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Supabase │ │ Claude │ │ Redis │
│ Database │ │ API │ │ Cache │
└──────────┘ └──────────┘ └──────────┘
```
---
## 档案结构
```
project/
├── frontend/
│ └── src/
│ ├── app/ # Next.js app router 页面
│ │ ├── api/ # API 路由
│ │ ├── (auth)/ # 需认证路由
│ │ └── workspace/ # 主应用程式工作区
│ ├── components/ # React 元件
│ │ ├── ui/ # 基础 UI 元件
│ │ ├── forms/ # 表单元件
│ │ └── layouts/ # 版面配置元件
│ ├── hooks/ # 自订 React hooks
│ ├── lib/ # 工具
│ ├── types/ # TypeScript 定义
│ └── config/ # 设定
│
├── backend/
│ ├── routers/ # FastAPI 路由处理器
│ ├── models.py # Pydantic 模型
│ ├── main.py # FastAPI app 进入点
│ ├── auth_system.py # 认证
│ ├── database.py # 资料库操作
│ ├── services/ # 业务逻辑
│ └── tests/ # pytest 测试
│
├── deploy/ # 部署设定
├── docs/ # 文件
└── scripts/ # 工具脚本
```
---
## 程式码模式
### API 回应格式(FastAPI)
```python
from pydantic import BaseModel
from typing import Generic, TypeVar, Optional
T = TypeVar('T')
class ApiResponse(BaseModel, Generic[T]):
success: bool
data: Optional[T] = None
error: Optional[str] = None
@classmethod
def ok(cls, data: T) -> "ApiResponse[T]":
return cls(success=True, data=data)
@classmethod
def fail(cls, error: str) -> "ApiResponse[T]":
return cls(success=False, error=error)
```
### 前端 API 呼叫(TypeScript)
```typescript
interface ApiResponse {
success: boolean
data?: T
error?: string
}
async function fetchApi(
endpoint: string,
options?: RequestInit
): Promise
云基础设施安全(cloud-infrastructure-security)
| name | description |
|------|-------------|
| cloud-infrastructure-security | Use this skill when deploying to cloud platforms, configuring infrastructure, managing IAM policies, setting up logging/monitoring, or implementing CI/CD pipelines. Provides cloud security checklist aligned with best practices. |
# 云端与基础设施安全技能
此技能确保云端基础设施、CI/CD 管线和部署设定遵循安全最佳实务并符合业界标准。
## 何时启用
- 部署应用程式到云端平台(AWS、Vercel、Railway、Cloudflare)
- 设定 IAM 角色和权限
- 设置 CI/CD 管线
- 实作基础设施即程式码(Terraform、CloudFormation)
- 设定日志和监控
- 在云端环境管理密钥
- 设置 CDN 和边缘安全
- 实作灾难复原和备份策略
## 云端安全检查清单
### 1. IAM 与存取控制
#### 最小权限原则
```yaml
# ✅ 正确:最小权限
iam_role:
permissions:
- s3:GetObject # 只有读取存取
- s3:ListBucket
resources:
- arn:aws:s3:::my-bucket/* # 只有特定 bucket
# ❌ 错误:过于广泛的权限
iam_role:
permissions:
- s3:* # 所有 S3 动作
resources:
- "*" # 所有资源
```
#### 多因素认证(MFA)
```bash
# 总是为 root/admin 帐户启用 MFA
aws iam enable-mfa-device \
--user-name admin \
--serial-number arn:aws:iam::123456789:mfa/admin \
--authentication-code1 123456 \
--authentication-code2 789012
```
#### 验证步骤
- [ ] 生产环境不使用 root 帐户
- [ ] 所有特权帐户启用 MFA
- [ ] 服务帐户使用角色,非长期凭证
- [ ] IAM 政策遵循最小权限
- [ ] 定期进行存取审查
- [ ] 未使用凭证已轮换或移除
### 2. 密钥管理
#### 云端密钥管理器
```typescript
// ✅ 正确:使用云端密钥管理器
import { SecretsManager } from '@aws-sdk/client-secrets-manager';
const client = new SecretsManager({ region: 'us-east-1' });
const secret = await client.getSecretValue({ SecretId: 'prod/api-key' });
const apiKey = JSON.parse(secret.SecretString).key;
// ❌ 错误:写死或只在环境变数
const apiKey = process.env.API_KEY; // 未轮换、未稽核
```
#### 密钥轮换
```bash
# 为资料库凭证设定自动轮换
aws secretsmanager rotate-secret \
--secret-id prod/db-password \
--rotation-lambda-arn arn:aws:lambda:region:account:function:rotate \
--rotation-rules AutomaticallyAfterDays=30
```
#### 验证步骤
- [ ] 所有密钥储存在云端密钥管理器(AWS Secrets Manager、Vercel Secrets)
- [ ] 资料库凭证启用自动轮换
- [ ] API 金钥至少每季轮换
- [ ] 程式码、日志或错误讯息中无密钥
- [ ] 密钥存取启用稽核日志
### 3. 网路安全
#### VPC 和防火墙设定
```terraform
# ✅ 正确:限制的安全群组
resource "aws_security_group" "app" {
name = "app-sg"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["10.0.0.0/16"] # 只有内部 VPC
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 只有 HTTPS 输出
}
}
# ❌ 错误:对网际网路开放
resource "aws_security_group" "bad" {
ingress {
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"] # 所有埠、所有 IP!
}
}
```
#### 验证步骤
- [ ] 资料库不可公开存取
- [ ] SSH/RDP 埠限制为 VPN/堡垒机
- [ ] 安全群组遵循最小权限
- [ ] 网路 ACL 已设定
- [ ] VPC 流量日志已启用
### 4. 日志与监控
#### CloudWatch/日志设定
```typescript
// ✅ 正确:全面日志记录
import { CloudWatchLogsClient, CreateLogStreamCommand } from '@aws-sdk/client-cloudwatch-logs';
const logSecurityEvent = async (event: SecurityEvent) => {
await cloudwatch.putLogEvents({
logGroupName: '/aws/security/events',
logStreamName: 'authentication',
logEvents: [{
timestamp: Date.now(),
message: JSON.stringify({
type: event.type,
userId: event.userId,
ip: event.ip,
result: event.result,
// 永远不要记录敏感资料
})
}]
});
};
```
#### 验证步骤
- [ ] 所有服务启用 CloudWatch/日志记录
- [ ] 失败的认证尝试被记录
- [ ] 管理员动作被稽核
- [ ] 日志保留已设定(合规需 90+ 天)
- [ ] 可疑活动设定警报
- [ ] 日志集中化且防篡改
### 5. CI/CD 管线安全
#### 安全管线设定
```yaml
# ✅ 正确:安全的 GitHub Actions 工作流程
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read # 最小权限
steps:
- uses: actions/checkout@v4
# 扫描密钥
- name: Secret scanning
uses: trufflesecurity/trufflehog@main
# 依赖稽核
- name: Audit dependencies
run: npm audit --audit-level=high
# 使用 OIDC,非长期 tokens
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/GitHubActionsRole
aws-region: us-east-1
```
#### 供应链安全
```json
// package.json - 使用 lock 档案和完整性检查
{
"scripts": {
"install": "npm ci", // 使用 ci 以获得可重现建置
"audit": "npm audit --audit-level=moderate",
"check": "npm outdated"
}
}
```
#### 验证步骤
- [ ] 使用 OIDC 而非长期凭证
- [ ] 管线中的密钥扫描
- [ ] 依赖漏洞扫描
- [ ] 容器映像扫描(如适用)
- [ ] 强制执行分支保护规则
- [ ] 合并前需要程式码审查
- [ ] 强制执行签署 commits
### 6. Cloudflare 与 CDN 安全
#### Cloudflare 安全设定
```typescript
// ✅ 正确:带安全标头的 Cloudflare Workers
export default {
async fetch(request: Request): Promise {
const response = await fetch(request);
// 新增安全标头
const headers = new Headers(response.headers);
headers.set('X-Frame-Options', 'DENY');
headers.set('X-Content-Type-Options', 'nosniff');
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
headers.set('Permissions-Policy', 'geolocation=(), microphone=()');
return new Response(response.body, {
status: response.status,
headers
});
}
};
```
#### WAF 规则
```bash
# 启用 Cloudflare WAF 管理规则
# - OWASP 核心规则集
# - Cloudflare 管理规则集
# - 速率限制规则
# - Bot 保护
```
#### 验证步骤
- [ ] WAF 启用 OWASP 规则
- [ ] 速率限制已设定
- [ ] Bot 保护启用
- [ ] DDoS 保护启用
- [ ] 安全标头已设定
- [ ] SSL/TLS 严格模式启用
### 7. 备份与灾难复原
#### 自动备份
```terraform
# ✅ 正确:自动 RDS 备份
resource "aws_db_instance" "main" {
allocated_storage = 20
engine = "postgres"
backup_retention_period = 30 # 30 天保留
backup_window = "03:00-04:00"
maintenance_window = "mon:04:00-mon:05:00"
enabled_cloudwatch_logs_exports = ["postgresql"]
deletion_protection = true # 防止意外删除
}
```
#### 验证步骤
- [ ] 已设定自动每日备份
- [ ] 备份保留符合合规要求
- [ ] 已启用时间点复原
- [ ] 每季执行备份测试
- [ ] 灾难复原计划已记录
- [ ] RPO 和 RTO 已定义并测试
## 部署前云端安全检查清单
任何生产云端部署前:
- [ ] **IAM**:不使用 root 帐户、启用 MFA、最小权限政策
- [ ] **密钥**:所有密钥在云端密钥管理器并有轮换
- [ ] **网路**:安全群组受限、无公开资料库
- [ ] **日志**:CloudWatch/日志启用并有保留
- [ ] **监控**:异常设定警报
- [ ] **CI/CD**:OIDC 认证、密钥扫描、依赖稽核
- [ ] **CDN/WAF**:Cloudflare WAF 启用 OWASP 规则
- [ ] **加密**:资料静态和传输中加密
- [ ] **备份**:自动备份并测试复原
- [ ] **合规**:符合 GDPR/HIPAA 要求(如适用)
- [ ] **文件**:基础设施已记录、建立操作手册
- [ ] **事件回应**:安全事件计划就位
## 常见云端安全错误设定
### S3 Bucket 暴露
```bash
# ❌ 错误:公开 bucket
aws s3api put-bucket-acl --bucket my-bucket --acl public-read
# ✅ 正确:私有 bucket 并有特定存取
aws s3api put-bucket-acl --bucket my-bucket --acl private
aws s3api put-bucket-policy --bucket my-bucket --policy file://policy.json
```
### RDS 公开存取
```terraform
# ❌ 错误
resource "aws_db_instance" "bad" {
publicly_accessible = true # 绝不这样做!
}
# ✅ 正确
resource "aws_db_instance" "good" {
publicly_accessible = false
vpc_security_group_ids = [aws_security_group.db.id]
}
```
## 资源
- [AWS Security Best Practices](https://aws.amazon.com/security/best-practices/)
- [CIS AWS Foundations Benchmark](https://www.cisecurity.org/benchmark/amazon_web_services)
- [Cloudflare Security Documentation](https://developers.cloudflare.com/security/)
- [OWASP Cloud Security](https://owasp.org/www-project-cloud-security/)
- [Terraform Security Best Practices](https://www.terraform.io/docs/cloud/guides/recommended-practices/)
**记住**:云端错误设定是资料外泄的主要原因。单一暴露的 S3 bucket 或过于宽松的 IAM 政策可能危及你的整个基础设施。总是遵循最小权限原则和深度防御。
安全性审查技能(SKILL)
---
name: security-review
description: Use this skill when adding authentication, handling user input, working with secrets, creating API endpoints, or implementing payment/sensitive features. Provides comprehensive security checklist and patterns.
---
# 安全性审查技能
此技能确保所有程式码遵循安全性最佳实务并识别潜在漏洞。
## 何时启用
- 实作认证或授权
- 处理使用者输入或档案上传
- 建立新的 API 端点
- 处理密钥或凭证
- 实作支付功能
- 储存或传输敏感资料
- 整合第三方 API
## 安全性检查清单
### 1. 密钥管理
#### ❌ 绝不这样做
```typescript
const apiKey = "sk-proj-xxxxx" // 写死的密钥
const dbPassword = "password123" // 在原始码中
```
#### ✅ 总是这样做
```typescript
const apiKey = process.env.OPENAI_API_KEY
const dbUrl = process.env.DATABASE_URL
// 验证密钥存在
if (!apiKey) {
throw new Error('OPENAI_API_KEY not configured')
}
```
#### 验证步骤
- [ ] 无写死的 API 金钥、Token 或密码
- [ ] 所有密钥在环境变数中
- [ ] `.env.local` 在 .gitignore 中
- [ ] git 历史中无密钥
- [ ] 生产密钥在托管平台(Vercel、Railway)中
### 2. 输入验证
#### 总是验证使用者输入
```typescript
import { z } from 'zod'
// 定义验证 schema
const CreateUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
age: z.number().int().min(0).max(150)
})
// 处理前验证
export async function createUser(input: unknown) {
try {
const validated = CreateUserSchema.parse(input)
return await db.users.create(validated)
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, errors: error.errors }
}
throw error
}
}
```
#### 档案上传验证
```typescript
function validateFileUpload(file: File) {
// 大小检查(最大 5MB)
const maxSize = 5 * 1024 * 1024
if (file.size > maxSize) {
throw new Error('File too large (max 5MB)')
}
// 类型检查
const allowedTypes = ['image/jpeg', 'image/png', 'image/gif']
if (!allowedTypes.includes(file.type)) {
throw new Error('Invalid file type')
}
// 副档名检查
const allowedExtensions = ['.jpg', '.jpeg', '.png', '.gif']
const extension = file.name.toLowerCase().match(/\.[^.]+$/)?.[0]
if (!extension || !allowedExtensions.includes(extension)) {
throw new Error('Invalid file extension')
}
return true
}
```
#### 验证步骤
- [ ] 所有使用者输入以 schema 验证
- [ ] 档案上传受限(大小、类型、副档名)
- [ ] 查询中不直接使用使用者输入
- [ ] 白名单验证(非黑名单)
- [ ] 错误讯息不泄露敏感资讯
### 3. SQL 注入预防
#### ❌ 绝不串接 SQL
```typescript
// 危险 - SQL 注入漏洞
const query = `SELECT * FROM users WHERE email = '${userEmail}'`
await db.query(query)
```
#### ✅ 总是使用参数化查询
```typescript
// 安全 - 参数化查询
const { data } = await supabase
.from('users')
.select('*')
.eq('email', userEmail)
// 或使用原始 SQL
await db.query(
'SELECT * FROM users WHERE email = $1',
[userEmail]
)
```
#### 验证步骤
- [ ] 所有资料库查询使用参数化查询
- [ ] SQL 中无字串串接
- [ ] ORM/查询建构器正确使用
- [ ] Supabase 查询正确净化
### 4. 认证与授权
#### JWT Token 处理
```typescript
// ❌ 错误:localStorage(易受 XSS 攻击)
localStorage.setItem('token', token)
// ✅ 正确:httpOnly cookies
res.setHeader('Set-Cookie',
`token=${token}; HttpOnly; Secure; SameSite=Strict; Max-Age=3600`)
```
#### 授权检查
```typescript
export async function deleteUser(userId: string, requesterId: string) {
// 总是先验证授权
const requester = await db.users.findUnique({
where: { id: requesterId }
})
if (requester.role !== 'admin') {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 403 }
)
}
// 继续删除
await db.users.delete({ where: { id: userId } })
}
```
#### Row Level Security(Supabase)
```sql
-- 在所有表格上启用 RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
-- 使用者只能查看自己的资料
CREATE POLICY "Users view own data"
ON users FOR SELECT
USING (auth.uid() = id);
-- 使用者只能更新自己的资料
CREATE POLICY "Users update own data"
ON users FOR UPDATE
USING (auth.uid() = id);
```
#### 验证步骤
- [ ] Token 储存在 httpOnly cookies(非 localStorage)
- [ ] 敏感操作前有授权检查
- [ ] Supabase 已启用 Row Level Security
- [ ] 已实作基于角色的存取控制
- [ ] 工作阶段管理安全
### 5. XSS 预防
#### 净化 HTML
```typescript
import DOMPurify from 'isomorphic-dompurify'
// 总是净化使用者提供的 HTML
function renderUserContent(html: string) {
const clean = DOMPurify.sanitize(html, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p'],
ALLOWED_ATTR: []
})
return
策略性压缩(strategic-compact)
---
name: strategic-compact
description: Suggests manual context compaction at logical intervals to preserve context through task phases rather than arbitrary auto-compaction.
---
# 策略性压缩技能
在工作流程的策略点建议手动 `/compact`,而非依赖任意的自动压缩。
## 为什么需要策略性压缩?
自动压缩在任意点触发:
- 经常在任务中途,丢失重要上下文
- 不知道逻辑任务边界
- 可能中断复杂的多步骤操作
逻辑边界的策略性压缩:
- **探索后、执行前** - 压缩研究上下文,保留实作计划
- **完成里程碑后** - 为下一阶段重新开始
- **主要上下文转换前** - 在不同任务前清除探索上下文
## 运作方式
`suggest-compact.sh` 脚本在 PreToolUse(Edit/Write)执行并:
1. **追踪工具呼叫** - 计算工作阶段中的工具呼叫次数
2. **门槛侦测** - 在可设定门槛建议(预设:50 次呼叫)
3. **定期提醒** - 门槛后每 25 次呼叫提醒一次
## Hook 设定
新增到你的 `~/.claude/settings.json`:
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "tool == \"Edit\" || tool == \"Write\"",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
}]
}]
}
}
```
## 设定
环境变数:
- `COMPACT_THRESHOLD` - 第一次建议前的工具呼叫次数(预设:50)
## 最佳实务
1. **规划后压缩** - 计划确定后,压缩以重新开始
2. **除错后压缩** - 继续前清除错误解决上下文
3. **不要在实作中途压缩** - 为相关变更保留上下文
4. **阅读建议** - Hook 告诉你*何时*,你决定*是否*
## 相关
- [Longform Guide](https://x.com/affaanmustafa/status/2014040193557471352) - Token 优化章节
- 记忆持久性 hooks - 用于压缩后存活的状态
TDD工作流(tdd-workflow)
---
name: tdd-workflow
description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage including unit, integration, and E2E tests.
---
# 测试驱动开发工作流程
此技能确保所有程式码开发遵循 TDD 原则,并具有完整的测试覆盖率。
## 何时启用
- 撰写新功能或功能性程式码
- 修复 Bug 或问题
- 重构现有程式码
- 新增 API 端点
- 建立新元件
## 核心原则
### 1. 测试先于程式码
总是先写测试,然后实作程式码使测试通过。
### 2. 覆盖率要求
- 最低 80% 覆盖率(单元 + 整合 + E2E)
- 涵盖所有边界案例
- 测试错误情境
- 验证边界条件
### 3. 测试类型
#### 单元测试
- 个别函式和工具
- 元件逻辑
- 纯函式
- 辅助函式和工具
#### 整合测试
- API 端点
- 资料库操作
- 服务互动
- 外部 API 呼叫
#### E2E 测试(Playwright)
- 关键使用者流程
- 完整工作流程
- 浏览器自动化
- UI 互动
## TDD 工作流程步骤
### 步骤 1:撰写使用者旅程
```
身为 [角色],我想要 [动作],以便 [好处]
范例:
身为使用者,我想要语意搜寻市场,
以便即使没有精确关键字也能找到相关市场。
```
### 步骤 2:产生测试案例
为每个使用者旅程建立完整的测试案例:
```typescript
describe('Semantic Search', () => {
it('returns relevant markets for query', async () => {
// 测试实作
})
it('handles empty query gracefully', async () => {
// 测试边界案例
})
it('falls back to substring search when Redis unavailable', async () => {
// 测试回退行为
})
it('sorts results by similarity score', async () => {
// 测试排序逻辑
})
})
```
### 步骤 3:执行测试(应该失败)
```bash
npm test
# 测试应该失败 - 我们还没实作
```
### 步骤 4:实作程式码
撰写最少的程式码使测试通过:
```typescript
// 由测试引导的实作
export async function searchMarkets(query: string) {
// 实作在此
}
```
### 步骤 5:再次执行测试
```bash
npm test
# 测试现在应该通过
```
### 步骤 6:重构
在保持测试通过的同时改善程式码品质:
- 移除重复
- 改善命名
- 优化效能
- 增强可读性
### 步骤 7:验证覆盖率
```bash
npm run test:coverage
# 验证达到 80%+ 覆盖率
```
## 测试模式
### 单元测试模式(Jest/Vitest)
```typescript
import { render, screen, fireEvent } from '@testing-library/react'
import { Button } from './Button'
describe('Button Component', () => {
it('renders with correct text', () => {
render(Click me)
expect(screen.getByText('Click me')).toBeInTheDocument()
})
it('calls onClick when clicked', () => {
const handleClick = jest.fn()
render(Click)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('is disabled when disabled prop is true', () => {
render(Click)
expect(screen.getByRole('button')).toBeDisabled()
})
})
```
### API 整合测试模式
```typescript
import { NextRequest } from 'next/server'
import { GET } from './route'
describe('GET /api/markets', () => {
it('returns markets successfully', async () => {
const request = new NextRequest('http://localhost/api/markets')
const response = await GET(request)
const data = await response.json()
expect(response.status).toBe(200)
expect(data.success).toBe(true)
expect(Array.isArray(data.data)).toBe(true)
})
it('validates query parameters', async () => {
const request = new NextRequest('http://localhost/api/markets?limit=invalid')
const response = await GET(request)
expect(response.status).toBe(400)
})
it('handles database errors gracefully', async () => {
// Mock 资料库失败
const request = new NextRequest('http://localhost/api/markets')
// 测试错误处理
})
})
```
### E2E 测试模式(Playwright)
```typescript
import { test, expect } from '@playwright/test'
test('user can search and filter markets', async ({ page }) => {
// 导航到市场页面
await page.goto('/')
await page.click('a[href="/markets"]')
// 验证页面载入
await expect(page.locator('h1')).toContainText('Markets')
// 搜寻市场
await page.fill('input[placeholder="Search markets"]', 'election')
// 等待 debounce 和结果
await page.waitForTimeout(600)
// 验证搜寻结果显示
const results = page.locator('[data-testid="market-card"]')
await expect(results).toHaveCount(5, { timeout: 5000 })
// 验证结果包含搜寻词
const firstResult = results.first()
await expect(firstResult).toContainText('election', { ignoreCase: true })
// 依状态筛选
await page.click('button:has-text("Active")')
// 验证筛选结果
await expect(results).toHaveCount(3)
})
test('user can create a new market', async ({ page }) => {
// 先登入
await page.goto('/creator-dashboard')
// 填写市场建立表单
await page.fill('input[name="name"]', 'Test Market')
await page.fill('textarea[name="description"]', 'Test description')
await page.fill('input[name="endDate"]', '2025-12-31')
// 提交表单
await page.click('button[type="submit"]')
// 验证成功讯息
await expect(page.locator('text=Market created successfully')).toBeVisible()
// 验证重导向到市场页面
await expect(page).toHaveURL(/\/markets\/test-market/)
})
```
## 测试档案组织
```
src/
├── components/
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx # 单元测试
│ │ └── Button.stories.tsx # Storybook
│ └── MarketCard/
│ ├── MarketCard.tsx
│ └── MarketCard.test.tsx
├── app/
│ └── api/
│ └── markets/
│ ├── route.ts
│ └── route.test.ts # 整合测试
└── e2e/
├── markets.spec.ts # E2E 测试
├── trading.spec.ts
└── auth.spec.ts
```
## Mock 外部服务
### Supabase Mock
```typescript
jest.mock('@/lib/supabase', () => ({
supabase: {
from: jest.fn(() => ({
select: jest.fn(() => ({
eq: jest.fn(() => Promise.resolve({
data: [{ id: 1, name: 'Test Market' }],
error: null
}))
}))
}))
}
}))
```
### Redis Mock
```typescript
jest.mock('@/lib/redis', () => ({
searchMarketsByVector: jest.fn(() => Promise.resolve([
{ slug: 'test-market', similarity_score: 0.95 }
])),
checkRedisHealth: jest.fn(() => Promise.resolve({ connected: true }))
}))
```
### OpenAI Mock
```typescript
jest.mock('@/lib/openai', () => ({
generateEmbedding: jest.fn(() => Promise.resolve(
new Array(1536).fill(0.1) // Mock 1536 维嵌入向量
))
}))
```
## 测试覆盖率验证
### 执行覆盖率报告
```bash
npm run test:coverage
```
### 覆盖率门槛
```json
{
"jest": {
"coverageThresholds": {
"global": {
"branches": 80,
"functions": 80,
"lines": 80,
"statements": 80
}
}
}
}
```
## 常见测试错误避免
### ❌ 错误:测试实作细节
```typescript
// 不要测试内部状态
expect(component.state.count).toBe(5)
```
### ✅ 正确:测试使用者可见行为
```typescript
// 测试使用者看到的内容
expect(screen.getByText('Count: 5')).toBeInTheDocument()
```
### ❌ 错误:脆弱的选择器
```typescript
// 容易坏掉
await page.click('.css-class-xyz')
```
### ✅ 正确:语意选择器
```typescript
// 对变更有弹性
await page.click('button:has-text("Submit")')
await page.click('[data-testid="submit-button"]')
```
### ❌ 错误:无测试隔离
```typescript
// 测试互相依赖
test('creates user', () => { /* ... */ })
test('updates same user', () => { /* 依赖前一个测试 */ })
```
### ✅ 正确:独立测试
```typescript
// 每个测试设置自己的资料
test('creates user', () => {
const user = createTestUser()
// 测试逻辑
})
test('updates user', () => {
const user = createTestUser()
// 更新逻辑
})
```
## 持续测试
### 开发期间的 Watch 模式
```bash
npm test -- --watch
# 档案变更时自动执行测试
```
### Pre-Commit Hook
```bash
# 每次 commit 前执行
npm test && npm run lint
```
### CI/CD 整合
```yaml
# GitHub Actions
- name: Run Tests
run: npm test -- --coverage
- name: Upload Coverage
uses: codecov/codecov-action@v3
```
## 最佳实务
1. **先写测试** - 总是 TDD
2. **一个测试一个断言** - 专注单一行为
3. **描述性测试名称** - 解释测试内容
4. **Arrange-Act-Assert** - 清晰的测试结构
5. **Mock 外部依赖** - 隔离单元测试
6. **测试边界案例** - Null、undefined、空值、大值
7. **测试错误路径** - 不只是快乐路径
8. **保持测试快速** - 单元测试每个 < 50ms
9. **测试后清理** - 无副作用
10. **检视覆盖率报告** - 识别缺口
## 成功指标
- 达到 80%+ 程式码覆盖率
- 所有测试通过(绿色)
- 无跳过或停用的测试
- 快速测试执行(单元测试 < 30s)
- E2E 测试涵盖关键使用者流程
- 测试在生产前捕捉 Bug
---
**记住**:测试不是可选的。它们是实现自信重构、快速开发和生产可靠性的安全网。
验证循环(verification-loop)
# 验证循环技能
Claude Code 工作阶段的完整验证系统。
## 何时使用
在以下情况呼叫此技能:
- 完成功能或重大程式码变更后
- 建立 PR 前
- 想确保品质门槛通过时
- 重构后
## 验证阶段
### 阶段 1:建置验证
```bash
# 检查专案是否建置
npm run build 2>&1 | tail -20
# 或
pnpm build 2>&1 | tail -20
```
如果建置失败,停止并在继续前修复。
### 阶段 2:型别检查
```bash
# TypeScript 专案
npx tsc --noEmit 2>&1 | head -30
# Python 专案
pyright . 2>&1 | head -30
```
报告所有型别错误。继续前修复关键错误。
### 阶段 3:Lint 检查
```bash
# JavaScript/TypeScript
npm run lint 2>&1 | head -30
# Python
ruff check . 2>&1 | head -30
```
### 阶段 4:测试套件
```bash
# 执行带覆盖率的测试
npm run test -- --coverage 2>&1 | tail -50
# 检查覆盖率门槛
# 目标:最低 80%
```
报告:
- 总测试数:X
- 通过:X
- 失败:X
- 覆盖率:X%
### 阶段 5:安全扫描
```bash
# 检查密钥
grep -rn "sk-" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
grep -rn "api_key" --include="*.ts" --include="*.js" . 2>/dev/null | head -10
# 检查 console.log
grep -rn "console.log" --include="*.ts" --include="*.tsx" src/ 2>/dev/null | head -10
```
### 阶段 6:差异审查
```bash
# 显示变更内容
git diff --stat
git diff HEAD~1 --name-only
```
审查每个变更的档案:
- 非预期变更
- 缺少错误处理
- 潜在边界案例
## 输出格式
执行所有阶段后,产生验证报告:
```
验证报告
==================
建置: [PASS/FAIL]
型别: [PASS/FAIL](X 个错误)
Lint: [PASS/FAIL](X 个警告)
测试: [PASS/FAIL](X/Y 通过,Z% 覆盖率)
安全性: [PASS/FAIL](X 个问题)
差异: [X 个档案变更]
整体: [READY/NOT READY] for PR
待修复问题:
1. ...
2. ...
```
## 持续模式
对于长时间工作阶段,每 15 分钟或重大变更后执行验证:
```markdown
设定心理检查点:
- 完成每个函式后
- 完成元件后
- 移至下一个任务前
执行:/verify
```
## 与 Hooks 整合
此技能补充 PostToolUse hooks 但提供更深入的验证。
Hooks 立即捕捉问题;此技能提供全面审查。
更多推荐

所有评论(0)