chore(marketplace): add publish-plugin, rewrite init.sh, cleanup obsolete plugins
- Add publish-plugin: marketplace publish + Feishu bot notification - Rewrite init.sh: SSE-first, fixed prod API, remove env selection - Update CLAUDE.md, README.md, claude-config.yaml - Update skills: req-plugin, req-prd-plugin, pull-request-plugin - Delete sync-skills.sh (obsolete) - Delete deprecated plugins: skills-ops/*, skills-projects/*, old skills-dev/req duplicates - Regenerate marketplace.json (27 plugins) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "agent-swarm-plugin",
|
||||
"description": "Multi-agent orchestration using OpenAI Swarm patterns. Coordinate specialized agents for complex development workflows with handoffs and context sharing.",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
}
|
||||
@@ -1,406 +0,0 @@
|
||||
---
|
||||
name: agent-swarm
|
||||
description: Multi-agent orchestration using OpenAI Swarm patterns. Coordinate specialized agents for complex development workflows with handoffs and context sharing.
|
||||
---
|
||||
|
||||
# Agent Swarm - Multi-Agent Orchestration
|
||||
|
||||
基于 OpenAI Swarm 设计模式的多智能体协作系统,用于复杂开发任务的智能分解与协调。
|
||||
|
||||
## 核心概念
|
||||
|
||||
### 1. Agent(智能体)
|
||||
每个 Agent 是具有特定职责的专家:
|
||||
- **Instructions**: Agent 的角色定义和行为准则
|
||||
- **Functions**: Agent 可以调用的工具函数
|
||||
- **Handoffs**: 何时移交给其他 Agent
|
||||
|
||||
### 2. Handoff(任务移交)
|
||||
Agent 之间的控制权转移机制:
|
||||
- 当前 Agent 完成自己的职责
|
||||
- 识别需要其他专长
|
||||
- 移交给最合适的 Agent
|
||||
|
||||
### 3. Context Variables(上下文变量)
|
||||
跨 Agent 共享的状态:
|
||||
- 项目目录
|
||||
- 技术栈信息
|
||||
- 当前进度
|
||||
- 发现的问题
|
||||
|
||||
---
|
||||
|
||||
## 预定义 Agent
|
||||
|
||||
### 1. Architect Agent(架构师)
|
||||
**职责**: 理解需求、技术选型、设计系统架构
|
||||
|
||||
**何时使用**:
|
||||
- 用户描述新功能或系统
|
||||
- 需要技术方案设计
|
||||
- 需要架构评审
|
||||
|
||||
**工具**:
|
||||
- Read codebase
|
||||
- Grep patterns
|
||||
- 设计文档生成
|
||||
|
||||
**Handoff to**:
|
||||
- Coder Agent(开始编码)
|
||||
- Reviewer Agent(评审设计)
|
||||
|
||||
---
|
||||
|
||||
### 2. Coder Agent(编码者)
|
||||
**职责**: 实现功能、编写代码、修复 bug
|
||||
|
||||
**何时使用**:
|
||||
- 架构师完成设计
|
||||
- 用户提出 bug 修复
|
||||
- 需要代码重构
|
||||
|
||||
**工具**:
|
||||
- Edit files
|
||||
- Write files
|
||||
- Git operations
|
||||
|
||||
**Handoff to**:
|
||||
- Tester Agent(代码完成后)
|
||||
- Architect Agent(遇到设计问题)
|
||||
|
||||
---
|
||||
|
||||
### 3. Tester Agent(测试员)
|
||||
**职责**: 编写测试、运行测试、验证功能
|
||||
|
||||
**何时使用**:
|
||||
- 代码编写完成
|
||||
- 需要测试覆盖
|
||||
- 验证 bug 修复
|
||||
|
||||
**工具**:
|
||||
- Run tests
|
||||
- Write test cases
|
||||
- Coverage reports
|
||||
|
||||
**Handoff to**:
|
||||
- Deployer Agent(测试通过)
|
||||
- Coder Agent(发现问题)
|
||||
|
||||
---
|
||||
|
||||
### 4. Deployer Agent(部署员)
|
||||
**职责**: 构建镜像、部署服务、监控上线
|
||||
|
||||
**何时使用**:
|
||||
- 测试全部通过
|
||||
- 需要发布到环境
|
||||
- 需要回滚版本
|
||||
|
||||
**工具**:
|
||||
- Docker build
|
||||
- SSH deployment
|
||||
- Health checks
|
||||
|
||||
**Handoff to**:
|
||||
- Monitor Agent(部署完成)
|
||||
- Coder Agent(部署失败)
|
||||
|
||||
---
|
||||
|
||||
### 5. Reviewer Agent(评审员)
|
||||
**职责**: 代码审查、文档审查、安全检查
|
||||
|
||||
**何时使用**:
|
||||
- PR 创建后
|
||||
- 重要功能完成
|
||||
- 需要质量把关
|
||||
|
||||
**工具**:
|
||||
- Diff analysis
|
||||
- Security scan
|
||||
- Best practices check
|
||||
|
||||
**Handoff to**:
|
||||
- Coder Agent(需要修改)
|
||||
- Deployer Agent(审查通过)
|
||||
|
||||
---
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基本调用
|
||||
|
||||
```bash
|
||||
/swarm start "在 new-ai-proj 中实现任务批量删除功能"
|
||||
```
|
||||
|
||||
**执行流程**:
|
||||
1. **Architect** 分析需求 → 设计 API 和前端交互
|
||||
2. **Coder** 实现后端 API → 实现前端 UI
|
||||
3. **Tester** 编写单元测试 → 运行测试
|
||||
4. **Reviewer** 代码审查 → 安全检查
|
||||
5. **Deployer** 部署到 staging → 验证功能
|
||||
|
||||
---
|
||||
|
||||
### 指定起始 Agent
|
||||
|
||||
```bash
|
||||
/swarm coder "修复 backend/handlers/task_handler.go 的空指针 bug"
|
||||
```
|
||||
|
||||
直接从 Coder Agent 开始,跳过架构设计阶段。
|
||||
|
||||
---
|
||||
|
||||
### 传递上下文
|
||||
|
||||
```bash
|
||||
/swarm start "优化数据库查询性能" \
|
||||
--context project=/Users/coolbuy-dev/coding/new-ai-proj \
|
||||
--context stack=Go,PostgreSQL,Redis \
|
||||
--context module=backend/services
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 查看执行轨迹
|
||||
|
||||
```bash
|
||||
/swarm trace
|
||||
```
|
||||
|
||||
显示 Agent 调用链:
|
||||
```
|
||||
Architect → analyzed requirements (3 min)
|
||||
↓ handoff: "Design complete, ready for implementation"
|
||||
Coder → implemented 5 files (12 min)
|
||||
↓ handoff: "Code complete, needs testing"
|
||||
Tester → wrote 8 test cases, all passed (5 min)
|
||||
↓ handoff: "Tests passed, ready for review"
|
||||
Reviewer → approved with 2 suggestions (2 min)
|
||||
↓ handoff: "Approved, ready for deployment"
|
||||
Deployer → deployed to staging, health check OK (3 min)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 配置文件
|
||||
|
||||
### swarm.yaml
|
||||
|
||||
在项目根目录创建 `swarm.yaml` 自定义 Agent 行为:
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
architect:
|
||||
instructions: |
|
||||
你是系统架构师,专注于 Go + Vue.js 技术栈。
|
||||
遵循 RESTful API 设计原则。
|
||||
考虑性能、安全性、可维护性。
|
||||
max_turns: 5
|
||||
|
||||
coder:
|
||||
instructions: |
|
||||
你是 Go 后端工程师和 Vue.js 前端工程师。
|
||||
编写清晰、简洁、高性能的代码。
|
||||
遵循项目现有代码风格。
|
||||
tools:
|
||||
- Edit
|
||||
- Write
|
||||
- Bash
|
||||
max_turns: 10
|
||||
|
||||
tester:
|
||||
instructions: |
|
||||
你是测试工程师,编写全面的测试用例。
|
||||
确保边界条件、错误处理、并发安全。
|
||||
tools:
|
||||
- Bash
|
||||
- Write
|
||||
test_command: "go test ./... -v"
|
||||
max_turns: 5
|
||||
|
||||
context_variables:
|
||||
project_root: /Users/coolbuy-dev/coding/new-ai-proj
|
||||
backend_lang: Go 1.21
|
||||
frontend_framework: Vue 3
|
||||
database: PostgreSQL 15
|
||||
deployment_target: staging.ai.pipexerp.com
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 高级功能
|
||||
|
||||
### 1. 自定义 Agent
|
||||
|
||||
```yaml
|
||||
agents:
|
||||
database-optimizer:
|
||||
instructions: |
|
||||
你是数据库性能优化专家。
|
||||
分析慢查询、优化索引、设计缓存策略。
|
||||
functions:
|
||||
- explain_analyze
|
||||
- create_index
|
||||
- cache_design
|
||||
handoff_to:
|
||||
- coder # 实现优化方案
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. 条件 Handoff
|
||||
|
||||
```yaml
|
||||
handoff_rules:
|
||||
- from: tester
|
||||
to: coder
|
||||
condition: "test_pass_rate < 90%"
|
||||
message: "测试失败率超过 10%,需要修复"
|
||||
|
||||
- from: tester
|
||||
to: deployer
|
||||
condition: "test_pass_rate == 100%"
|
||||
message: "所有测试通过,可以部署"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. 并行 Agent
|
||||
|
||||
对于独立任务,多个 Agent 可以并行工作:
|
||||
|
||||
```bash
|
||||
/swarm parallel \
|
||||
"coder: 实现后端 API" \
|
||||
"coder: 实现前端 UI" \
|
||||
"tester: 编写 API 测试"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与 Remote Coding 集成
|
||||
|
||||
在 OpenClaw 中调用本地 Claude Code 执行 Swarm 工作流:
|
||||
|
||||
```bash
|
||||
# OpenClaw 调用 Melbourne Claude Code
|
||||
ssh melbourne "cd /Users/coolbuy-dev/coding/new-ai-proj && \
|
||||
/opt/homebrew/bin/claude --dangerously-skip-permissions \
|
||||
-p '/swarm start 实现任务批量删除功能'"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实际案例
|
||||
|
||||
### 案例 1: 新功能开发
|
||||
|
||||
**任务**: "为 AI-Proj 实现需求批量导出功能"
|
||||
|
||||
**执行过程**:
|
||||
1. **Architect**:
|
||||
- 分析需求:导出格式(Excel/PDF)、筛选条件、数据脱敏
|
||||
- 设计 API: `POST /api/v1/requirements/export`
|
||||
- 设计前端:导出按钮、进度条、下载链接
|
||||
|
||||
2. **Coder**:
|
||||
- 后端实现 export service
|
||||
- 前端实现导出 UI 组件
|
||||
- 集成 file download 功能
|
||||
|
||||
3. **Tester**:
|
||||
- 测试大量数据导出(1000+ 需求)
|
||||
- 测试并发导出
|
||||
- 测试下载失败重试
|
||||
|
||||
4. **Reviewer**:
|
||||
- 检查文件大小限制
|
||||
- 检查内存泄漏风险
|
||||
- 检查数据权限控制
|
||||
|
||||
5. **Deployer**:
|
||||
- 部署到 staging
|
||||
- 验证导出功能
|
||||
- 监控资源使用
|
||||
|
||||
---
|
||||
|
||||
### 案例 2: Bug 修复
|
||||
|
||||
**任务**: "修复任务详情页加载缓慢问题"
|
||||
|
||||
**执行过程**:
|
||||
1. **Architect**:
|
||||
- 分析性能瓶颈:N+1 查询问题
|
||||
- 设计优化方案:使用 JOIN 和预加载
|
||||
|
||||
2. **Coder**:
|
||||
- 优化数据库查询
|
||||
- 添加 Redis 缓存
|
||||
- 更新前端数据获取逻辑
|
||||
|
||||
3. **Tester**:
|
||||
- 性能测试:加载时间从 3s → 300ms
|
||||
- 并发测试:100 用户同时访问
|
||||
- 缓存一致性测试
|
||||
|
||||
4. **Deployer**:
|
||||
- 灰度发布到 10% 用户
|
||||
- 监控性能指标
|
||||
- 全量发布
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **明确任务范围**: 复杂任务交给 Swarm,简单任务直接执行
|
||||
2. **合理设置 max_turns**: 避免 Agent 陷入死循环
|
||||
3. **记录 Handoff 原因**: 便于追溯和调试
|
||||
4. **定期审查轨迹**: 优化 Agent 协作流程
|
||||
5. **利用 Context Variables**: 避免重复传递信息
|
||||
|
||||
---
|
||||
|
||||
## 故障排查
|
||||
|
||||
| 问题 | 原因 | 解决方案 |
|
||||
|------|------|----------|
|
||||
| Agent 一直循环 | max_turns 设置过大 | 降低 max_turns,添加明确的 handoff 条件 |
|
||||
| Handoff 失败 | 目标 Agent 未定义 | 检查 swarm.yaml 配置 |
|
||||
| 上下文丢失 | Context Variables 未传递 | 在 handoff 时显式传递 context |
|
||||
| 执行太慢 | 串行执行可并行任务 | 使用 `/swarm parallel` |
|
||||
|
||||
---
|
||||
|
||||
## 与其他 Skills 集成
|
||||
|
||||
- **dev-coding**: Coder Agent 使用 dev-coding 的编码规范
|
||||
- **dev-test**: Tester Agent 使用 dev-test 的测试策略
|
||||
- **ops-tools**: Deployer Agent 使用 ops-tools 进行部署
|
||||
- **ai-proj**: 所有 Agent 使用 ai-proj MCP 进行任务同步
|
||||
|
||||
---
|
||||
|
||||
## 命令速查
|
||||
|
||||
| 命令 | 功能 |
|
||||
|------|------|
|
||||
| `/swarm start <task>` | 启动 Swarm 工作流(从 Architect 开始) |
|
||||
| `/swarm <agent> <task>` | 从指定 Agent 开始 |
|
||||
| `/swarm parallel <tasks>` | 并行执行多个任务 |
|
||||
| `/swarm trace` | 查看执行轨迹 |
|
||||
| `/swarm config` | 显示当前配置 |
|
||||
| `/swarm agents` | 列出所有可用 Agent |
|
||||
| `/swarm stop` | 终止当前 Swarm 执行 |
|
||||
|
||||
---
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [OpenAI Swarm 文档](https://github.com/openai/swarm)
|
||||
- [Multi-Agent Systems 设计模式](https://arxiv.org/abs/2308.00352)
|
||||
- [Claude Code Skills 文档](https://docs.anthropic.com/claude-code/skills)
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "dev-plugin",
|
||||
"description": "Plugin for dev",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
---
|
||||
name: dev
|
||||
description: 软件开发技能组入口。整合架构设计(dev-arch)、编码实现(dev-coding)、测试(dev-test)三个子技能,提供完整的软件开发工作流。支持全栈开发:Go、Vue、React、iOS、Android、小程序等。
|
||||
---
|
||||
|
||||
# 软件开发 Skill (dev)
|
||||
|
||||
## 概述
|
||||
|
||||
dev 是一个技能组入口,整合了软件开发的三个核心阶段:
|
||||
|
||||
| 子技能 | 用途 | 触发词 |
|
||||
|--------|------|--------|
|
||||
| **dev-arch** | 架构设计、技术选型、系统设计 | 架构、设计、技术方案 |
|
||||
| **dev-coding** | 编码实现、功能开发、代码审查 | 编码、开发、实现 |
|
||||
| **dev-test** | 单元测试、集成测试、E2E测试 | 测试、test、覆盖率 |
|
||||
|
||||
---
|
||||
|
||||
## 开发工作流
|
||||
|
||||
```
|
||||
需求分析 → 架构设计 → 编码实现 → 测试验证 → 部署上线
|
||||
↓ ↓ ↓
|
||||
dev-arch dev-coding dev-test
|
||||
```
|
||||
|
||||
### 典型流程
|
||||
|
||||
1. **架构设计** (dev-arch)
|
||||
- 需求分析
|
||||
- 技术选型
|
||||
- 系统设计文档
|
||||
- 架构评审
|
||||
|
||||
2. **编码实现** (dev-coding)
|
||||
- 任务分解
|
||||
- 功能开发
|
||||
- 代码审查
|
||||
- 文档记录
|
||||
|
||||
3. **测试验证** (dev-test)
|
||||
- 单元测试
|
||||
- 集成测试
|
||||
- E2E 测试
|
||||
- 覆盖率分析
|
||||
|
||||
---
|
||||
|
||||
## 支持的项目类型
|
||||
|
||||
### 当前项目生态
|
||||
|
||||
| 项目 | 类型 | 技术栈 |
|
||||
|------|------|--------|
|
||||
| **TWMS** | 仓储物流 | Go + Vue 3 + MySQL |
|
||||
| **AI-Proj** | 项目管理 | Go + React + PostgreSQL + iOS + Android |
|
||||
| **DICIAI** | 进销存SaaS | Go + Vue 3 + MySQL + Android PDA |
|
||||
|
||||
### 技术栈矩阵
|
||||
|
||||
| 端 | 语言/框架 | 工具 |
|
||||
|-----|----------|------|
|
||||
| **后端** | Go (Gin + GORM) | MySQL/PostgreSQL, Redis, Docker |
|
||||
| **Web前端** | Vue 3 / React 18 | TypeScript, Vite/CRA, Ant Design |
|
||||
| **iOS** | Swift + SwiftUI | Xcode, XCTest |
|
||||
| **Android** | Kotlin + Compose | Gradle, Hilt, Room |
|
||||
| **PDA** | Android 原生 | 扫码枪集成, 离线存储 |
|
||||
| **MCP** | TypeScript | Node.js, MCP SDK |
|
||||
|
||||
---
|
||||
|
||||
## ai-proj 任务管理集成
|
||||
|
||||
所有开发工作都通过 ai-proj CLI 进行任务管理:
|
||||
|
||||
### 快速开始
|
||||
|
||||
```bash
|
||||
# 1. 查看待办任务
|
||||
ai-proj task list --status in_progress,todo
|
||||
|
||||
# 2. 启动任务
|
||||
ai-proj task start --id <taskId>
|
||||
|
||||
# 3. 完成任务
|
||||
ai-proj task complete --id <taskId>
|
||||
|
||||
# 4. 记录文档
|
||||
ai-proj task append-doc --id <taskId> --content "实现说明"
|
||||
```
|
||||
|
||||
### 任务分解
|
||||
|
||||
```bash
|
||||
# 创建主任务
|
||||
ai-proj task create --title "功能名称"
|
||||
|
||||
# 创建子任务
|
||||
ai-proj task create --title "架构设计" --parent-id <parentId>
|
||||
ai-proj task create --title "功能开发" --parent-id <parentId>
|
||||
ai-proj task create --title "测试验证" --parent-id <parentId>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用命令速查
|
||||
|
||||
### Go 后端
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
make build
|
||||
|
||||
# 运行
|
||||
./_output/main --config ./configs/config.yaml
|
||||
|
||||
# 测试
|
||||
make test
|
||||
make cover
|
||||
```
|
||||
|
||||
### Vue 前端
|
||||
|
||||
```bash
|
||||
# 开发
|
||||
npm run dev
|
||||
|
||||
# 构建
|
||||
npm run build:prod
|
||||
|
||||
# 检查
|
||||
npm run lint:eslint
|
||||
```
|
||||
|
||||
### React 前端
|
||||
|
||||
```bash
|
||||
# 开发
|
||||
npm start
|
||||
|
||||
# 构建
|
||||
npm run build
|
||||
|
||||
# 测试
|
||||
npm test
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
### iOS
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
xcodebuild -scheme ProjectName -configuration Debug
|
||||
|
||||
# 测试
|
||||
xcodebuild test -scheme ProjectName
|
||||
```
|
||||
|
||||
### Android
|
||||
|
||||
```bash
|
||||
# 构建
|
||||
./gradlew assembleDebug
|
||||
./gradlew assembleRelease
|
||||
|
||||
# 测试
|
||||
./gradlew test
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Git 工作流
|
||||
|
||||
### 提交规范
|
||||
|
||||
| 类型 | 说明 |
|
||||
|------|------|
|
||||
| feat | 新功能 |
|
||||
| fix | Bug 修复 |
|
||||
| docs | 文档 |
|
||||
| refactor | 重构 |
|
||||
| test | 测试 |
|
||||
| chore | 杂项 |
|
||||
|
||||
### 分支策略
|
||||
|
||||
```bash
|
||||
# 功能开发
|
||||
git checkout -b feature/功能名称
|
||||
|
||||
# 提交
|
||||
git commit -m "feat: 功能描述"
|
||||
|
||||
# 推送
|
||||
git push origin feature/功能名称
|
||||
|
||||
# 合并
|
||||
git checkout main
|
||||
git merge feature/功能名称
|
||||
```
|
||||
|
||||
### 双电脑同步 (au-dev / cn-dev)
|
||||
|
||||
```bash
|
||||
# 离开时
|
||||
git add -A
|
||||
git commit -m "WIP: sync from $(hostname)"
|
||||
git push origin $(git branch --show-current)
|
||||
|
||||
# 到达时
|
||||
git fetch origin
|
||||
git pull origin $(git branch --show-current)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker 部署
|
||||
|
||||
### 标准配置
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
build: ./backend
|
||||
ports:
|
||||
- "8080:8080"
|
||||
depends_on:
|
||||
- db
|
||||
- redis
|
||||
|
||||
frontend:
|
||||
build: ./frontend
|
||||
ports:
|
||||
- "80:80"
|
||||
|
||||
db:
|
||||
image: mysql:8.0
|
||||
# 或 postgres:15
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
```
|
||||
|
||||
### 常用端口
|
||||
|
||||
| 服务 | 端口 |
|
||||
|------|------|
|
||||
| Backend | 8080 / 9099 |
|
||||
| Frontend | 80 / 3000 |
|
||||
| MySQL | 3306 |
|
||||
| PostgreSQL | 5432 |
|
||||
| Redis | 6379 |
|
||||
|
||||
---
|
||||
|
||||
## 子技能详情
|
||||
|
||||
### dev-arch (架构设计)
|
||||
|
||||
用于系统设计阶段:
|
||||
- 需求分析
|
||||
- 技术选型
|
||||
- 架构设计文档
|
||||
- API 设计
|
||||
- 数据库设计
|
||||
- 架构评审
|
||||
|
||||
### dev-coding (编码实现)
|
||||
|
||||
用于开发实现阶段:
|
||||
- Go 后端开发
|
||||
- Vue/React 前端开发
|
||||
- iOS/Android 移动开发
|
||||
- PDA 应用开发
|
||||
- MCP 桥接开发
|
||||
- 代码审查
|
||||
|
||||
### dev-test (测试)
|
||||
|
||||
用于测试验证阶段:
|
||||
- 单元测试
|
||||
- 集成测试
|
||||
- E2E 测试
|
||||
- UI 测试
|
||||
- 覆盖率分析
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **任务驱动** - 使用 ai-proj 管理所有开发任务
|
||||
2. **设计先行** - 复杂功能先设计后编码
|
||||
3. **分层清晰** - Controller → Service → Repository
|
||||
4. **小步提交** - 频繁提交,每次做一件事
|
||||
5. **测试覆盖** - 核心逻辑必须有测试
|
||||
6. **文档同步** - 代码变更同步更新文档
|
||||
7. **代码审查** - 重要变更必须审查
|
||||
|
||||
---
|
||||
|
||||
## 何时使用哪个子技能
|
||||
|
||||
| 场景 | 推荐技能 |
|
||||
|------|----------|
|
||||
| 新功能设计 | dev-arch |
|
||||
| 技术方案评审 | dev-arch |
|
||||
| 功能开发实现 | dev-coding |
|
||||
| Bug 修复 | dev-coding |
|
||||
| 编写测试 | dev-test |
|
||||
| 测试覆盖率提升 | dev-test |
|
||||
| 代码审查 | dev-coding |
|
||||
| 性能优化 | dev-arch + dev-coding |
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "finishing-a-development-branch-plugin",
|
||||
"description": "Plugin for finishing-a-development-branch",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
---
|
||||
name: finishing-a-development-branch
|
||||
description: Use when implementation is complete and all tests pass - verifies and creates PR
|
||||
---
|
||||
|
||||
# Finishing a Development Branch
|
||||
|
||||
## Overview
|
||||
|
||||
Verify tests pass, then push and create PR.
|
||||
|
||||
**Core principle:** Verify tests → Create PR → Done.
|
||||
|
||||
**Announce at start:** "I'm using the finishing-a-development-branch skill to complete this work."
|
||||
|
||||
## The Process
|
||||
|
||||
### Step 1: Verify Tests
|
||||
|
||||
**Before creating PR, verify tests pass:**
|
||||
|
||||
```bash
|
||||
# Run project's test suite
|
||||
npm test / cargo test / pytest / go test ./... / mvn test
|
||||
```
|
||||
|
||||
**If tests fail:**
|
||||
```
|
||||
Tests failing (<N> failures). Must fix before completing:
|
||||
|
||||
[Show failures]
|
||||
|
||||
Cannot proceed with PR until tests pass.
|
||||
```
|
||||
|
||||
Stop. Fix tests first.
|
||||
|
||||
**If tests pass:** Continue to Step 2.
|
||||
|
||||
### Step 2: Push and Create PR
|
||||
|
||||
Use the `/pr create` command which will:
|
||||
1. **Check for existing PR first** - avoids duplicates
|
||||
2. If PR exists: Report existing PR URL and skip
|
||||
3. If no PR: Analyze commits, generate title/description, push, create PR
|
||||
|
||||
```bash
|
||||
/pr create
|
||||
```
|
||||
|
||||
**Duplicate prevention:** The `/pr create` command checks for existing open PRs on the current branch before creating a new one.
|
||||
|
||||
Report the PR URL when complete (whether existing or newly created).
|
||||
|
||||
### Step 3: Cleanup Worktree (if applicable)
|
||||
|
||||
Check if working in a worktree:
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
```
|
||||
|
||||
If yes, ask user:
|
||||
```
|
||||
Worktree at <path>. Remove it now? (y/n)
|
||||
```
|
||||
|
||||
If confirmed:
|
||||
```bash
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```
|
||||
Tests Pass?
|
||||
↓ yes
|
||||
/pr create
|
||||
↓
|
||||
PR URL returned
|
||||
↓
|
||||
Cleanup worktree (optional)
|
||||
↓
|
||||
Done
|
||||
```
|
||||
|
||||
## Red Flags
|
||||
|
||||
**Never:**
|
||||
- Create PR with failing tests
|
||||
- Skip test verification
|
||||
- Force-push without explicit request
|
||||
|
||||
**Always:**
|
||||
- Verify tests before creating PR
|
||||
- Use `/pr create` for consistent PR format
|
||||
- Report the PR URL
|
||||
|
||||
## Integration
|
||||
|
||||
**Called by:**
|
||||
- **executing-plans** (Step 6) - After all batches complete
|
||||
|
||||
**Uses:**
|
||||
- **/pr create** - For pushing and PR creation
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "frontend-design-plugin",
|
||||
"description": "Plugin for frontend-design",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
}
|
||||
@@ -1,695 +0,0 @@
|
||||
---
|
||||
name: frontend-design
|
||||
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
|
||||
arguments: [component|page|storybook] <description>
|
||||
---
|
||||
|
||||
# Frontend Design 前端设计技能
|
||||
|
||||
创建高质量、有设计感的前端界面和组件,支持 Storybook 组件开发。
|
||||
|
||||
---
|
||||
|
||||
## 命令格式
|
||||
|
||||
| 命令 | 功能 | 示例 |
|
||||
|------|------|------|
|
||||
| `/frontend-design component <描述>` | 创建 React/Vue 组件 | `/frontend-design component 产品卡片` |
|
||||
| `/frontend-design page <描述>` | 创建完整页面 | `/frontend-design page 登录页` |
|
||||
| `/frontend-design storybook <描述>` | 创建带 Storybook 的组件 | `/frontend-design storybook 按钮组件` |
|
||||
|
||||
---
|
||||
|
||||
## 设计原则
|
||||
|
||||
### 1. 设计思维先行
|
||||
|
||||
在编码前,明确以下问题:
|
||||
|
||||
- **目的**:这个界面解决什么问题?谁在使用?
|
||||
- **调性**:选择一个明确的美学方向
|
||||
- **差异化**:什么让这个设计令人难忘?
|
||||
|
||||
### 2. 美学方向选择
|
||||
|
||||
| 风格 | 特点 | 适用场景 |
|
||||
|------|------|----------|
|
||||
| 极简主义 | 大量留白、精炼元素 | 工具类、专业平台 |
|
||||
| 现代商务 | 清晰层次、专业配色 | 企业官网、B2B |
|
||||
| 活力年轻 | 鲜艳色彩、动感动画 | 消费品、社交 |
|
||||
| 奢华精致 | 深色调、金属质感 | 高端品牌、金融 |
|
||||
| 自然有机 | 柔和曲线、自然色系 | 健康、环保 |
|
||||
| 复古怀旧 | 经典字体、做旧质感 | 文化、艺术 |
|
||||
| 未来科技 | 渐变、玻璃拟态 | 科技、创新 |
|
||||
|
||||
### 3. 避免的设计陷阱
|
||||
|
||||
**禁止使用**:
|
||||
- 过度使用的字体:Inter、Roboto、Arial
|
||||
- 陈词滥调的配色:紫色渐变白底
|
||||
- 千篇一律的布局
|
||||
- 缺乏个性的通用组件
|
||||
|
||||
**应该追求**:
|
||||
- 独特的字体组合
|
||||
- 有意图的配色方案
|
||||
- 打破常规的布局
|
||||
- 有记忆点的细节
|
||||
|
||||
---
|
||||
|
||||
## Storybook 组件开发
|
||||
|
||||
### 项目结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── components/
|
||||
│ ├── Button/
|
||||
│ │ ├── Button.tsx
|
||||
│ │ ├── Button.stories.tsx
|
||||
│ │ ├── Button.module.css
|
||||
│ │ └── index.ts
|
||||
│ ├── Card/
|
||||
│ │ ├── Card.tsx
|
||||
│ │ ├── Card.stories.tsx
|
||||
│ │ ├── Card.module.css
|
||||
│ │ └── index.ts
|
||||
│ └── index.ts
|
||||
├── styles/
|
||||
│ ├── variables.css
|
||||
│ ├── typography.css
|
||||
│ └── animations.css
|
||||
└── .storybook/
|
||||
├── main.ts
|
||||
└── preview.ts
|
||||
```
|
||||
|
||||
### 组件模板
|
||||
|
||||
#### 1. 组件文件 (Component.tsx)
|
||||
|
||||
```tsx
|
||||
import React from 'react';
|
||||
import styles from './Component.module.css';
|
||||
|
||||
export interface ComponentProps {
|
||||
/** 组件变体 */
|
||||
variant?: 'primary' | 'secondary' | 'outline';
|
||||
/** 尺寸 */
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
/** 是否禁用 */
|
||||
disabled?: boolean;
|
||||
/** 子元素 */
|
||||
children: React.ReactNode;
|
||||
/** 点击事件 */
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const Component: React.FC<ComponentProps> = ({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
disabled = false,
|
||||
children,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={`${styles.component} ${styles[variant]} ${styles[size]}`}
|
||||
data-disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 2. Storybook Stories (Component.stories.tsx)
|
||||
|
||||
```tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { Component } from './Component';
|
||||
|
||||
const meta: Meta<typeof Component> = {
|
||||
title: 'Components/Component',
|
||||
component: Component,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: '组件描述文档',
|
||||
},
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['primary', 'secondary', 'outline'],
|
||||
description: '组件变体样式',
|
||||
},
|
||||
size: {
|
||||
control: 'radio',
|
||||
options: ['sm', 'md', 'lg'],
|
||||
description: '组件尺寸',
|
||||
},
|
||||
disabled: {
|
||||
control: 'boolean',
|
||||
description: '是否禁用',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof Component>;
|
||||
|
||||
/** 默认状态 */
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: '默认组件',
|
||||
},
|
||||
};
|
||||
|
||||
/** 主要变体 */
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
children: '主要按钮',
|
||||
},
|
||||
};
|
||||
|
||||
/** 次要变体 */
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
children: '次要按钮',
|
||||
},
|
||||
};
|
||||
|
||||
/** 不同尺寸 */
|
||||
export const Sizes: Story = {
|
||||
render: () => (
|
||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center' }}>
|
||||
<Component size="sm">小号</Component>
|
||||
<Component size="md">中号</Component>
|
||||
<Component size="lg">大号</Component>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
/** 禁用状态 */
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
disabled: true,
|
||||
children: '禁用状态',
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
#### 3. 样式文件 (Component.module.css)
|
||||
|
||||
```css
|
||||
.component {
|
||||
/* 基础样式 */
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-md);
|
||||
font-family: var(--font-sans);
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
/* 变体 */
|
||||
.primary {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.primary:hover {
|
||||
background: var(--color-primary-dark);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px var(--color-primary-shadow);
|
||||
}
|
||||
|
||||
.secondary {
|
||||
background: var(--color-secondary);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.outline {
|
||||
background: transparent;
|
||||
border: 2px solid var(--color-border);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
/* 尺寸 */
|
||||
.sm {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.md {
|
||||
padding: 0.75rem 1.5rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.lg {
|
||||
padding: 1rem 2rem;
|
||||
font-size: 1.125rem;
|
||||
}
|
||||
|
||||
/* 状态 */
|
||||
[data-disabled="true"] {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计系统变量
|
||||
|
||||
### CSS 变量模板
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* 颜色 */
|
||||
--color-primary: #0066ff;
|
||||
--color-primary-dark: #0052cc;
|
||||
--color-primary-light: #4d94ff;
|
||||
--color-primary-shadow: rgba(0, 102, 255, 0.25);
|
||||
|
||||
--color-secondary: #f0f4f8;
|
||||
--color-accent: #ff6b35;
|
||||
|
||||
--color-text: #1a1a2e;
|
||||
--color-text-muted: #64748b;
|
||||
--color-text-inverse: #ffffff;
|
||||
|
||||
--color-background: #ffffff;
|
||||
--color-surface: #f8fafc;
|
||||
--color-border: #e2e8f0;
|
||||
|
||||
--color-success: #10b981;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
|
||||
/* 字体 */
|
||||
--font-sans: 'Plus Jakarta Sans', system-ui, sans-serif;
|
||||
--font-display: 'Clash Display', var(--font-sans);
|
||||
--font-mono: 'JetBrains Mono', monospace;
|
||||
|
||||
/* 字号 */
|
||||
--text-xs: 0.75rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-base: 1rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-3xl: 2rem;
|
||||
--text-4xl: 2.5rem;
|
||||
|
||||
/* 间距 */
|
||||
--space-1: 0.25rem;
|
||||
--space-2: 0.5rem;
|
||||
--space-3: 0.75rem;
|
||||
--space-4: 1rem;
|
||||
--space-6: 1.5rem;
|
||||
--space-8: 2rem;
|
||||
--space-12: 3rem;
|
||||
--space-16: 4rem;
|
||||
|
||||
/* 圆角 */
|
||||
--radius-sm: 0.25rem;
|
||||
--radius-md: 0.5rem;
|
||||
--radius-lg: 1rem;
|
||||
--radius-xl: 1.5rem;
|
||||
--radius-full: 9999px;
|
||||
|
||||
/* 阴影 */
|
||||
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
|
||||
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
|
||||
|
||||
/* 动画 */
|
||||
--duration-fast: 150ms;
|
||||
--duration-normal: 300ms;
|
||||
--duration-slow: 500ms;
|
||||
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
|
||||
--ease-bounce: cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
/* 暗色主题 */
|
||||
[data-theme="dark"] {
|
||||
--color-text: #f1f5f9;
|
||||
--color-text-muted: #94a3b8;
|
||||
--color-background: #0f172a;
|
||||
--color-surface: #1e293b;
|
||||
--color-border: #334155;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 常用组件示例
|
||||
|
||||
### 1. 产品卡片 (ProductCard)
|
||||
|
||||
```tsx
|
||||
// ProductCard.tsx
|
||||
import React from 'react';
|
||||
import styles from './ProductCard.module.css';
|
||||
|
||||
export interface ProductCardProps {
|
||||
image: string;
|
||||
title: string;
|
||||
location: string;
|
||||
rating: number;
|
||||
reviewCount: number;
|
||||
price: number;
|
||||
originalPrice?: number;
|
||||
tags?: string[];
|
||||
onAddToCart?: () => void;
|
||||
}
|
||||
|
||||
export const ProductCard: React.FC<ProductCardProps> = ({
|
||||
image,
|
||||
title,
|
||||
location,
|
||||
rating,
|
||||
reviewCount,
|
||||
price,
|
||||
originalPrice,
|
||||
tags = [],
|
||||
onAddToCart,
|
||||
}) => {
|
||||
return (
|
||||
<article className={styles.card}>
|
||||
<div className={styles.imageWrapper}>
|
||||
<img src={image} alt={title} className={styles.image} />
|
||||
{tags.length > 0 && (
|
||||
<div className={styles.tags}>
|
||||
{tags.map((tag) => (
|
||||
<span key={tag} className={styles.tag} data-tag={tag}>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<h3 className={styles.title}>{title}</h3>
|
||||
<p className={styles.location}>📍 {location}</p>
|
||||
|
||||
<div className={styles.rating}>
|
||||
<span className={styles.stars}>⭐ {rating.toFixed(1)}</span>
|
||||
<span className={styles.reviewCount}>({reviewCount}条评价)</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.priceRow}>
|
||||
<div className={styles.price}>
|
||||
<span className={styles.currency}>¥</span>
|
||||
<span className={styles.amount}>{price}</span>
|
||||
<span className={styles.suffix}>起</span>
|
||||
</div>
|
||||
{originalPrice && (
|
||||
<span className={styles.originalPrice}>¥{originalPrice}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button className={styles.addButton} onClick={onAddToCart}>
|
||||
加入购物车
|
||||
</button>
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
```tsx
|
||||
// ProductCard.stories.tsx
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import { ProductCard } from './ProductCard';
|
||||
|
||||
const meta: Meta<typeof ProductCard> = {
|
||||
title: 'Components/ProductCard',
|
||||
component: ProductCard,
|
||||
tags: ['autodocs'],
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
backgrounds: {
|
||||
default: 'light',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ProductCard>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
image: 'https://images.unsplash.com/photo-1494947665470-20322015e3a8',
|
||||
title: '袋鼠岛一日游',
|
||||
location: '阿德莱德出发',
|
||||
rating: 4.8,
|
||||
reviewCount: 126,
|
||||
price: 389,
|
||||
tags: ['热卖', '含午餐'],
|
||||
},
|
||||
};
|
||||
|
||||
export const WithDiscount: Story = {
|
||||
args: {
|
||||
...Default.args,
|
||||
originalPrice: 499,
|
||||
tags: ['特惠', '限时'],
|
||||
},
|
||||
};
|
||||
|
||||
export const Grid: Story = {
|
||||
render: () => (
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(3, 300px)',
|
||||
gap: '1.5rem'
|
||||
}}>
|
||||
<ProductCard
|
||||
image="https://images.unsplash.com/photo-1494947665470-20322015e3a8"
|
||||
title="袋鼠岛一日游"
|
||||
location="阿德莱德出发"
|
||||
rating={4.8}
|
||||
reviewCount={126}
|
||||
price={389}
|
||||
tags={['热卖']}
|
||||
/>
|
||||
<ProductCard
|
||||
image="https://images.unsplash.com/photo-1506905925346-21bda4d32df4"
|
||||
title="巴罗莎谷酒庄之旅"
|
||||
location="阿德莱德出发"
|
||||
rating={4.9}
|
||||
reviewCount={89}
|
||||
price={299}
|
||||
originalPrice={399}
|
||||
tags={['特惠', '含品酒']}
|
||||
/>
|
||||
<ProductCard
|
||||
image="https://images.unsplash.com/photo-1540202403-b7abd6747a18"
|
||||
title="海豚巡航体验"
|
||||
location="格雷尔海滩"
|
||||
rating={4.7}
|
||||
reviewCount={234}
|
||||
price={159}
|
||||
tags={['亲子']}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
```
|
||||
|
||||
### 2. 按钮组件 (Button)
|
||||
|
||||
```tsx
|
||||
// Button.tsx
|
||||
import React from 'react';
|
||||
import styles from './Button.module.css';
|
||||
|
||||
export interface ButtonProps {
|
||||
variant?: 'primary' | 'secondary' | 'outline' | 'ghost' | 'danger';
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
fullWidth?: boolean;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
leftIcon?: React.ReactNode;
|
||||
rightIcon?: React.ReactNode;
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const Button: React.FC<ButtonProps> = ({
|
||||
variant = 'primary',
|
||||
size = 'md',
|
||||
fullWidth = false,
|
||||
loading = false,
|
||||
disabled = false,
|
||||
leftIcon,
|
||||
rightIcon,
|
||||
children,
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
className={`
|
||||
${styles.button}
|
||||
${styles[variant]}
|
||||
${styles[size]}
|
||||
${fullWidth ? styles.fullWidth : ''}
|
||||
`}
|
||||
disabled={disabled || loading}
|
||||
onClick={onClick}
|
||||
>
|
||||
{loading ? (
|
||||
<span className={styles.spinner} />
|
||||
) : (
|
||||
<>
|
||||
{leftIcon && <span className={styles.icon}>{leftIcon}</span>}
|
||||
<span>{children}</span>
|
||||
{rightIcon && <span className={styles.icon}>{rightIcon}</span>}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Storybook 配置
|
||||
|
||||
### .storybook/main.ts
|
||||
|
||||
```ts
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
'@storybook/addon-a11y',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
||||
```
|
||||
|
||||
### .storybook/preview.ts
|
||||
|
||||
```ts
|
||||
import type { Preview } from '@storybook/react';
|
||||
import '../src/styles/variables.css';
|
||||
import '../src/styles/typography.css';
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
backgrounds: {
|
||||
default: 'light',
|
||||
values: [
|
||||
{ name: 'light', value: '#ffffff' },
|
||||
{ name: 'gray', value: '#f8fafc' },
|
||||
{ name: 'dark', value: '#0f172a' },
|
||||
],
|
||||
},
|
||||
},
|
||||
globalTypes: {
|
||||
theme: {
|
||||
description: 'Global theme for components',
|
||||
defaultValue: 'light',
|
||||
toolbar: {
|
||||
title: 'Theme',
|
||||
icon: 'circlehollow',
|
||||
items: ['light', 'dark'],
|
||||
dynamicTitle: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 快速启动命令
|
||||
|
||||
### 创建新组件
|
||||
|
||||
```bash
|
||||
# 创建组件目录
|
||||
mkdir -p src/components/ComponentName
|
||||
|
||||
# 创建文件
|
||||
touch src/components/ComponentName/{ComponentName.tsx,ComponentName.stories.tsx,ComponentName.module.css,index.ts}
|
||||
```
|
||||
|
||||
### 安装 Storybook
|
||||
|
||||
```bash
|
||||
# 初始化 Storybook
|
||||
npx storybook@latest init
|
||||
|
||||
# 安装额外插件
|
||||
npm install -D @storybook/addon-a11y @storybook/addon-interactions
|
||||
|
||||
# 启动 Storybook
|
||||
npm run storybook
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 设计检查清单
|
||||
|
||||
### 组件质量检查
|
||||
|
||||
- [ ] Props 接口定义完整,带 JSDoc 注释
|
||||
- [ ] 支持必要的变体(variant)和尺寸(size)
|
||||
- [ ] 处理禁用和加载状态
|
||||
- [ ] 支持自定义 className
|
||||
- [ ] 键盘可访问性
|
||||
- [ ] 屏幕阅读器友好
|
||||
|
||||
### Storybook 质量检查
|
||||
|
||||
- [ ] 所有变体都有对应 Story
|
||||
- [ ] argTypes 配置完整
|
||||
- [ ] 包含组件文档描述
|
||||
- [ ] 交互状态可测试
|
||||
- [ ] 响应式展示
|
||||
|
||||
### 视觉质量检查
|
||||
|
||||
- [ ] 字体选择有特色
|
||||
- [ ] 配色方案协调
|
||||
- [ ] 动画流畅自然
|
||||
- [ ] 间距一致
|
||||
- [ ] 暗色主题支持
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"name": "gitea-plugin",
|
||||
"description": "Gitea 代码托管与 CI/CD 管理。用于 Gitea Actions workflow 管理、Runner 管理、PR 操作、仓库配置。",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
#!/bin/bash
|
||||
# gitea-runs — Gitea Actions CLI helper
|
||||
# Usage:
|
||||
# gitea-runs List recent runs
|
||||
# gitea-runs list [limit] List recent runs (default 10)
|
||||
# gitea-runs view <run_number> View run details & jobs
|
||||
# gitea-runs open [run_number] Open run in browser
|
||||
# gitea-runs workflows List workflows
|
||||
# gitea-runs dispatch <wf> [ref] Trigger a workflow dispatch
|
||||
# gitea-runs help Show this help
|
||||
|
||||
set -e
|
||||
|
||||
# Config from tea CLI
|
||||
TEA_CONFIG="${XDG_CONFIG_HOME:-$HOME/Library/Application Support}/tea/config.yml"
|
||||
if [ ! -f "$TEA_CONFIG" ]; then
|
||||
TEA_CONFIG="$HOME/.config/tea/config.yml"
|
||||
fi
|
||||
|
||||
# Parse tea config (nested under logins)
|
||||
GITEA_URL=$(grep 'url:' "$TEA_CONFIG" | head -1 | awk '{print $NF}')
|
||||
GITEA_TOKEN=$(grep 'token:' "$TEA_CONFIG" | head -1 | awk '{print $NF}')
|
||||
|
||||
# Detect repo from git remote
|
||||
REPO=$(git remote get-url origin 2>/dev/null | sed 's|.*gitea.pipexerp.com[:/]*||;s|\.git$||;s|^10022/||')
|
||||
if [ -z "$REPO" ]; then
|
||||
echo "Error: not in a git repo or remote not configured"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
API="$GITEA_URL/api/v1"
|
||||
AUTH="Authorization: token $GITEA_TOKEN"
|
||||
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
cmd_dispatch() {
|
||||
local workflow="${1:-}"
|
||||
local ref="${2:-main}"
|
||||
if [ -z "$workflow" ]; then
|
||||
echo "Usage: gitea-runs dispatch <workflow> [ref]"
|
||||
echo ""
|
||||
echo "Available workflows:"
|
||||
curl -s -H "$AUTH" "$API/repos/$REPO/actions/workflows" 2>/dev/null \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
for w in data.get('workflows', []):
|
||||
print(f\" {w['id']:30s} {w['name']}\")
|
||||
" 2>/dev/null
|
||||
return
|
||||
fi
|
||||
|
||||
local http_code
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||
-H "$AUTH" \
|
||||
-H "Content-Type: application/json" \
|
||||
-X POST "$API/repos/$REPO/actions/workflows/$workflow/dispatches" \
|
||||
-d "{\"ref\":\"$ref\"}" 2>/dev/null)
|
||||
|
||||
if [ "$http_code" = "204" ]; then
|
||||
echo -e "${GREEN}✓${NC} Dispatched workflow: $workflow (ref: $ref)"
|
||||
echo " View: $GITEA_URL/$REPO/actions"
|
||||
else
|
||||
echo -e "${RED}✗${NC} Failed to dispatch (HTTP $http_code)"
|
||||
fi
|
||||
}
|
||||
|
||||
cmd_workflows() {
|
||||
echo -e "${CYAN}Workflows for $REPO${NC}"
|
||||
echo ""
|
||||
curl -s -H "$AUTH" "$API/repos/$REPO/actions/workflows" 2>/dev/null \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
for w in data.get('workflows', []):
|
||||
state = '✓' if w['state'] == 'active' else '✗'
|
||||
print(f\" {state} {w['id']:30s} {w['name']}\")
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
cmd_list() {
|
||||
local limit="${1:-10}"
|
||||
echo -e "${CYAN}Recent runs for $REPO${NC}"
|
||||
echo ""
|
||||
curl -s -H "$AUTH" "$API/repos/$REPO/actions/runs?limit=$limit" 2>/dev/null \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
limit = $limit
|
||||
data = json.load(sys.stdin)
|
||||
for r in data.get('workflow_runs', [])[:limit]:
|
||||
status = r.get('status', '?')
|
||||
num = r.get('run_number', 0)
|
||||
title = r.get('display_title', '')[:60]
|
||||
wf = r.get('path', '')
|
||||
wf = wf.split('@')[0] if '@' in wf else wf
|
||||
icon = {'success':'\u2713','completed':'\u2713','failure':'\u2717','cancelled':'\u2717','in_progress':'\u27f3','running':'\u27f3','queued':'\u25cc','waiting':'\u25cc'}.get(status, '?')
|
||||
color = {'success':'\033[0;32m','completed':'\033[0;32m','failure':'\033[0;31m','cancelled':'\033[0;31m','in_progress':'\033[0;33m','running':'\033[0;33m'}.get(status, '\033[0;37m')
|
||||
print(f\"{color}{icon}\033[0m #{num:<4} {status:<12} {wf:<20} {title}\")
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
cmd_view() {
|
||||
local run_number="${1:-}"
|
||||
if [ -z "$run_number" ]; then
|
||||
echo "Usage: gitea-runs view <run_number>"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Find run by run_number (API uses internal id, html uses run_number)
|
||||
local run_data
|
||||
run_data=$(curl -s -H "$AUTH" "$API/repos/$REPO/actions/runs?limit=50" 2>/dev/null \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
data = json.load(sys.stdin)
|
||||
for r in data.get('workflow_runs', []):
|
||||
if r['run_number'] == $run_number:
|
||||
print(json.dumps(r))
|
||||
break
|
||||
" 2>/dev/null)
|
||||
|
||||
if [ -z "$run_data" ]; then
|
||||
echo -e "${RED}✗${NC} Run #$run_number not found"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local run_id
|
||||
run_id=$(echo "$run_data" | python3 -c "import json,sys; print(json.load(sys.stdin)['id'])")
|
||||
|
||||
# Print run info
|
||||
echo "$run_data" | python3 -c "
|
||||
import json, sys
|
||||
r = json.load(sys.stdin)
|
||||
status = r.get('status', '?')
|
||||
icon = {'success':'\u2713','failure':'\u2717','in_progress':'\u27f3','queued':'\u25cc'}.get(status, '?')
|
||||
color = {'success':'\033[0;32m','failure':'\033[0;31m','in_progress':'\033[0;33m'}.get(status, '\033[0;37m')
|
||||
print(f\"{color}{icon} Run #{r.get('run_number',0)} \u2014 {status}\033[0m\")
|
||||
print(f\" Title: {r.get('display_title','')}\")
|
||||
print(f\" Event: {r.get('event','')}\")
|
||||
print(f\" Branch: {r.get('head_branch','')}\")
|
||||
print(f\" Commit: {r.get('head_sha','')[:8]}\")
|
||||
print(f\" Actor: {r.get('actor',{}).get('login','')}\")
|
||||
wf = r.get('path', '')
|
||||
wf = wf.split('@')[0] if '@' in wf else wf
|
||||
print(f\" Workflow: {wf}\")
|
||||
" 2>/dev/null
|
||||
|
||||
# Print jobs
|
||||
echo ""
|
||||
echo -e "${CYAN}Jobs:${NC}"
|
||||
curl -s -H "$AUTH" "$API/repos/$REPO/actions/runs/$run_id/jobs" 2>/dev/null \
|
||||
| python3 -c "
|
||||
import json, sys
|
||||
from datetime import datetime
|
||||
data = json.load(sys.stdin)
|
||||
for j in data.get('jobs', []):
|
||||
status = j.get('status', '?')
|
||||
icon = {'success':'\u2713','failure':'\u2717','in_progress':'\u27f3','queued':'\u25cc','waiting':'\u25cc'}.get(status, '?')
|
||||
color = {'success':'\033[0;32m','failure':'\033[0;31m','in_progress':'\033[0;33m'}.get(status, '\033[0;37m')
|
||||
runner = j.get('runner_name', '-')
|
||||
started = j.get('started_at', '')[:19].replace('T', ' ')
|
||||
completed = j.get('completed_at', '')[:19].replace('T', ' ')
|
||||
duration = ''
|
||||
if completed and not completed.startswith('1970'):
|
||||
try:
|
||||
d = datetime.fromisoformat(completed) - datetime.fromisoformat(started)
|
||||
duration = f' ({int(d.total_seconds())}s)'
|
||||
except: pass
|
||||
print(f\" {color}{icon}\033[0m {j.get('name',''):<30} {status:<12} runner: {runner}{duration}\")
|
||||
" 2>/dev/null
|
||||
}
|
||||
|
||||
cmd_open() {
|
||||
local run_id="${1:-}"
|
||||
local url="$GITEA_URL/$REPO/actions"
|
||||
if [ -n "$run_id" ]; then
|
||||
url="$url/runs/$run_id"
|
||||
fi
|
||||
echo "Opening: $url"
|
||||
open "$url" 2>/dev/null || xdg-open "$url" 2>/dev/null || echo "$url"
|
||||
}
|
||||
|
||||
cmd_help() {
|
||||
echo "gitea-runs — Gitea Actions CLI helper"
|
||||
echo ""
|
||||
echo "Usage:"
|
||||
echo " gitea-runs List recent runs"
|
||||
echo " gitea-runs list [limit] List recent runs (default 10)"
|
||||
echo " gitea-runs view <run_number> View run details & jobs"
|
||||
echo " gitea-runs open [run_number] Open run in browser"
|
||||
echo " gitea-runs workflows List workflows"
|
||||
echo " gitea-runs dispatch <wf> [ref] Trigger a workflow dispatch"
|
||||
echo " gitea-runs help Show this help"
|
||||
echo ""
|
||||
echo "Repo: $REPO"
|
||||
echo "Gitea: $GITEA_URL"
|
||||
}
|
||||
|
||||
# Main
|
||||
case "${1:-}" in
|
||||
list|ls) shift; cmd_list "$@" ;;
|
||||
view|v) shift; cmd_view "$@" ;;
|
||||
dispatch) shift; cmd_dispatch "$@" ;;
|
||||
workflows|wf) cmd_workflows ;;
|
||||
open|o) shift; cmd_open "$@" ;;
|
||||
help|--help|-h) cmd_help ;;
|
||||
"") cmd_list ;;
|
||||
*) cmd_view "$1" ;;
|
||||
esac
|
||||
@@ -1,281 +0,0 @@
|
||||
---
|
||||
name: gitea
|
||||
description: Gitea 代码托管与 CI/CD 管理。用于 Gitea Actions workflow 管理、Runner 管理、PR 操作、仓库配置。当用户提到 Gitea、Actions、Runner、CI/CD workflow、PR 检查相关任务时自动激活。
|
||||
---
|
||||
|
||||
# Gitea Skill
|
||||
|
||||
Gitea 代码托管平台管理,覆盖 Actions CI/CD、Runner、PR、仓库配置。
|
||||
|
||||
## 服务器信息
|
||||
|
||||
| 服务 | 地址 | SSH |
|
||||
|------|------|-----|
|
||||
| Gitea Web | https://gitea.pipexerp.com | — |
|
||||
| Gitea SSH | gitea.pipexerp.com:10022 | `ssh -i ~/.ssh/id_ed25519 git@gitea.pipexerp.com -p 10022` |
|
||||
| Gitea 服务器 | 123.56.89.187 | `ssh -i ~/.ssh/tools.pem root@123.56.89.187` |
|
||||
| Runner 服务器 | 101.200.136.200 (Jenkins 服务器) | `ssh -i ~/.ssh/tools.pem root@101.200.136.200` |
|
||||
|
||||
## API 访问
|
||||
|
||||
```bash
|
||||
# Gitea API Token (仓库级)
|
||||
GITEA_TOKEN="483a2b65219625ee382eb6d023cda39238c32e24"
|
||||
|
||||
# 通用请求格式
|
||||
curl -s "https://gitea.pipexerp.com/api/v1/repos/pipexerp/<repo>/..." \
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
```
|
||||
|
||||
### 常用 API
|
||||
|
||||
| 操作 | 方法 | 端点 |
|
||||
|------|------|------|
|
||||
| 创建 PR | POST | `/repos/{owner}/{repo}/pulls` |
|
||||
| 更新 PR | PATCH | `/repos/{owner}/{repo}/pulls/{id}` |
|
||||
| 列出 Runs | GET | `/repos/{owner}/{repo}/actions/runs` |
|
||||
| Run 详情 | GET | `/repos/{owner}/{repo}/actions/runs/{id}` |
|
||||
| Job 详情 | GET | `/repos/{owner}/{repo}/actions/runs/{id}/jobs` |
|
||||
| 手动触发 Workflow | POST | `/repos/{owner}/{repo}/actions/workflows/{file}/dispatches` body: `{"ref":"main"}` |
|
||||
| 获取 Runner Token | POST | `/repos/{owner}/{repo}/actions/runners/registration-token` |
|
||||
| 添加 Secret | PUT | `/repos/{owner}/{repo}/actions/secrets/{name}` body: `{"data":"value"}` |
|
||||
| 删除 Run(仅已完成)| DELETE | `/repos/{owner}/{repo}/actions/runs/{id}` |
|
||||
|
||||
**注意**: Gitea 1.25 **不支持**通过 API cancel 正在排队/运行的 run。
|
||||
|
||||
## 仓库
|
||||
|
||||
| 仓库 | 地址 | 主分支 |
|
||||
|------|------|--------|
|
||||
| coolbuy-paas | pipexerp/coolbuy-paas | main |
|
||||
| dotfiles | huangjun/dotfiles | main |
|
||||
| claude-marketplace | huangjun/claude-marketplace | main |
|
||||
|
||||
## Actions Runners
|
||||
|
||||
### 主 Runner (lint/test/e2e)
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 名称 | jenkins-runner |
|
||||
| 配置 | `/opt/act_runner/config.yaml` |
|
||||
| Capacity | 3 |
|
||||
| Labels | `ubuntu-latest`, `ubuntu-22.04`, `ubuntu-20.04` |
|
||||
| 进程 | `/usr/local/bin/act_runner daemon --config /opt/act_runner/config.yaml` |
|
||||
|
||||
### Deploy Runner (staging 部署专用)
|
||||
| 项目 | 值 |
|
||||
|------|-----|
|
||||
| 名称 | deploy-runner |
|
||||
| 配置 | `/opt/act_runner_deploy/config.yaml` |
|
||||
| Capacity | 1 |
|
||||
| Labels | `deploy:host` |
|
||||
| PID | `/opt/act_runner_deploy/runner.pid` |
|
||||
| 日志 | `/opt/act_runner_deploy/runner.log` |
|
||||
| 启动 | `cd /opt/act_runner_deploy && nohup act_runner daemon --config config.yaml > runner.log 2>&1 &` |
|
||||
|
||||
### 注册新 Runner
|
||||
|
||||
```bash
|
||||
# 1. 获取 registration token
|
||||
curl -s -X POST "https://gitea.pipexerp.com/api/v1/repos/pipexerp/coolbuy-paas/actions/runners/registration-token" \
|
||||
-H "Authorization: token $GITEA_TOKEN"
|
||||
|
||||
# 2. SSH 到 runner 服务器
|
||||
ssh -i ~/.ssh/tools.pem root@101.200.136.200
|
||||
|
||||
# 3. 创建目录和配置
|
||||
mkdir -p /opt/act_runner_<name>
|
||||
cat > /opt/act_runner_<name>/config.yaml << 'EOF'
|
||||
log:
|
||||
level: info
|
||||
runner:
|
||||
file: .runner
|
||||
capacity: 1
|
||||
timeout: 30m
|
||||
labels:
|
||||
- "<label>:host" # host 模式用系统 shell
|
||||
# 或 "<label>:docker://image" # docker 模式
|
||||
cache:
|
||||
enabled: false
|
||||
EOF
|
||||
|
||||
# 4. 注册
|
||||
cd /opt/act_runner_<name>
|
||||
act_runner register --instance https://gitea.pipexerp.com \
|
||||
--token <TOKEN> --name <NAME> --labels '<LABEL>:host' \
|
||||
--config config.yaml --no-interactive
|
||||
|
||||
# 5. 启动
|
||||
nohup act_runner daemon --config config.yaml > runner.log 2>&1 &
|
||||
echo $! > runner.pid
|
||||
```
|
||||
|
||||
### Runner 运维
|
||||
|
||||
```bash
|
||||
# 检查 runner 状态
|
||||
ssh -i ~/.ssh/tools.pem root@101.200.136.200 "ps aux | grep act_runner | grep -v grep"
|
||||
|
||||
# 查看 deploy runner 日志
|
||||
ssh -i ~/.ssh/tools.pem root@101.200.136.200 "tail -20 /opt/act_runner_deploy/runner.log"
|
||||
|
||||
# 重启 deploy runner
|
||||
ssh -i ~/.ssh/tools.pem root@101.200.136.200 "
|
||||
kill \$(cat /opt/act_runner_deploy/runner.pid) 2>/dev/null
|
||||
cd /opt/act_runner_deploy
|
||||
nohup act_runner daemon --config config.yaml > runner.log 2>&1 &
|
||||
echo \$! > runner.pid
|
||||
"
|
||||
```
|
||||
|
||||
## Workflows (coolbuy-paas)
|
||||
|
||||
| Workflow | 触发 | Runner | paths-ignore | 用途 |
|
||||
|----------|------|--------|-------------|------|
|
||||
| 🚀 deploy-staging.yml | push → main | `deploy` | md, docs, .gitea, scripts, *_test.go | 触发 Jenkins 部署到 staging |
|
||||
| 🔍 lint.yml | PR → main | `ubuntu-latest` | md, docs, .gitea, scripts | Go lint + ESLint auto-fix |
|
||||
| 🧪 unit-test.yml | PR → main | `ubuntu-latest` | md, docs, .gitea, scripts | 4 个 Go 服务单元测试 |
|
||||
| 🎭 e2e-tests.yml | schedule 12h | `ubuntu-latest` | — | Playwright E2E(仅定时) |
|
||||
| 📋 notify-aiproj.yml | PR merged | `ubuntu-latest` | — | 同步需求状态到 ai-proj |
|
||||
| 📦 build.yaml | 手动 | `ubuntu-latest` | — | Docker 构建推 Hub |
|
||||
|
||||
### Workflow 编写规范
|
||||
|
||||
```yaml
|
||||
# 1. 名称加 emoji 前缀
|
||||
name: "🚀 Deploy Staging"
|
||||
|
||||
# 2. 非代码变更加 paths-ignore
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
- '*.md'
|
||||
- 'docs/**'
|
||||
- '.gitea/**'
|
||||
- 'scripts/**'
|
||||
|
||||
# 3. 加 concurrency 防重复
|
||||
concurrency:
|
||||
group: deploy-staging
|
||||
cancel-in-progress: true
|
||||
|
||||
# 4. 仅定时的 workflow 加 event 守卫
|
||||
jobs:
|
||||
e2e:
|
||||
if: github.event_name == 'schedule'
|
||||
|
||||
# 5. auto-fix 提交加 [skip ci]
|
||||
git commit -m "style: auto-fix [skip ci]"
|
||||
```
|
||||
|
||||
### Checkout 模式(容器内)
|
||||
|
||||
Gitea Actions 不支持 `actions/checkout`,用原生 git:
|
||||
|
||||
```yaml
|
||||
- name: Checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git config --global --add safe.directory "$(pwd)"
|
||||
git init
|
||||
git remote add origin "https://oauth2:${TOKEN}@gitea.pipexerp.com/${{ github.repository }}.git"
|
||||
git fetch origin "${{ github.event.pull_request.head.ref }}"
|
||||
git checkout -b pr-branch "origin/${{ github.event.pull_request.head.ref }}"
|
||||
git config user.name "CI Bot"
|
||||
git config user.email "ci@pipexerp.com"
|
||||
```
|
||||
|
||||
## Secrets 管理
|
||||
|
||||
### 当前 Secrets (coolbuy-paas 仓库级)
|
||||
|
||||
| Secret | 用途 |
|
||||
|--------|------|
|
||||
| `JENKINS_USER` | Jenkins API 用户名 |
|
||||
| `JENKINS_TOKEN` | Jenkins API Token |
|
||||
| `DOCKER_HUB_TOKEN` | Docker Hub 推送 |
|
||||
| `AI_PROJ_TOKEN` | ai-proj API 认证 |
|
||||
|
||||
### 添加/更新 Secret
|
||||
|
||||
```bash
|
||||
curl -s -X PUT \
|
||||
"https://gitea.pipexerp.com/api/v1/repos/pipexerp/coolbuy-paas/actions/secrets/<NAME>" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"data": "<VALUE>"}'
|
||||
```
|
||||
|
||||
## CI/CD 完整流程
|
||||
|
||||
```
|
||||
PR → main
|
||||
├── 🧪 unit-test.yml (Go 服务测试)
|
||||
├── 🔍 lint.yml (auto-fix 格式)
|
||||
└── merge 后:
|
||||
├── 📋 notify-aiproj.yml (需求状态 → testing)
|
||||
└── 🚀 deploy-staging.yml (Jenkins → staging)
|
||||
├── ≥5 commits/2h → 立即部署
|
||||
└── <5 commits → 等3分钟 debounce
|
||||
|
||||
定时:
|
||||
└── 🎭 e2e-tests.yml (每12h Playwright)
|
||||
|
||||
手动:
|
||||
└── 📦 build.yaml (Docker 构建推 Hub)
|
||||
|
||||
生产部署:
|
||||
└── ./scripts/build-and-push.sh prod --deploy (触发 Jenkins)
|
||||
```
|
||||
|
||||
## 本地 CLI 工具
|
||||
|
||||
### tea CLI (Gitea 官方命令行)
|
||||
|
||||
tea 是 Gitea 官方 CLI 客户端,已配置好认证信息。
|
||||
|
||||
```bash
|
||||
# 配置文件位置
|
||||
~/Library/Application Support/tea/config.yml
|
||||
# 或 ~/.config/tea/config.yml
|
||||
|
||||
# gitea-runs 脚本从 tea config 读取 url 和 token
|
||||
```
|
||||
|
||||
### gitea-runs (Actions 快捷命令)
|
||||
|
||||
位置: `~/.local/bin/gitea-runs`
|
||||
|
||||
自动从 git remote 检测仓库,从 tea CLI 配置读取认证信息。
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `gitea-runs` | 列出最近 10 条 run |
|
||||
| `gitea-runs list [N]` | 列出最近 N 条 run |
|
||||
| `gitea-runs view <run_number>` | 查看 run 详情和 jobs |
|
||||
| `gitea-runs open [run_number]` | 在浏览器打开 run 页面 |
|
||||
| `gitea-runs workflows` | 列出所有 workflow |
|
||||
| `gitea-runs dispatch <wf> [ref]` | 手动触发 workflow |
|
||||
|
||||
```bash
|
||||
# 示例
|
||||
gitea-runs # 查看最近 runs
|
||||
gitea-runs view 303 # 查看 run #303 详情
|
||||
gitea-runs dispatch deploy-staging.yml main # 手动触发部署
|
||||
gitea-runs open # 打开 Actions 页面
|
||||
```
|
||||
|
||||
**优先使用 `gitea-runs` 而非 curl API**,更简洁且自动处理认证。
|
||||
|
||||
## 常见问题
|
||||
|
||||
| 问题 | 原因 | 解决 |
|
||||
|------|------|------|
|
||||
| Run 一直 queued | Runner 被占满 | 等其他 job 完成,或加 runner |
|
||||
| deploy 被 test 阻塞 | 共用 runner | 用 `runs-on: deploy` 专属 runner |
|
||||
| Workflow 被误触发 | push 新 workflow 文件到 main | 加 `if: github.event_name == 'schedule'` 守卫 |
|
||||
| auto-fix 无限循环 | 提交触发新 run | 提交信息加 `[skip ci]` |
|
||||
| API 无法 cancel run | Gitea 1.25 限制 | 网页手动取消,或等完成后 DELETE |
|
||||
| `date -d` 报错 | 容器 date 不兼容 | 用 host 模式 runner,或兼容写法 |
|
||||
@@ -199,3 +199,50 @@ One paragraph explaining the motivation.
|
||||
↓
|
||||
tea pr merge 42
|
||||
```
|
||||
|
||||
## Finishing a Branch
|
||||
|
||||
Verify tests pass, then create PR. Use after implementation is complete.
|
||||
|
||||
**Core principle:** Verify tests → Create PR → Done.
|
||||
|
||||
### Process
|
||||
|
||||
1. **Verify Tests** — Run project's test suite before creating PR:
|
||||
```bash
|
||||
npm test / cargo test / pytest / go test ./... / mvn test
|
||||
```
|
||||
- Tests fail → Stop, show failures, fix first. Cannot proceed.
|
||||
- Tests pass → Continue to step 2.
|
||||
|
||||
2. **Create PR** — Use `/pr create` (checks for existing PR, avoids duplicates). Report PR URL.
|
||||
|
||||
3. **Cleanup Worktree** (if applicable):
|
||||
```bash
|
||||
git worktree list | grep $(git branch --show-current)
|
||||
# If in worktree, ask user to confirm removal
|
||||
git worktree remove <worktree-path>
|
||||
```
|
||||
|
||||
**Never:** Create PR with failing tests. Skip test verification. Force-push without request.
|
||||
|
||||
## Plan Execution
|
||||
|
||||
Load plan → create branch → execute tasks in batches → report for review between batches.
|
||||
|
||||
### Process
|
||||
|
||||
1. **Load Plan**: Read plan file, review critically, raise concerns before starting
|
||||
2. **Setup Branch**: Use `/pr start` or manual `git checkout -b <type>/<name> origin/main`
|
||||
3. **Execute Batch**: Default 3 tasks per batch, mark in_progress → completed
|
||||
4. **Report**: Show implementation + verification output. Say: "Ready for feedback."
|
||||
5. **Continue**: Apply feedback, execute next batch, repeat
|
||||
6. **Complete**: Use "Finishing a Branch" workflow above
|
||||
|
||||
### Stop Conditions
|
||||
|
||||
- Hit blocker (missing dependency, test fails, instruction unclear)
|
||||
- Plan has critical gaps preventing starting
|
||||
- Verification fails repeatedly
|
||||
|
||||
**Ask for clarification rather than guessing.**
|
||||
|
||||
Reference in New Issue
Block a user