docs(zh-CN): sync Chinese docs with latest upstream changes (#304)

* docs(zh-CN): sync Chinese docs with latest upstream changes

* update

---------

Co-authored-by: neo <neo.dowithless@gmail.com>
This commit is contained in:
zdoc.app
2026-03-03 14:28:27 +08:00
committed by GitHub
parent adc0f67008
commit ada4cd75a3
114 changed files with 11161 additions and 4790 deletions

View File

@@ -0,0 +1,523 @@
---
name: api-design
description: REST API设计模式包括资源命名、状态码、分页、过滤、错误响应、版本控制和生产API的速率限制。
origin: ECC
---
# API 设计模式
用于设计一致、对开发者友好的 REST API 的约定和最佳实践。
## 何时启用
* 设计新的 API 端点时
* 审查现有的 API 契约时
* 添加分页、过滤或排序功能时
* 为 API 实现错误处理时
* 规划 API 版本策略时
* 构建面向公众或合作伙伴的 API 时
## 资源设计
### URL 结构
```
# Resources are nouns, plural, lowercase, kebab-case
GET /api/v1/users
GET /api/v1/users/:id
POST /api/v1/users
PUT /api/v1/users/:id
PATCH /api/v1/users/:id
DELETE /api/v1/users/:id
# Sub-resources for relationships
GET /api/v1/users/:id/orders
POST /api/v1/users/:id/orders
# Actions that don't map to CRUD (use verbs sparingly)
POST /api/v1/orders/:id/cancel
POST /api/v1/auth/login
POST /api/v1/auth/refresh
```
### 命名规则
```
# GOOD
/api/v1/team-members # kebab-case for multi-word resources
/api/v1/orders?status=active # query params for filtering
/api/v1/users/123/orders # nested resources for ownership
# BAD
/api/v1/getUsers # verb in URL
/api/v1/user # singular (use plural)
/api/v1/team_members # snake_case in URLs
/api/v1/users/123/getOrders # verb in nested resource
```
## HTTP 方法和状态码
### 方法语义
| 方法 | 幂等性 | 安全性 | 用途 |
|--------|-----------|------|---------|
| GET | 是 | 是 | 检索资源 |
| POST | 否 | 否 | 创建资源,触发操作 |
| PUT | 是 | 否 | 完全替换资源 |
| PATCH | 否\* | 否 | 部分更新资源 |
| DELETE | 是 | 否 | 删除资源 |
\*通过适当的实现PATCH 可以实现幂等
### 状态码参考
```
# Success
200 OK — GET, PUT, PATCH (with response body)
201 Created — POST (include Location header)
204 No Content — DELETE, PUT (no response body)
# Client Errors
400 Bad Request — Validation failure, malformed JSON
401 Unauthorized — Missing or invalid authentication
403 Forbidden — Authenticated but not authorized
404 Not Found — Resource doesn't exist
409 Conflict — Duplicate entry, state conflict
422 Unprocessable Entity — Semantically invalid (valid JSON, bad data)
429 Too Many Requests — Rate limit exceeded
# Server Errors
500 Internal Server Error — Unexpected failure (never expose details)
502 Bad Gateway — Upstream service failed
503 Service Unavailable — Temporary overload, include Retry-After
```
### 常见错误
```
# BAD: 200 for everything
{ "status": 200, "success": false, "error": "Not found" }
# GOOD: Use HTTP status codes semantically
HTTP/1.1 404 Not Found
{ "error": { "code": "not_found", "message": "User not found" } }
# BAD: 500 for validation errors
# GOOD: 400 or 422 with field-level details
# BAD: 200 for created resources
# GOOD: 201 with Location header
HTTP/1.1 201 Created
Location: /api/v1/users/abc-123
```
## 响应格式
### 成功响应
```json
{
"data": {
"id": "abc-123",
"email": "alice@example.com",
"name": "Alice",
"created_at": "2025-01-15T10:30:00Z"
}
}
```
### 集合响应(带分页)
```json
{
"data": [
{ "id": "abc-123", "name": "Alice" },
{ "id": "def-456", "name": "Bob" }
],
"meta": {
"total": 142,
"page": 1,
"per_page": 20,
"total_pages": 8
},
"links": {
"self": "/api/v1/users?page=1&per_page=20",
"next": "/api/v1/users?page=2&per_page=20",
"last": "/api/v1/users?page=8&per_page=20"
}
}
```
### 错误响应
```json
{
"error": {
"code": "validation_error",
"message": "Request validation failed",
"details": [
{
"field": "email",
"message": "Must be a valid email address",
"code": "invalid_format"
},
{
"field": "age",
"message": "Must be between 0 and 150",
"code": "out_of_range"
}
]
}
}
```
### 响应包装器变体
```typescript
// Option A: Envelope with data wrapper (recommended for public APIs)
interface ApiResponse<T> {
data: T;
meta?: PaginationMeta;
links?: PaginationLinks;
}
interface ApiError {
error: {
code: string;
message: string;
details?: FieldError[];
};
}
// Option B: Flat response (simpler, common for internal APIs)
// Success: just return the resource directly
// Error: return error object
// Distinguish by HTTP status code
```
## 分页
### 基于偏移量(简单)
```
GET /api/v1/users?page=2&per_page=20
# Implementation
SELECT * FROM users
ORDER BY created_at DESC
LIMIT 20 OFFSET 20;
```
**优点:** 易于实现,支持“跳转到第 N 页”
**缺点:** 在大偏移量时速度慢(例如 OFFSET 100000并发插入时结果不一致
### 基于游标(可扩展)
```
GET /api/v1/users?cursor=eyJpZCI6MTIzfQ&limit=20
# Implementation
SELECT * FROM users
WHERE id > :cursor_id
ORDER BY id ASC
LIMIT 21; -- fetch one extra to determine has_next
```
```json
{
"data": [...],
"meta": {
"has_next": true,
"next_cursor": "eyJpZCI6MTQzfQ"
}
}
```
**优点:** 无论位置如何,性能一致;在并发插入时结果稳定
**缺点:** 无法跳转到任意页面;游标是不透明的
### 何时使用哪种
| 用例 | 分页类型 |
|----------|----------------|
| 管理仪表板,小数据集 (<10K) | 偏移量 |
| 无限滚动,信息流,大数据集 | 游标 |
| 公共 API | 游标(默认)配合偏移量(可选) |
| 搜索结果 | 偏移量(用户期望有页码) |
## 过滤、排序和搜索
### 过滤
```
# Simple equality
GET /api/v1/orders?status=active&customer_id=abc-123
# Comparison operators (use bracket notation)
GET /api/v1/products?price[gte]=10&price[lte]=100
GET /api/v1/orders?created_at[after]=2025-01-01
# Multiple values (comma-separated)
GET /api/v1/products?category=electronics,clothing
# Nested fields (dot notation)
GET /api/v1/orders?customer.country=US
```
### 排序
```
# Single field (prefix - for descending)
GET /api/v1/products?sort=-created_at
# Multiple fields (comma-separated)
GET /api/v1/products?sort=-featured,price,-created_at
```
### 全文搜索
```
# Search query parameter
GET /api/v1/products?q=wireless+headphones
# Field-specific search
GET /api/v1/users?email=alice
```
### 稀疏字段集
```
# Return only specified fields (reduces payload)
GET /api/v1/users?fields=id,name,email
GET /api/v1/orders?fields=id,total,status&include=customer.name
```
## 认证和授权
### 基于令牌的认证
```
# Bearer token in Authorization header
GET /api/v1/users
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
# API key (for server-to-server)
GET /api/v1/data
X-API-Key: sk_live_abc123
```
### 授权模式
```typescript
// Resource-level: check ownership
app.get("/api/v1/orders/:id", async (req, res) => {
const order = await Order.findById(req.params.id);
if (!order) return res.status(404).json({ error: { code: "not_found" } });
if (order.userId !== req.user.id) return res.status(403).json({ error: { code: "forbidden" } });
return res.json({ data: order });
});
// Role-based: check permissions
app.delete("/api/v1/users/:id", requireRole("admin"), async (req, res) => {
await User.delete(req.params.id);
return res.status(204).send();
});
```
## 速率限制
### 响应头
```
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640000000
# When exceeded
HTTP/1.1 429 Too Many Requests
Retry-After: 60
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Try again in 60 seconds."
}
}
```
### 速率限制层级
| 层级 | 限制 | 时间窗口 | 用例 |
|------|-------|--------|----------|
| 匿名用户 | 30/分钟 | 每个 IP | 公共端点 |
| 认证用户 | 100/分钟 | 每个用户 | 标准 API 访问 |
| 高级用户 | 1000/分钟 | 每个 API 密钥 | 付费 API 套餐 |
| 内部服务 | 10000/分钟 | 每个服务 | 服务间调用 |
## 版本控制
### URL 路径版本控制(推荐)
```
/api/v1/users
/api/v2/users
```
**优点:** 明确,易于路由,可缓存
**缺点:** 版本间 URL 会变化
### 请求头版本控制
```
GET /api/users
Accept: application/vnd.myapp.v2+json
```
**优点:** URL 简洁
**缺点:** 测试更困难,容易忘记
### 版本控制策略
```
1. Start with /api/v1/ — don't version until you need to
2. Maintain at most 2 active versions (current + previous)
3. Deprecation timeline:
- Announce deprecation (6 months notice for public APIs)
- Add Sunset header: Sunset: Sat, 01 Jan 2026 00:00:00 GMT
- Return 410 Gone after sunset date
4. Non-breaking changes don't need a new version:
- Adding new fields to responses
- Adding new optional query parameters
- Adding new endpoints
5. Breaking changes require a new version:
- Removing or renaming fields
- Changing field types
- Changing URL structure
- Changing authentication method
```
## 实现模式
### TypeScript (Next.js API 路由)
```typescript
import { z } from "zod";
import { NextRequest, NextResponse } from "next/server";
const createUserSchema = z.object({
email: z.string().email(),
name: z.string().min(1).max(100),
});
export async function POST(req: NextRequest) {
const body = await req.json();
const parsed = createUserSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json({
error: {
code: "validation_error",
message: "Request validation failed",
details: parsed.error.issues.map(i => ({
field: i.path.join("."),
message: i.message,
code: i.code,
})),
},
}, { status: 422 });
}
const user = await createUser(parsed.data);
return NextResponse.json(
{ data: user },
{
status: 201,
headers: { Location: `/api/v1/users/${user.id}` },
},
);
}
```
### Python (Django REST Framework)
```python
from rest_framework import serializers, viewsets, status
from rest_framework.response import Response
class CreateUserSerializer(serializers.Serializer):
email = serializers.EmailField()
name = serializers.CharField(max_length=100)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "email", "name", "created_at"]
class UserViewSet(viewsets.ModelViewSet):
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
if self.action == "create":
return CreateUserSerializer
return UserSerializer
def create(self, request):
serializer = CreateUserSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = UserService.create(**serializer.validated_data)
return Response(
{"data": UserSerializer(user).data},
status=status.HTTP_201_CREATED,
headers={"Location": f"/api/v1/users/{user.id}"},
)
```
### Go (net/http)
```go
func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeError(w, http.StatusBadRequest, "invalid_json", "Invalid request body")
return
}
if err := req.Validate(); err != nil {
writeError(w, http.StatusUnprocessableEntity, "validation_error", err.Error())
return
}
user, err := h.service.Create(r.Context(), req)
if err != nil {
switch {
case errors.Is(err, domain.ErrEmailTaken):
writeError(w, http.StatusConflict, "email_taken", "Email already registered")
default:
writeError(w, http.StatusInternalServerError, "internal_error", "Internal error")
}
return
}
w.Header().Set("Location", fmt.Sprintf("/api/v1/users/%s", user.ID))
writeJSON(w, http.StatusCreated, map[string]any{"data": user})
}
```
## API 设计清单
发布新端点前请检查:
* \[ ] 资源 URL 遵循命名约定(复数、短横线连接、不含动词)
* \[ ] 使用了正确的 HTTP 方法GET 用于读取POST 用于创建等)
* \[ ] 返回了适当的状态码(不要所有情况都返回 200
* \[ ] 使用模式Zod, Pydantic, Bean Validation验证了输入
* \[ ] 错误响应遵循带代码和消息的标准格式
* \[ ] 列表端点实现了分页(游标或偏移量)
* \[ ] 需要认证(或明确标记为公开)
* \[ ] 检查了授权(用户只能访问自己的资源)
* \[ ] 配置了速率限制
* \[ ] 响应未泄露内部细节堆栈跟踪、SQL 错误)
* \[ ] 与现有端点命名一致camelCase 对比 snake\_case
* \[ ] 已记录(更新了 OpenAPI/Swagger 规范)

View File

@@ -0,0 +1,92 @@
---
name: article-writing
description: 根据提供的示例或品牌指导,以独特的语气撰写文章、指南、博客帖子、教程、新闻简报等长篇内容。当用户需要超过一段的精致书面内容时使用,尤其是当语气一致性、结构和可信度至关重要时。
origin: ECC
---
# 文章写作
撰写听起来像真人或真实品牌的长篇内容,而非通用的 AI 输出。
## 何时使用
* 起草博客文章、散文、发布帖、指南、教程或新闻简报时
* 将笔记、转录稿或研究转化为精炼文章时
* 根据示例匹配现有的创始人、运营者或品牌声音时
* 强化已有长篇文稿的结构、节奏和论据时
## 核心规则
1. **以具体事物开头**:示例、输出、轶事、数据、截图描述或代码块。
2. 先展示示例,再解释。
3. 倾向于简短、直接的句子,而非冗长的句子。
4. 尽可能使用具体且有来源的数据。
5. **绝不编造**传记事实、公司指标或客户证据。
## 声音捕捉工作流
如果用户需要特定的声音,请收集以下一项或多项:
* 已发表的文章
* 新闻简报
* X / LinkedIn 帖子
* 文档或备忘录
* 简短的风格指南
然后提取:
* 句子长度和节奏
* 声音是正式、对话式还是犀利的
* 偏好的修辞手法,如括号、列表、断句或设问
* 对幽默、观点和反主流框架的容忍度
* 格式习惯,如标题、项目符号、代码块和引用块
如果未提供声音参考,则默认为直接、运营者风格的声音:具体、实用,且少用夸张宣传。
## 禁止模式
删除并重写以下任何内容:
* 通用开头,如“在当今快速发展的格局中”
* 填充性过渡词,如“此外”和“而且”
* 夸张短语,如“游戏规则改变者”、“尖端”或“革命性的”
* 没有证据支持的模糊主张
* 没有提供上下文支持的传记或可信度声明
## 写作流程
1. 明确受众和目的。
2. 构建一个框架大纲,每个部分一个目的。
3. 每个部分都以证据、示例或场景开头。
4. 只在下一句话有其存在价值的地方展开。
5. 删除任何听起来像模板化或自我祝贺的内容。
## 结构指导
### 技术指南
* 以读者能获得什么开头
* 在每个主要部分使用代码或终端示例
* 以具体的要点结束,而非软性的总结
### 散文 / 观点文章
* 以张力、矛盾或尖锐的观察开头
* 每个部分只保持一个论点线索
* 使用能支撑观点的示例
### 新闻简报
* 保持首屏内容有力
* 将见解与更新结合,而非日记式填充
* 使用清晰的部分标签和易于浏览的结构
## 质量检查
交付前:
* 根据提供的来源核实事实主张
* 删除填充词和企业语言
* 确认声音与提供的示例匹配
* 确保每个部分都添加了新信息
* 检查针对目标平台的格式

View File

@@ -1,12 +1,23 @@
---
name: backend-patterns
description: 后端架构模式、API设计、数据库优化以及针对Node.js、Express和Next.js API路由的服务器端最佳实践。
description: 后端架构模式、API设计、数据库优化以及适用于Node.js、Express和Next.js API路由的服务器端最佳实践。
origin: ECC
---
# 后端开发模式
用于可扩展服务器端应用程序的后端架构模式和最佳实践。
## 何时激活
* 设计 REST 或 GraphQL API 端点时
* 实现仓储层、服务层或控制器层时
* 优化数据库查询N+1问题、索引、连接池
* 添加缓存Redis、内存缓存、HTTP 缓存头)时
* 设置后台作业或异步处理时
* 为 API 构建错误处理和验证结构时
* 构建中间件(认证、日志记录、速率限制)时
## API 设计模式
### RESTful API 结构

View File

@@ -1,12 +1,22 @@
---
name: clickhouse-io
description: ClickHouse数据库模式、查询优化、分析和数据工程最佳实践,适用于高性能分析工作负载
description: ClickHouse数据库模式、查询优化、分析以及高性能分析工作负载的数据工程最佳实践
origin: ECC
---
# ClickHouse 分析模式
用于高性能分析和数据工程的 ClickHouse 特定模式。
## 何时激活
* 设计 ClickHouse 表架构MergeTree 引擎选择)
* 编写分析查询(聚合、窗口函数、连接)
* 优化查询性能(分区裁剪、投影、物化视图)
* 摄取大量数据批量插入、Kafka 集成)
* 为分析目的从 PostgreSQL/MySQL 迁移到 ClickHouse
* 实现实时仪表板或时间序列分析
## 概述
ClickHouse 是一个用于在线分析处理 (OLAP) 的列式数据库管理系统 (DBMS)。它针对大型数据集上的快速分析查询进行了优化。

View File

@@ -1,12 +1,22 @@
---
name: coding-standards
description: 适用于TypeScript、JavaScript、React和Node.js开发的通用编码标准、最佳实践和模式。
origin: ECC
---
# 编码标准与最佳实践
适用于所有项目的通用编码标准。
## 何时激活
* 开始新项目或新模块时
* 审查代码质量和可维护性时
* 重构现有代码以遵循约定时
* 强制执行命名、格式或结构一致性时
* 设置代码检查、格式化或类型检查规则时
* 引导新贡献者熟悉编码规范时
## 代码质量原则
### 1. 可读性优先

View File

@@ -1,6 +1,7 @@
---
name: configure-ecc
description: Everything Claude Code 的交互式安装程序 — 引导用户选择并安装技能和规则到用户级或项目级目录,验证路径,并可选择性地优化已安装文件。
description: Everything Claude Code 的交互式安装程序 — 引导用户选择并安装技能和规则到用户级或项目级目录,验证路径,并可选择优化已安装文件。
origin: ECC
---
# 配置 Everything Claude Code (ECC)
@@ -83,25 +84,26 @@ Options:
对于每个选定的类别,打印下面的完整技能列表,并要求用户确认或取消选择特定的技能。如果列表超过 4 项,将列表打印为文本,并使用 `AskUserQuestion`,提供一个 "安装所有列出项" 的选项,以及一个 "其他" 选项供用户粘贴特定名称。
**类别框架与语言16 项技能)**
**类别框架与语言17 项技能)**
| 技能 | 描述 |
|-------|-------------|
| `backend-patterns` | Node.js/Express/Next.js 的后端架构、API 设计、服务器端最佳实践 |
| `coding-standards` | TypeScript、JavaScript、React、Node.js 的通用编码标准 |
| `django-patterns` | Django 架构、使用 DRF 的 REST API、ORM、缓存、信号、中间件 |
| `django-security` | Django 安全身份验证、CSRF、SQL 注入、XSS 防护 |
| `django-tdd` | 使用 pytest-django、factory\_boy、模拟、覆盖率 Django 测试 |
| `django-security` | Django 安全身份验证、CSRF、SQL 注入、XSS 防护 |
| `django-tdd` | 使用 pytest-django、factory\_boy、模拟、覆盖率进行 Django 测试 |
| `django-verification` | Django 验证循环:迁移、代码检查、测试、安全扫描 |
| `frontend-patterns` | React、Next.js、状态管理、性能、UI 模式 |
| `golang-patterns` | 地道的 Go 模式、健壮 Go 应用程序的约定 |
| `golang-testing` | Go 测试:表格驱动测试、子测试、基准测试、模糊测试 |
| `frontend-slides` | 零依赖的 HTML 演示文稿、样式预览以及 PPTX 到网页的转换 |
| `golang-patterns` | 地道的 Go 模式、构建健壮 Go 应用程序的约定 |
| `golang-testing` | Go 测试:表驱动测试、子测试、基准测试、模糊测试 |
| `java-coding-standards` | Spring Boot 的 Java 编码标准命名、不可变性、Optional、流 |
| `python-patterns` | Pythonic 惯用法、PEP 8、类型提示、最佳实践 |
| `python-testing` | 使用 pytest、TDD、夹具、模拟、参数化 Python 测试 |
| `python-testing` | 使用 pytest、TDD、固件、模拟、参数化进行 Python 测试 |
| `springboot-patterns` | Spring Boot 架构、REST API、分层服务、缓存、异步 |
| `springboot-security` | Spring Security身份验证/授权、验证、CSRF、密钥、速率限制 |
| `springboot-tdd` | 使用 JUnit 5、Mockito、MockMvc、Testcontainers Spring Boot TDD |
| `springboot-tdd` | 使用 JUnit 5、Mockito、MockMvc、Testcontainers 进行 Spring Boot TDD |
| `springboot-verification` | Spring Boot 验证:构建、静态分析、测试、安全扫描 |
**类别数据库3 项技能)**
@@ -125,6 +127,16 @@ Options:
| `tdd-workflow` | 强制要求 TDD覆盖率 80% 以上:单元测试、集成测试、端到端测试 |
| `verification-loop` | 验证和质量循环模式 |
**类别业务与内容5 项技能)**
| 技能 | 描述 |
|-------|-------------|
| `article-writing` | 使用笔记、示例或源文档,以指定的口吻进行长篇写作 |
| `content-engine` | 多平台社交内容、脚本和内容再利用工作流 |
| `market-research` | 带有来源标注的市场、竞争对手、基金和技术研究 |
| `investor-materials` | 宣传文稿、一页简介、投资者备忘录和财务模型 |
| `investor-outreach` | 个性化的投资者冷邮件、熟人介绍和后续跟进 |
**独立技能**
| 技能 | 描述 |

View File

@@ -0,0 +1,97 @@
---
name: content-engine
description: 为X、LinkedIn、TikTok、YouTube、新闻通讯和跨平台重新利用的多平台活动创建平台原生内容系统。适用于当用户需要社交媒体帖子、帖子串、脚本、内容日历或一个源资产在多个平台上清晰适配时。
origin: ECC
---
# 内容引擎
将一个想法转化为强大的、平台原生的内容,而不是到处发布相同的东西。
## 何时激活
* 撰写 X 帖子或主题串时
* 起草 LinkedIn 帖子或发布更新时
* 编写短视频或 YouTube 解说稿时
* 将文章、播客、演示或文档改写成社交内容时
* 围绕发布、里程碑或主题制定轻量级内容计划时
## 首要问题
明确:
* 来源素材:我们从什么内容改编
* 受众:构建者、投资者、客户、运营者,还是普通受众
* 平台X、LinkedIn、TikTok、YouTube、新闻简报还是多平台
* 目标:品牌认知、转化、招聘、建立权威、支持发布,还是互动参与
## 核心规则
1. 为平台进行适配。不要交叉发布相同的文案。
2. 开篇钩子比总结更重要。
3. 每篇帖子应承载一个清晰的想法。
4. 使用具体细节而非口号。
5. 保持呼吁行动小而清晰。
## 平台指南
### X
* 开场要快
* 每个帖子或主题串中的每条推文只讲一个想法
* 除非必要,避免在主文中放置链接
* 避免滥用话题标签
### LinkedIn
* 第一行要强有力
* 使用短段落
* 围绕经验教训、结果和要点进行更明确的框架构建
### TikTok / 短视频
* 前 3 秒必须抓住注意力
* 围绕视觉内容编写脚本,而不仅仅是旁白
* 一个演示、一个主张、一个行动号召
### YouTube
* 尽早展示结果
* 按章节构建内容
* 每 20-30 秒刷新一次视觉内容
### 新闻简报
* 提供一个清晰的视角,而不是一堆不相关的内容
* 使章节标题易于浏览
* 让开篇段落真正发挥作用
## 内容再利用流程
默认级联:
1. 锚定素材:文章、视频、演示、备忘录或发布文档
2. 提取 3-7 个原子化想法
3. 撰写平台原生的变体内容
4. 修剪不同输出内容中的重复部分
5. 使行动号召与平台意图保持一致
## 交付物
当被要求进行一项宣传活动时,请返回:
* 核心角度
* 针对特定平台的草稿
* 可选的发布顺序
* 可选的行动号召变体
* 发布前所需的任何缺失信息
## 质量门槛
在交付前检查:
* 每份草稿读起来都符合其平台原生风格
* 开篇钩子强大且具体
* 没有通用的炒作语言
* 除非特别要求,否则各平台间没有重复文案
* 行动号召与内容和受众相匹配

View File

