feat: 添加 dev-workflow 和 pr-review-gate 技能
## dev-workflow - Spec-Driven 开发流程 - 从需求生成 spec.md - 创建功能分支 - 调用子代理开发 - 自动创建 Gitea PR ## pr-review-gate - 3 审查者并行审查(安全/质量/逻辑) - 自动合并逻辑 - 修复循环(最多3次) ## .planning - PROJECT.md: 项目定义 - REQUIREMENTS.md: 需求列表 - ROADMAP.md: 开发路线图 - STATE.md: 当前状态
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
---
|
||||
name: dev-workflow
|
||||
description: "Spec-Driven 开发流程技能。当用户说'添加功能'、'开发XXX'时使用。"
|
||||
---
|
||||
|
||||
# dev-workflow - Spec-Driven 开发流程
|
||||
|
||||
## 什么时候使用
|
||||
|
||||
当用户发送包含以下模式的消息时触发:
|
||||
|
||||
| 触发词 | 行为 |
|
||||
|--------|------|
|
||||
| `添加功能` + 描述 | 开始 Spec-Driven 开发流程 |
|
||||
| `开发` + 描述 | 同上 |
|
||||
| `实现` + 描述 | 同上 |
|
||||
|
||||
## 环境变量
|
||||
|
||||
| 变量名 | 用途 | 必需 |
|
||||
|--------|------|------|
|
||||
| `GITEA_TOKEN` | Gitea API Token(创建 PR) | 是 |
|
||||
| `GITEA_URL` | Gitea 服务地址 | 否(默认 http://192.168.1.22:8418) |
|
||||
| `GITEA_REPO` | 仓库路径 | 否(默认 starmen1265/kb-skills) |
|
||||
|
||||
## 工作流程
|
||||
|
||||
```
|
||||
用户描述需求
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Phase 1: Spec 生成 │
|
||||
│ - 澄清需求 │
|
||||
│ - 生成 .planning/spec.md │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Phase 2: 分支创建 │
|
||||
│ - 创建 feature/* 分支 │
|
||||
│ - 切换到功能分支 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Phase 3: 子代理开发 │
|
||||
│ - 基于 spec 启动开发子代理 │
|
||||
│ - 子代理执行开发任务 │
|
||||
│ - 提交代码到功能分支 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Phase 4: 创建 PR │
|
||||
│ - 调用 Gitea API 创建 PR │
|
||||
│ - 返回 PR 编号 │
|
||||
└─────────────────────────────────────┘
|
||||
↓
|
||||
通知 pr-review-gate 开始审查
|
||||
```
|
||||
|
||||
## 执行步骤
|
||||
|
||||
### Step 1: 需求澄清
|
||||
|
||||
使用 GSD 风格的深度提问:
|
||||
|
||||
1. 理解用户要做什么
|
||||
2. 探索边界条件
|
||||
3. 确认技术约束
|
||||
4. 识别依赖关系
|
||||
|
||||
**示例问题:**
|
||||
- "这个功能解决什么问题?"
|
||||
- "期望的输入/输出是什么?"
|
||||
- "有什么技术约束?"
|
||||
- "需要与现有哪些模块交互?"
|
||||
|
||||
### Step 2: 生成 Spec
|
||||
|
||||
创建 `.planning/spec.md` 文件:
|
||||
|
||||
```bash
|
||||
python3 dev_workflow.py --create-spec --title "功能名称" --description "详细描述"
|
||||
```
|
||||
|
||||
Spec 文件结构参考 `templates/spec-template.md`。
|
||||
|
||||
### Step 3: 创建功能分支
|
||||
|
||||
```bash
|
||||
python3 dev_workflow.py --create-branch --spec .planning/spec.md
|
||||
```
|
||||
|
||||
分支命名规则:
|
||||
- `feature/<简短描述>` - 新功能
|
||||
- `fix/<简短描述>` - Bug 修复
|
||||
- `refactor/<简短描述>` - 重构
|
||||
|
||||
### Step 4: 启动开发子代理
|
||||
|
||||
使用 `sessions_spawn` 启动开发子代理:
|
||||
|
||||
```
|
||||
sessions_spawn(
|
||||
task="根据 spec 开发 <功能名称>",
|
||||
cwd="~/work/kb-skills",
|
||||
runtime="subagent"
|
||||
)
|
||||
```
|
||||
|
||||
子代理会:
|
||||
1. 读取 spec 文件
|
||||
2. 执行开发任务
|
||||
3. 提交代码到功能分支
|
||||
|
||||
### Step 5: 创建 PR
|
||||
|
||||
开发完成后:
|
||||
|
||||
```bash
|
||||
python3 dev_workflow.py --create-pr --branch feature/xxx --title "PR 标题"
|
||||
```
|
||||
|
||||
调用 Gitea API 创建 PR,返回 PR 编号。
|
||||
|
||||
### Step 6: 通知审查
|
||||
|
||||
PR 创建后,告知用户:
|
||||
|
||||
```
|
||||
✅ PR #xxx 已创建
|
||||
|
||||
**分支:** feature/xxx
|
||||
**链接:** http://192.168.1.22:8418/starmen1265/kb-skills/pulls/xxx
|
||||
|
||||
正在启动 pr-review-gate 审查流程...
|
||||
```
|
||||
|
||||
## CLI 命令
|
||||
|
||||
```bash
|
||||
# 创建 spec
|
||||
python3 dev_workflow.py --create-spec --title "标题" --description "描述"
|
||||
|
||||
# 从 spec 创建分支
|
||||
python3 dev_workflow.py --create-branch --spec .planning/spec.md
|
||||
|
||||
# 创建 PR
|
||||
python3 dev_workflow.py --create-pr --branch feature/xxx --title "PR标题"
|
||||
|
||||
# 查看当前状态
|
||||
python3 dev_workflow.py --status
|
||||
```
|
||||
|
||||
## 与其他技能的协作
|
||||
|
||||
| 技能 | 协作方式 |
|
||||
|------|----------|
|
||||
| **pr-review-gate** | PR 创建后自动触发审查 |
|
||||
| **kb-workflow** | 可复用其目录结构 |
|
||||
| **GSD** | 采用其 Spec-Driven 方法论 |
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. 确保 `GITEA_TOKEN` 已配置
|
||||
2. 功能分支命名遵循规范
|
||||
3. 提交信息遵循 Conventional Commits
|
||||
4. 开发前先 pull 最新代码
|
||||
@@ -0,0 +1,256 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
dev_workflow.py - Spec-Driven 开发流程辅助工具
|
||||
|
||||
用法:
|
||||
python3 dev_workflow.py --create-spec --title "标题" --description "描述"
|
||||
python3 dev_workflow.py --create-branch --spec .planning/spec.md
|
||||
python3 dev_workflow.py --create-pr --branch feature/xxx --title "PR标题"
|
||||
python3 dev_workflow.py --status
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import subprocess
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# 配置
|
||||
GITEA_URL = os.environ.get('GITEA_URL', 'http://192.168.1.22:8418')
|
||||
GITEA_TOKEN = os.environ.get('GITEA_TOKEN', '')
|
||||
GITEA_REPO = os.environ.get('GITEA_REPO', 'starmen1265/kb-skills')
|
||||
REPO_PATH = Path.home() / 'work' / 'kb-skills'
|
||||
PLANNING_DIR = REPO_PATH / '.planning'
|
||||
|
||||
|
||||
def run_cmd(cmd, cwd=None, capture=True):
|
||||
"""执行 shell 命令"""
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
shell=True,
|
||||
cwd=cwd or REPO_PATH,
|
||||
capture_output=capture,
|
||||
text=True
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
def create_spec(title: str, description: str) -> dict:
|
||||
"""创建 spec 文件"""
|
||||
PLANNING_DIR.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# 生成分支名
|
||||
branch_name = re.sub(r'[^a-z0-9]+', '-', title.lower()).strip('-')
|
||||
|
||||
# 读取模板
|
||||
template_path = Path(__file__).parent / 'templates' / 'spec-template.md'
|
||||
if template_path.exists():
|
||||
content = template_path.read_text(encoding='utf-8')
|
||||
else:
|
||||
content = """# Spec: {{TITLE}}
|
||||
|
||||
**创建时间:** {{DATE}}
|
||||
**状态:** draft
|
||||
**分支:** feature/{{BRANCH_NAME}}
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
{{DESCRIPTION}}
|
||||
|
||||
## 需求
|
||||
|
||||
### 功能需求
|
||||
|
||||
- [ ] **REQ-01**: 待补充
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. 待补充
|
||||
|
||||
---
|
||||
|
||||
## 变更日志
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| {{DATE}} | 初始创建 |
|
||||
"""
|
||||
|
||||
# 替换变量
|
||||
now = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
content = content.replace('{{TITLE}}', title)
|
||||
content = content.replace('{{DATE}}', now)
|
||||
content = content.replace('{{BRANCH_NAME}}', branch_name)
|
||||
content = content.replace('{{DESCRIPTION}}', description)
|
||||
|
||||
# 写入文件
|
||||
spec_file = PLANNING_DIR / 'spec.md'
|
||||
spec_file.write_text(content, encoding='utf-8')
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"spec_file": str(spec_file),
|
||||
"branch_name": branch_name,
|
||||
"title": title
|
||||
}
|
||||
|
||||
|
||||
def create_branch(spec_file: str) -> dict:
|
||||
"""从 spec 创建功能分支"""
|
||||
spec_path = Path(spec_file)
|
||||
if not spec_path.exists():
|
||||
return {"success": False, "error": f"Spec 文件不存在: {spec_file}"}
|
||||
|
||||
# 从 spec 提取分支名
|
||||
content = spec_path.read_text(encoding='utf-8')
|
||||
match = re.search(r'\*\*分支:\*\*\s*feature/([^\n]+)', content)
|
||||
if match:
|
||||
branch_suffix = match.group(1).strip()
|
||||
else:
|
||||
branch_suffix = f"feature-{datetime.now().strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
branch_name = f"feature/{branch_suffix}"
|
||||
|
||||
# 确保在 main 分支
|
||||
run_cmd('git checkout main')
|
||||
run_cmd('git pull origin main')
|
||||
|
||||
# 创建功能分支
|
||||
result = run_cmd(f'git checkout -b {branch_name}')
|
||||
if result.returncode != 0:
|
||||
# 分支可能已存在,尝试切换
|
||||
result = run_cmd(f'git checkout {branch_name}')
|
||||
if result.returncode != 0:
|
||||
return {"success": False, "error": f"无法创建分支: {result.stderr}"}
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"branch": branch_name,
|
||||
"spec_file": spec_file
|
||||
}
|
||||
|
||||
|
||||
def create_pr(branch: str, title: str, body: str = "") -> dict:
|
||||
"""调用 Gitea API 创建 PR"""
|
||||
if not GITEA_TOKEN:
|
||||
return {"success": False, "error": "GITEA_TOKEN 未配置"}
|
||||
|
||||
# 获取最新提交信息作为 body(如果未提供)
|
||||
if not body:
|
||||
result = run_cmd('git log -1 --pretty=%B')
|
||||
body = result.stdout.strip() if result.returncode == 0 else ""
|
||||
|
||||
# 调用 Gitea API
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json as json_module
|
||||
|
||||
url = f"{GITEA_URL}/api/v1/repos/{GITEA_REPO}/pulls"
|
||||
data = {
|
||||
"title": title,
|
||||
"head": branch,
|
||||
"base": "main",
|
||||
"body": body
|
||||
}
|
||||
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
data=json_module.dumps(data).encode('utf-8'),
|
||||
headers={
|
||||
'Authorization': f'token {GITEA_TOKEN}',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method='POST'
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as response:
|
||||
result = json_module.loads(response.read().decode('utf-8'))
|
||||
return {
|
||||
"success": True,
|
||||
"pr_number": result.get("number"),
|
||||
"pr_url": result.get("html_url"),
|
||||
"branch": branch
|
||||
}
|
||||
except urllib.error.HTTPError as e:
|
||||
error_body = e.read().decode('utf-8') if e.fp else str(e)
|
||||
return {"success": False, "error": f"API 错误: {e.code} - {error_body}"}
|
||||
except Exception as e:
|
||||
return {"success": False, "error": f"请求失败: {str(e)}"}
|
||||
|
||||
|
||||
def get_status() -> dict:
|
||||
"""获取当前开发状态"""
|
||||
result = {
|
||||
"current_branch": None,
|
||||
"spec_exists": False,
|
||||
"planning_exists": False,
|
||||
"uncommitted_changes": False
|
||||
}
|
||||
|
||||
# 当前分支
|
||||
branch_result = run_cmd('git branch --show-current')
|
||||
if branch_result.returncode == 0:
|
||||
result["current_branch"] = branch_result.stdout.strip()
|
||||
|
||||
# spec 文件
|
||||
result["spec_exists"] = (PLANNING_DIR / 'spec.md').exists()
|
||||
|
||||
# planning 目录
|
||||
result["planning_exists"] = PLANNING_DIR.exists()
|
||||
|
||||
# 未提交的更改
|
||||
status_result = run_cmd('git status --porcelain')
|
||||
result["uncommitted_changes"] = bool(status_result.stdout.strip())
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description='dev-workflow 辅助工具')
|
||||
subparsers = parser.add_subparsers(dest='command', help='命令')
|
||||
|
||||
# create-spec
|
||||
spec_parser = subparsers.add_parser('create-spec', help='创建 spec 文件')
|
||||
spec_parser.add_argument('--title', required=True, help='功能标题')
|
||||
spec_parser.add_argument('--description', default='', help='功能描述')
|
||||
|
||||
# create-branch
|
||||
branch_parser = subparsers.add_parser('create-branch', help='创建功能分支')
|
||||
branch_parser.add_argument('--spec', required=True, help='spec 文件路径')
|
||||
|
||||
# create-pr
|
||||
pr_parser = subparsers.add_parser('create-pr', help='创建 PR')
|
||||
pr_parser.add_argument('--branch', required=True, help='分支名')
|
||||
pr_parser.add_argument('--title', required=True, help='PR 标题')
|
||||
pr_parser.add_argument('--body', default='', help='PR 描述')
|
||||
|
||||
# status
|
||||
subparsers.add_parser('status', help='查看状态')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == 'create-spec':
|
||||
result = create_spec(args.title, args.description)
|
||||
elif args.command == 'create-branch':
|
||||
result = create_branch(args.spec)
|
||||
elif args.command == 'create-pr':
|
||||
result = create_pr(args.branch, args.title, args.body)
|
||||
elif args.command == 'status':
|
||||
result = get_status()
|
||||
else:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
print(json.dumps(result, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -0,0 +1,78 @@
|
||||
# Spec: {{TITLE}}
|
||||
|
||||
**创建时间:** {{DATE}}
|
||||
**状态:** draft
|
||||
**分支:** feature/{{BRANCH_NAME}}
|
||||
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
<!-- 简要描述这个功能做什么 -->
|
||||
|
||||
## 背景
|
||||
|
||||
<!-- 为什么需要这个功能?解决什么问题? -->
|
||||
|
||||
## 需求
|
||||
|
||||
### 功能需求
|
||||
|
||||
- [ ] **REQ-01**: <!-- 需求描述 -->
|
||||
- [ ] **REQ-02**: <!-- 需求描述 -->
|
||||
- [ ] **REQ-03**: <!-- 需求描述 -->
|
||||
|
||||
### 非功能需求
|
||||
|
||||
- <!-- 性能要求 -->
|
||||
- <!-- 安全要求 -->
|
||||
- <!-- 兼容性要求 -->
|
||||
|
||||
## 技术设计
|
||||
|
||||
### 实现方案
|
||||
|
||||
<!-- 描述技术实现方案 -->
|
||||
|
||||
### 文件结构
|
||||
|
||||
```
|
||||
kb-skills/
|
||||
└── <技能目录>/
|
||||
├── SKILL.md
|
||||
└── <其他文件>
|
||||
```
|
||||
|
||||
### 依赖
|
||||
|
||||
- <!-- 依赖的库或模块 -->
|
||||
|
||||
## 测试计划
|
||||
|
||||
### 单元测试
|
||||
|
||||
- [ ] <!-- 测试用例 -->
|
||||
|
||||
### 集成测试
|
||||
|
||||
- [ ] <!-- 测试用例 -->
|
||||
|
||||
## 验收标准
|
||||
|
||||
1. <!-- 验收标准 1 -->
|
||||
2. <!-- 验收标准 2 -->
|
||||
3. <!-- 验收标准 3 -->
|
||||
|
||||
## 风险与缓解
|
||||
|
||||
| 风险 | 缓解措施 |
|
||||
|------|----------|
|
||||
| <!-- 风险 --> | <!-- 措施 --> |
|
||||
|
||||
---
|
||||
|
||||
## 变更日志
|
||||
|
||||
| 日期 | 变更 |
|
||||
|------|------|
|
||||
| {{DATE}} | 初始创建 |
|
||||
Reference in New Issue
Block a user