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:
2026-03-14 15:09:07 +10:30
parent 5878c1f852
commit dcdae8c636
79 changed files with 610 additions and 13407 deletions

View File

@@ -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"
}
}

View File

@@ -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)

View File

@@ -1,8 +0,0 @@
{
"name": "dev-plugin",
"description": "Plugin for dev",
"version": "1.0.0",
"author": {
"name": "qiudl"
}
}

View File

@@ -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 |

View File

@@ -1,8 +0,0 @@
{
"name": "finishing-a-development-branch-plugin",
"description": "Plugin for finishing-a-development-branch",
"version": "1.0.0",
"author": {
"name": "qiudl"
}
}

View File

@@ -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

View File

@@ -1,8 +0,0 @@
{
"name": "frontend-design-plugin",
"description": "Plugin for frontend-design",
"version": "1.0.0",
"author": {
"name": "qiudl"
}
}

View File

@@ -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 配置完整
- [ ] 包含组件文档描述
- [ ] 交互状态可测试
- [ ] 响应式展示
### 视觉质量检查
- [ ] 字体选择有特色
- [ ] 配色方案协调
- [ ] 动画流畅自然
- [ ] 间距一致
- [ ] 暗色主题支持

View File

@@ -1,8 +0,0 @@
{
"name": "gitea-plugin",
"description": "Gitea 代码托管与 CI/CD 管理。用于 Gitea Actions workflow 管理、Runner 管理、PR 操作、仓库配置。",
"version": "1.0.0",
"author": {
"name": "qiudl"
}
}

View File

@@ -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

View File

@@ -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或兼容写法 |

View File

@@ -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.**