@@ -0,0 +1,161 @@
---
name: content-hash-cache-pattern
description: 使用SHA-256内容哈希缓存昂贵的文件处理结果——路径无关、自动失效、服务层分离。
origin: ECC
---
# 内容哈希文件缓存模式
使用 SHA-256 内容哈希作为缓存键缓存昂贵的文件处理结果PDF 解析、文本提取、图像分析)。与基于路径的缓存不同,此方法在文件移动/重命名后仍然有效,并在内容更改时自动失效。
## 何时激活
* 构建文件处理管道时PDF、图像、文本提取
* 处理成本高且同一文件被重复处理时
* 需要一个 `--cache/--no-cache` CLI 选项时
* 希望在不修改现有纯函数的情况下为其添加缓存时
## 核心模式
### 1. 基于内容哈希的缓存键
使用文件内容(而非路径)作为缓存键:
```python
import hashlib
from pathlib import Path
_HASH_CHUNK_SIZE = 65536 # 64KB chunks for large files
def compute_file_hash(path: Path) -> str:
"""SHA-256 of file contents (chunked for large files)."""
if not path.is_file():
raise FileNotFoundError(f"File not found: {path}")
sha256 = hashlib.sha256()
with open(path, "rb") as f:
while True:
chunk = f.read(_HASH_CHUNK_SIZE)
if not chunk:
break
sha256.update(chunk)
return sha256.hexdigest()
```
**为什么使用内容哈希?** 文件重命名/移动 = 缓存命中。内容更改 = 自动失效。无需索引文件。
### 2. 用于缓存条目的冻结数据类
```python
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class CacheEntry:
file_hash: str
source_path: str
document: ExtractedDocument # The cached result
```
### 3. 基于文件的缓存存储
每个缓存条目都存储为 `{hash}.json` —— 通过哈希实现 O(1) 查找,无需索引文件。
```python
import json
from typing import Any
def write_cache(cache_dir: Path, entry: CacheEntry) -> None:
cache_dir.mkdir(parents=True, exist_ok=True)
cache_file = cache_dir / f"{entry.file_hash}.json"
data = serialize_entry(entry)
cache_file.write_text(json.dumps(data, ensure_ascii=False), encoding="utf-8")
def read_cache(cache_dir: Path, file_hash: str) -> CacheEntry | None:
cache_file = cache_dir / f"{file_hash}.json"
if not cache_file.is_file():
return None
try:
raw = cache_file.read_text(encoding="utf-8")
data = json.loads(raw)
return deserialize_entry(data)
except (json.JSONDecodeError, ValueError, KeyError):
return None # Treat corruption as cache miss
```
### 4. 服务层包装器(单一职责原则)
保持处理函数的纯净性。将缓存作为一个单独的服务层添加。
```python
def extract_with_cache(
file_path: Path,
*,
cache_enabled: bool = True,
cache_dir: Path = Path(".cache"),
) -> ExtractedDocument:
"""Service layer: cache check -> extraction -> cache write."""
if not cache_enabled:
return extract_text(file_path) # Pure function, no cache knowledge
file_hash = compute_file_hash(file_path)
# Check cache
cached = read_cache(cache_dir, file_hash)
if cached is not None:
logger.info("Cache hit: %s (hash=%s)", file_path.name, file_hash[:12])
return cached.document
# Cache miss -> extract -> store
logger.info("Cache miss: %s (hash=%s)", file_path.name, file_hash[:12])
doc = extract_text(file_path)
entry = CacheEntry(file_hash=file_hash, source_path=str(file_path), document=doc)
write_cache(cache_dir, entry)
return doc
```
## 关键设计决策
| 决策 | 理由 |
|----------|-----------|
| SHA-256 内容哈希 | 与路径无关,内容更改时自动失效 |
| `{hash}.json` 文件命名 | O(1) 查找,无需索引文件 |
| 服务层包装器 | 单一职责原则:提取功能保持纯净,缓存是独立的关注点 |
| 手动 JSON 序列化 | 完全控制冻结数据类的序列化 |
| 损坏时返回 `None` | 优雅降级,在下次运行时重新处理 |
| `cache_dir.mkdir(parents=True)` | 在首次写入时惰性创建目录 |
## 最佳实践
* **哈希内容,而非路径** —— 路径会变,内容标识不变
* 对大文件进行哈希时**分块处理** —— 避免将整个文件加载到内存中
* **保持处理函数的纯净性** —— 它们不应了解任何关于缓存的信息
* **记录缓存命中/未命中**,并使用截断的哈希值以便调试
* **优雅地处理损坏** —— 将无效的缓存条目视为未命中,永不崩溃
## 应避免的反模式
```python
# BAD: Path-based caching (breaks on file move/rename)
cache = {"/path/to/file.pdf": result}
# BAD: Adding cache logic inside the processing function (SRP violation)
def extract_text(path, *, cache_enabled=False, cache_dir=None):
if cache_enabled: # Now this function has two responsibilities
...
# BAD: Using dataclasses.asdict() with nested frozen dataclasses
# (can cause issues with complex nested types)
data = dataclasses.asdict(entry) # Use manual serialization instead
```
## 适用场景
* 文件处理管道PDF 解析、OCR、文本提取、图像分析
* 受益于 `--cache/--no-cache` 选项的 CLI 工具
* 跨多次运行出现相同文件的批处理
* 在不修改现有纯函数的情况下为其添加缓存
## 不适用场景
* 必须始终保持最新的数据(实时数据流)
* 缓存条目可能极其庞大的情况(应考虑使用流式处理)
* 结果依赖于文件内容之外参数的情况(例如,不同的提取配置)

View File

@@ -1,6 +1,7 @@
---
name: continuous-learning-v2
description: 基于本能的学习系统,通过钩子观察会话,创建具有置信度评分的原子本能,并将其化为技能/命令/代理。
description: 基于本能的学习系统,通过钩子观察会话,创建具有置信度评分的原子本能,并将其化为技能/命令/代理。
origin: ECC
version: 2.0.0
---
@@ -8,6 +9,16 @@ version: 2.0.0
一个高级学习系统,通过原子化的“本能”——带有置信度评分的小型习得行为——将你的 Claude Code 会话转化为可重用的知识。
部分灵感来源于 [humanplane](https://github.com/humanplane) 的 Homunculus 项目。
## 何时激活
* 设置从 Claude Code 会话中自动学习时
* 通过钩子配置基于本能的行为提取时
* 调整学习行为的置信度阈值时
* 审查、导出或导入本能库时
* 将本能进化为完整技能、命令或代理时
## v2 的新特性
| 特性 | v1 | v2 |
@@ -282,8 +293,8 @@ v2 与 v1 完全兼容:
## 相关链接
* [技能创建器](https://skill-creator.app) - 从仓库历史生成本能
* Homunculus - 启发 v2 架构的社区项目(原子观察、置信度评分、本能化管线
* [指南](https://x.com/affaanmustafa/status/2014040193557471352) - 持续学习部分
* Homunculus - 启发 v2 基于本能的架构的社区项目(原子观察、置信度评分、本能化管
* [指南](https://x.com/affaanmustafa/status/2014040193557471352) - 持续学习部分
***

View File

@@ -1,12 +1,21 @@
---
name: continuous-learning
description: 自动从Claude Code会话中提取可重模式,并将其保存为学习技能供未来使用。
description: 自动从Claude Code会话中提取可重复使用的模式,并将其保存为学习到的技能以供将来使用。
origin: ECC
---
# 持续学习技能
自动评估 Claude Code 会话的结尾,以提取可重用的模式,这些模式可以保存为学习到的技能。
## 何时激活
* 设置从 Claude Code 会话中自动提取模式
* 为会话评估配置停止钩子
*`~/.claude/skills/learned/` 中审查或整理已学习的技能
* 调整提取阈值或模式类别
* 比较 v1本方法与 v2基于本能的方法
## 工作原理
此技能作为 **停止钩子** 在每个会话结束时运行:
@@ -83,7 +92,7 @@ description: 自动从Claude Code会话中提取可重用模式并将其保
## 对比说明研究2025年1月
### 与 Homunculus 对比
### 与 Homunculus 对比
Homunculus v2 采用了更复杂的方法:

View File

@@ -0,0 +1,183 @@
---
name: cost-aware-llm-pipeline
description: LLM API 使用成本优化模式 —— 基于任务复杂度的模型路由、预算跟踪、重试逻辑和提示缓存。
origin: ECC
---
# 成本感知型 LLM 流水线
在保持质量的同时控制 LLM API 成本的模式。将模型路由、预算跟踪、重试逻辑和提示词缓存组合成一个可组合的流水线。
## 何时激活
* 构建调用 LLM APIClaude、GPT 等)的应用程序时
* 处理具有不同复杂度的批量项目时
* 需要将 API 支出控制在预算范围内时
* 需要在复杂任务上优化成本而不牺牲质量时
## 核心概念
### 1. 根据任务复杂度进行模型路由
自动为简单任务选择更便宜的模型,为复杂任务保留昂贵的模型。
```python
MODEL_SONNET = "claude-sonnet-4-6"
MODEL_HAIKU = "claude-haiku-4-5-20251001"
_SONNET_TEXT_THRESHOLD = 10_000 # chars
_SONNET_ITEM_THRESHOLD = 30 # items
def select_model(
text_length: int,
item_count: int,
force_model: str | None = None,
) -> str:
"""Select model based on task complexity."""
if force_model is not None:
return force_model
if text_length >= _SONNET_TEXT_THRESHOLD or item_count >= _SONNET_ITEM_THRESHOLD:
return MODEL_SONNET # Complex task
return MODEL_HAIKU # Simple task (3-4x cheaper)
```
### 2. 不可变的成本跟踪
使用冻结的数据类跟踪累计支出。每个 API 调用都会返回一个新的跟踪器 —— 永不改变状态。
```python
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class CostRecord:
model: str
input_tokens: int
output_tokens: int
cost_usd: float
@dataclass(frozen=True, slots=True)
class CostTracker:
budget_limit: float = 1.00
records: tuple[CostRecord, ...] = ()
def add(self, record: CostRecord) -> "CostTracker":
"""Return new tracker with added record (never mutates self)."""
return CostTracker(
budget_limit=self.budget_limit,
records=(*self.records, record),
)
@property
def total_cost(self) -> float:
return sum(r.cost_usd for r in self.records)
@property
def over_budget(self) -> bool:
return self.total_cost > self.budget_limit
```
### 3. 窄范围重试逻辑
仅在暂时性错误时重试。对于认证或错误请求错误,快速失败。
```python
from anthropic import (
APIConnectionError,
InternalServerError,
RateLimitError,
)
_RETRYABLE_ERRORS = (APIConnectionError, RateLimitError, InternalServerError)
_MAX_RETRIES = 3
def call_with_retry(func, *, max_retries: int = _MAX_RETRIES):
"""Retry only on transient errors, fail fast on others."""
for attempt in range(max_retries):
try:
return func()
except _RETRYABLE_ERRORS:
if attempt == max_retries - 1:
raise
time.sleep(2 ** attempt) # Exponential backoff
# AuthenticationError, BadRequestError etc. → raise immediately
```
### 4. 提示词缓存
缓存长的系统提示词,以避免在每个请求上重新发送它们。
```python
messages = [
{
"role": "user",
"content": [
{
"type": "text",
"text": system_prompt,
"cache_control": {"type": "ephemeral"}, # Cache this
},
{
"type": "text",
"text": user_input, # Variable part
},
],
}
]
```
## 组合
将所有四种技术组合到一个流水线函数中:
```python
def process(text: str, config: Config, tracker: CostTracker) -> tuple[Result, CostTracker]:
# 1. Route model
model = select_model(len(text), estimated_items, config.force_model)
# 2. Check budget
if tracker.over_budget:
raise BudgetExceededError(tracker.total_cost, tracker.budget_limit)
# 3. Call with retry + caching
response = call_with_retry(lambda: client.messages.create(
model=model,
messages=build_cached_messages(system_prompt, text),
))
# 4. Track cost (immutable)
record = CostRecord(model=model, input_tokens=..., output_tokens=..., cost_usd=...)
tracker = tracker.add(record)
return parse_result(response), tracker
```
## 价格参考2025-2026
| 模型 | 输入(美元/百万令牌) | 输出(美元/百万令牌) | 相对成本 |
|-------|---------------------|----------------------|---------------|
| Haiku 4.5 | $0.80 | $4.00 | 1x |
| Sonnet 4.6 | $3.00 | $15.00 | ~4x |
| Opus 4.5 | $15.00 | $75.00 | ~19x |
## 最佳实践
* **从最便宜的模型开始**,仅在达到复杂度阈值时才路由到昂贵的模型
* **在处理批次之前设置明确的预算限制** —— 尽早失败而不是超支
* **记录模型选择决策**,以便您可以根据实际数据调整阈值
* **对于超过 1024 个令牌的系统提示词,使用提示词缓存** —— 既能节省成本,又能降低延迟
* **切勿在认证或验证错误时重试** —— 仅针对暂时性故障(网络、速率限制、服务器错误)重试
## 应避免的反模式
* 无论复杂度如何,对所有请求都使用最昂贵的模型
* 对所有错误都进行重试(在永久性故障上浪费预算)
* 改变成本跟踪状态(使调试和审计变得困难)
* 在整个代码库中硬编码模型名称(使用常量或配置)
* 对重复的系统提示词忽略提示词缓存
## 适用场景
* 任何调用 Claude、OpenAI 或类似 LLM API 的应用程序
* 成本快速累积的批处理流水线
* 需要智能路由的多模型架构
* 需要预算护栏的生产系统

View File

@@ -0,0 +1,723 @@
---
name: cpp-coding-standards
description: 基于C++核心指南isocpp.github.io的C++编码标准。在编写、审查或重构C++代码时使用,以强制实施现代、安全和惯用的实践。
origin: ECC
---
# C++ 编码标准C++ 核心准则)
源自 [C++ 核心准则](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) 的现代 C++C++17/20/23综合编码标准。强制执行类型安全、资源安全、不变性和清晰性。
## 何时使用
* 编写新的 C++ 代码(类、函数、模板)
* 审查或重构现有的 C++ 代码
* 在 C++ 项目中做出架构决策
* 在 C++ 代码库中强制执行一致的风格
* 在语言特性之间做出选择(例如,`enum` 对比 `enum class`,原始指针对比智能指针)
### 何时不应使用
* 非 C++ 项目
* 无法采用现代 C++ 特性的遗留 C 代码库
* 特定准则与硬件限制冲突的嵌入式/裸机环境(选择性适配)
## 贯穿性原则
这些主题在整个准则中反复出现,并构成了基础:
1. **处处使用 RAII** (P.8, R.1, E.6, CP.20):将资源生命周期绑定到对象生命周期
2. **默认为不可变性** (P.10, Con.1-5, ES.25):从 `const`/`constexpr` 开始;可变性是例外
3. **类型安全** (P.4, I.4, ES.46-49, Enum.3):使用类型系统在编译时防止错误
4. **表达意图** (P.3, F.1, NL.1-2, T.10):名称、类型和概念应传达目的
5. **最小化复杂性** (F.2-3, ES.5, Per.4-5):简单的代码就是正确的代码
6. **值语义优于指针语义** (C.10, R.3-5, F.20, CP.31):优先按值返回和作用域对象
## 哲学与接口 (P.\*, I.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **P.1** | 直接在代码中表达想法 |
| **P.3** | 表达意图 |
| **P.4** | 理想情况下,程序应是静态类型安全的 |
| **P.5** | 优先编译时检查而非运行时检查 |
| **P.8** | 不要泄漏任何资源 |
| **P.10** | 优先不可变数据而非可变数据 |
| **I.1** | 使接口明确 |
| **I.2** | 避免非 const 全局变量 |
| **I.4** | 使接口精确且强类型化 |
| **I.11** | 切勿通过原始指针或引用转移所有权 |
| **I.23** | 保持函数参数数量少 |
### 应该做
```cpp
// P.10 + I.4: Immutable, strongly typed interface
struct Temperature {
double kelvin;
};
Temperature boil(const Temperature& water);
```
### 不应该做
```cpp
// Weak interface: unclear ownership, unclear units
double boil(double* temp);
// Non-const global variable
int g_counter = 0; // I.2 violation
```
## 函数 (F.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **F.1** | 将有意义的操作打包为精心命名的函数 |
| **F.2** | 函数应执行单一逻辑操作 |
| **F.3** | 保持函数简短简单 |
| **F.4** | 如果函数可能在编译时求值,则将其声明为 `constexpr` |
| **F.6** | 如果你的函数绝不能抛出异常,则将其声明为 `noexcept` |
| **F.8** | 优先纯函数 |
| **F.16** | 对于 "输入" 参数,按值传递廉价可复制类型,其他类型通过 `const&` 传递 |
| **F.20** | 对于 "输出" 值,优先返回值而非输出参数 |
| **F.21** | 要返回多个 "输出" 值,优先返回结构体 |
| **F.43** | 切勿返回指向局部对象的指针或引用 |
### 参数传递
```cpp
// F.16: Cheap types by value, others by const&
void print(int x); // cheap: by value
void analyze(const std::string& data); // expensive: by const&
void transform(std::string s); // sink: by value (will move)
// F.20 + F.21: Return values, not output parameters
struct ParseResult {
std::string token;
int position;
};
ParseResult parse(std::string_view input); // GOOD: return struct
// BAD: output parameters
void parse(std::string_view input,
std::string& token, int& pos); // avoid this
```
### 纯函数和 constexpr
```cpp
// F.4 + F.8: Pure, constexpr where possible
constexpr int factorial(int n) noexcept {
return (n <= 1) ? 1 : n * factorial(n - 1);
}
static_assert(factorial(5) == 120);
```
### 反模式
* 从函数返回 `T&&` (F.45)
* 使用 `va_arg` / C 风格可变参数 (F.55)
* 在传递给其他线程的 lambda 中通过引用捕获 (F.53)
* 返回 `const T`,这会抑制移动语义 (F.49)
## 类与类层次结构 (C.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **C.2** | 如果存在不变式,使用 `class`;如果数据成员独立变化,使用 `struct` |
| **C.9** | 最小化成员的暴露 |
| **C.20** | 如果你能避免定义默认操作,就这么做(零规则) |
| **C.21** | 如果你定义或 `=delete` 任何拷贝/移动/析构函数,则处理所有(五规则) |
| **C.35** | 基类析构函数:公开虚函数或受保护非虚函数 |
| **C.41** | 构造函数应创建完全初始化的对象 |
| **C.46** | 将单参数构造函数声明为 `explicit` |
| **C.67** | 多态类应禁止公开拷贝/移动 |
| **C.128** | 虚函数:精确指定 `virtual``override``final` 中的一个 |
### 零规则
```cpp
// C.20: Let the compiler generate special members
struct Employee {
std::string name;
std::string department;
int id;
// No destructor, copy/move constructors, or assignment operators needed
};
```
### 五规则
```cpp
// C.21: If you must manage a resource, define all five
class Buffer {
public:
explicit Buffer(std::size_t size)
: data_(std::make_unique<char[]>(size)), size_(size) {}
~Buffer() = default;
Buffer(const Buffer& other)
: data_(std::make_unique<char[]>(other.size_)), size_(other.size_) {
std::copy_n(other.data_.get(), size_, data_.get());
}
Buffer& operator=(const Buffer& other) {
if (this != &other) {
auto new_data = std::make_unique<char[]>(other.size_);
std::copy_n(other.data_.get(), other.size_, new_data.get());
data_ = std::move(new_data);
size_ = other.size_;
}
return *this;
}
Buffer(Buffer&&) noexcept = default;
Buffer& operator=(Buffer&&) noexcept = default;
private:
std::unique_ptr<char[]> data_;
std::size_t size_;
};
```
### 类层次结构
```cpp
// C.35 + C.128: Virtual destructor, use override
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0; // C.121: pure interface
};
class Circle : public Shape {
public:
explicit Circle(double r) : radius_(r) {}
double area() const override { return 3.14159 * radius_ * radius_; }
private:
double radius_;
};
```
### 反模式
* 在构造函数/析构函数中调用虚函数 (C.82)
* 在非平凡类型上使用 `memset`/`memcpy` (C.90)
* 为虚函数和重写函数提供不同的默认参数 (C.140)
* 将数据成员设为 `const` 或引用,这会抑制移动/拷贝 (C.12)
## 资源管理 (R.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **R.1** | 使用 RAII 自动管理资源 |
| **R.3** | 原始指针 (`T*`) 是非拥有的 |
| **R.5** | 优先作用域对象;不要不必要地在堆上分配 |
| **R.10** | 避免 `malloc()`/`free()` |
| **R.11** | 避免显式调用 `new``delete` |
| **R.20** | 使用 `unique_ptr``shared_ptr` 表示所有权 |
| **R.21** | 除非共享所有权,否则优先 `unique_ptr` 而非 `shared_ptr` |
| **R.22** | 使用 `make_shared()` 来创建 `shared_ptr` |
### 智能指针使用
```cpp
// R.11 + R.20 + R.21: RAII with smart pointers
auto widget = std::make_unique<Widget>("config"); // unique ownership
auto cache = std::make_shared<Cache>(1024); // shared ownership
// R.3: Raw pointer = non-owning observer
void render(const Widget* w) { // does NOT own w
if (w) w->draw();
}
render(widget.get());
```
### RAII 模式
```cpp
// R.1: Resource acquisition is initialization
class FileHandle {
public:
explicit FileHandle(const std::string& path)
: handle_(std::fopen(path.c_str(), "r")) {
if (!handle_) throw std::runtime_error("Failed to open: " + path);
}
~FileHandle() {
if (handle_) std::fclose(handle_);
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&& other) noexcept
: handle_(std::exchange(other.handle_, nullptr)) {}
FileHandle& operator=(FileHandle&& other) noexcept {
if (this != &other) {
if (handle_) std::fclose(handle_);
handle_ = std::exchange(other.handle_, nullptr);
}
return *this;
}
private:
std::FILE* handle_;
};
```
### 反模式
*`new`/`delete` (R.11)
* C++ 代码中的 `malloc()`/`free()` (R.10)
* 在单个表达式中进行多次资源分配 (R.13 -- 异常安全风险)
*`unique_ptr` 足够时使用 `shared_ptr` (R.21)
## 表达式与语句 (ES.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **ES.5** | 保持作用域小 |
| **ES.20** | 始终初始化对象 |
| **ES.23** | 优先 `{}` 初始化语法 |
| **ES.25** | 除非打算修改,否则将对象声明为 `const``constexpr` |
| **ES.28** | 使用 lambda 进行 `const` 变量的复杂初始化 |
| **ES.45** | 避免魔法常量;使用符号常量 |
| **ES.46** | 避免有损的算术转换 |
| **ES.47** | 使用 `nullptr` 而非 `0``NULL` |
| **ES.48** | 避免强制类型转换 |
| **ES.50** | 不要丢弃 `const` |
### 初始化
```cpp
// ES.20 + ES.23 + ES.25: Always initialize, prefer {}, default to const
const int max_retries{3};
const std::string name{"widget"};
const std::vector<int> primes{2, 3, 5, 7, 11};
// ES.28: Lambda for complex const initialization
const auto config = [&] {
Config c;
c.timeout = std::chrono::seconds{30};
c.retries = max_retries;
c.verbose = debug_mode;
return c;
}();
```
### 反模式
* 未初始化的变量 (ES.20)
* 使用 `0``NULL` 作为指针 (ES.47 -- 使用 `nullptr`)
* C 风格强制类型转换 (ES.48 -- 使用 `static_cast``const_cast` 等)
* 丢弃 `const` (ES.50)
* 没有命名常量的魔法数字 (ES.45)
* 混合有符号和无符号算术 (ES.100)
* 在嵌套作用域中重用名称 (ES.12)
## 错误处理 (E.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **E.1** | 在设计早期制定错误处理策略 |
| **E.2** | 抛出异常以表示函数无法执行其分配的任务 |
| **E.6** | 使用 RAII 防止泄漏 |
| **E.12** | 当抛出异常不可能或不可接受时,使用 `noexcept` |
| **E.14** | 使用专门设计的用户定义类型作为异常 |
| **E.15** | 按值抛出,按引用捕获 |
| **E.16** | 析构函数、释放和 swap 绝不能失败 |
| **E.17** | 不要试图在每个函数中捕获每个异常 |
### 异常层次结构
```cpp
// E.14 + E.15: Custom exception types, throw by value, catch by reference
class AppError : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
class NetworkError : public AppError {
public:
NetworkError(const std::string& msg, int code)
: AppError(msg), status_code(code) {}
int status_code;
};
void fetch_data(const std::string& url) {
// E.2: Throw to signal failure
throw NetworkError("connection refused", 503);
}
void run() {
try {
fetch_data("https://api.example.com");
} catch (const NetworkError& e) {
log_error(e.what(), e.status_code);
} catch (const AppError& e) {
log_error(e.what());
}
// E.17: Don't catch everything here -- let unexpected errors propagate
}
```
### 反模式
* 抛出内置类型,如 `int` 或字符串字面量 (E.14)
* 按值捕获(有切片风险) (E.15)
* 静默吞掉错误的空 catch 块
* 使用异常进行流程控制 (E.3)
* 基于全局状态(如 `errno`)的错误处理 (E.28)
## 常量与不可变性 (Con.\*)
### 所有规则
| 规则 | 摘要 |
|------|---------|
| **Con.1** | 默认情况下,使对象不可变 |
| **Con.2** | 默认情况下,使成员函数为 `const` |
| **Con.3** | 默认情况下,传递指向 `const` 的指针和引用 |
| **Con.4** | 对构造后不改变的值使用 `const` |
| **Con.5** | 对可在编译时计算的值使用 `constexpr` |
```cpp
// Con.1 through Con.5: Immutability by default
class Sensor {
public:
explicit Sensor(std::string id) : id_(std::move(id)) {}
// Con.2: const member functions by default
const std::string& id() const { return id_; }
double last_reading() const { return reading_; }
// Only non-const when mutation is required
void record(double value) { reading_ = value; }
private:
const std::string id_; // Con.4: never changes after construction
double reading_{0.0};
};
// Con.3: Pass by const reference
void display(const Sensor& s) {
std::cout << s.id() << ": " << s.last_reading() << '\n';
}
// Con.5: Compile-time constants
constexpr double PI = 3.14159265358979;
constexpr int MAX_SENSORS = 256;
```
## 并发与并行 (CP.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **CP.2** | 避免数据竞争 |
| **CP.3** | 最小化可写数据的显式共享 |
| **CP.4** | 从任务的角度思考,而非线程 |
| **CP.8** | 不要使用 `volatile` 进行同步 |
| **CP.20** | 使用 RAII切勿使用普通的 `lock()`/`unlock()` |
| **CP.21** | 使用 `std::scoped_lock` 来获取多个互斥量 |
| **CP.22** | 持有锁时切勿调用未知代码 |
| **CP.42** | 不要在没有条件的情况下等待 |
| **CP.44** | 记得为你的 `lock_guard``unique_lock` 命名 |
| **CP.100** | 除非绝对必要,否则不要使用无锁编程 |
### 安全加锁
```cpp
// CP.20 + CP.44: RAII locks, always named
class ThreadSafeQueue {
public:
void push(int value) {
std::lock_guard<std::mutex> lock(mutex_); // CP.44: named!
queue_.push(value);
cv_.notify_one();
}
int pop() {
std::unique_lock<std::mutex> lock(mutex_);
// CP.42: Always wait with a condition
cv_.wait(lock, [this] { return !queue_.empty(); });
const int value = queue_.front();
queue_.pop();
return value;
}
private:
std::mutex mutex_; // CP.50: mutex with its data
std::condition_variable cv_;
std::queue<int> queue_;
};
```
### 多个互斥量
```cpp
// CP.21: std::scoped_lock for multiple mutexes (deadlock-free)
void transfer(Account& from, Account& to, double amount) {
std::scoped_lock lock(from.mutex_, to.mutex_);
from.balance_ -= amount;
to.balance_ += amount;
}
```
### 反模式
* 使用 `volatile` 进行同步 (CP.8 -- 它仅用于硬件 I/O)
* 分离线程 (CP.26 -- 生命周期管理变得几乎不可能)
* 未命名的锁保护:`std::lock_guard<std::mutex>(m);` 会立即销毁 (CP.44)
* 调用回调时持有锁 (CP.22 -- 死锁风险)
* 没有深厚专业知识就进行无锁编程 (CP.100)
## 模板与泛型编程 (T.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **T.1** | 使用模板来提高抽象级别 |
| **T.2** | 使用模板为多种参数类型表达算法 |
| **T.10** | 为所有模板参数指定概念 |
| **T.11** | 尽可能使用标准概念 |
| **T.13** | 对于简单概念,优先使用简写符号 |
| **T.43** | 优先 `using` 而非 `typedef` |
| **T.120** | 仅在确实需要时使用模板元编程 |
| **T.144** | 不要特化函数模板(改用重载) |
### 概念 (C++20)
```cpp
#include <concepts>
// T.10 + T.11: Constrain templates with standard concepts
template<std::integral T>
T gcd(T a, T b) {
while (b != 0) {
a = std::exchange(b, a % b);
}
return a;
}
// T.13: Shorthand concept syntax
void sort(std::ranges::random_access_range auto& range) {
std::ranges::sort(range);
}
// Custom concept for domain-specific constraints
template<typename T>
concept Serializable = requires(const T& t) {
{ t.serialize() } -> std::convertible_to<std::string>;
};
template<Serializable T>
void save(const T& obj, const std::string& path);
```
### 反模式
* 在可见命名空间中使用无约束模板 (T.47)
* 特化函数模板而非重载 (T.144)
*`constexpr` 足够时使用模板元编程 (T.120)
* 使用 `typedef` 而非 `using` (T.43)
## 标准库 (SL.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **SL.1** | 尽可能使用库 |
| **SL.2** | 优先标准库而非其他库 |
| **SL.con.1** | 优先 `std::array``std::vector` 而非 C 数组 |
| **SL.con.2** | 默认情况下优先 `std::vector` |
| **SL.str.1** | 使用 `std::string` 来拥有字符序列 |
| **SL.str.2** | 使用 `std::string_view` 来引用字符序列 |
| **SL.io.50** | 避免 `endl`(使用 `'\n'` -- `endl` 会强制刷新) |
```cpp
// SL.con.1 + SL.con.2: Prefer vector/array over C arrays
const std::array<int, 4> fixed_data{1, 2, 3, 4};
std::vector<std::string> dynamic_data;
// SL.str.1 + SL.str.2: string owns, string_view observes
std::string build_greeting(std::string_view name) {
return "Hello, " + std::string(name) + "!";
}
// SL.io.50: Use '\n' not endl
std::cout << "result: " << value << '\n';
```
## 枚举 (Enum.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **Enum.1** | 优先枚举而非宏 |
| **Enum.3** | 优先 `enum class` 而非普通 `enum` |
| **Enum.5** | 不要对枚举项使用全大写 |
| **Enum.6** | 避免未命名的枚举 |
```cpp
// Enum.3 + Enum.5: Scoped enum, no ALL_CAPS
enum class Color { red, green, blue };
enum class LogLevel { debug, info, warning, error };
// BAD: plain enum leaks names, ALL_CAPS clashes with macros
enum { RED, GREEN, BLUE }; // Enum.3 + Enum.5 + Enum.6 violation
#define MAX_SIZE 100 // Enum.1 violation -- use constexpr
```
## 源文件与命名 (SF.*, NL.*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **SF.1** | 代码文件使用 `.cpp`,接口文件使用 `.h` |
| **SF.7** | 不要在头文件的全局作用域内写 `using namespace` |
| **SF.8** | 所有 `.h` 文件都应使用 `#include` 防护 |
| **SF.11** | 头文件应是自包含的 |
| **NL.5** | 避免在名称中编码类型信息(不要使用匈牙利命名法) |
| **NL.8** | 使用一致的命名风格 |
| **NL.9** | 仅宏名使用 ALL\_CAPS |
| **NL.10** | 优先使用 `underscore_style` 命名 |
### 头文件防护
```cpp
// SF.8: Include guard (or #pragma once)
#ifndef PROJECT_MODULE_WIDGET_H
#define PROJECT_MODULE_WIDGET_H
// SF.11: Self-contained -- include everything this header needs
#include <string>
#include <vector>
namespace project::module {
class Widget {
public:
explicit Widget(std::string name);
const std::string& name() const;
private:
std::string name_;
};
} // namespace project::module
#endif // PROJECT_MODULE_WIDGET_H
```
### 命名约定
```cpp
// NL.8 + NL.10: Consistent underscore_style
namespace my_project {
constexpr int max_buffer_size = 4096; // NL.9: not ALL_CAPS (it's not a macro)
class tcp_connection { // underscore_style class
public:
void send_message(std::string_view msg);
bool is_connected() const;
private:
std::string host_; // trailing underscore for members
int port_;
};
} // namespace my_project
```
### 反模式
* 在头文件的全局作用域内使用 `using namespace std;` (SF.7)
* 依赖包含顺序的头文件 (SF.10, SF.11)
* 匈牙利命名法,如 `strName``iCount` (NL.5)
* 宏以外的事物使用 ALL\_CAPS (NL.9)
## 性能 (Per.\*)
### 关键规则
| 规则 | 摘要 |
|------|---------|
| **Per.1** | 不要无故优化 |
| **Per.2** | 不要过早优化 |
| **Per.6** | 没有测量数据,不要断言性能 |
| **Per.7** | 设计时应考虑便于优化 |
| **Per.10** | 依赖静态类型系统 |
| **Per.11** | 将计算从运行时移至编译时 |
| **Per.19** | 以可预测的方式访问内存 |
### 指导原则
```cpp
// Per.11: Compile-time computation where possible
constexpr auto lookup_table = [] {
std::array<int, 256> table{};
for (int i = 0; i < 256; ++i) {
table[i] = i * i;
}
return table;
}();
// Per.19: Prefer contiguous data for cache-friendliness
std::vector<Point> points; // GOOD: contiguous
std::vector<std::unique_ptr<Point>> indirect_points; // BAD: pointer chasing
```
### 反模式
* 在没有性能分析数据的情况下进行优化 (Per.1, Per.6)
* 选择“巧妙”的低级代码而非清晰的抽象 (Per.4, Per.5)
* 忽略数据布局和缓存行为 (Per.19)
## 快速参考检查清单
在标记 C++ 工作完成之前:
* \[ ] 没有裸 `new`/`delete` —— 使用智能指针或 RAII (R.11)
* \[ ] 对象在声明时初始化 (ES.20)
* \[ ] 变量默认是 `const`/`constexpr` (Con.1, ES.25)
* \[ ] 成员函数尽可能设为 `const` (Con.2)
* \[ ] 使用 `enum class` 而非普通 `enum` (Enum.3)
* \[ ] 使用 `nullptr` 而非 `0`/`NULL` (ES.47)
* \[ ] 没有窄化转换 (ES.46)
* \[ ] 没有 C 风格转换 (ES.48)
* \[ ] 单参数构造函数是 `explicit` (C.46)
* \[ ] 应用了零法则或五法则 (C.20, C.21)
* \[ ] 基类析构函数是 public virtual 或 protected non-virtual (C.35)
* \[ ] 模板使用概念进行约束 (T.10)
* \[ ] 头文件全局作用域内没有 `using namespace` (SF.7)
* \[ ] 头文件有包含防护且是自包含的 (SF.8, SF.11)
* \[ ] 锁使用 RAII (`scoped_lock`/`lock_guard`) (CP.20)
* \[ ] 异常是自定义类型,按值抛出,按引用捕获 (E.14, E.15)
* \[ ] 使用 `'\n'` 而非 `std::endl` (SL.io.50)
* \[ ] 没有魔数 (ES.45)

View File

@@ -1,6 +1,7 @@
---
name: cpp-testing
description: 仅编写/更新/修复C++测试、配置GoogleTest/CTest、诊断失败或不稳定的测试或添加覆盖率/消毒器时使用。
description: 仅用于编写/更新/修复C++测试、配置GoogleTest/CTest、诊断失败或不稳定的测试或添加覆盖率/消毒器时使用。
origin: ECC
---
# C++ 测试(代理技能)

View File

@@ -0,0 +1,335 @@
---
name: database-migrations
description: 数据库迁移最佳实践涵盖模式变更、数据迁移、回滚以及零停机部署适用于PostgreSQL、MySQL及常用ORMPrisma、Drizzle、Django、TypeORM、golang-migrate
origin: ECC
---
# 数据库迁移模式
为生产系统提供安全、可逆的数据库模式变更。
## 何时激活
* 创建或修改数据库表
* 添加/删除列或索引
* 运行数据迁移(回填、转换)
* 计划零停机模式变更
* 为新项目设置迁移工具
## 核心原则
1. **每个变更都是一次迁移** — 切勿手动更改生产数据库
2. **迁移在生产环境中是只进不退的** — 回滚使用新的前向迁移
3. **模式迁移和数据迁移是分开的** — 切勿在一个迁移中混合 DDL 和 DML
4. **针对生产规模的数据测试迁移** — 适用于 100 行的迁移可能在 1000 万行时锁定
5. **迁移一旦部署就是不可变的** — 切勿编辑已在生产中运行的迁移
## 迁移安全检查清单
应用任何迁移之前:
* \[ ] 迁移同时包含 UP 和 DOWN或明确标记为不可逆
* \[ ] 对大表没有全表锁(使用并发操作)
* \[ ] 新列有默认值或可为空(切勿添加没有默认值的 NOT NULL
* \[ ] 索引是并发创建的(对于现有表,不与 CREATE TABLE 内联创建)
* \[ ] 数据回填是与模式变更分开的迁移
* \[ ] 已针对生产数据副本进行测试
* \[ ] 回滚计划已记录
## PostgreSQL 模式
### 安全地添加列
```sql
-- GOOD: Nullable column, no lock
ALTER TABLE users ADD COLUMN avatar_url TEXT;
-- GOOD: Column with default (Postgres 11+ is instant, no rewrite)
ALTER TABLE users ADD COLUMN is_active BOOLEAN NOT NULL DEFAULT true;
-- BAD: NOT NULL without default on existing table (requires full rewrite)
ALTER TABLE users ADD COLUMN role TEXT NOT NULL;
-- This locks the table and rewrites every row
```
### 无停机添加索引
```sql
-- BAD: Blocks writes on large tables
CREATE INDEX idx_users_email ON users (email);
-- GOOD: Non-blocking, allows concurrent writes
CREATE INDEX CONCURRENTLY idx_users_email ON users (email);
-- Note: CONCURRENTLY cannot run inside a transaction block
-- Most migration tools need special handling for this
```
### 重命名列(零停机)
切勿在生产中直接重命名。使用扩展-收缩模式:
```sql
-- Step 1: Add new column (migration 001)
ALTER TABLE users ADD COLUMN display_name TEXT;
-- Step 2: Backfill data (migration 002, data migration)
UPDATE users SET display_name = username WHERE display_name IS NULL;
-- Step 3: Update application code to read/write both columns
-- Deploy application changes
-- Step 4: Stop writing to old column, drop it (migration 003)
ALTER TABLE users DROP COLUMN username;
```
### 安全地删除列
```sql
-- Step 1: Remove all application references to the column
-- Step 2: Deploy application without the column reference
-- Step 3: Drop column in next migration
ALTER TABLE orders DROP COLUMN legacy_status;
-- For Django: use SeparateDatabaseAndState to remove from model
-- without generating DROP COLUMN (then drop in next migration)
```
### 大型数据迁移
```sql
-- BAD: Updates all rows in one transaction (locks table)
UPDATE users SET normalized_email = LOWER(email);
-- GOOD: Batch update with progress
DO $$
DECLARE
batch_size INT := 10000;
rows_updated INT;
BEGIN
LOOP
UPDATE users
SET normalized_email = LOWER(email)
WHERE id IN (
SELECT id FROM users
WHERE normalized_email IS NULL
LIMIT batch_size
FOR UPDATE SKIP LOCKED
);
GET DIAGNOSTICS rows_updated = ROW_COUNT;
RAISE NOTICE 'Updated % rows', rows_updated;
EXIT WHEN rows_updated = 0;
COMMIT;
END LOOP;
END $$;
```
## Prisma (TypeScript/Node.js)
### 工作流
```bash
# Create migration from schema changes
npx prisma migrate dev --name add_user_avatar
# Apply pending migrations in production
npx prisma migrate deploy
# Reset database (dev only)
npx prisma migrate reset
# Generate client after schema changes
npx prisma generate
```
### 模式示例
```prisma
model User {
id String @id @default(cuid())
email String @unique
name String?
avatarUrl String? @map("avatar_url")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
orders Order[]
@@map("users")
@@index([email])
}
```
### 自定义 SQL 迁移
对于 Prisma 无法表达的操作(并发索引、数据回填):
```bash
# Create empty migration, then edit the SQL manually
npx prisma migrate dev --create-only --name add_email_index
```
```sql
-- migrations/20240115_add_email_index/migration.sql
-- Prisma cannot generate CONCURRENTLY, so we write it manually
CREATE INDEX CONCURRENTLY IF NOT EXISTS idx_users_email ON users (email);
```
## Drizzle (TypeScript/Node.js)
### 工作流
```bash
# Generate migration from schema changes
npx drizzle-kit generate
# Apply migrations
npx drizzle-kit migrate
# Push schema directly (dev only, no migration file)
npx drizzle-kit push
```
### 模式示例
```typescript
import { pgTable, text, timestamp, uuid, boolean } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: uuid("id").primaryKey().defaultRandom(),
email: text("email").notNull().unique(),
name: text("name"),
isActive: boolean("is_active").notNull().default(true),
createdAt: timestamp("created_at").notNull().defaultNow(),
updatedAt: timestamp("updated_at").notNull().defaultNow(),
});
```
## Django (Python)
### 工作流
```bash
# Generate migration from model changes
python manage.py makemigrations
# Apply migrations
python manage.py migrate
# Show migration status
python manage.py showmigrations
# Generate empty migration for custom SQL
python manage.py makemigrations --empty app_name -n description
```
### 数据迁移
```python
from django.db import migrations
def backfill_display_names(apps, schema_editor):
User = apps.get_model("accounts", "User")
batch_size = 5000
users = User.objects.filter(display_name="")
while users.exists():
batch = list(users[:batch_size])
for user in batch:
user.display_name = user.username
User.objects.bulk_update(batch, ["display_name"], batch_size=batch_size)
def reverse_backfill(apps, schema_editor):
pass # Data migration, no reverse needed
class Migration(migrations.Migration):
dependencies = [("accounts", "0015_add_display_name")]
operations = [
migrations.RunPython(backfill_display_names, reverse_backfill),
]
```
### SeparateDatabaseAndState
从 Django 模型中删除列,而不立即从数据库中删除:
```python
class Migration(migrations.Migration):
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(model_name="user", name="legacy_field"),
],
database_operations=[], # Don't touch the DB yet
),
]
```
## golang-migrate (Go)
### 工作流
```bash
# Create migration pair
migrate create -ext sql -dir migrations -seq add_user_avatar
# Apply all pending migrations
migrate -path migrations -database "$DATABASE_URL" up
# Rollback last migration
migrate -path migrations -database "$DATABASE_URL" down 1
# Force version (fix dirty state)
migrate -path migrations -database "$DATABASE_URL" force VERSION
```
### 迁移文件
```sql
-- migrations/000003_add_user_avatar.up.sql
ALTER TABLE users ADD COLUMN avatar_url TEXT;
CREATE INDEX CONCURRENTLY idx_users_avatar ON users (avatar_url) WHERE avatar_url IS NOT NULL;
-- migrations/000003_add_user_avatar.down.sql
DROP INDEX IF EXISTS idx_users_avatar;
ALTER TABLE users DROP COLUMN IF EXISTS avatar_url;
```
## 零停机迁移策略
对于关键的生产变更,遵循扩展-收缩模式:
```
Phase 1: EXPAND
- Add new column/table (nullable or with default)
- Deploy: app writes to BOTH old and new
- Backfill existing data
Phase 2: MIGRATE
- Deploy: app reads from NEW, writes to BOTH
- Verify data consistency
Phase 3: CONTRACT
- Deploy: app only uses NEW
- Drop old column/table in separate migration
```
### 时间线示例
```
Day 1: Migration adds new_status column (nullable)
Day 1: Deploy app v2 — writes to both status and new_status
Day 2: Run backfill migration for existing rows
Day 3: Deploy app v3 — reads from new_status only
Day 7: Migration drops old status column
```
## 反模式
| 反模式 | 为何会失败 | 更好的方法 |
|-------------|-------------|-----------------|
| 在生产中手动执行 SQL | 没有审计追踪,不可重复 | 始终使用迁移文件 |
| 编辑已部署的迁移 | 导致环境间出现差异 | 改为创建新迁移 |
| 没有默认值的 NOT NULL | 锁定表,重写所有行 | 添加可为空列,回填数据,然后添加约束 |
| 在大表上内联创建索引 | 在构建期间阻塞写入 | 使用 CREATE INDEX CONCURRENTLY |
| 在一个迁移中混合模式和数据的变更 | 难以回滚,事务时间长 | 分开的迁移 |
| 在移除代码之前删除列 | 应用程序在缺失列时出错 | 先移除代码,下一次部署再删除列 |

View File

@@ -0,0 +1,432 @@
---
name: deployment-patterns
description: 部署工作流、CI/CD流水线模式、Docker容器化、健康检查、回滚策略以及Web应用程序的生产就绪检查清单。
origin: ECC
---
# 部署模式
生产环境部署工作流和 CI/CD 最佳实践。
## 何时启用
* 设置 CI/CD 流水线时
* 将应用容器化Docker
* 规划部署策略(蓝绿、金丝雀、滚动)时
* 实现健康检查和就绪探针时
* 准备生产发布时
* 配置环境特定设置时
## 部署策略
### 滚动部署(默认)
逐步替换实例——在发布过程中,新旧版本同时运行。
```
Instance 1: v1 → v2 (update first)
Instance 2: v1 (still running v1)
Instance 3: v1 (still running v1)
Instance 1: v2
Instance 2: v1 → v2 (update second)
Instance 3: v1
Instance 1: v2
Instance 2: v2
Instance 3: v1 → v2 (update last)
```
**优点:** 零停机时间,渐进式发布
**缺点:** 两个版本同时运行——需要向后兼容的更改
**适用场景:** 标准部署,向后兼容的更改
### 蓝绿部署
运行两个相同的环境。原子化地切换流量。
```
Blue (v1) ← traffic
Green (v2) idle, running new version
# After verification:
Blue (v1) idle (becomes standby)
Green (v2) ← traffic
```
**优点:** 即时回滚(切换回蓝色环境),切换干净利落
**缺点:** 部署期间需要双倍的基础设施
**适用场景:** 关键服务,对问题零容忍
### 金丝雀部署
首先将一小部分流量路由到新版本。
```
v1: 95% of traffic
v2: 5% of traffic (canary)
# If metrics look good:
v1: 50% of traffic
v2: 50% of traffic
# Final:
v2: 100% of traffic
```
**优点:** 在全量发布前,通过真实流量发现问题
**缺点:** 需要流量分割基础设施和监控
**适用场景:** 高流量服务,风险性更改,功能标志
## Docker
### 多阶段 Dockerfile (Node.js)
```dockerfile
# Stage 1: Install dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --production=false
# Stage 2: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
RUN npm prune --production
# Stage 3: Production image
FROM node:22-alpine AS runner
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=builder --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=builder --chown=appuser:appgroup /app/dist ./dist
COPY --from=builder --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
```
### 多阶段 Dockerfile (Go)
```dockerfile
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server
FROM alpine:3.19 AS runner
RUN apk --no-cache add ca-certificates
RUN adduser -D -u 1001 appuser
USER appuser
COPY --from=builder /server /server
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:8080/health || exit 1
CMD ["/server"]
```
### 多阶段 Dockerfile (Python/Django)
```dockerfile
FROM python:3.12-slim AS builder
WORKDIR /app
RUN pip install --no-cache-dir uv
COPY requirements.txt .
RUN uv pip install --system --no-cache -r requirements.txt
FROM python:3.12-slim AS runner
WORKDIR /app
RUN useradd -r -u 1001 appuser
USER appuser
COPY --from=builder /usr/local/lib/python3.12/site-packages /usr/local/lib/python3.12/site-packages
COPY --from=builder /usr/local/bin /usr/local/bin
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health/')" || exit 1
CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
```
### Docker 最佳实践
```
# GOOD practices
- Use specific version tags (node:22-alpine, not node:latest)
- Multi-stage builds to minimize image size
- Run as non-root user
- Copy dependency files first (layer caching)
- Use .dockerignore to exclude node_modules, .git, tests
- Add HEALTHCHECK instruction
- Set resource limits in docker-compose or k8s
# BAD practices
- Running as root
- Using :latest tags
- Copying entire repo in one COPY layer
- Installing dev dependencies in production image
- Storing secrets in image (use env vars or secrets manager)
```
## CI/CD 流水线
### GitHub Actions (标准流水线)
```yaml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
if: always()
with:
name: coverage
path: coverage/
build:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production
steps:
- name: Deploy to production
run: |
# Platform-specific deployment command
# Railway: railway up
# Vercel: vercel --prod
# K8s: kubectl set image deployment/app app=ghcr.io/${{ github.repository }}:${{ github.sha }}
echo "Deploying ${{ github.sha }}"
```
### 流水线阶段
```
PR opened:
lint → typecheck → unit tests → integration tests → preview deploy
Merged to main:
lint → typecheck → unit tests → integration tests → build image → deploy staging → smoke tests → deploy production
```
## 健康检查
### 健康检查端点
```typescript
// Simple health check
app.get("/health", (req, res) => {
res.status(200).json({ status: "ok" });
});
// Detailed health check (for internal monitoring)
app.get("/health/detailed", async (req, res) => {
const checks = {
database: await checkDatabase(),
redis: await checkRedis(),
externalApi: await checkExternalApi(),
};
const allHealthy = Object.values(checks).every(c => c.status === "ok");
res.status(allHealthy ? 200 : 503).json({
status: allHealthy ? "ok" : "degraded",
timestamp: new Date().toISOString(),
version: process.env.APP_VERSION || "unknown",
uptime: process.uptime(),
checks,
});
});
async function checkDatabase(): Promise<HealthCheck> {
try {
await db.query("SELECT 1");
return { status: "ok", latency_ms: 2 };
} catch (err) {
return { status: "error", message: "Database unreachable" };
}
}
```
### Kubernetes 探针
```yaml
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
failureThreshold: 2
startupProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 0
periodSeconds: 5
failureThreshold: 30 # 30 * 5s = 150s max startup time
```
## 环境配置
### 十二要素应用模式
```bash
# All config via environment variables — never in code
DATABASE_URL=postgres://user:pass@host:5432/db
REDIS_URL=redis://host:6379/0
API_KEY=${API_KEY} # injected by secrets manager
LOG_LEVEL=info
PORT=3000
# Environment-specific behavior
NODE_ENV=production # or staging, development
APP_ENV=production # explicit app environment
```
### 配置验证
```typescript
import { z } from "zod";
const envSchema = z.object({
NODE_ENV: z.enum(["development", "staging", "production"]),
PORT: z.coerce.number().default(3000),
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]).default("info"),
});
// Validate at startup — fail fast if config is wrong
export const env = envSchema.parse(process.env);
```
## 回滚策略
### 即时回滚
```bash
# Docker/Kubernetes: point to previous image
kubectl rollout undo deployment/app
# Vercel: promote previous deployment
vercel rollback
# Railway: redeploy previous commit
railway up --commit <previous-sha>
# Database: rollback migration (if reversible)
npx prisma migrate resolve --rolled-back <migration-name>
```
### 回滚检查清单
* \[ ] 之前的镜像/制品可用且已标记
* \[ ] 数据库迁移向后兼容(无破坏性更改)
* \[ ] 功能标志可以在不部署的情况下禁用新功能
* \[ ] 监控警报已配置,用于错误率飙升
* \[ ] 在生产发布前,回滚已在预演环境测试
## 生产就绪检查清单
在任何生产部署之前:
### 应用
* \[ ] 所有测试通过(单元、集成、端到端)
* \[ ] 代码或配置文件中没有硬编码的密钥
* \[ ] 错误处理覆盖所有边缘情况
* \[ ] 日志是结构化的JSON且不包含 PII
* \[ ] 健康检查端点返回有意义的状态
### 基础设施
* \[ ] Docker 镜像可重复构建(版本已固定)
* \[ ] 环境变量已记录并在启动时验证
* \[ ] 资源限制已设置CPU、内存
* \[ ] 水平伸缩已配置(最小/最大实例数)
* \[ ] 所有端点均已启用 SSL/TLS
### 监控
* \[ ] 应用指标已导出(请求率、延迟、错误)
* \[ ] 已配置错误率超过阈值的警报
* \[ ] 日志聚合已设置(结构化日志,可搜索)
* \[ ] 健康端点有正常运行时间监控
### 安全
* \[ ] 依赖项已扫描 CVE
* \[ ] CORS 仅配置允许的来源
* \[ ] 公共端点已启用速率限制
* \[ ] 身份验证和授权已验证
* \[ ] 安全头已设置CSP、HSTS、X-Frame-Options
### 运维
* \[ ] 回滚计划已记录并测试
* \[ ] 数据库迁移已针对生产规模的数据进行测试
* \[ ] 常见故障场景的应急预案
* \[ ] 待命轮换和升级路径已定义

View File

@@ -1,6 +1,7 @@
---
name: django-patterns
description: Django架构模式使用DRFREST API设计、ORM最佳实践缓存信号中间件以及生产级Django应用程序。
description: Django架构模式使用DRF设计REST APIORM最佳实践缓存信号中间件以及生产级Django应用程序。
origin: ECC
---
# Django 开发模式

View File

@@ -1,6 +1,7 @@
---
name: django-security
description: Django安全最佳实践,身份验证,授权CSRF保护,SQL注入预防XSS预防和安全部署配置。
description: Django 安全最佳实践、认证、授权CSRF 防护、SQL 注入预防XSS 预防和安全部署配置。
origin: ECC
---
# Django 安全最佳实践

View File

@@ -1,6 +1,7 @@
---
name: django-tdd
description: Django测试策略包括pytest-django、TDD方法、factory_boy、模拟、覆盖率以及测试Django REST Framework API。
description: Django 测试策略,包括 pytest-django、TDD 方法、factory_boy、模拟、覆盖率以及测试 Django REST Framework API。
origin: ECC
---
# 使用 TDD 进行 Django 测试

View File

@@ -1,12 +1,21 @@
---
name: django-verification
description: Verification loop for Django projects: migrations, linting, tests with coverage, security scans, and deployment readiness checks before release or PR.
description: "Django项目的验证循环迁移、代码检查、带覆盖率的测试、安全扫描以及在发布或PR前的部署就绪检查。"
origin: ECC
---
# Django 验证循环
在发起 PR 之前、进行重大更改之后以及部署之前运行,以确保 Django 应用程序的质量和安全性。
## 何时激活
* 在为一个 Django 项目开启拉取请求之前
* 在重大模型变更、迁移更新或依赖升级之后
* 用于暂存或生产环境的预部署验证
* 运行完整的环境 → 代码检查 → 测试 → 安全 → 部署就绪流水线时
* 验证迁移安全性和测试覆盖率时
## 阶段 1: 环境检查
```bash

View File

@@ -0,0 +1,365 @@
---
name: docker-patterns
description: 用于本地开发的Docker和Docker Compose模式包括容器安全、网络、卷策略和多服务编排。
origin: ECC
---
# Docker 模式
适用于容器化开发的 Docker 和 Docker Compose 最佳实践。
## 何时启用
* 为本地开发设置 Docker Compose
* 设计多容器架构
* 排查容器网络或卷问题
* 审查 Dockerfile 的安全性和大小
* 从本地开发迁移到容器化工作流
## 用于本地开发的 Docker Compose
### 标准 Web 应用栈
```yaml
# docker-compose.yml
services:
app:
build:
context: .
target: dev # Use dev stage of multi-stage Dockerfile
ports:
- "3000:3000"
volumes:
- .:/app # Bind mount for hot reload
- /app/node_modules # Anonymous volume -- preserves container deps
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/app_dev
- REDIS_URL=redis://redis:6379/0
- NODE_ENV=development
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: npm run dev
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: app_dev
volumes:
- pgdata:/var/lib/postgresql/data
- ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redisdata:/data
mailpit: # Local email testing
image: axllent/mailpit
ports:
- "8025:8025" # Web UI
- "1025:1025" # SMTP
volumes:
pgdata:
redisdata:
```
### 开发与生产 Dockerfile
```dockerfile
# Stage: dependencies
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Stage: dev (hot reload, debug tools)
FROM node:22-alpine AS dev
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["npm", "run", "dev"]
# Stage: build
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build && npm prune --production
# Stage: production (minimal image)
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S appgroup && adduser -S appuser -u 1001
USER appuser
COPY --from=build --chown=appuser:appgroup /app/dist ./dist
COPY --from=build --chown=appuser:appgroup /app/node_modules ./node_modules
COPY --from=build --chown=appuser:appgroup /app/package.json ./
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
```
### 覆盖文件
```yaml
# docker-compose.override.yml (auto-loaded, dev-only settings)
services:
app:
environment:
- DEBUG=app:*
- LOG_LEVEL=debug
ports:
- "9229:9229" # Node.js debugger
# docker-compose.prod.yml (explicit for production)
services:
app:
build:
target: production
restart: always
deploy:
resources:
limits:
cpus: "1.0"
memory: 512M
```
```bash
# Development (auto-loads override)
docker compose up
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
## 网络
### 服务发现
同一 Compose 网络中的服务可通过服务名解析:
```
# From "app" container:
postgres://postgres:postgres@db:5432/app_dev # "db" resolves to the db container
redis://redis:6379/0 # "redis" resolves to the redis container
```
### 自定义网络
```yaml
services:
frontend:
networks:
- frontend-net
api:
networks:
- frontend-net
- backend-net
db:
networks:
- backend-net # Only reachable from api, not frontend
networks:
frontend-net:
backend-net:
```
### 仅暴露所需内容
```yaml
services:
db:
ports:
- "127.0.0.1:5432:5432" # Only accessible from host, not network
# Omit ports entirely in production -- accessible only within Docker network
```
## 卷策略
```yaml
volumes:
# Named volume: persists across container restarts, managed by Docker
pgdata:
# Bind mount: maps host directory into container (for development)
# - ./src:/app/src
# Anonymous volume: preserves container-generated content from bind mount override
# - /app/node_modules
```
### 常见模式
```yaml
services:
app:
volumes:
- .:/app # Source code (bind mount for hot reload)
- /app/node_modules # Protect container's node_modules from host
- /app/.next # Protect build cache
db:
volumes:
- pgdata:/var/lib/postgresql/data # Persistent data
- ./scripts/init.sql:/docker-entrypoint-initdb.d/init.sql # Init scripts
```
## 容器安全
### Dockerfile 加固
```dockerfile
# 1. Use specific tags (never :latest)
FROM node:22.12-alpine3.20
# 2. Run as non-root
RUN addgroup -g 1001 -S app && adduser -S app -u 1001
USER app
# 3. Drop capabilities (in compose)
# 4. Read-only root filesystem where possible
# 5. No secrets in image layers
```
### Compose 安全
```yaml
services:
app:
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp
- /app/.cache
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE # Only if binding to ports < 1024
```
### 密钥管理
```yaml
# GOOD: Use environment variables (injected at runtime)
services:
app:
env_file:
- .env # Never commit .env to git
environment:
- API_KEY # Inherits from host environment
# GOOD: Docker secrets (Swarm mode)
secrets:
db_password:
file: ./secrets/db_password.txt
services:
db:
secrets:
- db_password
# BAD: Hardcoded in image
# ENV API_KEY=sk-proj-xxxxx # NEVER DO THIS
```
## .dockerignore
```
node_modules
.git
.env
.env.*
dist
coverage
*.log
.next
.cache
docker-compose*.yml
Dockerfile*
README.md
tests/
```
## 调试
### 常用命令
```bash
# View logs
docker compose logs -f app # Follow app logs
docker compose logs --tail=50 db # Last 50 lines from db
# Execute commands in running container
docker compose exec app sh # Shell into app
docker compose exec db psql -U postgres # Connect to postgres
# Inspect
docker compose ps # Running services
docker compose top # Processes in each container
docker stats # Resource usage
# Rebuild
docker compose up --build # Rebuild images
docker compose build --no-cache app # Force full rebuild
# Clean up
docker compose down # Stop and remove containers
docker compose down -v # Also remove volumes (DESTRUCTIVE)
docker system prune # Remove unused images/containers
```
### 调试网络问题
```bash
# Check DNS resolution inside container
docker compose exec app nslookup db
# Check connectivity
docker compose exec app wget -qO- http://api:3000/health
# Inspect network
docker network ls
docker network inspect <project>_default
```
## 反模式
```
# BAD: Using docker compose in production without orchestration
# Use Kubernetes, ECS, or Docker Swarm for production multi-container workloads
# BAD: Storing data in containers without volumes
# Containers are ephemeral -- all data lost on restart without volumes
# BAD: Running as root
# Always create and use a non-root user
# BAD: Using :latest tag
# Pin to specific versions for reproducible builds
# BAD: One giant container with all services
# Separate concerns: one process per container
# BAD: Putting secrets in docker-compose.yml
# Use .env files (gitignored) or Docker secrets
```

View File

@@ -0,0 +1,329 @@
---
name: e2e-testing
description: Playwright E2E 测试模式、页面对象模型、配置、CI/CD 集成、工件管理和不稳定测试策略。
origin: ECC
---
# E2E 测试模式
用于构建稳定、快速且可维护的 E2E 测试套件的全面 Playwright 模式。
## 测试文件组织
```
tests/
├── e2e/
│ ├── auth/
│ │ ├── login.spec.ts
│ │ ├── logout.spec.ts
│ │ └── register.spec.ts
│ ├── features/
│ │ ├── browse.spec.ts
│ │ ├── search.spec.ts
│ │ └── create.spec.ts
│ └── api/
│ └── endpoints.spec.ts
├── fixtures/
│ ├── auth.ts
│ └── data.ts
└── playwright.config.ts
```
## 页面对象模型 (POM)
```typescript
import { Page, Locator } from '@playwright/test'
export class ItemsPage {
readonly page: Page
readonly searchInput: Locator
readonly itemCards: Locator
readonly createButton: Locator
constructor(page: Page) {
this.page = page
this.searchInput = page.locator('[data-testid="search-input"]')
this.itemCards = page.locator('[data-testid="item-card"]')
this.createButton = page.locator('[data-testid="create-btn"]')
}
async goto() {
await this.page.goto('/items')
await this.page.waitForLoadState('networkidle')
}
async search(query: string) {
await this.searchInput.fill(query)
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
await this.page.waitForLoadState('networkidle')
}
async getItemCount() {
return await this.itemCards.count()
}
}
```
## 测试结构
```typescript
import { test, expect } from '@playwright/test'
import { ItemsPage } from '../../pages/ItemsPage'
test.describe('Item Search', () => {
let itemsPage: ItemsPage
test.beforeEach(async ({ page }) => {
itemsPage = new ItemsPage(page)
await itemsPage.goto()
})
test('should search by keyword', async ({ page }) => {
await itemsPage.search('test')
const count = await itemsPage.getItemCount()
expect(count).toBeGreaterThan(0)
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
await page.screenshot({ path: 'artifacts/search-results.png' })
})
test('should handle no results', async ({ page }) => {
await itemsPage.search('xyznonexistent123')
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
expect(await itemsPage.getItemCount()).toBe(0)
})
})
```
## Playwright 配置
```typescript
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: [
['html', { outputFolder: 'playwright-report' }],
['junit', { outputFile: 'playwright-results.xml' }],
['json', { outputFile: 'playwright-results.json' }]
],
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
actionTimeout: 10000,
navigationTimeout: 30000,
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
timeout: 120000,
},
})
```
## 不稳定测试模式
### 隔离
```typescript
test('flaky: complex search', async ({ page }) => {
test.fixme(true, 'Flaky - Issue #123')
// test code...
})
test('conditional skip', async ({ page }) => {
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
// test code...
})
```
### 识别不稳定性
```bash
npx playwright test tests/search.spec.ts --repeat-each=10
npx playwright test tests/search.spec.ts --retries=3
```
### 常见原因与修复
**竞态条件:**
```typescript
// Bad: assumes element is ready
await page.click('[data-testid="button"]')
// Good: auto-wait locator
await page.locator('[data-testid="button"]').click()
```
**网络时序:**
```typescript
// Bad: arbitrary timeout
await page.waitForTimeout(5000)
// Good: wait for specific condition
await page.waitForResponse(resp => resp.url().includes('/api/data'))
```
**动画时序:**
```typescript
// Bad: click during animation
await page.click('[data-testid="menu-item"]')
// Good: wait for stability
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
await page.waitForLoadState('networkidle')
await page.locator('[data-testid="menu-item"]').click()
```
## 产物管理
### 截图
```typescript
await page.screenshot({ path: 'artifacts/after-login.png' })
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
```
### 跟踪记录
```typescript
await browser.startTracing(page, {
path: 'artifacts/trace.json',
screenshots: true,
snapshots: true,
})
// ... test actions ...
await browser.stopTracing()
```
### 视频
```typescript
// In playwright.config.ts
use: {
video: 'retain-on-failure',
videosPath: 'artifacts/videos/'
}
```
## CI/CD 集成
```yaml
# .github/workflows/e2e.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx playwright install --with-deps
- run: npx playwright test
env:
BASE_URL: ${{ vars.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
```
## 测试报告模板
```markdown
# E2E 测试报告
**日期:** YYYY-MM-DD HH:MM
**持续时间:** Xm Ys
**状态:** 通过 / 失败
## 概要
- 总计X | 通过Y (Z%) | 失败A | 不稳定B | 跳过C
## 失败的测试
### test-name
**文件:** `tests/e2e/feature.spec.ts:45`
**错误:** 期望元素可见
**截图:** artifacts/failed.png
**建议修复:** [description]
## 产物
- HTML 报告playwright-report/index.html
- 截图artifacts/*.png
- 视频artifacts/videos/*.webm
- 追踪文件artifacts/*.zip
```
## 钱包 / Web3 测试
```typescript
test('wallet connection', async ({ page, context }) => {
// Mock wallet provider
await context.addInitScript(() => {
window.ethereum = {
isMetaMask: true,
request: async ({ method }) => {
if (method === 'eth_requestAccounts')
return ['0x1234567890123456789012345678901234567890']
if (method === 'eth_chainId') return '0x1'
}
}
})
await page.goto('/')
await page.locator('[data-testid="connect-wallet"]').click()
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
})
```
## 金融 / 关键流程测试
```typescript
test('trade execution', async ({ page }) => {
// Skip on production — real money
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
await page.goto('/markets/test-market')
await page.locator('[data-testid="position-yes"]').click()
await page.locator('[data-testid="trade-amount"]').fill('1.0')
// Verify preview
const preview = page.locator('[data-testid="trade-preview"]')
await expect(preview).toContainText('1.0')
// Confirm and wait for blockchain
await page.locator('[data-testid="confirm-trade"]').click()
await page.waitForResponse(
resp => resp.url().includes('/api/trade') && resp.status() === 200,
{ timeout: 30000 }
)
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
})
```

View File

@@ -1,6 +1,7 @@
---
name: eval-harness
description: 克劳德代码会话的正式评估框架实施评估驱动开发EDD原则
origin: ECC
tools: Read, Write, Edit, Bash, Grep, Glob
---
@@ -8,6 +9,14 @@ tools: Read, Write, Edit, Bash, Grep, Glob
一个用于 Claude Code 会话的正式评估框架,实现了评估驱动开发 (EDD) 原则。
## 何时激活
* 为 AI 辅助工作流程设置评估驱动开发 (EDD)
* 定义 Claude Code 任务完成的标准(通过/失败)
* 使用 pass@k 指标衡量代理可靠性
* 为提示或代理变更创建回归测试套件
* 跨模型版本对代理性能进行基准测试
## 理念
评估驱动开发将评估视为 "AI 开发的单元测试"

View File

@@ -0,0 +1,244 @@
---
name: foundation-models-on-device
description: 苹果FoundationModels框架用于设备上的LLM——文本生成、使用@Generable进行引导生成、工具调用以及在iOS 26+中的快照流。
---
# FoundationModels设备端 LLMiOS 26
使用 FoundationModels 框架将苹果的设备端语言模型集成到应用中的模式。涵盖文本生成、使用 `@Generable` 的结构化输出、自定义工具调用以及快照流式传输——全部在设备端运行,以保护隐私并支持离线使用。
## 何时启用
* 使用 Apple Intelligence 在设备端构建 AI 功能
* 无需依赖云端即可生成或总结文本
* 从自然语言输入中提取结构化数据
* 为特定领域的 AI 操作实现自定义工具调用
* 流式传输结构化响应以实现实时 UI 更新
* 需要保护隐私的 AI数据不离开设备
## 核心模式 — 可用性检查
在创建会话之前,始终检查模型可用性:
```swift
struct GenerativeView: View {
private var model = SystemLanguageModel.default
var body: some View {
switch model.availability {
case .available:
ContentView()
case .unavailable(.deviceNotEligible):
Text("Device not eligible for Apple Intelligence")
case .unavailable(.appleIntelligenceNotEnabled):
Text("Please enable Apple Intelligence in Settings")
case .unavailable(.modelNotReady):
Text("Model is downloading or not ready")
case .unavailable(let other):
Text("Model unavailable: \(other)")
}
}
}
```
## 核心模式 — 基础会话
```swift
// Single-turn: create a new session each time
let session = LanguageModelSession()
let response = try await session.respond(to: "What's a good month to visit Paris?")
print(response.content)
// Multi-turn: reuse session for conversation context
let session = LanguageModelSession(instructions: """
You are a cooking assistant.
Provide recipe suggestions based on ingredients.
Keep suggestions brief and practical.
""")
let first = try await session.respond(to: "I have chicken and rice")
let followUp = try await session.respond(to: "What about a vegetarian option?")
```
指令的关键点:
* 定义模型的角色("你是一位导师"
* 指定要做什么("帮助提取日历事件"
* 设置风格偏好("尽可能简短地回答"
* 添加安全措施("对于危险请求,回复'我无法提供帮助'"
## 核心模式 — 使用 @Generable 进行引导式生成
生成结构化的 Swift 类型,而不是原始字符串:
### 1. 定义可生成类型
```swift
@Generable(description: "Basic profile information about a cat")
struct CatProfile {
var name: String
@Guide(description: "The age of the cat", .range(0...20))
var age: Int
@Guide(description: "A one sentence profile about the cat's personality")
var profile: String
}
```
### 2. 请求结构化输出
```swift
let response = try await session.respond(
to: "Generate a cute rescue cat",
generating: CatProfile.self
)
// Access structured fields directly
print("Name: \(response.content.name)")
print("Age: \(response.content.age)")
print("Profile: \(response.content.profile)")
```
### 支持的 @Guide 约束
* `.range(0...20)` — 数值范围
* `.count(3)` — 数组元素数量
* `description:` — 生成的语义引导
## 核心模式 — 工具调用
让模型调用自定义代码以执行特定领域的任务:
### 1. 定义工具
```swift
struct RecipeSearchTool: Tool {
let name = "recipe_search"
let description = "Search for recipes matching a given term and return a list of results."
@Generable
struct Arguments {
var searchTerm: String
var numberOfResults: Int
}
func call(arguments: Arguments) async throws -> ToolOutput {
let recipes = await searchRecipes(
term: arguments.searchTerm,
limit: arguments.numberOfResults
)
return .string(recipes.map { "- \($0.name): \($0.description)" }.joined(separator: "\n"))
}
}
```
### 2. 创建带工具的会话
```swift
let session = LanguageModelSession(tools: [RecipeSearchTool()])
let response = try await session.respond(to: "Find me some pasta recipes")
```
### 3. 处理工具错误
```swift
do {
let answer = try await session.respond(to: "Find a recipe for tomato soup.")
} catch let error as LanguageModelSession.ToolCallError {
print(error.tool.name)
if case .databaseIsEmpty = error.underlyingError as? RecipeSearchToolError {
// Handle specific tool error
}
}
```
## 核心模式 — 快照流式传输
使用 `PartiallyGenerated` 类型为实时 UI 流式传输结构化响应:
```swift
@Generable
struct TripIdeas {
@Guide(description: "Ideas for upcoming trips")
var ideas: [String]
}
let stream = session.streamResponse(
to: "What are some exciting trip ideas?",
generating: TripIdeas.self
)
for try await partial in stream {
// partial: TripIdeas.PartiallyGenerated (all properties Optional)
print(partial)
}
```
### SwiftUI 集成
```swift
@State private var partialResult: TripIdeas.PartiallyGenerated?
@State private var errorMessage: String?
var body: some View {
List {
ForEach(partialResult?.ideas ?? [], id: \.self) { idea in
Text(idea)
}
}
.overlay {
if let errorMessage { Text(errorMessage).foregroundStyle(.red) }
}
.task {
do {
let stream = session.streamResponse(to: prompt, generating: TripIdeas.self)
for try await partial in stream {
partialResult = partial
}
} catch {
errorMessage = error.localizedDescription
}
}
}
```
## 关键设计决策
| 决策 | 理由 |
|----------|-----------|
| 设备端执行 | 隐私性——数据不离开设备;支持离线工作 |
| 4,096 个令牌限制 | 设备端模型约束;跨会话分块处理大数据 |
| 快照流式传输(非增量) | 对结构化输出友好;每个快照都是一个完整的部分状态 |
| `@Generable` 宏 | 为结构化生成提供编译时安全性;自动生成 `PartiallyGenerated` 类型 |
| 每个会话单次请求 | `isResponding` 防止并发请求;如有需要,创建多个会话 |
| `response.content`(而非 `.output` | 正确的 API——始终通过 `.content` 属性访问结果 |
## 最佳实践
* 在创建会话之前**始终检查 `model.availability`**——处理所有不可用的情况
* **使用 `instructions`** 来引导模型行为——它们的优先级高于提示词
* 在发送新请求之前**检查 `isResponding`**——会话一次处理一个请求
* 通过 `response.content` **访问结果**——而不是 `.output`
* **将大型输入分块处理**——4,096 个令牌的限制适用于指令、提示词和输出的总和
* 对于结构化输出**使用 `@Generable`**——比解析原始字符串提供更强的保证
* **使用 `GenerationOptions(temperature:)`** 来调整创造力(值越高越有创意)
* **使用 Instruments 进行监控**——使用 Xcode Instruments 来分析请求性能
## 应避免的反模式
* 未先检查 `model.availability` 就创建会话
* 发送超过 4,096 个令牌上下文窗口的输入
* 尝试在单个会话上进行并发请求
* 使用 `.output` 而不是 `.content` 来访问响应数据
*`@Generable` 结构化输出可行时,却去解析原始字符串响应
* 在单个提示词中构建复杂的多步逻辑——将其拆分为多个聚焦的提示词
* 假设模型始终可用——设备的资格和设置各不相同
## 何时使用
* 为注重隐私的应用进行设备端文本生成
* 从用户输入(表单、自然语言命令)中提取结构化数据
* 必须离线工作的 AI 辅助功能
* 逐步显示生成内容的流式 UI
* 通过工具调用(搜索、计算、查找)执行特定领域的 AI 操作

View File

@@ -1,12 +1,23 @@
---
name: frontend-patterns
description: React、Next.js、状态管理、性能优化和UI最佳实践的前端开发模式。
origin: ECC
---
# 前端开发模式
适用于 React、Next.js 和高性能用户界面的现代前端模式。
## 何时激活
* 构建 React 组件(组合、属性、渲染)
* 管理状态useState、useReducer、Zustand、Context
* 实现数据获取SWR、React Query、服务器组件
* 优化性能(记忆化、虚拟化、代码分割)
* 处理表单验证、受控输入、Zod 模式)
* 处理客户端路由和导航
* 构建可访问、响应式的 UI 模式
## 组件模式
### 组合优于继承

View File

@@ -0,0 +1,195 @@
---
name: frontend-slides
description: 从零开始或通过转换PowerPoint文件创建令人惊艳、动画丰富的HTML演示文稿。当用户想要构建演示文稿、将PPT/PPTX转换为网页格式或为演讲/推介创建幻灯片时使用。帮助非设计师通过视觉探索而非抽象选择发现他们的美学。
origin: ECC
---
# 前端幻灯片
创建零依赖、动画丰富的 HTML 演示文稿,完全在浏览器中运行。
灵感来源于 [zarazhangrui](https://github.com/zarazhangrui) 的作品中展示的视觉探索方法。
## 何时启用
* 创建演讲文稿、推介文稿、研讨会文稿或内部演示文稿时
*`.ppt``.pptx` 幻灯片转换为 HTML 演示文稿时
* 改进现有 HTML 演示文稿的布局、动效或排版时
* 与尚不清楚其设计偏好的用户一起探索演示文稿风格时
## 不可妥协的原则
1. **零依赖**:默认使用一个包含内联 CSS 和 JS 的自包含 HTML 文件。
2. **必须适配视口**:每张幻灯片必须适配一个视口,内部不允许滚动。
3. **展示,而非描述**:使用视觉预览,而非抽象的风格问卷。
4. **独特设计**:避免通用的紫色渐变、白色背景加 Inter 字体、模板化的文稿外观。
5. **生产质量**:保持代码注释清晰、可访问、响应式且性能良好。
在生成之前,请阅读 `STYLE_PRESETS.md` 以了解视口安全的 CSS 基础、密度限制、预设目录和 CSS 陷阱。
## 工作流程
### 1. 检测模式
选择一条路径:
* **新演示文稿**:用户有主题、笔记或完整草稿
* **PPT 转换**:用户有 `.ppt``.pptx`
* **增强**:用户已有 HTML 幻灯片并希望改进
### 2. 发现内容
只询问最低限度的必要信息:
* 目的:推介、教学、会议演讲、内部更新
* 长度:短 (5-10张)、中 (10-20张)、长 (20+张)
* 内容状态:已完成文案、粗略笔记、仅主题
如果用户有内容,请他们在进行样式设计前粘贴内容。
### 3. 发现风格
默认采用视觉探索方式。
如果用户已经知道所需的预设,则跳过预览并直接使用。
否则:
1. 询问文稿应营造何种感觉:印象深刻、充满活力、专注、激发灵感。
2.`.ecc-design/slide-previews/` 中生成 **3 个单幻灯片预览文件**
3. 每个预览必须是自包含的,清晰地展示排版/色彩/动效,并且幻灯片内容大约保持在 100 行以内。
4. 询问用户保留哪个预览或混合哪些元素。
在将情绪映射到风格时,请使用 `STYLE_PRESETS.md` 中的预设指南。
### 4. 构建演示文稿
输出以下之一:
* `presentation.html`
* `[presentation-name].html`
仅当文稿包含提取的或用户提供的图像时,才使用 `assets/` 文件夹。
必需的结构:
* 语义化的幻灯片部分
* 来自 `STYLE_PRESETS.md` 的视口安全的 CSS 基础
* 用于主题值的 CSS 自定义属性
* 用于键盘、滚轮和触摸导航的演示文稿控制器类
* 用于揭示动画的 Intersection Observer
* 支持减少动效
### 5. 强制执行视口适配
将此视为硬性规定。
规则:
* 每个 `.slide` 必须使用 `height: 100vh; height: 100dvh; overflow: hidden;`
* 所有字体和间距必须随 `clamp()` 缩放
* 当内容无法适配时,将其拆分为多张幻灯片
* 切勿通过将文本缩小到可读尺寸以下来解决溢出问题
* 绝不允许幻灯片内部出现滚动条
使用 `STYLE_PRESETS.md` 中的密度限制和强制性 CSS 代码块。
### 6. 验证
在这些尺寸下检查完成的文稿:
* 1920x1080
* 1280x720
* 768x1024
* 375x667
* 667x375
如果可以使用浏览器自动化,请使用它来验证没有幻灯片溢出且键盘导航正常工作。
### 7. 交付
在交付时:
* 除非用户希望保留,否则删除临时预览文件
* 在有用时使用适合当前平台的开源工具打开文稿
* 总结文件路径、使用的预设、幻灯片数量以及简单的主题自定义点
为当前操作系统使用正确的开源工具:
* macOS: `open file.html`
* Linux: `xdg-open file.html`
* Windows: `start "" file.html`
## PPT / PPTX 转换
对于 PowerPoint 转换:
1. 优先使用 `python3``python-pptx` 来提取文本、图像和备注。
2. 如果 `python-pptx` 不可用,询问是安装它还是回退到基于手动/导出的工作流程。
3. 保留幻灯片顺序、演讲者备注和提取的资源。
4. 提取后,运行与新演示文稿相同的风格选择工作流程。
保持转换跨平台。当 Python 可以完成任务时,不要依赖仅限 macOS 的工具。
## 实现要求
### HTML / CSS
* 除非用户明确希望使用多文件项目,否则使用内联 CSS 和 JS。
* 字体可以来自 Google Fonts 或 Fontshare。
* 优先使用氛围背景、强烈的字体层次结构和清晰的视觉方向。
* 使用抽象形状、渐变、网格、噪点和几何图形,而非插图。
### JavaScript
包含:
* 键盘导航
* 触摸/滑动导航
* 鼠标滚轮导航
* 进度指示器或幻灯片索引
* 进入时触发的揭示动画
### 可访问性
* 使用语义化结构 (`main`, `section`, `nav`)
* 保持对比度可读
* 支持仅键盘导航
* 尊重 `prefers-reduced-motion`
## 内容密度限制
除非用户明确要求更密集的幻灯片且可读性仍然保持,否则使用以下最大值:
| 幻灯片类型 | 限制 |
|------------|-------|
| 标题 | 1 个标题 + 1 个副标题 + 可选标语 |
| 内容 | 1 个标题 + 4-6 个要点或 2 个短段落 |
| 功能网格 | 最多 6 张卡片 |
| 代码 | 最多 8-10 行 |
| 引用 | 1 条引用 + 出处 |
| 图像 | 1 张受视口约束的图像 |
## 反模式
* 没有视觉标识的通用初创公司渐变
* 除非是特意采用编辑风格,否则避免系统字体文稿
* 冗长的要点列表
* 需要滚动的代码块
* 在短屏幕上会损坏的固定高度内容框
* 无效的否定 CSS 函数,如 `-clamp(...)`
## 相关 ECC 技能
* `frontend-patterns` 用于围绕文稿的组件和交互模式
* `liquid-glass-design` 当演示文稿有意借鉴苹果玻璃美学时
* `e2e-testing` 如果您需要为最终文稿进行自动化浏览器验证
## 交付清单
* 演示文稿可在浏览器中从本地文件运行
* 每张幻灯片适配视口,无需滚动
* 风格独特且有意图
* 动画有意义,不喧闹
* 尊重减少动效设置
* 在交付时解释文件路径和自定义点

View File

@@ -0,0 +1,333 @@
# 样式预设参考
`frontend-slides` 整理的视觉样式。
使用此文件用于:
* 强制性的视口适配 CSS 基础
* 预设选择和情绪映射
* CSS 陷阱和验证规则
仅使用抽象形状。除非用户明确要求,否则避免使用插图。
## 视口适配不容妥协
每张幻灯片必须完全适配一个视口。
### 黄金法则
```text
Each slide = exactly one viewport height.
Too much content = split into more slides.
Never scroll inside a slide.
```
### 内容密度限制
| 幻灯片类型 | 最大内容量 |
|---|---|
| 标题幻灯片 | 1 个标题 + 1 个副标题 + 可选标语 |
| 内容幻灯片 | 1 个标题 + 4-6 个要点或 2 个段落 |
| 功能网格 | 最多 6 张卡片 |
| 代码幻灯片 | 最多 8-10 行 |
| 引用幻灯片 | 1 条引用 + 出处 |
| 图片幻灯片 | 1 张图片,理想情况下低于 60vh |
## 强制基础 CSS
将此代码块复制到每个生成的演示文稿中,然后在其基础上应用主题。
```css
/* ===========================================
VIEWPORT FITTING: MANDATORY BASE STYLES
=========================================== */
html, body {
height: 100%;
overflow-x: hidden;
}
html {
scroll-snap-type: y mandatory;
scroll-behavior: smooth;
}
.slide {
width: 100vw;
height: 100vh;
height: 100dvh;
overflow: hidden;
scroll-snap-align: start;
display: flex;
flex-direction: column;
position: relative;
}
.slide-content {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
max-height: 100%;
overflow: hidden;
padding: var(--slide-padding);
}
:root {
--title-size: clamp(1.5rem, 5vw, 4rem);
--h2-size: clamp(1.25rem, 3.5vw, 2.5rem);
--h3-size: clamp(1rem, 2.5vw, 1.75rem);
--body-size: clamp(0.75rem, 1.5vw, 1.125rem);
--small-size: clamp(0.65rem, 1vw, 0.875rem);
--slide-padding: clamp(1rem, 4vw, 4rem);
--content-gap: clamp(0.5rem, 2vw, 2rem);
--element-gap: clamp(0.25rem, 1vw, 1rem);
}
.card, .container, .content-box {
max-width: min(90vw, 1000px);
max-height: min(80vh, 700px);
}
.feature-list, .bullet-list {
gap: clamp(0.4rem, 1vh, 1rem);
}
.feature-list li, .bullet-list li {
font-size: var(--body-size);
line-height: 1.4;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr));
gap: clamp(0.5rem, 1.5vw, 1rem);
}
img, .image-container {
max-width: 100%;
max-height: min(50vh, 400px);
object-fit: contain;
}
@media (max-height: 700px) {
:root {
--slide-padding: clamp(0.75rem, 3vw, 2rem);
--content-gap: clamp(0.4rem, 1.5vw, 1rem);
--title-size: clamp(1.25rem, 4.5vw, 2.5rem);
--h2-size: clamp(1rem, 3vw, 1.75rem);
}
}
@media (max-height: 600px) {
:root {
--slide-padding: clamp(0.5rem, 2.5vw, 1.5rem);
--content-gap: clamp(0.3rem, 1vw, 0.75rem);
--title-size: clamp(1.1rem, 4vw, 2rem);
--body-size: clamp(0.7rem, 1.2vw, 0.95rem);
}
.nav-dots, .keyboard-hint, .decorative {
display: none;
}
}
@media (max-height: 500px) {
:root {
--slide-padding: clamp(0.4rem, 2vw, 1rem);
--title-size: clamp(1rem, 3.5vw, 1.5rem);
--h2-size: clamp(0.9rem, 2.5vw, 1.25rem);
--body-size: clamp(0.65rem, 1vw, 0.85rem);
}
}
@media (max-width: 600px) {
:root {
--title-size: clamp(1.25rem, 7vw, 2.5rem);
}
.grid {
grid-template-columns: 1fr;
}
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.2s !important;
}
html {
scroll-behavior: auto;
}
}
```
## 视口检查清单
* 每个 `.slide` 都有 `height: 100vh``height: 100dvh``overflow: hidden`
* 所有排版都使用 `clamp()`
* 所有间距都使用 `clamp()` 或视口单位
* 图片有 `max-height` 约束
* 网格使用 `auto-fit` + `minmax()` 进行适配
* 短高度断点存在于 `700px``600px``500px`
* 如果感觉任何内容拥挤,请拆分幻灯片
## 情绪到预设的映射
| 情绪 | 推荐的预设 |
|---|---|
| 印象深刻 / 自信 | Bold Signal, Electric Studio, Dark Botanical |
| 兴奋 / 充满活力 | Creative Voltage, Neon Cyber, Split Pastel |
| 平静 / 专注 | Notebook Tabs, Paper & Ink, Swiss Modern |
| 受启发 / 感动 | Dark Botanical, Vintage Editorial, Pastel Geometry |
## 预设目录
### 1. Bold Signal
* 氛围:自信,高冲击力,适合主题演讲
* 最适合:推介演示,产品发布,声明
* 字体Archivo Black + Space Grotesk
* 调色板:炭灰色基底,亮橙色焦点卡片,纯白色文本
* 特色:超大章节编号,深色背景上的高对比度卡片
### 2. Electric Studio
* 氛围:简洁,大胆,机构级精致
* 最适合:客户演示,战略评审
* 字体:仅 Manrope
* 调色板:黑色,白色,饱和钴蓝色点缀
* 特色:双面板分割和锐利的编辑式对齐
### 3. Creative Voltage
* 氛围:充满活力,复古现代,俏皮自信
* 最适合:创意工作室,品牌工作,产品故事叙述
* 字体Syne + Space Mono
* 调色板:电光蓝,霓虹黄,深海军蓝
* 特色:半色调纹理,徽章,强烈的对比
### 4. Dark Botanical
* 氛围:优雅,高端,有氛围感
* 最适合:奢侈品牌,深思熟虑的叙述,高端产品演示
* 字体Cormorant + IBM Plex Sans
* 调色板:接近黑色,温暖的象牙色,腮红,金色,赤陶色
* 特色:模糊的抽象圆形,精细的线条,克制的动效
### 5. Notebook Tabs
* 氛围:编辑感,有条理,有触感
* 最适合:报告,评审,结构化的故事叙述
* 字体Bodoni Moda + DM Sans
* 调色板:炭灰色上的奶油色纸张搭配柔和色彩标签
* 特色:纸张效果,彩色侧边标签,活页夹细节
### 6. Pastel Geometry
* 氛围:平易近人,现代,友好
* 最适合:产品概览,入门介绍,较轻松的品牌演示
* 字体:仅 Plus Jakarta Sans
* 调色板:淡蓝色背景,奶油色卡片,柔和的粉色/薄荷色/薰衣草色点缀
* 特色:垂直药丸形状,圆角卡片,柔和阴影
### 7. Split Pastel
* 氛围:有趣,现代,有创意
* 最适合:机构介绍,研讨会,作品集
* 字体:仅 Outfit
* 调色板:桃色 + 薰衣草色分割背景搭配薄荷色徽章
* 特色:分割背景,圆角标签,轻网格叠加层
### 8. Vintage Editorial
* 氛围:诙谐,个性鲜明,受杂志启发
* 最适合:个人品牌,观点性演讲,故事叙述
* 字体Fraunces + Work Sans
* 调色板:奶油色,炭灰色,灰暗的暖色点缀
* 特色:几何点缀,带边框的标注,醒目的衬线标题
### 9. Neon Cyber
* 氛围:未来感,科技感,动感
* 最适合AI基础设施开发工具关于未来趋势的演讲
* 字体Clash Display + Satoshi
* 调色板:午夜海军蓝,青色,洋红色
* 特色:发光效果,粒子,网格,数据雷达能量感
### 10. Terminal Green
* 氛围:面向开发者,黑客风格简洁
* 最适合APICLI 工具,工程演示
* 字体:仅 JetBrains Mono
* 调色板GitHub 深色 + 终端绿色
* 特色:扫描线,命令行框架,精确的等宽字体节奏
### 11. Swiss Modern
* 氛围:极简,精确,数据导向
* 最适合:企业,产品战略,分析
* 字体Archivo + Nunito
* 调色板:白色,黑色,信号红色
* 特色:可见的网格,不对称,几何秩序感
### 12. Paper & Ink
* 氛围:文学性,深思熟虑,故事驱动
* 最适合:散文,主题演讲叙述,宣言式演示
* 字体Cormorant Garamond + Source Serif 4
* 调色板:温暖的奶油色,炭灰色,深红色点缀
* 特色:引文突出,首字下沉,优雅的线条
## 直接选择提示
如果用户已经知道他们想要的样式,让他们直接从上面的预设名称中选择,而不是强制生成预览。
## 动画感觉映射
| 感觉 | 动效方向 |
|---|---|
| 戏剧性 / 电影感 | 缓慢淡入淡出,视差滚动,大比例缩放进入 |
| 科技感 / 未来感 | 发光,粒子,网格运动,文字乱序出现 |
| 有趣 / 友好 | 弹性缓动,圆角形状,漂浮运动 |
| 专业 / 企业 | 微妙的 200-300 毫秒过渡,干净的幻灯片切换 |
| 平静 / 极简 | 非常克制的运动,留白优先 |
| 编辑感 / 杂志感 | 强烈的层次感,错落的文字和图片互动 |
## CSS 陷阱:否定函数
切勿编写这些:
```css
right: -clamp(28px, 3.5vw, 44px);
margin-left: -min(10vw, 100px);
```
浏览器会静默忽略它们。
始终改为编写这个:
```css
right: calc(-1 * clamp(28px, 3.5vw, 44px));
margin-left: calc(-1 * min(10vw, 100px));
```
## 验证尺寸
至少测试以下尺寸:
* 桌面:`1920x1080``1440x900``1280x720`
* 平板:`1024x768``768x1024`
* 手机:`375x667``414x896`
* 横屏手机:`667x375``896x414`
## 反模式
请勿使用:
* 紫底白字的初创公司模板
* Inter / Roboto / Arial 作为视觉声音,除非用户明确想要实用主义的中性风格
* 要点堆砌、过小字体或需要滚动的代码块
* 装饰性插图,当抽象几何形状能更好地完成工作时

View File

@@ -1,6 +1,7 @@
---
name: golang-patterns
description: 构建稳健、高效且可维护的Go应用程序的惯用Go模式、最佳实践和约定。
description: 用于构建健壮、高效且可维护的Go应用程序的惯用Go模式、最佳实践和约定。
origin: ECC
---
# Go 开发模式

View File

@@ -1,6 +1,7 @@
---
name: golang-testing
description: Go测试模式包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循TDD方法论采用地道的Go实践。
origin: ECC
---
# Go 测试模式

View File

@@ -0,0 +1,104 @@
---
name: investor-materials
description: 创建和更新宣传文稿、一页简介、投资者备忘录、加速器申请、财务模型和融资材料。当用户需要面向投资者的文件、预测、资金用途表、里程碑计划或必须在多个融资资产中保持内部一致性的材料时使用。
origin: ECC
---
# 投资者材料
构建面向投资者的材料,要求一致、可信且易于辩护。
## 何时启用
* 创建或修订融资演讲稿
* 撰写投资者备忘录或一页摘要
* 构建财务模型、里程碑计划或资金使用表
* 回答加速器或孵化器申请问题
* 围绕单一事实来源统一多个融资文件
## 黄金法则
所有投资者材料必须彼此一致。
在撰写前创建或确认单一事实来源:
* 增长指标
* 定价和收入假设
* 融资规模和工具
* 资金用途
* 团队简介和头衔
* 里程碑和时间线
如果出现冲突的数字,请停止起草并解决它们。
## 核心工作流程
1. 清点规范事实
2. 识别缺失的假设
3. 选择资产类型
4. 用明确的逻辑起草资产
5. 根据事实来源交叉核对每个数字
## 资产指南
### 融资演讲稿
推荐流程:
1. 公司 + 切入点
2. 问题
3. 解决方案
4. 产品 / 演示
5. 市场
6. 商业模式
7. 增长
8. 团队
9. 竞争 / 差异化
10. 融资需求
11. 资金用途 / 里程碑
12. 附录
如果用户想要一个基于网页的演讲稿,请将此技能与 `frontend-slides` 配对使用。
### 一页摘要 / 备忘录
* 用一句清晰的话说明公司做什么
* 展示为什么是现在
* 尽早包含增长数据和证明点
* 使融资需求精确
* 保持主张易于验证
### 财务模型
包含:
* 明确的假设
* 在有用时包含悲观/基准/乐观情景
* 清晰的逐层收入逻辑
* 与里程碑挂钩的支出
* 在决策依赖于假设的地方进行敏感性分析
### 加速器申请
* 回答被问的确切问题
* 优先考虑增长数据、洞察力和团队优势
* 避免夸大其词
* 保持内部指标与演讲稿和模型一致
## 需避免的危险信号
* 无法验证的主张
* 没有假设的模糊市场规模估算
* 不一致的团队角色或头衔
* 收入计算不清晰
* 在假设脆弱的地方夸大确定性
## 质量关卡
在交付前:
* 每个数字都与当前事实来源匹配
* 资金用途和收入层级计算正确
* 假设可见,而非隐藏
* 故事清晰,没有夸张语言
* 最终资产在合伙人会议上可辩护

View File

@@ -0,0 +1,81 @@
---
name: investor-outreach
description: 草拟冷邮件、热情介绍简介、跟进邮件、更新邮件和投资者沟通以筹集资金。当用户需要向天使投资人、风险投资公司、战略投资者或加速器进行推广,并需要简洁、个性化的面向投资者的消息时使用。
origin: ECC
---
# 投资者接洽
撰写简短、个性化且易于采取行动的投资者沟通内容。
## 何时激活
* 向投资者发送冷邮件时
* 起草熟人介绍请求时
* 在会议后或无回复时发送跟进邮件时
* 在融资过程中撰写投资者更新时
* 根据基金投资主题或合伙人契合度定制接洽内容时
## 核心规则
1. 个性化每一条外发信息。
2. 保持请求低门槛。
3. 使用证据,而非形容词。
4. 保持简洁。
5. 绝不发送可发给任何投资者的通用文案。
## 冷邮件结构
1. 主题行:简短且具体
2. 开头:说明为何选择这位特定投资者
3. 推介:公司做什么,为何是现在,什么证据重要
4. 请求:一个具体的下一步行动
5. 签名:姓名、职位,如需可加上一个可信度锚点
## 个性化来源
参考以下一项或多项:
* 相关的投资组合公司
* 公开的投资主题、演讲、帖子或文章
* 共同的联系人
* 与投资者关注点明确匹配的市场或产品契合度
如果缺少相关背景信息,请询问或说明草稿是等待个性化的模板。
## 跟进节奏
默认节奏:
* 第 0 天:初次外发
* 第 4-5 天:简短跟进,附带一个新数据点
* 第 10-12 天:最终跟进,干净利落地收尾
之后除非用户要求更长的跟进序列,否则不再继续提醒。
## 熟人介绍请求
为介绍人提供便利:
* 解释为何这次介绍是合适的
* 包含可转发的简介
* 将可转发的简介控制在 100 字以内
## 会后更新
包含:
* 讨论的具体事项
* 承诺的答复或更新
* 如有可能,提供一个新证据点
* 下一步行动
## 质量关卡
在交付前检查:
* 信息已个性化
* 请求明确
* 没有废话或乞求性语言
* 证据点具体
* 字数保持紧凑

View File

@@ -1,12 +1,21 @@
---
name: iterative-retrieval
description: 用于逐步优化上下文检索以解决子代理上下文问题的模式
description: 逐步优化上下文检索以解决子代理上下文问题的模式
origin: ECC
---
# 迭代检索模式
解决多智能体工作流中的“上下文问题”,即子智能体在开始工作前不知道需要哪些上下文。
## 何时激活
* 当需要生成需要代码库上下文但无法预先预测的子代理时
* 构建需要逐步完善上下文的多代理工作流时
* 在代理任务中遇到"上下文过大"或"缺少上下文"的失败时
* 为代码探索设计类似 RAG 的检索管道时
* 在代理编排中优化令牌使用时
## 问题
子智能体被生成时上下文有限。它们不知道:

View File

@@ -1,12 +1,21 @@
---
name: java-coding-standards
description: Java coding standards for Spring Boot services: naming, immutability, Optional usage, streams, exceptions, generics, and project layout.
description: "Spring Boot服务的Java编码标准命名、不可变性、Optional用法、流、异常、泛型和项目布局。"
origin: ECC
---
# Java 编码规范
适用于 Spring Boot 服务中可读、可维护的 Java (17+) 代码的规范。
## 何时激活
* 在 Spring Boot 项目中编写或审查 Java 代码时
* 强制执行命名、不可变性或异常处理约定时
* 使用记录类、密封类或模式匹配Java 17+)时
* 审查 Optional、流或泛型的使用时
* 构建包和项目布局时
## 核心原则
* 清晰优于巧妙

View File

@@ -1,12 +1,22 @@
---
name: jpa-patterns
description: Spring Boot中的JPA/Hibernate实体设计、关系、查询优化、事务、审计、索引、分页和连接池模式
description: Spring Boot中的JPA/Hibernate模式,用于实体设计、关系处理、查询优化、事务管理、审计、索引、分页和连接池。
origin: ECC
---
# JPA/Hibernate 模式
用于 Spring Boot 中的数据建模、存储库和性能调优。
## 何时激活
* 设计 JPA 实体和表映射时
* 定义关系时 (@OneToMany, @ManyToOne, @ManyToMany)
* 优化查询时 (N+1 问题预防、获取策略、投影)
* 配置事务、审计或软删除时
* 设置分页、排序或自定义存储库方法时
* 调整连接池 (HikariCP) 或二级缓存时
## 实体设计
```java

View File

@@ -0,0 +1,280 @@
---
name: liquid-glass-design
description: iOS 26 液态玻璃设计系统 — 适用于 SwiftUI、UIKit 和 WidgetKit 的动态玻璃材质,具有模糊、反射和交互式变形效果。
---
# Liquid Glass 设计系统 (iOS 26)
实现苹果 Liquid Glass 的模式指南——这是一种动态材质,会模糊其后的内容,反射周围内容的颜色和光线,并对触摸和指针交互做出反应。涵盖 SwiftUI、UIKit 和 WidgetKit 集成。
## 何时启用
* 为 iOS 26+ 构建或更新采用新设计语言的应用程序时
* 实现玻璃风格的按钮、卡片、工具栏或容器时
* 在玻璃元素之间创建变形过渡时
* 将 Liquid Glass 效果应用于小组件时
* 将现有的模糊/材质效果迁移到新的 Liquid Glass API 时
## 核心模式 — SwiftUI
### 基本玻璃效果
为任何视图添加 Liquid Glass 的最简单方法:
```swift
Text("Hello, World!")
.font(.title)
.padding()
.glassEffect() // Default: regular variant, capsule shape
```
### 自定义形状和色调
```swift
Text("Hello, World!")
.font(.title)
.padding()
.glassEffect(.regular.tint(.orange).interactive(), in: .rect(cornerRadius: 16.0))
```
关键自定义选项:
* `.regular` — 标准玻璃效果
* `.tint(Color)` — 添加颜色色调以增强突出度
* `.interactive()` — 对触摸和指针交互做出反应
* 形状:`.capsule`(默认)、`.rect(cornerRadius:)``.circle`
### 玻璃按钮样式
```swift
Button("Click Me") { /* action */ }
.buttonStyle(.glass)
Button("Important") { /* action */ }
.buttonStyle(.glassProminent)
```
### 用于多个元素的 GlassEffectContainer
出于性能和变形考虑,始终将多个玻璃视图包装在一个容器中:
```swift
GlassEffectContainer(spacing: 40.0) {
HStack(spacing: 40.0) {
Image(systemName: "scribble.variable")
.frame(width: 80.0, height: 80.0)
.font(.system(size: 36))
.glassEffect()
Image(systemName: "eraser.fill")
.frame(width: 80.0, height: 80.0)
.font(.system(size: 36))
.glassEffect()
}
}
```
`spacing` 参数控制合并距离——距离更近的元素会将其玻璃形状融合在一起。
### 统一玻璃效果
使用 `glassEffectUnion` 将多个视图组合成单个玻璃形状:
```swift
@Namespace private var namespace
GlassEffectContainer(spacing: 20.0) {
HStack(spacing: 20.0) {
ForEach(symbolSet.indices, id: \.self) { item in
Image(systemName: symbolSet[item])
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectUnion(id: item < 2 ? "group1" : "group2", namespace: namespace)
}
}
}
```
### 变形过渡
在玻璃元素出现/消失时创建平滑的变形效果:
```swift
@State private var isExpanded = false
@Namespace private var namespace
GlassEffectContainer(spacing: 40.0) {
HStack(spacing: 40.0) {
Image(systemName: "scribble.variable")
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectID("pencil", in: namespace)
if isExpanded {
Image(systemName: "eraser.fill")
.frame(width: 80.0, height: 80.0)
.glassEffect()
.glassEffectID("eraser", in: namespace)
}
}
}
Button("Toggle") {
withAnimation { isExpanded.toggle() }
}
.buttonStyle(.glass)
```
### 将水平滚动延伸到侧边栏下方
要允许水平滚动内容延伸到侧边栏或检查器下方,请确保 `ScrollView` 内容到达容器的 leading/trailing 边缘。当布局延伸到边缘时,系统会自动处理侧边栏下方的滚动行为——无需额外的修饰符。
## 核心模式 — UIKit
### 基本 UIGlassEffect
```swift
let glassEffect = UIGlassEffect()
glassEffect.tintColor = UIColor.systemBlue.withAlphaComponent(0.3)
glassEffect.isInteractive = true
let visualEffectView = UIVisualEffectView(effect: glassEffect)
visualEffectView.translatesAutoresizingMaskIntoConstraints = false
visualEffectView.layer.cornerRadius = 20
visualEffectView.clipsToBounds = true
view.addSubview(visualEffectView)
NSLayoutConstraint.activate([
visualEffectView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
visualEffectView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
visualEffectView.widthAnchor.constraint(equalToConstant: 200),
visualEffectView.heightAnchor.constraint(equalToConstant: 120)
])
// Add content to contentView
let label = UILabel()
label.text = "Liquid Glass"
label.translatesAutoresizingMaskIntoConstraints = false
visualEffectView.contentView.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: visualEffectView.contentView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: visualEffectView.contentView.centerYAnchor)
])
```
### 用于多个元素的 UIGlassContainerEffect
```swift
let containerEffect = UIGlassContainerEffect()
containerEffect.spacing = 40.0
let containerView = UIVisualEffectView(effect: containerEffect)
let firstGlass = UIVisualEffectView(effect: UIGlassEffect())
let secondGlass = UIVisualEffectView(effect: UIGlassEffect())
containerView.contentView.addSubview(firstGlass)
containerView.contentView.addSubview(secondGlass)
```
### 滚动边缘效果
```swift
scrollView.topEdgeEffect.style = .automatic
scrollView.bottomEdgeEffect.style = .hard
scrollView.leftEdgeEffect.isHidden = true
```
### 工具栏玻璃集成
```swift
let favoriteButton = UIBarButtonItem(image: UIImage(systemName: "heart"), style: .plain, target: self, action: #selector(favoriteAction))
favoriteButton.hidesSharedBackground = true // Opt out of shared glass background
```
## 核心模式 — WidgetKit
### 渲染模式检测
```swift
struct MyWidgetView: View {
@Environment(\.widgetRenderingMode) var renderingMode
var body: some View {
if renderingMode == .accented {
// Tinted mode: white-tinted, themed glass background
} else {
// Full color mode: standard appearance
}
}
}
```
### 用于视觉层次结构的强调色组
```swift
HStack {
VStack(alignment: .leading) {
Text("Title")
.widgetAccentable() // Accent group
Text("Subtitle")
// Primary group (default)
}
Image(systemName: "star.fill")
.widgetAccentable() // Accent group
}
```
### 强调模式下的图像渲染
```swift
Image("myImage")
.widgetAccentedRenderingMode(.monochrome)
```
### 容器背景
```swift
VStack { /* content */ }
.containerBackground(for: .widget) {
Color.blue.opacity(0.2)
}
```
## 关键设计决策
| 决策 | 理由 |
|----------|-----------|
| 使用 GlassEffectContainer 包装 | 性能优化,实现玻璃元素之间的变形 |
| `spacing` 参数 | 控制合并距离——微调元素需要多近才能融合 |
| `@Namespace` + `glassEffectID` | 在视图层次结构变化时实现平滑的变形过渡 |
| `interactive()` 修饰符 | 明确选择加入触摸/指针反应——并非所有玻璃都应响应 |
| UIKit 中的 UIGlassContainerEffect | 与 SwiftUI 保持一致的容器模式 |
| 小组件中的强调色渲染模式 | 当用户选择带色调的主屏幕时,系统会应用带色调的玻璃效果 |
## 最佳实践
* **始终使用 GlassEffectContainer** 来为多个兄弟视图应用玻璃效果——它支持变形并提高渲染性能
* **在其他外观修饰符**frame、font、padding**之后应用** `.glassEffect()`
* **仅在响应用户交互的元素**(按钮、可切换项目)**上使用** `.interactive()`
* **仔细选择容器中的间距**,以控制玻璃效果何时合并
* 在更改视图层次结构时**使用** `withAnimation`,以启用平滑的变形过渡
* **在各种外观模式下测试**——浅色模式、深色模式和强调色/色调模式
* **确保可访问性对比度**——玻璃上的文本必须保持可读性
## 应避免的反模式
* 使用多个独立的 `.glassEffect()` 视图而不使用 GlassEffectContainer
* 嵌套过多玻璃效果——会降低性能和视觉清晰度
* 对每个视图都应用玻璃效果——保留给交互元素、工具栏和卡片
* 在 UIKit 中使用圆角时忘记 `clipsToBounds = true`
* 忽略小组件中的强调色渲染模式——破坏带色调的主屏幕外观
* 在玻璃效果后面使用不透明背景——破坏了半透明效果
## 使用场景
* 采用 iOS 26 新设计的导航栏、工具栏和标签栏
* 浮动操作按钮和卡片式容器
* 需要视觉深度和触摸反馈的交互控件
* 应与系统 Liquid Glass 外观集成的小组件
* 相关 UI 状态之间的变形过渡

View File

@@ -0,0 +1,85 @@
---
name: market-research
description: 进行市场研究、竞争分析、投资者尽职调查和行业情报,附带来源归属和决策导向的摘要。适用于用户需要市场规模、竞争对手比较、基金研究、技术扫描或为商业决策提供信息的研究时。
origin: ECC
---
# 市场研究
产出支持决策的研究,而非研究表演。
## 何时激活
* 研究市场、品类、公司、投资者或技术趋势时
* 构建 TAM/SAM/SOM 估算时
* 比较竞争对手或相邻产品时
* 在接触前准备投资者档案时
* 在构建、投资或进入市场前对论点进行压力测试时
## 研究标准
1. 每个重要主张都需要有来源。
2. 优先使用近期数据,并明确指出陈旧数据。
3. 包含反面证据和不利情况。
4. 将发现转化为决策,而不仅仅是总结。
5. 清晰区分事实、推论和建议。
## 常见研究模式
### 投资者 / 基金尽职调查
收集:
* 基金规模、阶段和典型投资额度
* 相关的投资组合公司
* 公开的投资理念和近期动态
* 该基金适合或不适合的理由
* 任何明显的危险信号或不匹配之处
### 竞争分析
收集:
* 产品现实情况,而非营销文案
* 公开的融资和投资者历史
* 公开的吸引力指标
* 分销和定价线索
* 优势、劣势和定位差距
### 市场规模估算
使用:
* 来自报告或公共数据集的"自上而下"估算
* 基于现实的客户获取假设进行的"自下而上"合理性检查
* 对每个逻辑跳跃的明确假设
### 技术 / 供应商研究
收集:
* 其工作原理
* 权衡取舍和采用信号
* 集成复杂度
* 锁定、安全、合规和运营风险
## 输出格式
默认结构:
1. 执行摘要
2. 关键发现
3. 影响
4. 风险和注意事项
5. 建议
6. 来源
## 质量门
在交付前检查:
* 所有数字均已注明来源或标记为估算
* 陈旧数据已标注
* 建议源自证据
* 风险和反对论点已包含在内
* 输出使决策更容易

View File

@@ -1,6 +1,7 @@
---
name: nutrient-document-processing
description: 使用Nutrient DWS API处理、转换、OCR、提取、编辑、签和填写文档。支持PDF、DOCX、XLSX、PPTX、HTML和图像文件
description: 使用Nutrient DWS API处理、转换、OCR识别、提取、编辑、签和填写文档。支持PDF、DOCX、XLSX、PPTX、HTML和图像格式
origin: ECC
---
# 文档处理
@@ -159,6 +160,6 @@ curl -X POST https://api.nutrient.io/build \
## 链接
* [API 演练](https://dashboard.nutrient.io/processor-api/playground/)
* [API 游乐](https://dashboard.nutrient.io/processor-api/playground/)
* [完整 API 文档](https://www.nutrient.io/guides/dws-processor/)
* [npm MCP 服务器](https://www.npmjs.com/package/@nutrient-sdk/dws-mcp-server)

View File

@@ -1,6 +1,7 @@
---
name: postgres-patterns
description: 基于Supabase最佳实践的PostgreSQL数据库模式用于查询优化、架构设计、索引和安全
description: 用于查询优化、模式设计、索引和安全性的PostgreSQL数据库模式。基于Supabase最佳实践。
origin: ECC
---
# PostgreSQL 模式

View File

@@ -1,11 +1,15 @@
---
name: project-guidelines-example
description: "基于真实生产应用的示例项目特定技能模板。"
origin: ECC
---
# 项目指南技能(示例)
这是一个项目特定技能的示例。将其用作您自己项目的模板。
基于一个真实的生产应用程序:[Zenith](https://zenith.chat) - 由 AI 驱动的客户发现平台。
***
## 何时使用
在为其设计的特定项目上工作时,请参考此技能。项目技能包含:

View File

@@ -1,6 +1,7 @@
---
name: python-patterns
description: Pythonic 惯用法、PEP 8 标准、类型提示以及构建健、高效可维护的 Python 应用程序的最佳实践。
description: Pythonic 惯用法、PEP 8 标准、类型提示以及构建健、高效可维护的 Python 应用程序的最佳实践。
origin: ECC
---
# Python 开发模式

View File

@@ -1,6 +1,7 @@
---
name: python-testing
description: 使用pytestTDD方法、夹具、模拟、参数化和覆盖率要求的Python测试策略
description: 使用pytest的Python测试策略包括TDD方法、夹具、模拟、参数化和覆盖率要求。
origin: ECC
---
# Python 测试模式

View File

@@ -0,0 +1,220 @@
---
name: regex-vs-llm-structured-text
description: 选择在解析结构化文本时使用正则表达式还是大型语言模型的决策框架——从正则表达式开始,仅在低置信度的边缘情况下添加大型语言模型。
origin: ECC
---
# 正则表达式 vs LLM 用于结构化文本解析
一个用于解析结构化文本(测验、表单、发票、文档)的实用决策框架。核心见解是:正则表达式能以低成本、确定性的方式处理 95-98% 的情况。将昂贵的 LLM 调用留给剩余的边缘情况。
## 何时使用
* 解析具有重复模式的结构化文本(问题、表单、表格)
* 决定在文本提取时使用正则表达式还是 LLM
* 构建结合两种方法的混合管道
* 在文本处理中优化成本/准确性权衡
## 决策框架
```
Is the text format consistent and repeating?
├── Yes (>90% follows a pattern) → Start with Regex
│ ├── Regex handles 95%+ → Done, no LLM needed
│ └── Regex handles <95% → Add LLM for edge cases only
└── No (free-form, highly variable) → Use LLM directly
```
## 架构模式
```
Source Text
[Regex Parser] ─── Extracts structure (95-98% accuracy)
[Text Cleaner] ─── Removes noise (markers, page numbers, artifacts)
[Confidence Scorer] ─── Flags low-confidence extractions
├── High confidence (≥0.95) → Direct output
└── Low confidence (<0.95) → [LLM Validator] → Output
```
## 实现
### 1. 正则表达式解析器(处理大多数情况)
```python
import re
from dataclasses import dataclass
@dataclass(frozen=True)
class ParsedItem:
id: str
text: str
choices: tuple[str, ...]
answer: str
confidence: float = 1.0
def parse_structured_text(content: str) -> list[ParsedItem]:
"""Parse structured text using regex patterns."""
pattern = re.compile(
r"(?P<id>\d+)\.\s*(?P<text>.+?)\n"
r"(?P<choices>(?:[A-D]\..+?\n)+)"
r"Answer:\s*(?P<answer>[A-D])",
re.MULTILINE | re.DOTALL,
)
items = []
for match in pattern.finditer(content):
choices = tuple(
c.strip() for c in re.findall(r"[A-D]\.\s*(.+)", match.group("choices"))
)
items.append(ParsedItem(
id=match.group("id"),
text=match.group("text").strip(),
choices=choices,
answer=match.group("answer"),
))
return items
```
### 2. 置信度评分
标记可能需要 LLM 审核的项:
```python
@dataclass(frozen=True)
class ConfidenceFlag:
item_id: str
score: float
reasons: tuple[str, ...]
def score_confidence(item: ParsedItem) -> ConfidenceFlag:
"""Score extraction confidence and flag issues."""
reasons = []
score = 1.0
if len(item.choices) < 3:
reasons.append("few_choices")
score -= 0.3
if not item.answer:
reasons.append("missing_answer")
score -= 0.5
if len(item.text) < 10:
reasons.append("short_text")
score -= 0.2
return ConfidenceFlag(
item_id=item.id,
score=max(0.0, score),
reasons=tuple(reasons),
)
def identify_low_confidence(
items: list[ParsedItem],
threshold: float = 0.95,
) -> list[ConfidenceFlag]:
"""Return items below confidence threshold."""
flags = [score_confidence(item) for item in items]
return [f for f in flags if f.score < threshold]
```
### 3. LLM 验证器(仅用于边缘情况)
```python
def validate_with_llm(
item: ParsedItem,
original_text: str,
client,
) -> ParsedItem:
"""Use LLM to fix low-confidence extractions."""
response = client.messages.create(
model="claude-haiku-4-5-20251001", # Cheapest model for validation
max_tokens=500,
messages=[{
"role": "user",
"content": (
f"Extract the question, choices, and answer from this text.\n\n"
f"Text: {original_text}\n\n"
f"Current extraction: {item}\n\n"
f"Return corrected JSON if needed, or 'CORRECT' if accurate."
),
}],
)
# Parse LLM response and return corrected item...
return corrected_item
```
### 4. 混合管道
```python
def process_document(
content: str,
*,
llm_client=None,
confidence_threshold: float = 0.95,
) -> list[ParsedItem]:
"""Full pipeline: regex -> confidence check -> LLM for edge cases."""
# Step 1: Regex extraction (handles 95-98%)
items = parse_structured_text(content)
# Step 2: Confidence scoring
low_confidence = identify_low_confidence(items, confidence_threshold)
if not low_confidence or llm_client is None:
return items
# Step 3: LLM validation (only for flagged items)
low_conf_ids = {f.item_id for f in low_confidence}
result = []
for item in items:
if item.id in low_conf_ids:
result.append(validate_with_llm(item, content, llm_client))
else:
result.append(item)
return result
```
## 实际指标
来自一个生产中的测验解析管道410 个项目):
| 指标 | 值 |
|--------|-------|
| 正则表达式成功率 | 98.0% |
| 低置信度项目 | 8 (2.0%) |
| 所需 LLM 调用次数 | ~5 |
| 相比全 LLM 的成本节省 | ~95% |
| 测试覆盖率 | 93% |
## 最佳实践
* **从正则表达式开始** — 即使不完美的正则表达式也能提供一个改进的基线
* **使用置信度评分** 来以编程方式识别需要 LLM 帮助的内容
* **使用最便宜的 LLM** 进行验证Haiku 类模型已足够)
* **切勿修改** 已解析的项 — 从清理/验证步骤返回新实例
* **TDD 效果很好** 用于解析器 — 首先为已知模式编写测试,然后是边缘情况
* **记录指标**正则表达式成功率、LLM 调用次数)以跟踪管道健康状况
## 应避免的反模式
* 当正则表达式能处理 95% 以上的情况时,将所有文本发送给 LLM昂贵且缓慢
* 对自由格式、高度可变的文本使用正则表达式LLM 在此处更合适)
* 跳过置信度评分,希望正则表达式“能正常工作”
* 在清理/验证步骤中修改已解析的对象
* 不测试边缘情况(格式错误的输入、缺失字段、编码问题)
## 适用场景
* 测验/考试题目解析
* 表单数据提取
* 发票/收据处理
* 文档结构解析(标题、章节、表格)
* 任何具有重复模式且成本重要的结构化文本

View File

@@ -0,0 +1,174 @@
---
name: search-first
description: 研究优先于编码的工作流程。在编写自定义代码之前,搜索现有的工具、库和模式。调用研究员代理。
origin: ECC
---
# /search-first — 编码前先研究
系统化“在实现之前先寻找现有解决方案”的工作流程。
## 触发时机
在以下情况使用此技能:
* 开始一项很可能已有解决方案的新功能
* 添加依赖项或集成
* 用户要求“添加 X 功能”而你准备开始编写代码
* 在创建新的实用程序、助手或抽象之前
## 工作流程
```
┌─────────────────────────────────────────────┐
│ 1. NEED ANALYSIS │
│ Define what functionality is needed │
│ Identify language/framework constraints │
├─────────────────────────────────────────────┤
│ 2. PARALLEL SEARCH (researcher agent) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ npm / │ │ MCP / │ │ GitHub / │ │
│ │ PyPI │ │ Skills │ │ Web │ │
│ └──────────┘ └──────────┘ └──────────┘ │
├─────────────────────────────────────────────┤
│ 3. EVALUATE │
│ Score candidates (functionality, maint, │
│ community, docs, license, deps) │
├─────────────────────────────────────────────┤
│ 4. DECIDE │
│ ┌─────────┐ ┌──────────┐ ┌─────────┐ │
│ │ Adopt │ │ Extend │ │ Build │ │
│ │ as-is │ │ /Wrap │ │ Custom │ │
│ └─────────┘ └──────────┘ └─────────┘ │
├─────────────────────────────────────────────┤
│ 5. IMPLEMENT │
│ Install package / Configure MCP / │
│ Write minimal custom code │
└─────────────────────────────────────────────┘
```
## 决策矩阵
| 信号 | 行动 |
|--------|--------|
| 完全匹配维护良好MIT/Apache 许可证 | **采纳** — 直接安装并使用 |
| 部分匹配,基础良好 | **扩展** — 安装 + 编写薄封装层 |
| 多个弱匹配 | **组合** — 组合 2-3 个小包 |
| 未找到合适的 | **构建** — 编写自定义代码,但需基于研究 |
## 使用方法
### 快速模式(内联)
在编写实用程序或添加功能之前,在脑中过一遍:
1. 这是常见问题吗? → 搜索 npm/PyPI
2. 有相关的 MCP 吗? → 检查 `~/.claude/settings.json` 并搜索
3. 有相关的技能吗? → 检查 `~/.claude/skills/`
4. 有 GitHub 模板吗? → 搜索 GitHub
### 完整模式(代理)
对于非平凡的功能,启动研究员代理:
```
Task(subagent_type="general-purpose", prompt="
Research existing tools for: [DESCRIPTION]
Language/framework: [LANG]
Constraints: [ANY]
Search: npm/PyPI, MCP servers, Claude Code skills, GitHub
Return: Structured comparison with recommendation
")
```
## 按类别搜索快捷方式
### 开发工具
* Linting → `eslint`, `ruff`, `textlint`, `markdownlint`
* Formatting → `prettier`, `black`, `gofmt`
* Testing → `jest`, `pytest`, `go test`
* Pre-commit → `husky`, `lint-staged`, `pre-commit`
### AI/LLM 集成
* Claude SDK → 使用 Context7 获取最新文档
* 提示词管理 → 检查 MCP 服务器
* 文档处理 → `unstructured`, `pdfplumber`, `mammoth`
### 数据与 API
* HTTP 客户端 → `httpx` (Python), `ky`/`got` (Node)
* 验证 → `zod` (TS), `pydantic` (Python)
* 数据库 → 首先检查是否有 MCP 服务器
### 内容与发布
* Markdown 处理 → `remark`, `unified`, `markdown-it`
* 图片优化 → `sharp`, `imagemin`
## 集成点
### 与规划器代理
规划器应在阶段 1架构评审之前调用研究员
* 研究员识别可用的工具
* 规划器将它们纳入实施计划
* 避免在计划中“重新发明轮子”
### 与架构师代理
架构师应向研究员咨询:
* 技术栈决策
* 集成模式发现
* 现有参考架构
### 与迭代检索技能
结合进行渐进式发现:
* 循环 1广泛搜索 (npm, PyPI, MCP)
* 循环 2详细评估顶级候选方案
* 循环 3测试与项目约束的兼容性
## 示例
### 示例 1“添加死链检查”
```
Need: Check markdown files for broken links
Search: npm "markdown dead link checker"
Found: textlint-rule-no-dead-link (score: 9/10)
Action: ADOPT — npm install textlint-rule-no-dead-link
Result: Zero custom code, battle-tested solution
```
### 示例 2“添加 HTTP 客户端包装器”
```
Need: Resilient HTTP client with retries and timeout handling
Search: npm "http client retry", PyPI "httpx retry"
Found: got (Node) with retry plugin, httpx (Python) with built-in retry
Action: ADOPT — use got/httpx directly with retry config
Result: Zero custom code, production-proven libraries
```
### 示例 3“添加配置文件 linter”
```
Need: Validate project config files against a schema
Search: npm "config linter schema", "json schema validator cli"
Found: ajv-cli (score: 8/10)
Action: ADOPT + EXTEND — install ajv-cli, write project-specific schema
Result: 1 package + 1 schema file, no custom validation logic
```
## 反模式
* **直接跳转到编码**:不检查是否存在就编写实用程序
* **忽略 MCP**:不检查 MCP 服务器是否已提供该能力
* **过度定制**:对库进行如此厚重的包装以至于失去了其优势
* **依赖项膨胀**:为了一个小功能安装一个庞大的包

View File

@@ -1,6 +1,7 @@
---
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.
description: 在添加身份验证、处理用户输入、处理机密信息、创建API端点或实现支付/敏感功能时使用此技能。提供全面的安全检查清单和模式。
origin: ECC
---
# 安全审查技能

View File

@@ -1,6 +1,7 @@
---
name: security-scan
description: 使用AgentShield扫描您的Claude Code配置(.claude/目录),检测安全漏洞、错误配置和注入风险。检查CLAUDE.md、settings.json、MCP服务器、钩子和代理定义。
description: 使用AgentShield扫描您的Claude代码配置(.claude/目录),以发现安全漏洞、配置错误和注入风险。检查CLAUDE.md、settings.json、MCP服务器、钩子和代理定义。
origin: ECC
---
# 安全扫描技能

View File

@@ -0,0 +1,176 @@
---
description: "用于审计Claude技能和命令的质量。支持快速扫描仅变更技能和全面盘点模式采用顺序子代理批量评估。"
origin: ECC
---
# skill-stocktake
斜杠命令 (`/skill-stocktake`),用于使用质量检查清单 + AI 整体判断来审核所有 Claude 技能和命令。支持两种模式:用于最近更改技能的快速扫描,以及用于完整审查的全面盘点。
## 范围
该命令针对以下**相对于调用命令所在目录**的路径:
| 路径 | 描述 |
|------|-------------|
| `~/.claude/skills/` | 全局技能(所有项目) |
| `{cwd}/.claude/skills/` | 项目级技能(如果目录存在) |
**在第 1 阶段开始时,该命令会明确列出找到并扫描了哪些路径。**
### 针对特定项目
要包含项目级技能,请从该项目根目录运行:
```bash
cd ~/path/to/my-project
/skill-stocktake
```
如果项目没有 `.claude/skills/` 目录,则只评估全局技能和命令。
## 模式
| 模式 | 触发条件 | 持续时间 |
|------|---------|---------|
| 快速扫描 | `results.json` 存在(默认) | 510 分钟 |
| 全面盘点 | `results.json` 不存在,或 `/skill-stocktake full` | 2030 分钟 |
**结果缓存:** `~/.claude/skills/skill-stocktake/results.json`
## 快速扫描流程
仅重新评估自上次运行以来发生更改的技能510 分钟)。
1. 读取 `~/.claude/skills/skill-stocktake/results.json`
2. 运行:`bash ~/.claude/skills/skill-stocktake/scripts/quick-diff.sh \ ~/.claude/skills/skill-stocktake/results.json`
(项目目录从 `$PWD/.claude/skills` 自动检测;仅在需要时显式传递)
3. 如果输出是 `[]`:报告“自上次运行以来无更改。”并停止
4. 使用相同的第 2 阶段标准仅重新评估那些已更改的文件
5. 沿用先前结果中未更改的技能
6. 仅输出差异
7. 运行:`bash ~/.claude/skills/skill-stocktake/scripts/save-results.sh \ ~/.claude/skills/skill-stocktake/results.json <<< "$EVAL_RESULTS"`
## 全面盘点流程
### 第 1 阶段 — 清单
运行:`bash ~/.claude/skills/skill-stocktake/scripts/scan.sh`
脚本枚举技能文件,提取 frontmatter并收集 UTC 修改时间。
项目目录从 `$PWD/.claude/skills` 自动检测;仅在需要时显式传递。
从脚本输出中呈现扫描摘要和清单表:
```
Scanning:
✓ ~/.claude/skills/ (17 files)
✗ {cwd}/.claude/skills/ (not found — global skills only)
```
| 技能 | 7天使用 | 30天使用 | 描述 |
|-------|--------|---------|-------------|
### 第 2 阶段 — 质量评估
启动一个 Task 工具子代理(**Explore 代理模型opus**),提供完整的清单和检查清单。
子代理读取每个技能,应用检查清单,并返回每个技能的 JSON
`{ "verdict": "Keep"|"Improve"|"Update"|"Retire"|"Merge into [X]", "reason": "..." }`
**分块指导:** 每个子代理调用处理约 20 个技能,以保持上下文可管理。在每个块之后将中间结果保存到 `results.json` (`status: "in_progress"`)。
所有技能评估完成后:设置 `status: "completed"`,进入第 3 阶段。
**恢复检测:** 如果在启动时找到 `status: "in_progress"`,则从第一个未评估的技能处恢复。
每个技能都根据此检查清单进行评估:
```
- [ ] Content overlap with other skills checked
- [ ] Overlap with MEMORY.md / CLAUDE.md checked
- [ ] Freshness of technical references verified (use WebSearch if tool names / CLI flags / APIs are present)
- [ ] Usage frequency considered
```
判定标准:
| 判定 | 含义 |
|---------|---------|
| Keep | 有用且最新 |
| Improve | 值得保留,但需要特定改进 |
| Update | 引用的技术已过时(通过 WebSearch 验证) |
| Retire | 质量低、陈旧或成本不对称 |
| Merge into \[X] | 与另一技能有大量重叠;命名合并目标 |
评估是**整体 AI 判断** — 不是数字评分标准。指导维度:
* **可操作性**:代码示例、命令或步骤,让你可以立即行动
* **范围契合度**:名称、触发器和内容保持一致;不过于宽泛或狭窄
* **独特性**:价值不能被 MEMORY.md / CLAUDE.md / 其他技能取代
* **时效性**:技术引用在当前环境中有效
**原因质量要求**`reason` 字段必须是自包含且能支持决策的:
* 不要只写“未更改” — 始终重述核心证据
* 对于 **Retire**:说明 (1) 发现了什么具体缺陷,(2) 有什么替代方案覆盖了相同需求
* 差:`"Superseded"`
* 好:`"disable-model-invocation: true already set; superseded by continuous-learning-v2 which covers all the same patterns plus confidence scoring. No unique content remains."`
* 对于 **Merge**:命名目标并描述要集成什么内容
* 差:`"Overlaps with X"`
* 好:`"42-line thin content; Step 4 of chatlog-to-article already covers the same workflow. Integrate the 'article angle' tip as a note in that skill."`
* 对于 **Improve**:描述所需的具体更改(哪个部分,什么操作,如果相关则说明目标大小)
* 差:`"Too long"`
* 好:`"276 lines; Section 'Framework Comparison' (L80140) duplicates ai-era-architecture-principles; delete it to reach ~150 lines."`
* 对于 **Keep**(快速扫描中仅 mtime 更改):重述原始判定理由,不要写“未更改”
* 差:`"Unchanged"`
* 好:`"mtime updated but content unchanged. Unique Python reference explicitly imported by rules/python/; no overlap found."`
### 第 3 阶段 — 摘要表
| 技能 | 7天使用 | 判定 | 原因 |
|-------|--------|---------|--------|
### 第 4 阶段 — 整合
1. **Retire / Merge**:在用户确认之前,按文件呈现详细理由:
* 发现了什么具体问题(重叠、陈旧、引用损坏等)
* 什么替代方案覆盖了相同功能(对于 Retire哪个现有技能/规则;对于 Merge目标文件以及要集成什么内容
* 移除的影响是否有依赖技能、MEMORY.md 引用或受影响的工作流)
2. **Improve**:呈现具体的改进建议及理由:
* 更改什么以及为什么(例如,“将 430 行压缩至 200 行,因为 X/Y 部分与 python-patterns 重复”)
* 用户决定是否采取行动
3. **Update**:呈现已检查来源的更新后内容
4. 检查 MEMORY.md 行数;如果超过 100 行,则建议压缩
## 结果文件模式
`~/.claude/skills/skill-stocktake/results.json`
**`evaluated_at`**:必须设置为评估完成时的实际 UTC 时间。
通过 Bash 获取:`date -u +%Y-%m-%dT%H:%M:%SZ`。切勿使用仅日期的近似值,如 `T00:00:00Z`
```json
{
"evaluated_at": "2026-02-21T10:00:00Z",
"mode": "full",
"batch_progress": {
"total": 80,
"evaluated": 80,
"status": "completed"
},
"skills": {
"skill-name": {
"path": "~/.claude/skills/skill-name/SKILL.md",
"verdict": "Keep",
"reason": "Concrete, actionable, unique value for X workflow",
"mtime": "2026-01-15T08:30:00Z"
}
}
}
```
## 注意事项
* 评估是盲目的无论来源如何ECC、自创、自动提取所有技能都应用相同的检查清单
* 归档 / 删除操作始终需要明确的用户确认
* 不按技能来源进行判定分支

View File

@@ -1,12 +1,22 @@
---
name: springboot-patterns
description: Spring Boot 架构模式、REST API 设计、分层服务、数据访问、缓存、异步处理和日志记录。用于 Java Spring Boot 后端工作。
description: Spring Boot架构模式、REST API设计、分层服务、数据访问、缓存、异步处理和日志记录。用于Java Spring Boot后端工作。
origin: ECC
---
# Spring Boot 开发模式
用于可扩展、生产级服务的 Spring Boot 架构和 API 模式。
## 何时激活
* 使用 Spring MVC 或 WebFlux 构建 REST API
* 构建控制器 → 服务 → 仓库层结构
* 配置 Spring Data JPA、缓存或异步处理
* 添加验证、异常处理或分页
* 为开发/预发布/生产环境设置配置文件
* 使用 Spring Events 或 Kafka 实现事件驱动模式
## REST API 结构
```java

View File

@@ -1,12 +1,23 @@
---
name: springboot-security
description: Java Spring Boot 服务中关于身份验证/授权、验证、CSRF、密钥、标头、速率限制和依赖安全的 Spring Security 最佳实践。
description: Java Spring Boot 服务中证/授权、验证、CSRF、密钥、标头、速率限制和依赖安全的 Spring Security 最佳实践。
origin: ECC
---
# Spring Boot 安全审查
在添加身份验证、处理输入、创建端点或处理密钥时使用。
## 何时激活
* 添加身份验证JWT、OAuth2、基于会话
* 实现授权(@PreAuthorize、基于角色的访问控制)
* 验证用户输入Bean Validation、自定义验证器
* 配置 CORS、CSRF 或安全标头
* 管理密钥Vault、环境变量
* 添加速率限制或暴力破解防护
* 扫描依赖项以查找 CVE
## 身份验证
* 优先使用无状态 JWT 或带有撤销列表的不透明令牌
@@ -42,17 +53,88 @@ public class JwtAuthFilter extends OncePerRequestFilter {
* 使用 `@PreAuthorize("hasRole('ADMIN')")``@PreAuthorize("@authz.canEdit(#id)")`
* 默认拒绝;仅公开必需的 scope
```java
@RestController
@RequestMapping("/api/admin")
public class AdminController {
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/users")
public List<UserDto> listUsers() {
return userService.findAll();
}
@PreAuthorize("@authz.isOwner(#id, authentication)")
@DeleteMapping("/users/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.delete(id);
return ResponseEntity.noContent().build();
}
}
```
## 输入验证
* 在控制器上使用带有 `@Valid` 的 Bean 验证
* 在 DTO 上应用约束:`@NotBlank``@Email``@Size`、自定义验证器
* 在渲染之前使用白名单清理任何 HTML
```java
// BAD: No validation
@PostMapping("/users")
public User createUser(@RequestBody UserDto dto) {
return userService.create(dto);
}
// GOOD: Validated DTO
public record CreateUserDto(
@NotBlank @Size(max = 100) String name,
@NotBlank @Email String email,
@NotNull @Min(0) @Max(150) Integer age
) {}
@PostMapping("/users")
public ResponseEntity<UserDto> createUser(@Valid @RequestBody CreateUserDto dto) {
return ResponseEntity.status(HttpStatus.CREATED)
.body(userService.create(dto));
}
```
## SQL 注入预防
* 使用 Spring Data 存储库或参数化查询
* 对于原生查询,使用 `:param` 绑定;切勿拼接字符串
```java
// BAD: String concatenation in native query
@Query(value = "SELECT * FROM users WHERE name = '" + name + "'", nativeQuery = true)
// GOOD: Parameterized native query
@Query(value = "SELECT * FROM users WHERE name = :name", nativeQuery = true)
List<User> findByName(@Param("name") String name);
// GOOD: Spring Data derived query (auto-parameterized)
List<User> findByEmailAndActiveTrue(String email);
```
## 密码编码
* 始终使用 BCrypt 或 Argon2 哈希密码——切勿存储明文
* 使用 `PasswordEncoder` Bean而非手动哈希
```java
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // cost factor 12
}
// In service
public User register(CreateUserDto dto) {
String hashedPassword = passwordEncoder.encode(dto.password());
return userRepository.save(new User(dto.email(), hashedPassword));
}
```
## CSRF 保护
* 对于浏览器会话应用程序,保持 CSRF 启用;在表单/头中包含令牌
@@ -70,6 +152,25 @@ http
* 保持 `application.yml` 不包含凭据;使用占位符
* 定期轮换令牌和数据库凭据
```yaml
# BAD: Hardcoded in application.yml
spring:
datasource:
password: mySecretPassword123
# GOOD: Environment variable placeholder
spring:
datasource:
password: ${DB_PASSWORD}
# GOOD: Spring Cloud Vault integration
spring:
cloud:
vault:
uri: https://vault.example.com
token: ${VAULT_TOKEN}
```
## 安全头
```java
@@ -82,11 +183,63 @@ http
.referrerPolicy(rp -> rp.policy(ReferrerPolicyHeaderWriter.ReferrerPolicy.NO_REFERRER)));
```
## CORS 配置
* 在安全过滤器级别配置 CORS而非按控制器配置
* 限制允许的来源——在生产环境中切勿使用 `*`
```java
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(List.of("https://app.example.com"));
config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE"));
config.setAllowedHeaders(List.of("Authorization", "Content-Type"));
config.setAllowCredentials(true);
config.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", config);
return source;
}
// In SecurityFilterChain:
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
```
## 速率限制
* 在昂贵的端点上应用 Bucket4j 或网关级限制
* 记录突发流量并告警;返回 429 并提供重试提示
```java
// Using Bucket4j for per-endpoint rate limiting
@Component
public class RateLimitFilter extends OncePerRequestFilter {
private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
private Bucket createBucket() {
return Bucket.builder()
.addLimit(Bandwidth.classic(100, Refill.intervally(100, Duration.ofMinutes(1))))
.build();
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
String clientIp = request.getRemoteAddr();
Bucket bucket = buckets.computeIfAbsent(clientIp, k -> createBucket());
if (bucket.tryConsume(1)) {
chain.doFilter(request, response);
} else {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.getWriter().write("{\"error\": \"Rate limit exceeded\"}");
}
}
}
```
## 依赖项安全
* 在 CI 中运行 OWASP Dependency Check / Snyk

View File

@@ -1,6 +1,7 @@
---
name: springboot-tdd
description: 使用JUnit 5、Mockito、MockMvc、Testcontainers和JaCoCo进行Spring Boot的测试驱动开发。适用于添加功能、修复错误或重构时。
origin: ECC
---
# Spring Boot TDD 工作流程

View File

@@ -1,12 +1,21 @@
---
name: springboot-verification
description: Verification loop for Spring Boot projects: build, static analysis, tests with coverage, security scans, and diff review before release or PR.
description: "Spring Boot项目验证循环构建、静态分析、测试覆盖、安全扫描以及发布或PR前的差异审查。"
origin: ECC
---
# Spring Boot 验证循环
在提交 PR 前、重大变更后以及部署前运行。
## 何时激活
* 为 Spring Boot 服务开启拉取请求之前
* 在重大重构或依赖项升级之后
* 用于暂存或生产环境的部署前验证
* 运行完整的构建 → 代码检查 → 测试 → 安全扫描流水线
* 验证测试覆盖率是否满足阈值
## 阶段 1构建
```bash
@@ -45,6 +54,111 @@ mvn jacoco:report # verify 80%+ coverage
* 总测试数,通过/失败
* 覆盖率百分比(行/分支)
### 单元测试
使用模拟的依赖项来隔离测试服务逻辑:
```java
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private UserRepository userRepository;
@InjectMocks private UserService userService;
@Test
void createUser_validInput_returnsUser() {
var dto = new CreateUserDto("Alice", "alice@example.com");
var expected = new User(1L, "Alice", "alice@example.com");
when(userRepository.save(any(User.class))).thenReturn(expected);
var result = userService.create(dto);
assertThat(result.name()).isEqualTo("Alice");
verify(userRepository).save(any(User.class));
}
@Test
void createUser_duplicateEmail_throwsException() {
var dto = new CreateUserDto("Alice", "existing@example.com");
when(userRepository.existsByEmail(dto.email())).thenReturn(true);
assertThatThrownBy(() -> userService.create(dto))
.isInstanceOf(DuplicateEmailException.class);
}
}
```
### 使用 Testcontainers 进行集成测试
针对真实数据库(而非 H2进行测试
```java
@SpringBootTest
@Testcontainers
class UserRepositoryIntegrationTest {
@Container
static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16-alpine")
.withDatabaseName("testdb");
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgres::getJdbcUrl);
registry.add("spring.datasource.username", postgres::getUsername);
registry.add("spring.datasource.password", postgres::getPassword);
}
@Autowired private UserRepository userRepository;
@Test
void findByEmail_existingUser_returnsUser() {
userRepository.save(new User("Alice", "alice@example.com"));
var found = userRepository.findByEmail("alice@example.com");
assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Alice");
}
}
```
### 使用 MockMvc 进行 API 测试
在完整的 Spring 上下文中测试控制器层:
```java
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired private MockMvc mockMvc;
@MockBean private UserService userService;
@Test
void createUser_validInput_returns201() throws Exception {
var user = new UserDto(1L, "Alice", "alice@example.com");
when(userService.create(any())).thenReturn(user);
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "alice@example.com"}
"""))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Alice"));
}
@Test
void createUser_invalidEmail_returns400() throws Exception {
mockMvc.perform(post("/api/users")
.contentType(MediaType.APPLICATION_JSON)
.content("""
{"name": "Alice", "email": "not-an-email"}
"""))
.andExpect(status().isBadRequest());
}
}
```
## 阶段 4安全扫描
```bash
@@ -53,10 +167,27 @@ mvn org.owasp:dependency-check-maven:check
# or
./gradlew dependencyCheckAnalyze
# Secrets (git)
# Secrets in source
grep -rn "password\s*=\s*\"" src/ --include="*.java" --include="*.yml" --include="*.properties"
grep -rn "sk-\|api_key\|secret" src/ --include="*.java" --include="*.yml"
# Secrets (git history)
git secrets --scan # if configured
```
### 常见安全发现
```
# Check for System.out.println (use logger instead)
grep -rn "System\.out\.print" src/main/ --include="*.java"
# Check for raw exception messages in responses
grep -rn "e\.getMessage()" src/main/ --include="*.java"
# Check for wildcard CORS
grep -rn "allowedOrigins.*\*" src/main/ --include="*.java"
```
## 阶段 5代码检查/格式化(可选关卡)
```bash

View File

@@ -1,12 +1,21 @@
---
name: strategic-compact
description: 建议在逻辑间隔处进行手动上下文压缩,以在任务阶段中保留上下文,而非任意的自动压缩。
description: 建议在逻辑间隔处手动压缩上下文,以在任务阶段中保留上下文,而非任意的自动压缩。
origin: ECC
---
# 战略精简技能
建议在你的工作流程中的战略节点手动执行 `/compact`,而不是依赖任意的自动精简。
## 何时激活
* 运行长时间会话接近上下文限制时200K+ tokens
* 处理多阶段任务时(研究 → 规划 → 实施 → 测试)
* 在同一会话中切换不相关的任务时
* 完成一个主要里程碑并开始新工作时
* 当响应变慢或连贯性下降时(上下文压力)
## 为何采用战略精简?
自动精简会在任意时间点触发:
@@ -17,17 +26,17 @@ description: 建议在逻辑间隔处进行手动上下文压缩,以在任务
在逻辑边界进行战略精简:
* **探索之后,执行之前** - 精简研究上下文,保留实施计划
* **完成一个里程碑之后** - 为下一阶段新开始
* **主要上下文切换之前** - 在不同任务开始前清理探索上下文
* **探索之后,执行之前** — 压缩研究上下文,保留实施计划
* **完成里程碑之后** 为下一阶段新开始
* **主要上下文切换之前** 开始不同任务前清理探索上下文
## 工作原理
`suggest-compact.sh` 脚本在 PreToolUse(编辑/写入)时运行并执行
`suggest-compact.js` 脚本在 PreToolUse (Edit/Write) 时运行,并且
1. **踪工具调用** - 计算会话中的工具调用次数
2. **阈值检测** - 在可配置的阈值默认50 次调用)处建议精简
3. **定期提醒** - 在达到阈值后,每 25 次调用提醒一次
1. **踪工具调用** — 统计会话中的工具调用次数
2. **阈值检测** 在可配置的阈值处建议压缩默认50次调用
3. **定期提醒** 达到阈值后每25次调用提醒一次
## 钩子设置
@@ -36,13 +45,16 @@ description: 建议在逻辑间隔处进行手动上下文压缩,以在任务
```json
{
"hooks": {
"PreToolUse": [{
"matcher": "tool == \"Edit\" || tool == \"Write\"",
"hooks": [{
"type": "command",
"command": "~/.claude/skills/strategic-compact/suggest-compact.sh"
}]
}]
"PreToolUse": [
{
"matcher": "Edit",
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
},
{
"matcher": "Write",
"hooks": [{ "type": "command", "command": "node ~/.claude/skills/strategic-compact/suggest-compact.js" }]
}
]
}
}
```
@@ -51,16 +63,44 @@ description: 建议在逻辑间隔处进行手动上下文压缩,以在任务
环境变量:
* `COMPACT_THRESHOLD` - 首次建议前的工具调用次数默认50
* `COMPACT_THRESHOLD` 首次建议前的工具调用次数默认50
## 压缩决策指南
使用此表来决定何时压缩:
| 阶段转换 | 压缩? | 原因 |
| ------------------------ | ------ | -------------------------------------------------------------------- |
| 研究 → 规划 | 是 | 研究上下文很庞大;规划是提炼后的输出 |
| 规划 → 实施 | 是 | 规划已保存在 TodoWrite 或文件中;释放上下文以进行编码 |
| 实施 → 测试 | 可能 | 如果测试引用最近的代码则保留;如果要切换焦点则压缩 |
| 调试 → 下一项功能 | 是 | 调试痕迹会污染不相关工作的上下文 |
| 实施过程中 | 否 | 丢失变量名、文件路径和部分状态代价高昂 |
| 尝试失败的方法之后 | 是 | 在尝试新方法之前,清理掉无效的推理过程 |
## 压缩后保留的内容
了解哪些内容会保留有助于您自信地进行压缩:
| 保留的内容 | 丢失的内容 |
| ---------------------------------------- | ---------------------------------------- |
| CLAUDE.md 指令 | 中间的推理和分析 |
| TodoWrite 任务列表 | 您之前读取过的文件内容 |
| 记忆文件 (`~/.claude/memory/`) | 多轮对话的上下文 |
| Git 状态(提交、分支) | 工具调用历史和计数 |
| 磁盘上的文件 | 口头陈述的细微用户偏好 |
## 最佳实践
1. **规划后精简** - 一旦计划确定,精简以全新开始
2. **调试后精简** - 在继续之前,清理错误解决上下文
3. **不要在实施中途精简** - 保留相关更改上下文
4. **阅读建议** - 钩子告诉*何时*由你决定*是否*
1. **规划后压缩** 一旦计划在 TodoWrite 中最终确定,就压缩以重新开始
2. **调试后压缩** 在继续之前,清理错误解决上下文
3. **不要在实施过程中压缩** — 为相关更改保留上下文
4. **阅读建议** 钩子告诉*何时*决定*是否*
5. **压缩前写入** — 在压缩前将重要上下文保存到文件或记忆中
6. **使用带摘要的 `/compact`** — 添加自定义消息:`/compact Focus on implementing auth middleware next`
## 相关
* [长篇指南](https://x.com/affaanmustafa/status/2014040193557471352) - 令牌优化部分
* 内存持久化钩子 - 用于在精简后保留状态
* [长篇指南](https://x.com/affaanmustafa/status/2014040193557471352) — Token 优化部分
* 记忆持久化钩子 用于在压缩后保留状态
* `continuous-learning` 技能 — 在会话结束前提取模式

View File

@@ -0,0 +1,143 @@
---
name: swift-actor-persistence
description: 在 Swift 中使用 actor 实现线程安全的数据持久化——基于内存缓存与文件支持的存储,通过设计消除数据竞争。
origin: ECC
---
# 用于线程安全持久化的 Swift Actor
使用 Swift actor 构建线程安全数据持久化层的模式。结合内存缓存与文件支持的存储,利用 actor 模型在编译时消除数据竞争。
## 何时激活
* 在 Swift 5.5+ 中构建数据持久化层
* 需要对共享可变状态进行线程安全访问
* 希望消除手动同步锁、DispatchQueue
* 构建具有本地存储的离线优先应用
## 核心模式
### 基于 Actor 的存储库
Actor 模型保证了序列化访问 —— 没有数据竞争,由编译器强制执行。
```swift
public actor LocalRepository<T: Codable & Identifiable> where T.ID == String {
private var cache: [String: T] = [:]
private let fileURL: URL
public init(directory: URL = .documentsDirectory, filename: String = "data.json") {
self.fileURL = directory.appendingPathComponent(filename)
// Synchronous load during init (actor isolation not yet active)
self.cache = Self.loadSynchronously(from: fileURL)
}
// MARK: - Public API
public func save(_ item: T) throws {
cache[item.id] = item
try persistToFile()
}
public func delete(_ id: String) throws {
cache[id] = nil
try persistToFile()
}
public func find(by id: String) -> T? {
cache[id]
}
public func loadAll() -> [T] {
Array(cache.values)
}
// MARK: - Private
private func persistToFile() throws {
let data = try JSONEncoder().encode(Array(cache.values))
try data.write(to: fileURL, options: .atomic)
}
private static func loadSynchronously(from url: URL) -> [String: T] {
guard let data = try? Data(contentsOf: url),
let items = try? JSONDecoder().decode([T].self, from: data) else {
return [:]
}
return Dictionary(uniqueKeysWithValues: items.map { ($0.id, $0) })
}
}
```
### 用法
由于 actor 隔离,所有调用都会自动变为异步:
```swift
let repository = LocalRepository<Question>()
// Read fast O(1) lookup from in-memory cache
let question = await repository.find(by: "q-001")
let allQuestions = await repository.loadAll()
// Write updates cache and persists to file atomically
try await repository.save(newQuestion)
try await repository.delete("q-001")
```
### 与 @Observable ViewModel 结合使用
```swift
@Observable
final class QuestionListViewModel {
private(set) var questions: [Question] = []
private let repository: LocalRepository<Question>
init(repository: LocalRepository<Question> = LocalRepository()) {
self.repository = repository
}
func load() async {
questions = await repository.loadAll()
}
func add(_ question: Question) async throws {
try await repository.save(question)
questions = await repository.loadAll()
}
}
```
## 关键设计决策
| 决策 | 理由 |
|----------|-----------|
| Actor而非类 + 锁) | 编译器强制执行的线程安全性,无需手动同步 |
| 内存缓存 + 文件持久化 | 从缓存中快速读取,持久化写入磁盘 |
| 同步初始化加载 | 避免异步初始化的复杂性 |
| 按 ID 键控的字典 | 按标识符进行 O(1) 查找 |
| 泛型化 `Codable & Identifiable` | 可在任何模型类型中重复使用 |
| 原子文件写入 (`.atomic`) | 防止崩溃时部分写入 |
## 最佳实践
* **对所有跨越 actor 边界的数据使用 `Sendable` 类型**
* **保持 actor 的公共 API 最小化** —— 仅暴露领域操作,而非持久化细节
* **使用 `.atomic` 写入** 以防止应用在写入过程中崩溃导致数据损坏
* **在 `init` 中同步加载** —— 异步初始化器会增加复杂性,而对本地文件的益处微乎其微
* **与 `@Observable` ViewModel 结合使用** 以实现响应式 UI 更新
## 应避免的反模式
* 在 Swift 并发新代码中使用 `DispatchQueue``NSLock` 而非 actor
* 将内部缓存字典暴露给外部调用者
* 在不进行验证的情况下使文件 URL 可配置
* 忘记所有 actor 方法调用都是 `await` —— 调用者必须处理异步上下文
* 使用 `nonisolated` 来绕过 actor 隔离(违背了初衷)
## 何时使用
* iOS/macOS 应用中的本地数据存储(用户数据、设置、缓存内容)
* 稍后同步到服务器的离线优先架构
* 应用中多个部分并发访问的任何共享可变状态
* 用现代 Swift 并发性替换基于 `DispatchQueue` 的旧式线程安全机制

View File

@@ -0,0 +1,217 @@
---
name: swift-concurrency-6-2
description: Swift 6.2 可接近的并发性 — 默认单线程,@concurrent 用于显式后台卸载,隔离一致性用于主 actor 类型。
---
# Swift 6.2 可接近的并发
采用 Swift 6.2 并发模型的模式,其中代码默认在单线程上运行,并发是显式引入的。在无需牺牲性能的情况下消除常见的数据竞争错误。
## 何时启用
* 将 Swift 5.x 或 6.0/6.1 项目迁移到 Swift 6.2
* 解决数据竞争安全编译器错误
* 设计基于 MainActor 的应用架构
* 将 CPU 密集型工作卸载到后台线程
* 在 MainActor 隔离的类型上实现协议一致性
* 在 Xcode 26 中启用“可接近的并发”构建设置
## 核心问题:隐式的后台卸载
在 Swift 6.1 及更早版本中,异步函数可能会被隐式卸载到后台线程,即使在看似安全的代码中也会导致数据竞争错误:
```swift
// Swift 6.1: ERROR
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
// Error: Sending 'self.photoProcessor' risks causing data races
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
```
Swift 6.2 修复了这个问题:异步函数默认保持在调用者所在的 actor 上。
```swift
// Swift 6.2: OK async stays on MainActor, no data race
@MainActor
final class StickerModel {
let photoProcessor = PhotoProcessor()
func extractSticker(_ item: PhotosPickerItem) async throws -> Sticker? {
guard let data = try await item.loadTransferable(type: Data.self) else { return nil }
return await photoProcessor.extractSticker(data: data, with: item.itemIdentifier)
}
}
```
## 核心模式 — 隔离的一致性
MainActor 类型现在可以安全地符合非隔离协议:
```swift
protocol Exportable {
func export()
}
// Swift 6.1: ERROR crosses into main actor-isolated code
// Swift 6.2: OK with isolated conformance
extension StickerModel: @MainActor Exportable {
func export() {
photoProcessor.exportAsPNG()
}
}
```
编译器确保该一致性仅在主 actor 上使用:
```swift
// OK ImageExporter is also @MainActor
@MainActor
struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Safe: same actor isolation
}
}
// ERROR nonisolated context can't use MainActor conformance
nonisolated struct ImageExporter {
var items: [any Exportable]
mutating func add(_ item: StickerModel) {
items.append(item) // Error: Main actor-isolated conformance cannot be used here
}
}
```
## 核心模式 — 全局和静态变量
使用 MainActor 保护全局/静态状态:
```swift
// Swift 6.1: ERROR non-Sendable type may have shared mutable state
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Error
}
// Fix: Annotate with @MainActor
@MainActor
final class StickerLibrary {
static let shared: StickerLibrary = .init() // OK
}
```
### MainActor 默认推断模式
Swift 6.2 引入了一种模式,默认推断 MainActor — 无需手动标注:
```swift
// With MainActor default inference enabled:
final class StickerLibrary {
static let shared: StickerLibrary = .init() // Implicitly @MainActor
}
final class StickerModel {
let photoProcessor: PhotoProcessor
var selection: [PhotosPickerItem] // Implicitly @MainActor
}
extension StickerModel: Exportable { // Implicitly @MainActor conformance
func export() {
photoProcessor.exportAsPNG()
}
}
```
此模式是选择启用的,推荐用于应用、脚本和其他可执行目标。
## 核心模式 — 使用 @concurrent 进行后台工作
当需要真正的并行性时,使用 `@concurrent` 显式卸载:
> **重要:** 此示例需要启用“可接近的并发”构建设置 — SE-0466 (MainActor 默认隔离) 和 SE-0461 (默认非隔离非发送)。启用这些设置后,`extractSticker` 会保持在调用者所在的 actor 上,使得可变状态的访问变得安全。**如果没有这些设置,此代码存在数据竞争** — 编译器会标记它。
```swift
nonisolated final class PhotoProcessor {
private var cachedStickers: [String: Sticker] = [:]
func extractSticker(data: Data, with id: String) async -> Sticker {
if let sticker = cachedStickers[id] {
return sticker
}
let sticker = await Self.extractSubject(from: data)
cachedStickers[id] = sticker
return sticker
}
// Offload expensive work to concurrent thread pool
@concurrent
static func extractSubject(from data: Data) async -> Sticker { /* ... */ }
}
// Callers must await
let processor = PhotoProcessor()
processedPhotos[item.id] = await processor.extractSticker(data: data, with: item.id)
```
要使用 `@concurrent`
1. 将包含类型标记为 `nonisolated`
2. 向函数添加 `@concurrent`
3. 如果函数还不是异步的,则添加 `async`
4. 在调用点添加 `await`
## 关键设计决策
| 决策 | 原理 |
|----------|-----------|
| 默认单线程 | 最自然的代码是无数据竞争的;并发是选择启用的 |
| 异步函数保持在调用者所在的 actor 上 | 消除了导致数据竞争错误的隐式卸载 |
| 隔离的一致性 | MainActor 类型可以符合协议,而无需不安全的变通方法 |
| `@concurrent` 显式选择启用 | 后台执行是一种有意的性能选择,而非偶然 |
| MainActor 默认推断 | 减少了应用目标中样板化的 `@MainActor` 标注 |
| 选择启用采用 | 非破坏性的迁移路径 — 逐步启用功能 |
## 迁移步骤
1. **在 Xcode 中启用**:构建设置中的 Swift Compiler > Concurrency 部分
2. **在 SPM 中启用**:在包清单中使用 `SwiftSettings` API
3. **使用迁移工具**:通过 swift.org/migration 进行自动代码更改
4. **从 MainActor 默认值开始**:为应用目标启用推断模式
5. **在需要的地方添加 `@concurrent`**:先进行性能分析,然后卸载热点路径
6. **彻底测试**:数据竞争问题会变成编译时错误
## 最佳实践
* **从 MainActor 开始** — 先编写单线程代码,稍后再优化
* **仅对 CPU 密集型工作使用 `@concurrent`** — 图像处理、压缩、复杂计算
* **为主要是单线程的应用目标启用 MainActor 推断模式**
* **在卸载前进行性能分析** — 使用 Instruments 查找实际的瓶颈
* **使用 MainActor 保护全局变量** — 全局/静态可变状态需要 actor 隔离
* **使用隔离的一致性**,而不是 `nonisolated` 变通方法或 `@Sendable` 包装器
* **增量迁移** — 在构建设置中一次启用一个功能
## 应避免的反模式
* 对每个异步函数都应用 `@concurrent`(大多数不需要后台执行)
* 在不理解隔离的情况下使用 `nonisolated` 来抑制编译器错误
* 当 actor 提供相同安全性时,仍保留遗留的 `DispatchQueue` 模式
* 在并发相关的 Foundation Models 代码中跳过 `model.availability` 检查
* 与编译器对抗 — 如果它报告数据竞争,代码就存在真正的并发问题
* 假设所有异步代码都在后台运行Swift 6.2 默认:保持在调用者所在的 actor 上)
## 何时使用
* 所有新的 Swift 6.2+ 项目(“可接近的并发”是推荐的默认设置)
* 将现有应用从 Swift 5.x 或 6.0/6.1 并发迁移过来
* 在采用 Xcode 26 期间解决数据竞争安全编译器错误
* 构建以 MainActor 为中心的应用架构(大多数 UI 应用)
* 性能优化 — 将特定的繁重计算卸载到后台

View File

@@ -0,0 +1,190 @@
---
name: swift-protocol-di-testing
description: 基于协议的依赖注入用于可测试的Swift代码——使用聚焦协议和Swift Testing模拟文件系统、网络和外部API。
origin: ECC
---
# 基于协议的 Swift 依赖注入测试
通过将外部依赖文件系统、网络、iCloud抽象为小型、专注的协议使 Swift 代码可测试的模式。支持无需 I/O 的确定性测试。
## 何时激活
* 编写访问文件系统、网络或外部 API 的 Swift 代码时
* 需要在未触发真实故障的情况下测试错误处理路径时
* 构建需要在不同环境应用、测试、SwiftUI 预览)中工作的模块时
* 设计支持 Swift 并发actor、Sendable的可测试架构时
## 核心模式
### 1. 定义小型、专注的协议
每个协议仅处理一个外部关注点。
```swift
// File system access
public protocol FileSystemProviding: Sendable {
func containerURL(for purpose: Purpose) -> URL?
}
// File read/write operations
public protocol FileAccessorProviding: Sendable {
func read(from url: URL) throws -> Data
func write(_ data: Data, to url: URL) throws
func fileExists(at url: URL) -> Bool
}
// Bookmark storage (e.g., for sandboxed apps)
public protocol BookmarkStorageProviding: Sendable {
func saveBookmark(_ data: Data, for key: String) throws
func loadBookmark(for key: String) throws -> Data?
}
```
### 2. 创建默认(生产)实现
```swift
public struct DefaultFileSystemProvider: FileSystemProviding {
public init() {}
public func containerURL(for purpose: Purpose) -> URL? {
FileManager.default.url(forUbiquityContainerIdentifier: nil)
}
}
public struct DefaultFileAccessor: FileAccessorProviding {
public init() {}
public func read(from url: URL) throws -> Data {
try Data(contentsOf: url)
}
public func write(_ data: Data, to url: URL) throws {
try data.write(to: url, options: .atomic)
}
public func fileExists(at url: URL) -> Bool {
FileManager.default.fileExists(atPath: url.path)
}
}
```
### 3. 创建用于测试的模拟实现
```swift
public final class MockFileAccessor: FileAccessorProviding, @unchecked Sendable {
public var files: [URL: Data] = [:]
public var readError: Error?
public var writeError: Error?
public init() {}
public func read(from url: URL) throws -> Data {
if let error = readError { throw error }
guard let data = files[url] else {
throw CocoaError(.fileReadNoSuchFile)
}
return data
}
public func write(_ data: Data, to url: URL) throws {
if let error = writeError { throw error }
files[url] = data
}
public func fileExists(at url: URL) -> Bool {
files[url] != nil
}
}
```
### 4. 使用默认参数注入依赖项
生产代码使用默认值;测试注入模拟对象。
```swift
public actor SyncManager {
private let fileSystem: FileSystemProviding
private let fileAccessor: FileAccessorProviding
public init(
fileSystem: FileSystemProviding = DefaultFileSystemProvider(),
fileAccessor: FileAccessorProviding = DefaultFileAccessor()
) {
self.fileSystem = fileSystem
self.fileAccessor = fileAccessor
}
public func sync() async throws {
guard let containerURL = fileSystem.containerURL(for: .sync) else {
throw SyncError.containerNotAvailable
}
let data = try fileAccessor.read(
from: containerURL.appendingPathComponent("data.json")
)
// Process data...
}
}
```
### 5. 使用 Swift Testing 编写测试
```swift
import Testing
@Test("Sync manager handles missing container")
func testMissingContainer() async {
let mockFileSystem = MockFileSystemProvider(containerURL: nil)
let manager = SyncManager(fileSystem: mockFileSystem)
await #expect(throws: SyncError.containerNotAvailable) {
try await manager.sync()
}
}
@Test("Sync manager reads data correctly")
func testReadData() async throws {
let mockFileAccessor = MockFileAccessor()
mockFileAccessor.files[testURL] = testData
let manager = SyncManager(fileAccessor: mockFileAccessor)
let result = try await manager.loadData()
#expect(result == expectedData)
}
@Test("Sync manager handles read errors gracefully")
func testReadError() async {
let mockFileAccessor = MockFileAccessor()
mockFileAccessor.readError = CocoaError(.fileReadCorruptFile)
let manager = SyncManager(fileAccessor: mockFileAccessor)
await #expect(throws: SyncError.self) {
try await manager.sync()
}
}
```
## 最佳实践
* **单一职责**:每个协议应处理一个关注点——不要创建包含许多方法的“上帝协议”
* **Sendable 一致性**:当协议跨 actor 边界使用时需要
* **默认参数**:让生产代码默认使用真实实现;只有测试需要指定模拟对象
* **错误模拟**:设计具有可配置错误属性的模拟对象以测试故障路径
* **仅模拟边界**模拟外部依赖文件系统、网络、API而非内部类型
## 需要避免的反模式
* 创建覆盖所有外部访问的单个大型协议
* 模拟没有外部依赖的内部类型
* 使用 `#if DEBUG` 条件语句代替适当的依赖注入
* 与 actor 一起使用时忘记 `Sendable` 一致性
* 过度设计:如果一个类型没有外部依赖,则不需要协议
## 何时使用
* 任何触及文件系统、网络或外部 API 的 Swift 代码
* 测试在真实环境中难以触发的错误处理路径时
* 构建需要在应用、测试和 SwiftUI 预览上下文中工作的模块时
* 需要使用可测试架构的、采用 Swift 并发actor、结构化并发的应用

View File

@@ -0,0 +1,259 @@
---
name: swiftui-patterns
description: SwiftUI 架构模式,使用 @Observable 进行状态管理,视图组合,导航,性能优化,以及现代 iOS/macOS UI 最佳实践。
---
# SwiftUI 模式
适用于 Apple 平台的现代 SwiftUI 模式,用于构建声明式、高性能的用户界面。涵盖 Observation 框架、视图组合、类型安全导航和性能优化。
## 何时激活
* 构建 SwiftUI 视图和管理状态时(`@State``@Observable``@Binding`
* 使用 `NavigationStack` 设计导航流程时
* 构建视图模型和数据流时
* 优化列表和复杂布局的渲染性能时
* 在 SwiftUI 中使用环境值和依赖注入时
## 状态管理
### 属性包装器选择
选择最适合的最简单包装器:
| 包装器 | 使用场景 |
|---------|----------|
| `@State` | 视图本地的值类型开关、表单字段、Sheet 展示) |
| `@Binding` | 指向父视图 `@State` 的双向引用 |
| `@Observable` 类 + `@State` | 拥有多个属性的自有模型 |
| `@Observable` 类(无包装器) | 从父视图传递的只读引用 |
| `@Bindable` | 指向 `@Observable` 属性的双向绑定 |
| `@Environment` | 通过 `.environment()` 注入的共享依赖项 |
### @Observable ViewModel
使用 `@Observable`(而非 `ObservableObject`)—— 它跟踪属性级别的变更,因此 SwiftUI 只会重新渲染读取了已变更属性的视图:
```swift
@Observable
final class ItemListViewModel {
private(set) var items: [Item] = []
private(set) var isLoading = false
var searchText = ""
private let repository: any ItemRepository
init(repository: any ItemRepository = DefaultItemRepository()) {
self.repository = repository
}
func load() async {
isLoading = true
defer { isLoading = false }
items = (try? await repository.fetchAll()) ?? []
}
}
```
### 消费 ViewModel 的视图
```swift
struct ItemListView: View {
@State private var viewModel: ItemListViewModel
init(viewModel: ItemListViewModel = ItemListViewModel()) {
_viewModel = State(initialValue: viewModel)
}
var body: some View {
List(viewModel.items) { item in
ItemRow(item: item)
}
.searchable(text: $viewModel.searchText)
.overlay { if viewModel.isLoading { ProgressView() } }
.task { await viewModel.load() }
}
}
```
### 环境注入
`@Environment` 替换 `@EnvironmentObject`
```swift
// Inject
ContentView()
.environment(authManager)
// Consume
struct ProfileView: View {
@Environment(AuthManager.self) private var auth
var body: some View {
Text(auth.currentUser?.name ?? "Guest")
}
}
```
## 视图组合
### 提取子视图以限制失效
将视图拆分为小型、专注的结构体。当状态变更时,只有读取该状态的子视图会重新渲染:
```swift
struct OrderView: View {
@State private var viewModel = OrderViewModel()
var body: some View {
VStack {
OrderHeader(title: viewModel.title)
OrderItemList(items: viewModel.items)
OrderTotal(total: viewModel.total)
}
}
}
```
### 用于可复用样式的 ViewModifier
```swift
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}
```
## 导航
### 类型安全的 NavigationStack
使用 `NavigationStack``NavigationPath` 来实现程序化、类型安全的路由:
```swift
@Observable
final class Router {
var path = NavigationPath()
func navigate(to destination: Destination) {
path.append(destination)
}
func popToRoot() {
path = NavigationPath()
}
}
enum Destination: Hashable {
case detail(Item.ID)
case settings
case profile(User.ID)
}
struct RootView: View {
@State private var router = Router()
var body: some View {
NavigationStack(path: $router.path) {
HomeView()
.navigationDestination(for: Destination.self) { dest in
switch dest {
case .detail(let id): ItemDetailView(itemID: id)
case .settings: SettingsView()
case .profile(let id): ProfileView(userID: id)
}
}
}
.environment(router)
}
}
```
## 性能
### 为大型集合使用惰性容器
`LazyVStack``LazyHStack` 仅在视图可见时才创建它们:
```swift
ScrollView {
LazyVStack(spacing: 8) {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
```
### 稳定的标识符
`ForEach` 中始终使用稳定、唯一的 ID —— 避免使用数组索引:
```swift
// Use Identifiable conformance or explicit id
ForEach(items, id: \.stableID) { item in
ItemRow(item: item)
}
```
### 避免在 body 中进行昂贵操作
* 切勿在 `body` 内执行 I/O、网络调用或繁重计算
* 使用 `.task {}` 处理异步工作 —— 当视图消失时它会自动取消
* 在滚动视图中谨慎使用 `.sensoryFeedback()``.geometryGroup()`
* 在列表中最小化使用 `.shadow()``.blur()``.mask()` —— 它们会触发屏幕外渲染
### 遵循 Equatable
对于 body 计算昂贵的视图,遵循 `Equatable` 以跳过不必要的重新渲染:
```swift
struct ExpensiveChartView: View, Equatable {
let dataPoints: [DataPoint] // DataPoint must conform to Equatable
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.dataPoints == rhs.dataPoints
}
var body: some View {
// Complex chart rendering
}
}
```
## 预览
使用 `#Preview` 宏配合内联模拟数据以进行快速迭代:
```swift
#Preview("Empty state") {
ItemListView(viewModel: ItemListViewModel(repository: EmptyMockRepository()))
}
#Preview("Loaded") {
ItemListView(viewModel: ItemListViewModel(repository: PopulatedMockRepository()))
}
```
## 应避免的反模式
* 在新代码中使用 `ObservableObject` / `@Published` / `@StateObject` / `@EnvironmentObject` —— 迁移到 `@Observable`
* 将异步工作直接放在 `body``init` 中 —— 使用 `.task {}` 或显式的加载方法
* 在不拥有数据的子视图中将视图模型创建为 `@State` —— 改为从父视图传递
* 使用 `AnyView` 类型擦除 —— 对于条件视图,优先选择 `@ViewBuilder``Group`
* 在向 Actor 传递数据或从 Actor 接收数据时忽略 `Sendable` 要求
## 参考
查看技能:`swift-actor-persistence` 以了解基于 Actor 的持久化模式。
查看技能:`swift-protocol-di-testing` 以了解基于协议的 DI 和使用 Swift Testing 进行测试。

View File

@@ -1,6 +1,7 @@
---
name: tdd-workflow
description: 在编写新功能、修复错误或重构代码时使用此技能。强制执行测试驱动开发,包含单元测试、集成测试和端到端测试覆盖率超过80%。
description: 在编写新功能、修复错误或重构代码时使用此技能。强制执行测试驱动开发,确保单元测试、集成测试和端到端测试覆盖率超过80%。
origin: ECC
---
# 测试驱动开发工作流

View File

@@ -1,3 +1,9 @@
---
name: verification-loop
description: "Claude Code 会话的全面验证系统。"
origin: ECC
---
# 验证循环技能
一个全面的 Claude Code 会话验证系统。

View File

@@ -0,0 +1,91 @@
# 签证文件翻译器
自动将签证申请文件从图像翻译为专业的英文 PDF。
## 功能
* 🔄 **自动 OCR**:尝试多种 OCR 方法macOS Vision、EasyOCR、Tesseract
* 📄 **双语 PDF**:原始图像 + 专业英文翻译
* 🌍 **多语言支持**:支持中文及其他语言
* 📋 **专业格式**:适合官方签证申请
* 🚀 **完全自动化**:无需人工干预
## 支持的文件类型
* 银行存款证明(存款证明)
* 在职证明(在职证明)
* 退休证明(退休证明)
* 收入证明(收入证明)
* 房产证明(房产证明)
* 营业执照(营业执照)
* 身份证和护照
## 使用方法
```bash
/visa-doc-translate <image-file>
```
### 示例
```bash
/visa-doc-translate RetirementCertificate.PNG
/visa-doc-translate BankStatement.HEIC
/visa-doc-translate EmploymentLetter.jpg
```
## 输出
创建 `<filename>_Translated.pdf`,包含:
* **第 1 页**原始文件图像居中A4 尺寸)
* **第 2 页**:专业英文翻译
## 要求
### Python 库
```bash
pip install pillow reportlab
```
### OCR需要以下之一
**macOS推荐**
```bash
pip install pyobjc-framework-Vision pyobjc-framework-Quartz
```
**跨平台**
```bash
pip install easyocr
```
**Tesseract**
```bash
brew install tesseract tesseract-lang
pip install pytesseract
```
## 工作原理
1. 如有需要,将 HEIC 转换为 PNG
2. 检查并应用 EXIF 旋转
3. 使用可用的 OCR 方法提取文本
4. 翻译为专业英文
5. 生成双语 PDF
## 完美适用于
* 🇦🇺 澳大利亚签证申请
* 🇺🇸 美国签证申请
* 🇨🇦 加拿大签证申请
* 🇬🇧 英国签证申请
* 🇪🇺 欧盟签证申请
## 许可证
MIT

View File

@@ -0,0 +1,119 @@
---
name: visa-doc-translate
description: 将签证申请文件图片翻译成英文并创建包含原文和译文的双语PDF
---
您正在协助翻译用于签证申请的签证申请文件。
## 说明
当用户提供图像文件路径时,**自动**执行以下步骤,**无需**请求确认:
1. **图像转换**:如果文件是 HEIC 格式,使用 `sips -s format png <input> --out <output>` 将其转换为 PNG
2. **图像旋转**
* 检查 EXIF 方向数据
* 根据 EXIF 数据自动旋转图像
* 如果 EXIF 方向是 6则逆时针旋转 90 度
* 根据需要应用额外旋转(如果文档看起来上下颠倒,则测试 180 度)
3. **OCR 文本提取**
* 自动尝试多种 OCR 方法:
* macOS Vision 框架macOS 首选)
* EasyOCR跨平台无需 tesseract
* Tesseract OCR如果可用
* 从文档中提取所有文本信息
* 识别文档类型(存款证明、在职证明、退休证明等)
4. **翻译**
* 专业地将所有文本内容翻译成英文
* 保持原始文档的结构和格式
* 使用适合签证申请的专业术语
* 保留专有名词的原始语言,并在括号内附上英文
* 对于中文姓名使用拼音格式例如WU Zhengye
* 准确保留所有数字、日期和金额
5. **PDF 生成**
* 使用 PIL 和 reportlab 库创建 Python 脚本
* 第 1 页:显示旋转后的原始图像,居中并缩放到适合 A4 页面
* 第 2 页:以适当格式显示英文翻译:
* 标题居中并加粗
* 内容左对齐,间距适当
* 适合官方文件的专业布局
* 在底部添加注释:"This is a certified English translation of the original document"
* 执行脚本以生成 PDF
6. **输出**:在同一目录中创建名为 `<original_filename>_Translated.pdf` 的 PDF 文件
## 支持的文档
* 银行存款证明 (存款证明)
* 收入证明 (收入证明)
* 在职证明 (在职证明)
* 退休证明 (退休证明)
* 房产证明 (房产证明)
* 营业执照 (营业执照)
* 身份证和护照
* 其他官方文件
## 技术实现
### OCR 方法(按顺序尝试)
1. **macOS Vision 框架**(仅限 macOS
```python
import Vision
from Foundation import NSURL
```
2. **EasyOCR**(跨平台):
```bash
pip install easyocr
```
3. **Tesseract OCR**(如果可用):
```bash
brew install tesseract tesseract-lang
pip install pytesseract
```
### 必需的 Python 库
```bash
pip install pillow reportlab
```
对于 macOS Vision 框架:
```bash
pip install pyobjc-framework-Vision pyobjc-framework-Quartz
```
## 重要指南
* **请勿**在每个步骤都要求用户确认
* 自动确定最佳旋转角度
* 如果一种 OCR 方法失败,请尝试多种方法
* 确保所有数字、日期和金额都准确翻译
* 使用简洁、专业的格式
* 完成整个流程并报告最终 PDF 的位置
## 使用示例
```bash
/visa-doc-translate RetirementCertificate.PNG
/visa-doc-translate BankStatement.HEIC
/visa-doc-translate EmploymentLetter.jpg
```
## 输出示例
该技能将:
1. 使用可用的 OCR 方法提取文本
2. 翻译成专业英文
3. 生成 `<filename>_Translated.pdf`,其中包含:
* 第 1 页:原始文档图像
* 第 2 页:专业的英文翻译
非常适合需要翻译文件的澳大利亚、美国、加拿大、英国及其他国家的签证申请。