Compare commits
26 Commits
2309e31e74
...
feat/devfl
| Author | SHA1 | Date | |
|---|---|---|---|
| bcea648e3c | |||
| e3513f137b | |||
| 011916ceb9 | |||
|
|
48b792fb5a | ||
| 2ab0a61eb9 | |||
| de25f096e7 | |||
| 84d4e35a42 | |||
| e5805cbb51 | |||
| 79c4e55719 | |||
| 23ea8fdca5 | |||
| bfe3815626 | |||
| 3706d7f32d | |||
|
|
31c2d5a474 | ||
|
|
ccbdfd7eb3 | ||
|
|
f0e5735ffa | ||
|
|
52b8c85b94 | ||
|
|
0e24828a6d | ||
|
|
d564e6dbf9 | ||
|
|
a58dc39795 | ||
|
|
b5f44ac6aa | ||
| b6bd4bbfed | |||
| 187f5621c9 | |||
| b9c808cce0 | |||
| e3924e6b2b | |||
| 22107fa7b8 | |||
| 0724357ff4 |
@@ -23,6 +23,30 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "pm-ask-plugin",
|
||||||
|
"source": "./skills-core/pm-ask-plugin",
|
||||||
|
"description": "基于真实数据的项目问答 /ask。必须引用 MCP/git 真实数据,禁止编造",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pm-risk-plugin",
|
||||||
|
"source": "./skills-core/pm-risk-plugin",
|
||||||
|
"description": "三维度项目风险扫描:需求层/代码层/流程层。当用户说'/risk'、'风险扫描'、'有什么风险'时自动激活",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "publish-plugin",
|
"name": "publish-plugin",
|
||||||
"source": "./skills-core/publish-plugin",
|
"source": "./skills-core/publish-plugin",
|
||||||
@@ -47,6 +71,80 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "agent-swarm-plugin",
|
||||||
|
"source": "./skills-dev/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",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ai-chat-plugin",
|
||||||
|
"source": "./skills-dev/ai-chat-plugin",
|
||||||
|
"description": "AI Chat 测试与管理。发送消息测试 AI Chat 工具调用链路,管理工具开关和 Provider 配置,支持 local/staging 环境切换。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "db-migration-plugin",
|
||||||
|
"source": "./skills-dev/db-migration-plugin",
|
||||||
|
"description": "数据库变更方案插件。Migration 脚本生成、数据迁移策略、回滚方案。挂载在 design 阶段,涉及数据库变更时激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "defect-analysis-plugin",
|
||||||
|
"source": "./skills-dev/defect-analysis-plugin",
|
||||||
|
"description": "系统性设计缺陷分析。对需求方案/代码架构进行多维度检查,发现隐藏的技术风险和设计漏洞。当用户提到缺陷检查、方案审查、设计审计时自动激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "deploy-rollback-plugin",
|
||||||
|
"source": "./skills-dev/deploy-rollback-plugin",
|
||||||
|
"description": "回滚方案插件。部署后发现问题时的回滚策略、数据修复、灰度回退。挂载在 deploy 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "devops",
|
||||||
|
"keywords": [
|
||||||
|
"devops",
|
||||||
|
"deployment",
|
||||||
|
"operations"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-android-plugin",
|
||||||
|
"source": "./skills-dev/dev-android-plugin",
|
||||||
|
"description": "Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dev-arch-plugin",
|
"name": "dev-arch-plugin",
|
||||||
"source": "./skills-dev/dev-arch-plugin",
|
"source": "./skills-dev/dev-arch-plugin",
|
||||||
@@ -60,10 +158,127 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-cicd-plugin",
|
||||||
|
"source": "./skills-dev/dev-cicd-plugin",
|
||||||
|
"description": "Plugin for dev-cicd",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "dev-coding-plugin",
|
"name": "dev-coding-plugin",
|
||||||
"source": "./skills-dev/dev-coding-plugin",
|
"source": "./skills-dev/dev-coding-plugin",
|
||||||
"description": "Plugin for dev-coding",
|
"description": "软件编码开发技能。Go 后端 + Vue/React 前端编码实现,集成 ai-proj 任务管理。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-commit-plugin",
|
||||||
|
"source": "./skills-dev/dev-commit-plugin",
|
||||||
|
"description": "智能 /commit 命令:分支保护 + 自动建功能分支 + Conventional Commits 生成",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-deploy-plugin",
|
||||||
|
"source": "./skills-dev/dev-deploy-plugin",
|
||||||
|
"description": "Plugin for dev-deploy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-integration-plugin",
|
||||||
|
"source": "./skills-dev/dev-integration-plugin",
|
||||||
|
"description": "前后端联调技能。API 契约验证、联调报告、纯后端需求自动跳过。对应 req 流程 integration 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-ios-plugin",
|
||||||
|
"source": "./skills-dev/dev-ios-plugin",
|
||||||
|
"description": "iOS 开发插件。Swift/SwiftUI + MVVM 架构、TestFlight 部署、Xcode 构建。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-mcp-plugin",
|
||||||
|
"source": "./skills-dev/dev-mcp-plugin",
|
||||||
|
"description": "MCP Bridge 开发插件。TypeScript MCP 服务开发、Token 管理、HTTP 客户端模式。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-pda-plugin",
|
||||||
|
"source": "./skills-dev/dev-pda-plugin",
|
||||||
|
"description": "PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先模式。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-review-plugin",
|
||||||
|
"source": "./skills-dev/dev-review-plugin",
|
||||||
|
"description": "代码评审技能。五视角对抗性扫描法(攻击者/泄露者/并发者/边界者/依赖者),CR 报告生成,独立于 req 工作流可单独使用。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dev-scaffold-plugin",
|
||||||
|
"source": "./skills-dev/dev-scaffold-plugin",
|
||||||
|
"description": "模块脚手架插件。新建模块时自动生成分层代码骨架(Model/Repository/Service/Handler/Route)。挂载在 dev 阶段。",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"category": "development",
|
"category": "development",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -86,6 +301,43 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "executing-plans-plugin",
|
||||||
|
"source": "./skills-dev/executing-plans-plugin",
|
||||||
|
"description": "Use when you have a written implementation plan to execute in a separate session with review checkpoints.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "finishing-branch-plugin",
|
||||||
|
"source": "./skills-dev/finishing-branch-plugin",
|
||||||
|
"description": "Use when implementation is complete and all tests pass - verifies and creates PR.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "frontend-design-plugin",
|
||||||
|
"source": "./skills-dev/frontend-design-plugin",
|
||||||
|
"description": "Create distinctive, production-grade frontend interfaces with high design quality. Generates creative, polished code that avoids generic AI aesthetics.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "development",
|
||||||
|
"keywords": [
|
||||||
|
"development",
|
||||||
|
"coding",
|
||||||
|
"workflow"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pull-request-plugin",
|
"name": "pull-request-plugin",
|
||||||
"source": "./skills-dev/pull-request-plugin",
|
"source": "./skills-dev/pull-request-plugin",
|
||||||
@@ -99,10 +351,61 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "review-checklist-plugin",
|
||||||
|
"source": "./skills-dev/review-checklist-plugin",
|
||||||
|
"description": "项目级代码评审检查清单。按项目积累的特定检查项,挂载在 dev-review 下自动加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "utility",
|
||||||
|
"keywords": [
|
||||||
|
"utility",
|
||||||
|
"tools"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-audit-plugin",
|
||||||
|
"source": "./skills-req/req-audit-plugin",
|
||||||
|
"description": "部署后审计。运行时日志检查 + 静态缺陷分析 + 设计偏移检测。可独立调用或由 /req done 自动触发。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-compare-plugin",
|
||||||
|
"source": "./skills-req/req-compare-plugin",
|
||||||
|
"description": "对比式需求分析插件。系统平移、竞品借鉴、版本升级时的参考对象对比分析。挂载在 analysis 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-design-plugin",
|
||||||
|
"source": "./skills-req/req-design-plugin",
|
||||||
|
"description": "需求开发设计技能。PRD 到开发设计的转换:API 契约、数据模型变更、任务拆分、风险评估。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "req-dev-plugin",
|
"name": "req-dev-plugin",
|
||||||
"source": "./skills-req/req-dev-plugin",
|
"source": "./skills-req/req-dev-plugin",
|
||||||
"description": "Plugin for req-dev",
|
"description": "[已废弃] 请使用 req-design-plugin。需求开发设计功能已迁移。",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"category": "development",
|
"category": "development",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -112,6 +415,19 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "req-lookback-plugin",
|
||||||
|
"source": "./skills-req/req-lookback-plugin",
|
||||||
|
"description": "回归测试。部署后自动验证变更涉及的功能是否正常。可独立调用或由 /req done 自动触发。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "req-plugin",
|
"name": "req-plugin",
|
||||||
"source": "./skills-req/req-plugin",
|
"source": "./skills-req/req-plugin",
|
||||||
@@ -128,7 +444,59 @@
|
|||||||
{
|
{
|
||||||
"name": "req-prd-plugin",
|
"name": "req-prd-plugin",
|
||||||
"source": "./skills-req/req-prd-plugin",
|
"source": "./skills-req/req-prd-plugin",
|
||||||
"description": "Plugin for req-prd",
|
"description": "产品需求设计技能。PRD 文档编写、需求分析、用户故事、对比式分析。纯产品视角,不含技术实现。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-prototype-plugin",
|
||||||
|
"source": "./skills-req/req-prototype-plugin",
|
||||||
|
"description": "原型生成与关联。支持 HTML 上传(/req prototype upload,iframe 嵌入详情页)和 Stitch AI 生成两种模式。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-research-plugin",
|
||||||
|
"source": "./skills-req/req-research-plugin",
|
||||||
|
"description": "需求调研插件。代码审计、数据库分析、现有功能调研。挂载在 analysis 阶段,需要深度调研时激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-retro-plugin",
|
||||||
|
"source": "./skills-req/req-retro-plugin",
|
||||||
|
"description": "复盘总结。自动采集数据、计算质量评分、跨需求模式识别、技能自动进化。可独立调用或由 /req done 自动触发。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-review-plugin",
|
||||||
|
"source": "./skills-req/req-review-plugin",
|
||||||
|
"description": "PRD 评审方法论。用于需求评审、PRD 文档审查、评审意见编写。",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -141,7 +509,20 @@
|
|||||||
{
|
{
|
||||||
"name": "req-test-gate-plugin",
|
"name": "req-test-gate-plugin",
|
||||||
"source": "./skills-req/req-test-gate-plugin",
|
"source": "./skills-req/req-test-gate-plugin",
|
||||||
"description": "测试与质量门禁制度。覆盖需求级测试(Gates 1-5,含前后端联调+视觉验证)、部署级验证(Deploy Gates)、持续回归(Regression)。",
|
"description": "测试与质量门禁制度。覆盖需求级测试(Gates 1-5)、部署级验证(Deploy Gates)、持续回归(Regression)、Harness Engineering 工程约束方法论(Ratchet、约定建立、门禁层级)。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "productivity",
|
||||||
|
"keywords": [
|
||||||
|
"project-management",
|
||||||
|
"tasks",
|
||||||
|
"requirements"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "req-workflow-plugin",
|
||||||
|
"source": "./skills-req/req-workflow-plugin",
|
||||||
|
"description": "需求完整工作流。从创建到归档的完整流程、Hook 自动同步、测试环境流程。",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"category": "productivity",
|
"category": "productivity",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -328,6 +709,19 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ops-servers-plugin",
|
||||||
|
"source": "./skills-personal/ops-servers-plugin",
|
||||||
|
"description": "企业服务器管理。用于云服务器分组管理、系统监控、备份管理、故障排查。当用户提到云服务器、生产环境、腾讯云、阿里云相关任务时自动激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "devops",
|
||||||
|
"keywords": [
|
||||||
|
"devops",
|
||||||
|
"deployment",
|
||||||
|
"operations"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "ops-tools-plugin",
|
"name": "ops-tools-plugin",
|
||||||
"source": "./skills-personal/ops-tools-plugin",
|
"source": "./skills-personal/ops-tools-plugin",
|
||||||
@@ -353,6 +747,19 @@
|
|||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "reload-session-plugin",
|
||||||
|
"source": "./skills-personal/reload-session-plugin",
|
||||||
|
"description": "Reload a previously saved Claude session to continue the conversation.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "workflow",
|
||||||
|
"keywords": [
|
||||||
|
"session",
|
||||||
|
"workflow",
|
||||||
|
"productivity"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "req-deploy-plugin",
|
"name": "req-deploy-plugin",
|
||||||
"source": "./skills-personal/req-deploy-plugin",
|
"source": "./skills-personal/req-deploy-plugin",
|
||||||
@@ -365,6 +772,32 @@
|
|||||||
"operations"
|
"operations"
|
||||||
],
|
],
|
||||||
"strict": false
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "save-session-plugin",
|
||||||
|
"source": "./skills-personal/save-session-plugin",
|
||||||
|
"description": "Auto-save Claude session conversation with AI-generated title, summary, and tags in searchable JSON format.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "workflow",
|
||||||
|
"keywords": [
|
||||||
|
"session",
|
||||||
|
"workflow",
|
||||||
|
"productivity"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "search-sessions-plugin",
|
||||||
|
"source": "./skills-personal/search-sessions-plugin",
|
||||||
|
"description": "Search saved Claude sessions by title, tags, date, or content.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "workflow",
|
||||||
|
"keywords": [
|
||||||
|
"session",
|
||||||
|
"workflow",
|
||||||
|
"productivity"
|
||||||
|
],
|
||||||
|
"strict": false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
253
docs/design/release-draft-gate.md
Normal file
253
docs/design/release-draft-gate.md
Normal file
@@ -0,0 +1,253 @@
|
|||||||
|
# Release Draft 闸门机制 — 设计文档
|
||||||
|
|
||||||
|
**REQ**: REQ-20260416-0017 P0-3
|
||||||
|
**状态**: 设计阶段
|
||||||
|
**创建时间**: 2026-04-16
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 背景
|
||||||
|
|
||||||
|
### 当前问题
|
||||||
|
|
||||||
|
ai-proj 当前 CI/CD 流程(`.gitea/workflows/build.yaml`):
|
||||||
|
|
||||||
|
```
|
||||||
|
push 到 main → 自动 build 镜像 → 自动部署生产
|
||||||
|
```
|
||||||
|
|
||||||
|
**这个链路没有"最后一道人工闸门"**。merge develop→main 的 PR 一合并,生产就开始部署。一旦合并,只能事后回滚。
|
||||||
|
|
||||||
|
**典型事故场景**:PR 评审时漏了某个改动,merge 后立即推到线上,5 分钟后发现问题,但已经影响用户 5 分钟。
|
||||||
|
|
||||||
|
### 借鉴方案
|
||||||
|
|
||||||
|
**devflow-claude `/req:release` 命令的核心做法:**
|
||||||
|
|
||||||
|
1. merge PR 不直接部署
|
||||||
|
2. 创建一个 **draft release**(草稿)
|
||||||
|
3. **需要人工在 Gitea/GitHub 平台点 "Publish" 按钮** 才触发部署
|
||||||
|
4. Draft 可以审查 SQL migration、回滚脚本、changelog 等"产物清单"
|
||||||
|
5. Publish 后才打 tag + 触发 build.yaml
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 目标
|
||||||
|
|
||||||
|
- 在 merge main 和"真正部署"之间插入 **人工 publish 闸门**
|
||||||
|
- 提供 **release 产物清单预览**(SQL migration / 回滚脚本 / changelog)
|
||||||
|
- 支持 **一键回滚**(从 draft release 追溯回滚脚本)
|
||||||
|
- 与现有 ai-proj MCP 工具链集成
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 架构设计
|
||||||
|
|
||||||
|
### 3.1 流程对比
|
||||||
|
|
||||||
|
**当前流程:**
|
||||||
|
```
|
||||||
|
[feat/xxx] → PR → merge develop → 测试
|
||||||
|
[develop] → PR → merge main → ✗ 立即触发 build.yaml → 生产部署
|
||||||
|
```
|
||||||
|
|
||||||
|
**新流程:**
|
||||||
|
```
|
||||||
|
[feat/xxx] → PR → merge develop → 测试
|
||||||
|
[develop] → PR → merge main → Gitea draft release(待审查)
|
||||||
|
↓
|
||||||
|
人工 publish
|
||||||
|
↓
|
||||||
|
tag 推送 + 触发 build.yaml → 生产部署
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 组件职责
|
||||||
|
|
||||||
|
| 组件 | 新增/改造 | 职责 |
|
||||||
|
|------|---------|------|
|
||||||
|
| **ai-proj backend** | 新增 | `/api/v1/releases` REST API(创建 draft、publish、查询、回滚) |
|
||||||
|
| **mcp-task-bridge** | 新增工具 | `create_release_draft` / `publish_release` / `list_releases` / `get_release` / `rollback_release` |
|
||||||
|
| **Gitea Actions** | 改造 | `build.yaml` 触发条件改为 `release.published` 事件 |
|
||||||
|
| **`release` skill** | 新增 | AI 侧命令封装(`/release new`, `/release publish`, `/release rollback`) |
|
||||||
|
| **release-draft.sh** | 新增脚本 | 本地命令行工具,用于在 CI 外手动创建 draft |
|
||||||
|
|
||||||
|
### 3.3 数据模型
|
||||||
|
|
||||||
|
**新增 `releases` 表:**
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE releases (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
display_id VARCHAR(64) UNIQUE NOT NULL, -- e.g. "RELEASE-20260416-001"
|
||||||
|
version VARCHAR(64) NOT NULL, -- e.g. "v1.2.0"
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(32) NOT NULL, -- draft / published / rolled_back
|
||||||
|
git_base_ref VARCHAR(255) NOT NULL, -- e.g. previous tag "v1.1.9"
|
||||||
|
git_head_ref VARCHAR(255) NOT NULL, -- e.g. commit sha after merge
|
||||||
|
git_tag VARCHAR(64), -- 生成的 tag,publish 后才有
|
||||||
|
changelog_md TEXT NOT NULL, -- 自动生成的 changelog
|
||||||
|
sql_migration_paths TEXT[], -- 合并的 SQL 文件路径列表
|
||||||
|
sql_rollback_md TEXT, -- 回滚脚本
|
||||||
|
gitea_release_id BIGINT, -- Gitea 上的 release ID
|
||||||
|
gitea_release_url VARCHAR(512),
|
||||||
|
created_by INT REFERENCES users(id),
|
||||||
|
published_by INT REFERENCES users(id),
|
||||||
|
published_at TIMESTAMP,
|
||||||
|
rolled_back_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 API 设计
|
||||||
|
|
||||||
|
| Endpoint | 作用 | 权限 |
|
||||||
|
|----------|------|------|
|
||||||
|
| `POST /api/v1/releases/drafts` | 创建 draft release(自动推导版本、生成 changelog) | 开发者 |
|
||||||
|
| `POST /api/v1/releases/:id/publish` | 发布(推 tag、触发部署) | 需确认 |
|
||||||
|
| `GET /api/v1/releases` | 列表 | 所有人 |
|
||||||
|
| `GET /api/v1/releases/:id` | 详情 | 所有人 |
|
||||||
|
| `POST /api/v1/releases/:id/rollback` | 回滚到上一版本 | 管理员 |
|
||||||
|
|
||||||
|
### 3.5 Gitea 集成
|
||||||
|
|
||||||
|
**创建 draft release(调 Gitea API):**
|
||||||
|
```http
|
||||||
|
POST /api/v1/repos/{owner}/{repo}/releases
|
||||||
|
{
|
||||||
|
"tag_name": "v1.2.0",
|
||||||
|
"target_commitish": "main",
|
||||||
|
"name": "v1.2.0",
|
||||||
|
"body": "...<changelog>...",
|
||||||
|
"draft": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**build.yaml 改造:**
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Before
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
# After
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
workflow_dispatch: # 保留手动触发
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键变化**:`release.published` 事件只在草稿被 publish 时触发,merge main 不再自动触发。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 用户流程(典型场景)
|
||||||
|
|
||||||
|
### 4.1 开发者发版
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 合并 PR 到 main(照旧)
|
||||||
|
2. 在 Claude Code 里:/release new
|
||||||
|
- AI 读 git log,自动推导版本号
|
||||||
|
- 汇总 SQL migration
|
||||||
|
- 生成 changelog
|
||||||
|
- 生成回滚脚本
|
||||||
|
- 展示产物清单,等用户确认
|
||||||
|
3. 确认后:MCP create_release_draft → 后端 API → Gitea draft
|
||||||
|
4. 收到 Slack/企微通知:"draft release v1.2.0 已创建,请审查"
|
||||||
|
5. 打开 Gitea 查看产物清单
|
||||||
|
6. 点 "Publish release" 按钮
|
||||||
|
7. CI/CD 自动触发 → 生产部署
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 回滚
|
||||||
|
|
||||||
|
```
|
||||||
|
1. /release rollback v1.2.0
|
||||||
|
2. MCP rollback_release → 后端执行回滚 SQL → 重新部署上一版本镜像
|
||||||
|
3. 记录到 releases 表:status=rolled_back
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 渐进式落地路径(P0-3 拆分子任务)
|
||||||
|
|
||||||
|
由于 P0-3 完整实现涉及后端 + MCP + CI/CD + skill 四个层面,**不建议单会话完成**。拆分为:
|
||||||
|
|
||||||
|
| 子任务 | 范围 | 风险 |
|
||||||
|
|-------|------|------|
|
||||||
|
| **P0-3.1** | 后端 release model + migration | 低(只是建表) |
|
||||||
|
| **P0-3.2** | 后端 /api/v1/releases API(draft + publish) | 中 |
|
||||||
|
| **P0-3.3** | MCP 工具 create_release_draft / publish_release | 中 |
|
||||||
|
| **P0-3.4** | `release` skill 创建 | 低 |
|
||||||
|
| **P0-3.5** | Gitea draft release 集成 | 中 |
|
||||||
|
| **P0-3.6** | build.yaml 改为 release.published 触发 | **高**(影响所有人的发版流程) |
|
||||||
|
| **P0-3.7** | 回滚能力 | 中 |
|
||||||
|
|
||||||
|
**推荐落地节奏:**
|
||||||
|
- 先做 P0-3.1 ~ P0-3.5(构建基础能力)
|
||||||
|
- **保留旧 push-to-main 流程作为 fallback**
|
||||||
|
- 跑 2 周观察
|
||||||
|
- 再做 P0-3.6(切换 CI/CD 触发源)
|
||||||
|
- 最后 P0-3.7(回滚能力)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 风险与应对
|
||||||
|
|
||||||
|
### R1: build.yaml 触发源切换影响所有开发者
|
||||||
|
|
||||||
|
**影响**:目前大家习惯 "merge main 即部署",切换后需等 publish
|
||||||
|
|
||||||
|
**应对**:
|
||||||
|
- 上线前 1 周发邮件 + 企微通知
|
||||||
|
- 提供手动触发(workflow_dispatch)作为应急入口
|
||||||
|
- 文档化新流程
|
||||||
|
|
||||||
|
### R2: 回滚脚本自动生成不够可靠
|
||||||
|
|
||||||
|
**影响**:复杂 migration(如数据迁移)的回滚无法自动推导
|
||||||
|
|
||||||
|
**应对**:
|
||||||
|
- AI 只生成简单反向 DDL(CREATE→DROP 等)
|
||||||
|
- 复杂情况标记 `⚠️ 需手工补全`
|
||||||
|
- 回滚 SQL 由人工审查后纳入 draft release
|
||||||
|
|
||||||
|
### R3: draft release 被忘记 publish
|
||||||
|
|
||||||
|
**影响**:功能开发完但生产没部署
|
||||||
|
|
||||||
|
**应对**:
|
||||||
|
- 企微机器人每天检查超 24h 未 publish 的 draft,发提醒
|
||||||
|
- 管理员 dashboard 显示 draft 列表
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 验收标准
|
||||||
|
|
||||||
|
- [ ] `releases` 表创建,migration 落地
|
||||||
|
- [ ] `/api/v1/releases/drafts` 创建 draft(至少支持最小字段)
|
||||||
|
- [ ] MCP `create_release_draft` 工具可用
|
||||||
|
- [ ] 能从 Claude Code 一键创建 Gitea draft release
|
||||||
|
- [ ] build.yaml 支持 `release.published` 触发
|
||||||
|
- [ ] 至少跑通 1 次完整流程(draft → 人工 publish → 自动部署)
|
||||||
|
- [ ] 回滚脚本自动生成覆盖 >80% 的 DDL 场景
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 参考
|
||||||
|
|
||||||
|
- devflow-claude: `plugins/req/commands/release.md`
|
||||||
|
- REQ-20260416-0017(母需求)
|
||||||
|
- 当前 build.yaml: `/Users/donglinlai/coding/qiudl/new-ai-proj/.gitea/workflows/build.yaml`
|
||||||
|
- Gitea Release API: https://docs.gitea.com/api/next/#tag/repository/operation/repoCreateRelease
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 下一步行动
|
||||||
|
|
||||||
|
- [ ] 本文档提交评审
|
||||||
|
- [ ] 评审通过后拆 7 个子任务(P0-3.1 ~ P0-3.7)并分别关联到 REQ-20260416-0017
|
||||||
|
- [ ] 从 P0-3.1(数据模型)开始实施
|
||||||
100
hooks/README.md
Normal file
100
hooks/README.md
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
# Claude Code Hooks
|
||||||
|
|
||||||
|
本目录包含 ai-proj-helper 体系的 Claude Code 钩子脚本。
|
||||||
|
|
||||||
|
**REQ-20260416-0017 P0 批 — 源自 devflow-claude 借鉴**
|
||||||
|
|
||||||
|
## 脚本清单
|
||||||
|
|
||||||
|
| 脚本 | 事件 | 作用 |
|
||||||
|
|------|------|------|
|
||||||
|
| `session-context.sh` | SessionStart | 从分支名解析 REQ-ID,注入需求上下文到会话 |
|
||||||
|
| `pre-tool-confirm.sh` | PreToolUse (Bash) | 拦截生产发布、force push、docker 生产容器、reset --hard 等危险操作 |
|
||||||
|
|
||||||
|
## 安装(用户级,一次即可)
|
||||||
|
|
||||||
|
### 方式 1:编辑 `~/.claude/settings.json`
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"SessionStart": [
|
||||||
|
{
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/session-context.sh",
|
||||||
|
"timeout": 10
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"PreToolUse": [
|
||||||
|
{
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [
|
||||||
|
{
|
||||||
|
"type": "command",
|
||||||
|
"command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/pre-tool-confirm.sh",
|
||||||
|
"timeout": 30
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 方式 2:一键安装脚本
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash /Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/install.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## 验证
|
||||||
|
|
||||||
|
### SessionStart hook
|
||||||
|
|
||||||
|
1. 切到一个带 REQ-ID 的分支:`git checkout feat/REQ-20260416-0017-xxx`
|
||||||
|
2. 打开新的 Claude Code 会话
|
||||||
|
3. 会话开头应看到 `# 需求上下文(SessionStart Hook)` 块
|
||||||
|
|
||||||
|
### PreToolUse hook
|
||||||
|
|
||||||
|
让 AI 尝试执行这些命令中的任一条,应弹出原生确认对话框:
|
||||||
|
|
||||||
|
- `git push origin main`
|
||||||
|
- `git push --force`
|
||||||
|
- `tea pr merge --base main`
|
||||||
|
- `docker rm ai_postgres_prod`
|
||||||
|
- `git reset --hard`
|
||||||
|
|
||||||
|
## 依赖
|
||||||
|
|
||||||
|
- `bash` (macOS / Linux 默认)
|
||||||
|
- `jq` (PreToolUse hook 需要,`brew install jq`)
|
||||||
|
- `python3` (SessionStart hook 解析 JSON)
|
||||||
|
- `curl` (SessionStart hook 调 MCP API)
|
||||||
|
|
||||||
|
## 自定义
|
||||||
|
|
||||||
|
### 修改 MCP API 地址
|
||||||
|
|
||||||
|
在项目根目录创建 `.ai-proj-env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export AI_PROJ_API_BASE="https://api.ai-proj.example.com"
|
||||||
|
export AI_PROJ_MCP_KEY="your-mcp-api-key"
|
||||||
|
```
|
||||||
|
|
||||||
|
或设置全局环境变量。
|
||||||
|
|
||||||
|
### 拦截更多命令
|
||||||
|
|
||||||
|
编辑 `pre-tool-confirm.sh`,在 `REASON` 赋值的 elif 链中增加规则。
|
||||||
|
|
||||||
|
## 设计原则
|
||||||
|
|
||||||
|
1. **快速失败退出**:不处理的命令立即 `exit 0`,不影响性能
|
||||||
|
2. **非侵入性**:网络/依赖缺失时静默退出,不阻塞正常工作流
|
||||||
|
3. **可复现**:hook 脚本跟随仓库分发,方便团队一致部署
|
||||||
81
hooks/install.sh
Executable file
81
hooks/install.sh
Executable file
@@ -0,0 +1,81 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# install.sh
|
||||||
|
# 一键把 ai-proj-helper hooks 注册到 ~/.claude/settings.json
|
||||||
|
#
|
||||||
|
# 用法: bash hooks/install.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
HOOKS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SETTINGS_FILE="$HOME/.claude/settings.json"
|
||||||
|
|
||||||
|
if ! command -v python3 >/dev/null 2>&1; then
|
||||||
|
echo "❌ 需要 python3(用于合并 JSON)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
echo "⚠️ 未安装 jq,PreToolUse hook 将无法正常工作"
|
||||||
|
echo " 请执行: brew install jq"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$HOME/.claude"
|
||||||
|
|
||||||
|
python3 << EOF
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
settings_file = "$SETTINGS_FILE"
|
||||||
|
hooks_dir = "$HOOKS_DIR"
|
||||||
|
|
||||||
|
# 读已有配置
|
||||||
|
if os.path.exists(settings_file):
|
||||||
|
with open(settings_file) as f:
|
||||||
|
data = json.load(f)
|
||||||
|
else:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
# 合并 hooks
|
||||||
|
hooks = data.setdefault("hooks", {})
|
||||||
|
|
||||||
|
# SessionStart
|
||||||
|
session_start = hooks.setdefault("SessionStart", [])
|
||||||
|
session_cmd = f"{hooks_dir}/session-context.sh"
|
||||||
|
already_registered = any(
|
||||||
|
any(h.get("command") == session_cmd for h in entry.get("hooks", []))
|
||||||
|
for entry in session_start
|
||||||
|
)
|
||||||
|
if not already_registered:
|
||||||
|
session_start.append({
|
||||||
|
"hooks": [{"type": "command", "command": session_cmd, "timeout": 10}]
|
||||||
|
})
|
||||||
|
print("✅ 注册 SessionStart hook")
|
||||||
|
else:
|
||||||
|
print("⏭️ SessionStart hook 已存在")
|
||||||
|
|
||||||
|
# PreToolUse (Bash)
|
||||||
|
pre_tool = hooks.setdefault("PreToolUse", [])
|
||||||
|
pre_cmd = f"{hooks_dir}/pre-tool-confirm.sh"
|
||||||
|
already_registered = any(
|
||||||
|
entry.get("matcher") == "Bash" and any(h.get("command") == pre_cmd for h in entry.get("hooks", []))
|
||||||
|
for entry in pre_tool
|
||||||
|
)
|
||||||
|
if not already_registered:
|
||||||
|
pre_tool.append({
|
||||||
|
"matcher": "Bash",
|
||||||
|
"hooks": [{"type": "command", "command": pre_cmd, "timeout": 30}]
|
||||||
|
})
|
||||||
|
print("✅ 注册 PreToolUse (Bash) hook")
|
||||||
|
else:
|
||||||
|
print("⏭️ PreToolUse hook 已存在")
|
||||||
|
|
||||||
|
# 写回
|
||||||
|
with open(settings_file, "w") as f:
|
||||||
|
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||||
|
|
||||||
|
print(f"\n📝 已更新: {settings_file}")
|
||||||
|
print("\n下一步:")
|
||||||
|
print(" 1. 重启 Claude Code 会话")
|
||||||
|
print(" 2. 切到含 REQ-ID 的分支测试 SessionStart hook")
|
||||||
|
print(" 3. 让 AI 尝试 'git push origin main' 测试 PreToolUse hook")
|
||||||
|
EOF
|
||||||
94
hooks/pre-tool-confirm.sh
Executable file
94
hooks/pre-tool-confirm.sh
Executable file
@@ -0,0 +1,94 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# pre-tool-confirm.sh
|
||||||
|
# PreToolUse Hook: 拦截危险操作,强制原生确认对话框
|
||||||
|
#
|
||||||
|
# 输入格式(stdin JSON):
|
||||||
|
# { "tool_name": "Bash", "tool_input": { "command": "..." } }
|
||||||
|
#
|
||||||
|
# 输出格式(stdout JSON):
|
||||||
|
# { "hookSpecificOutput": { "hookEventName": "PreToolUse",
|
||||||
|
# "permissionDecision": "ask",
|
||||||
|
# "permissionDecisionReason": "..." } }
|
||||||
|
#
|
||||||
|
# 安装方式:在 ~/.claude/settings.json 配置
|
||||||
|
# hooks.PreToolUse:
|
||||||
|
# - matcher: "Bash"
|
||||||
|
# hooks:
|
||||||
|
# - type: command
|
||||||
|
# command: "<path>/hooks/pre-tool-confirm.sh"
|
||||||
|
# timeout: 30
|
||||||
|
#
|
||||||
|
# 参考:devflow-claude confirm-before-commit.sh + ai-proj memory 规则
|
||||||
|
# REQ-20260416-0017 P0-2
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
INPUT=$(cat)
|
||||||
|
|
||||||
|
if ! command -v jq >/dev/null 2>&1; then
|
||||||
|
# 没有 jq 就不处理
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
|
||||||
|
|
||||||
|
if [ -z "$COMMAND" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
REASON=""
|
||||||
|
|
||||||
|
# ============ 1. 生产分支推送 ============
|
||||||
|
if echo "$COMMAND" | grep -qE '\bgit\s+push\b.*\b(origin\s+)?(main|master)\b'; then
|
||||||
|
REASON="⚠️ 即将推送到 main/master 生产分支。确认已过 PR 评审?"
|
||||||
|
|
||||||
|
# ============ 2. 强制推送 ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\bgit\s+push\b.*(--force|--force-with-lease|-f\b)'; then
|
||||||
|
REASON="⛔ 危险:force push 会覆盖远程历史,可能丢失他人提交。确认继续?"
|
||||||
|
|
||||||
|
# ============ 3. tea pr merge --base main ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\btea\s+pr\s+merge\b.*--base\s+main\b'; then
|
||||||
|
REASON="⚠️ 即将合并 PR 到 main 分支,合并后将触发生产部署。确认 PR 已测试?"
|
||||||
|
|
||||||
|
# ============ 4. docker rm/stop 生产容器 ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*\b(ai_postgres_prod|ai_backend_prod|ai_frontend_prod|ai_redis_prod)\b'; then
|
||||||
|
REASON="⛔ 危险:即将停止/删除生产容器!2026-04-05 曾因此宕机 15 分钟。确认是灾难恢复?"
|
||||||
|
|
||||||
|
# ============ 5. docker rm/stop 其他 prod 相关 ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\bdocker\s+(rm|stop|kill)\b.*_prod\b'; then
|
||||||
|
REASON="⚠️ 即将停止/删除含 _prod 的容器。确认是生产环境?"
|
||||||
|
|
||||||
|
# ============ 6. git reset --hard / clean -fd ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\bgit\s+reset\s+--hard\b'; then
|
||||||
|
REASON="⚠️ git reset --hard 会丢弃所有未提交改动。确认继续?"
|
||||||
|
|
||||||
|
elif echo "$COMMAND" | grep -qE '\bgit\s+clean\s+.*-f'; then
|
||||||
|
REASON="⚠️ git clean -f 会删除未跟踪文件。确认继续?"
|
||||||
|
|
||||||
|
# ============ 7. rm -rf / 系统路径 ============
|
||||||
|
elif echo "$COMMAND" | grep -qE '\brm\s+.*-[rf]+.*\s+(/|/\*|~|\$HOME)'; then
|
||||||
|
REASON="⛔ 危险:rm -rf 指向系统根或家目录。确认继续?"
|
||||||
|
|
||||||
|
# ============ 8. ssh 生产服务器 + 破坏性命令 ============
|
||||||
|
elif echo "$COMMAND" | grep -qE 'ssh\s+\S*prod\S*.*\b(rm|drop|truncate|delete)\b'; then
|
||||||
|
REASON="⛔ 危险:在生产服务器执行破坏性命令。确认继续?"
|
||||||
|
|
||||||
|
# ============ 9. psql/mysql 生产数据库 + DROP/TRUNCATE/DELETE ============
|
||||||
|
elif echo "$COMMAND" | grep -qiE '(psql|mysql).*prod.*\b(DROP|TRUNCATE|DELETE FROM)\b'; then
|
||||||
|
REASON="⛔ 危险:生产数据库 DROP/TRUNCATE/DELETE。确认已备份?"
|
||||||
|
|
||||||
|
# ============ 10. MCP advance_delivery_stage force=true ============
|
||||||
|
# 这种会走 MCP 而不是 Bash,本 hook 不好拦,留给另一个 matcher 处理
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$REASON" ]; then
|
||||||
|
jq -n --arg reason "$REASON" '{
|
||||||
|
hookSpecificOutput: {
|
||||||
|
hookEventName: "PreToolUse",
|
||||||
|
permissionDecision: "ask",
|
||||||
|
permissionDecisionReason: $reason
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
else
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
161
hooks/release-draft.sh
Executable file
161
hooks/release-draft.sh
Executable file
@@ -0,0 +1,161 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# release-draft.sh
|
||||||
|
# 最小可用版:从本地 git 仓库创建 Gitea draft release
|
||||||
|
#
|
||||||
|
# 用法:
|
||||||
|
# export GITEA_TOKEN=$(bw get password "Gitea - qiudl Token")
|
||||||
|
# bash release-draft.sh v1.2.0 [--from v1.1.9]
|
||||||
|
#
|
||||||
|
# 输出: 创建的 draft release URL(待人工 publish)
|
||||||
|
# REQ-20260416-0017 P0-3 最小可用脚本
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
VERSION="$1"
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "❌ 用法: $0 <version> [--from <previous_tag>]"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FROM_TAG=""
|
||||||
|
if [ "$2" = "--from" ]; then
|
||||||
|
FROM_TAG="$3"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
echo "❌ 不在 git 仓库内"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_SLUG=$(git remote get-url origin 2>/dev/null | \
|
||||||
|
sed -E 's|.*[:/]([^/]+/[^/]+)\.git$|\1|' | \
|
||||||
|
sed -E 's|.*[:/]([^/]+/[^/]+)$|\1|')
|
||||||
|
|
||||||
|
if [ -z "$REPO_SLUG" ]; then
|
||||||
|
echo "❌ 无法从 git remote 推断 OWNER/REPO"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "❌ 需要 GITEA_TOKEN 环境变量"
|
||||||
|
echo " export GITEA_TOKEN=\$(bw get password 'Gitea - qiudl Token')"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
GITEA_URL="${GITEA_URL:-https://gitea.pipexerp.com}"
|
||||||
|
|
||||||
|
# 推断 from
|
||||||
|
if [ -z "$FROM_TAG" ]; then
|
||||||
|
FROM_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
TO_REF="HEAD"
|
||||||
|
|
||||||
|
# 生成 changelog 内容
|
||||||
|
echo "📋 生成 changelog..."
|
||||||
|
|
||||||
|
CHANGELOG=""
|
||||||
|
if [ -n "$FROM_TAG" ]; then
|
||||||
|
COMMITS=$(git log --pretty=format:'- %s (%h)' "${FROM_TAG}..${TO_REF}" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
COMMITS=$(git log --pretty=format:'- %s (%h)' "${TO_REF}" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$COMMITS" ]; then
|
||||||
|
echo "⚠️ 无 commit,放弃"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 按类型分组
|
||||||
|
FEATS=$(echo "$COMMITS" | grep -iE 'feat(\(|:)|新功能' || true)
|
||||||
|
FIXES=$(echo "$COMMITS" | grep -iE 'fix(\(|:)|修复' || true)
|
||||||
|
CHORES=$(echo "$COMMITS" | grep -iE 'chore(\(|:)' || true)
|
||||||
|
OTHERS=$(echo "$COMMITS" | grep -vE 'feat(\(|:)|fix(\(|:)|chore(\(|:)|新功能|修复' || true)
|
||||||
|
|
||||||
|
CHANGELOG="## 发布内容
|
||||||
|
|
||||||
|
**版本**: \`${VERSION}\`
|
||||||
|
**区间**: \`${FROM_TAG:-init}..${TO_REF}\`
|
||||||
|
|
||||||
|
"
|
||||||
|
|
||||||
|
if [ -n "$FEATS" ]; then
|
||||||
|
CHANGELOG="${CHANGELOG}### 新功能
|
||||||
|
|
||||||
|
${FEATS}
|
||||||
|
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$FIXES" ]; then
|
||||||
|
CHANGELOG="${CHANGELOG}### Bug 修复
|
||||||
|
|
||||||
|
${FIXES}
|
||||||
|
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$CHORES" ]; then
|
||||||
|
CHANGELOG="${CHANGELOG}### 杂项
|
||||||
|
|
||||||
|
${CHORES}
|
||||||
|
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$OTHERS" ]; then
|
||||||
|
CHANGELOG="${CHANGELOG}### 其他
|
||||||
|
|
||||||
|
${OTHERS}
|
||||||
|
|
||||||
|
"
|
||||||
|
fi
|
||||||
|
|
||||||
|
CHANGELOG="${CHANGELOG}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
⚠️ **这是 draft release**,审查无误后点击 'Publish release' 按钮才会触发生产部署。
|
||||||
|
|
||||||
|
📋 审查要点:
|
||||||
|
- [ ] 所有改动已过 PR 评审
|
||||||
|
- [ ] SQL migration 已验证(如有)
|
||||||
|
- [ ] 回滚方案已确认(如有)
|
||||||
|
- [ ] 生产环境准备就绪"
|
||||||
|
|
||||||
|
# 创建 draft release
|
||||||
|
echo "🚀 创建 Gitea draft release..."
|
||||||
|
|
||||||
|
BODY_JSON=$(python3 -c "
|
||||||
|
import json
|
||||||
|
print(json.dumps({
|
||||||
|
'tag_name': '$VERSION',
|
||||||
|
'target_commitish': 'main',
|
||||||
|
'name': '$VERSION',
|
||||||
|
'body': '''$CHANGELOG''',
|
||||||
|
'draft': True,
|
||||||
|
'prerelease': False,
|
||||||
|
}))
|
||||||
|
")
|
||||||
|
|
||||||
|
RESP=$(curl -s -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$BODY_JSON" \
|
||||||
|
"${GITEA_URL}/api/v1/repos/${REPO_SLUG}/releases")
|
||||||
|
|
||||||
|
HTML_URL=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin).get('html_url',''))" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$HTML_URL" ]; then
|
||||||
|
echo "✅ Draft release 已创建"
|
||||||
|
echo "🔗 $HTML_URL"
|
||||||
|
echo ""
|
||||||
|
echo "⏭️ 下一步:"
|
||||||
|
echo " 1. 打开链接审查产物清单"
|
||||||
|
echo " 2. 确认无误后点 'Publish release' 按钮"
|
||||||
|
echo " 3. CI/CD 将自动触发生产部署"
|
||||||
|
else
|
||||||
|
echo "❌ 创建失败"
|
||||||
|
echo "$RESP" | head -20
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
130
hooks/session-context.sh
Executable file
130
hooks/session-context.sh
Executable file
@@ -0,0 +1,130 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# session-context.sh
|
||||||
|
# SessionStart Hook: 会话启动时自动注入需求上下文
|
||||||
|
#
|
||||||
|
# 从当前 Git 分支名解析 REQ-ID,调用 ai-proj MCP API 查询需求详情,
|
||||||
|
# 把标题 / 状态 / delivery_stage / reviewer / 进行中需求数注入 system-reminder。
|
||||||
|
#
|
||||||
|
# 安装方式:
|
||||||
|
# 在 ~/.claude/settings.json 的 hooks.SessionStart 配置:
|
||||||
|
# {
|
||||||
|
# "command": "/Users/donglinlai/coding/qiudl/ai-proj-helper/hooks/session-context.sh",
|
||||||
|
# "timeout": 10
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# 参考:devflow-claude 同名脚本 + ai-proj MCP 适配
|
||||||
|
# REQ-20260416-0017 P0-1
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# 仅在 git 仓库内执行
|
||||||
|
if ! git rev-parse --git-dir >/dev/null 2>&1; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null)
|
||||||
|
if [ -z "$REPO_ROOT" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
# ============ 1. 当前分支 → REQ ID ============
|
||||||
|
BRANCH=$(git symbolic-ref --short HEAD 2>/dev/null || echo "")
|
||||||
|
REQ_ID=""
|
||||||
|
|
||||||
|
if [ -n "$BRANCH" ]; then
|
||||||
|
# 匹配 feat/REQ-20260416-0017-xxx / fix/REQ-20260416-0017 / feature/req-20260416-0017
|
||||||
|
REQ_ID=$(echo "$BRANCH" | grep -oiE 'REQ-[0-9]{8}-[0-9]{4}' | head -1 | tr '[:lower:]' '[:upper:]')
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============ 2. 无 REQ 时仅输出分支信息(静默退出条件) ============
|
||||||
|
if [ -z "$REQ_ID" ]; then
|
||||||
|
# 只要不在 main/develop 上就提示一下
|
||||||
|
case "$BRANCH" in
|
||||||
|
main|master|develop|"") exit 0 ;;
|
||||||
|
esac
|
||||||
|
echo "# 会话上下文"
|
||||||
|
echo ""
|
||||||
|
echo "- 当前分支: \`${BRANCH}\`(未检测到 REQ ID)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============ 3. 查询 MCP API ============
|
||||||
|
# MCP API 通过 localhost:8080 直连(ai-proj 本地后端)或 ai-proj-prod
|
||||||
|
# 这里优先读项目根的 .ai-proj-env 决定环境
|
||||||
|
API_BASE="${AI_PROJ_API_BASE:-}"
|
||||||
|
API_TOKEN="${AI_PROJ_MCP_KEY:-}"
|
||||||
|
|
||||||
|
if [ -f "$REPO_ROOT/.ai-proj-env" ]; then
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source "$REPO_ROOT/.ai-proj-env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$API_BASE" ]; then
|
||||||
|
# 默认走本地 dev
|
||||||
|
API_BASE="http://localhost:8080"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 查询需求
|
||||||
|
RESP=""
|
||||||
|
if command -v curl >/dev/null 2>&1; then
|
||||||
|
if [ -n "$API_TOKEN" ]; then
|
||||||
|
RESP=$(curl -s --max-time 3 -H "X-MCP-API-Key: $API_TOKEN" \
|
||||||
|
"${API_BASE}/api/v1/mcp/requirements/by-display-id/${REQ_ID}" 2>/dev/null || echo "")
|
||||||
|
else
|
||||||
|
RESP=$(curl -s --max-time 3 \
|
||||||
|
"${API_BASE}/api/v1/mcp/requirements/by-display-id/${REQ_ID}" 2>/dev/null || echo "")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ============ 4. 解析并输出 ============
|
||||||
|
echo "# 需求上下文(SessionStart Hook)"
|
||||||
|
echo ""
|
||||||
|
echo "- 分支: \`${BRANCH}\`"
|
||||||
|
echo "- 需求: **${REQ_ID}**"
|
||||||
|
|
||||||
|
if [ -n "$RESP" ] && command -v python3 >/dev/null 2>&1; then
|
||||||
|
# 尝试用 python 解析
|
||||||
|
PARSED=$(python3 -c "
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
d = json.loads('''$RESP''')
|
||||||
|
data = d.get('data', {})
|
||||||
|
if not data:
|
||||||
|
sys.exit(0)
|
||||||
|
title = data.get('title', '?')
|
||||||
|
status = data.get('status', '?')
|
||||||
|
stage = data.get('delivery_stage', '?')
|
||||||
|
priority = data.get('priority', '?')
|
||||||
|
project = data.get('project_name', '?')
|
||||||
|
print(f'title={title}')
|
||||||
|
print(f'status={status}')
|
||||||
|
print(f'stage={stage}')
|
||||||
|
print(f'priority={priority}')
|
||||||
|
print(f'project={project}')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$PARSED" ]; then
|
||||||
|
TITLE=$(echo "$PARSED" | grep '^title=' | sed 's/^title=//')
|
||||||
|
STATUS=$(echo "$PARSED" | grep '^status=' | sed 's/^status=//')
|
||||||
|
STAGE=$(echo "$PARSED" | grep '^stage=' | sed 's/^stage=//')
|
||||||
|
PRIORITY=$(echo "$PARSED" | grep '^priority=' | sed 's/^priority=//')
|
||||||
|
PROJECT=$(echo "$PARSED" | grep '^project=' | sed 's/^project=//')
|
||||||
|
|
||||||
|
[ -n "$TITLE" ] && echo "- 标题: ${TITLE}"
|
||||||
|
[ -n "$PROJECT" ] && echo "- 项目: ${PROJECT}"
|
||||||
|
[ -n "$STATUS" ] && echo "- 状态: ${STATUS}"
|
||||||
|
[ -n "$STAGE" ] && echo "- 交付阶段: ${STAGE}"
|
||||||
|
[ -n "$PRIORITY" ] && echo "- 优先级: ${PRIORITY}"
|
||||||
|
else
|
||||||
|
echo "- 📡 MCP API 响应为空或未授权(API_BASE=${API_BASE})"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "- ⚠️ 无法连接 MCP API(${API_BASE}),仅显示分支信息"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "💡 相关命令:\`/req get ${REQ_ID}\` 查看详情 · \`/commit\` 智能提交"
|
||||||
98
hooks/validate-prd.sh
Executable file
98
hooks/validate-prd.sh
Executable file
@@ -0,0 +1,98 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# validate-prd.sh
|
||||||
|
# PostToolUse Hook: PRD 文档写入/编辑后自动校验章节完整性
|
||||||
|
#
|
||||||
|
# 检查标准 PRD 模板的必需章节是否存在。
|
||||||
|
# 借鉴 devflow-claude validate-requirement.sh。
|
||||||
|
# REQ-20260416-0017 P1-14
|
||||||
|
#
|
||||||
|
# 安装方式(可选,加到 ~/.claude/settings.json):
|
||||||
|
# hooks.PostToolUse:
|
||||||
|
# - matcher: "Write|Edit"
|
||||||
|
# hooks:
|
||||||
|
# - type: command
|
||||||
|
# command: "<path>/hooks/validate-prd.sh"
|
||||||
|
# timeout: 5
|
||||||
|
#
|
||||||
|
# 也可由 skill 脚本在 PRD 写完后手动调用:
|
||||||
|
# bash validate-prd.sh /path/to/PRD.md
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# ============ 1. 获取文件路径 ============
|
||||||
|
FILE_PATH="$1"
|
||||||
|
|
||||||
|
# 如果没有参数,从 stdin JSON 提取(PostToolUse hook 模式)
|
||||||
|
if [ -z "$FILE_PATH" ] && command -v jq >/dev/null 2>&1; then
|
||||||
|
INPUT=$(cat 2>/dev/null || echo "")
|
||||||
|
if [ -n "$INPUT" ]; then
|
||||||
|
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.content // empty' 2>/dev/null)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 没有文件路径,静默退出
|
||||||
|
if [ -z "$FILE_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 不是 .md 文件,跳过
|
||||||
|
case "$FILE_PATH" in
|
||||||
|
*.md) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# 文件不存在,跳过
|
||||||
|
if [ ! -f "$FILE_PATH" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 不含 PRD / 需求 关键词的文件名,跳过
|
||||||
|
BASENAME=$(basename "$FILE_PATH")
|
||||||
|
case "$BASENAME" in
|
||||||
|
*PRD*|*prd*|*需求*|*requirement*|*REQ*) ;;
|
||||||
|
*) exit 0 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# ============ 2. 检查章节 ============
|
||||||
|
|
||||||
|
# 标准 PRD 必需章节(来自 req-prd SKILL.md 的模板)
|
||||||
|
REQUIRED_SECTIONS=(
|
||||||
|
"## 1. 概述"
|
||||||
|
"### 1.1 背景"
|
||||||
|
"### 1.2 目标"
|
||||||
|
"### 1.4 客户原始诉求"
|
||||||
|
"## 2. 用户分析"
|
||||||
|
"## 3. 功能需求"
|
||||||
|
"## 4. 交互设计"
|
||||||
|
"## 5. 技术要求"
|
||||||
|
"## 6. 上线计划"
|
||||||
|
"## 7. 风险评估"
|
||||||
|
)
|
||||||
|
|
||||||
|
MISSING=()
|
||||||
|
CONTENT=$(cat "$FILE_PATH")
|
||||||
|
|
||||||
|
for section in "${REQUIRED_SECTIONS[@]}"; do
|
||||||
|
# 模糊匹配:忽略空格差异和标点
|
||||||
|
PATTERN=$(echo "$section" | sed 's/[[:space:]]*//g')
|
||||||
|
CONTENT_CLEAN=$(echo "$CONTENT" | sed 's/[[:space:]]*//g')
|
||||||
|
if ! echo "$CONTENT_CLEAN" | grep -qi "$(echo "$PATTERN" | sed 's/#//g')"; then
|
||||||
|
MISSING+=("$section")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# ============ 3. 输出 ============
|
||||||
|
if [ ${#MISSING[@]} -gt 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "⚠️ PRD 章节检查:${BASENAME}"
|
||||||
|
echo ""
|
||||||
|
echo "缺少 ${#MISSING[@]} 个必需章节:"
|
||||||
|
for m in "${MISSING[@]}"; do
|
||||||
|
echo " ❌ $m"
|
||||||
|
done
|
||||||
|
echo ""
|
||||||
|
echo "💡 请参考 req-prd skill 的 PRD 模板补充缺失章节。"
|
||||||
|
echo " 章节结构不可变:不得新增、删除、合并或重命名模板中的章节。"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
338
install-skills.sh
Executable file
338
install-skills.sh
Executable file
@@ -0,0 +1,338 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# install-skills.sh — Cross-machine Claude skill sync from ai-proj-helper
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./install-skills.sh [options]
|
||||||
|
#
|
||||||
|
# Options:
|
||||||
|
# --dry-run Preview changes without writing anything
|
||||||
|
# --category <cat> Only install plugins in dir_category=<cat>
|
||||||
|
# Valid values: biz, core, dev, integration, personal, req
|
||||||
|
# --force Overwrite even if local files were modified
|
||||||
|
# --cleanup Remove locally installed skills that are no longer in repo
|
||||||
|
# --list List all available plugins without installing
|
||||||
|
# --help Show this help
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
SKILLS_DIR="${HOME}/.claude/skills"
|
||||||
|
COMMANDS_DIR="${HOME}/.claude/commands"
|
||||||
|
STATE_FILE="${HOME}/.claude/.installed-skills.json"
|
||||||
|
|
||||||
|
DRY_RUN=false
|
||||||
|
CATEGORY_FILTER=""
|
||||||
|
FORCE=false
|
||||||
|
CLEANUP=false
|
||||||
|
LIST_ONLY=false
|
||||||
|
|
||||||
|
# ── Colour helpers ─────────────────────────────────────────────────────────────
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
RED='\033[0;31m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
RESET='\033[0m'
|
||||||
|
|
||||||
|
info() { echo -e "${BLUE}[info]${RESET} $*"; }
|
||||||
|
ok() { echo -e "${GREEN}[ok]${RESET} $*"; }
|
||||||
|
warn() { echo -e "${YELLOW}[warn]${RESET} $*"; }
|
||||||
|
error() { echo -e "${RED}[error]${RESET} $*" >&2; }
|
||||||
|
dry() { echo -e "${YELLOW}[dry]${RESET} $*"; }
|
||||||
|
|
||||||
|
# ── Argument parsing ───────────────────────────────────────────────────────────
|
||||||
|
while [[ $# -gt 0 ]]; do
|
||||||
|
case "$1" in
|
||||||
|
--dry-run) DRY_RUN=true ;;
|
||||||
|
--force) FORCE=true ;;
|
||||||
|
--cleanup) CLEANUP=true ;;
|
||||||
|
--list) LIST_ONLY=true ;;
|
||||||
|
--category) CATEGORY_FILTER="$2"; shift ;;
|
||||||
|
--help|-h)
|
||||||
|
grep '^#' "$0" | grep -v '!/usr' | sed 's/^# \?//'
|
||||||
|
exit 0 ;;
|
||||||
|
*)
|
||||||
|
error "Unknown argument: $1"
|
||||||
|
exit 1 ;;
|
||||||
|
esac
|
||||||
|
shift
|
||||||
|
done
|
||||||
|
|
||||||
|
# ── State helpers (plain JSON via python3) ─────────────────────────────────────
|
||||||
|
state_get() {
|
||||||
|
# state_get <install_name> -> prints version or empty string
|
||||||
|
local name="$1"
|
||||||
|
if [[ -f "$STATE_FILE" ]]; then
|
||||||
|
python3 -c "
|
||||||
|
import json,sys
|
||||||
|
try:
|
||||||
|
d=json.load(open('$STATE_FILE'))
|
||||||
|
print(d.get('$name',{}).get('version',''))
|
||||||
|
except: pass
|
||||||
|
" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
state_set() {
|
||||||
|
# state_set <install_name> <version> <install_type>
|
||||||
|
local name="$1" ver="$2" itype="$3"
|
||||||
|
python3 -c "
|
||||||
|
import json,os
|
||||||
|
f='$STATE_FILE'
|
||||||
|
d=json.load(open(f)) if os.path.exists(f) else {}
|
||||||
|
d['$name']={'version':'$ver','install_type':'$itype'}
|
||||||
|
json.dump(d,open(f,'w'),indent=2)
|
||||||
|
" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
state_remove() {
|
||||||
|
local name="$1"
|
||||||
|
python3 -c "
|
||||||
|
import json,os
|
||||||
|
f='$STATE_FILE'
|
||||||
|
if not os.path.exists(f): exit()
|
||||||
|
d=json.load(open(f))
|
||||||
|
d.pop('$name',None)
|
||||||
|
json.dump(d,open(f,'w'),indent=2)
|
||||||
|
" 2>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
state_all_names() {
|
||||||
|
if [[ -f "$STATE_FILE" ]]; then
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
d=json.load(open('$STATE_FILE'))
|
||||||
|
for k in d: print(k)
|
||||||
|
" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Plugin discovery ───────────────────────────────────────────────────────────
|
||||||
|
find_plugins() {
|
||||||
|
find "$REPO_DIR" -path "*/skills-*/*-plugin/.claude-plugin/plugin.json" | sort
|
||||||
|
}
|
||||||
|
|
||||||
|
read_field() {
|
||||||
|
# read_field <json_file> <field>
|
||||||
|
python3 -c "import json,sys; d=json.load(open('$1')); print(d.get('$2',''))" 2>/dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve the actual source directory to rsync from.
|
||||||
|
# If skills/ has SKILL.md at the top level, use it directly.
|
||||||
|
# If skills/ has a single subdirectory (e.g. skills/dev-test/SKILL.md), use that subdirectory.
|
||||||
|
resolve_skills_src() {
|
||||||
|
local skills_dir="$1"
|
||||||
|
if [[ -f "$skills_dir/SKILL.md" ]]; then
|
||||||
|
echo "$skills_dir"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
# Find the first subdirectory that contains SKILL.md
|
||||||
|
local sub
|
||||||
|
sub="$(find "$skills_dir" -maxdepth 2 -name 'SKILL.md' | head -1)"
|
||||||
|
if [[ -n "$sub" ]]; then
|
||||||
|
echo "$(dirname "$sub")"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
echo "$skills_dir"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Conflict detection (has local been modified since we installed it?) ────────
|
||||||
|
has_local_modification() {
|
||||||
|
# Returns 0 (true) if local target differs from repo source, 1 if identical or new
|
||||||
|
local install_name="$1" install_type="$2" plugin_skills_dir="$3"
|
||||||
|
|
||||||
|
if [[ "$install_type" == "command" ]]; then
|
||||||
|
local src="$plugin_skills_dir/SKILL.md"
|
||||||
|
local dst="$COMMANDS_DIR/${install_name}.md"
|
||||||
|
[[ -f "$dst" ]] && ! diff -q "$src" "$dst" &>/dev/null && return 0
|
||||||
|
else
|
||||||
|
local dst_dir="$SKILLS_DIR/$install_name"
|
||||||
|
if [[ -d "$dst_dir" ]]; then
|
||||||
|
# Compare each file from source
|
||||||
|
while IFS= read -r -d '' src_file; do
|
||||||
|
local rel="${src_file#$plugin_skills_dir/}"
|
||||||
|
local dst_file="$dst_dir/$rel"
|
||||||
|
if [[ -f "$dst_file" ]] && ! diff -q "$src_file" "$dst_file" &>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done < <(find "$plugin_skills_dir" -type f -print0)
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Install a single plugin ────────────────────────────────────────────────────
|
||||||
|
install_plugin() {
|
||||||
|
local json_path="$1"
|
||||||
|
local plugin_dir
|
||||||
|
plugin_dir="$(dirname "$(dirname "$json_path")")" # strip /.claude-plugin/plugin.json
|
||||||
|
local skills_dir="$plugin_dir/skills"
|
||||||
|
|
||||||
|
local install_name install_type dir_category version
|
||||||
|
install_name="$(read_field "$json_path" install_name)"
|
||||||
|
install_type="$(read_field "$json_path" install_type)"
|
||||||
|
dir_category="$(read_field "$json_path" dir_category)"
|
||||||
|
version="$(read_field "$json_path" version)"
|
||||||
|
|
||||||
|
# Skip if no install metadata (legacy plugin without our new fields)
|
||||||
|
if [[ -z "$install_name" || -z "$install_type" ]]; then
|
||||||
|
warn "$(basename "$plugin_dir"): missing install_name/install_type, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Category filter
|
||||||
|
if [[ -n "$CATEGORY_FILTER" && "$dir_category" != "$CATEGORY_FILTER" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Verify skills directory exists
|
||||||
|
if [[ ! -d "$skills_dir" ]]; then
|
||||||
|
warn "$install_name: no skills/ directory in plugin, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$LIST_ONLY" == true ]]; then
|
||||||
|
echo " [$dir_category] $install_type:$install_name v$version"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check current installed version
|
||||||
|
local current_version
|
||||||
|
current_version="$(state_get "$install_name")"
|
||||||
|
|
||||||
|
# Skip if up-to-date (same version) and no force
|
||||||
|
if [[ "$current_version" == "$version" && "$FORCE" == false ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Conflict detection: warn if local files were modified
|
||||||
|
if [[ -n "$current_version" && "$FORCE" == false ]]; then
|
||||||
|
if has_local_modification "$install_name" "$install_type" "$skills_dir"; then
|
||||||
|
warn "$install_name: local files were modified — skipping (use --force to overwrite)"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve actual source (handles plugins where content sits one level deeper,
|
||||||
|
# e.g. skills/dev-test/SKILL.md instead of skills/SKILL.md)
|
||||||
|
local src_dir
|
||||||
|
src_dir="$(resolve_skills_src "$skills_dir")"
|
||||||
|
|
||||||
|
# Perform install
|
||||||
|
if [[ "$install_type" == "command" ]]; then
|
||||||
|
# Single-file command → ~/.claude/commands/<name>.md
|
||||||
|
local src_md="$src_dir/SKILL.md"
|
||||||
|
if [[ ! -f "$src_md" ]]; then
|
||||||
|
warn "$install_name: SKILL.md not found, skipping"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
dry "$install_name → $COMMANDS_DIR/${install_name}.md"
|
||||||
|
else
|
||||||
|
mkdir -p "$COMMANDS_DIR"
|
||||||
|
cp "$src_md" "$COMMANDS_DIR/${install_name}.md"
|
||||||
|
state_set "$install_name" "$version" "$install_type"
|
||||||
|
ok "$install_name → command (v$version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
# Skill directory → ~/.claude/skills/<name>/
|
||||||
|
local dst_dir="$SKILLS_DIR/$install_name"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
dry "$install_name → $dst_dir/"
|
||||||
|
else
|
||||||
|
mkdir -p "$dst_dir"
|
||||||
|
# rsync resolved source (handles nested skills/ structures)
|
||||||
|
rsync -a --delete "$src_dir/" "$dst_dir/"
|
||||||
|
state_set "$install_name" "$version" "$install_type"
|
||||||
|
ok "$install_name → skill (v$version)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Cleanup removed plugins ────────────────────────────────────────────────────
|
||||||
|
cleanup_removed() {
|
||||||
|
if [[ "$CLEANUP" == false ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Checking for removed plugins to clean up..."
|
||||||
|
|
||||||
|
# Collect all install_names still in repo
|
||||||
|
local repo_names=()
|
||||||
|
while IFS= read -r json_path; do
|
||||||
|
local name
|
||||||
|
name="$(read_field "$json_path" install_name)"
|
||||||
|
[[ -n "$name" ]] && repo_names+=("$name")
|
||||||
|
done < <(find_plugins)
|
||||||
|
|
||||||
|
# Check state file for installed plugins no longer in repo
|
||||||
|
while IFS= read -r installed_name; do
|
||||||
|
local found=false
|
||||||
|
for repo_name in "${repo_names[@]}"; do
|
||||||
|
[[ "$repo_name" == "$installed_name" ]] && found=true && break
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ "$found" == false ]]; then
|
||||||
|
local itype
|
||||||
|
itype="$(python3 -c "import json; d=json.load(open('$STATE_FILE')); print(d.get('$installed_name',{}).get('install_type',''))" 2>/dev/null || true)"
|
||||||
|
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
dry "Would remove: $installed_name ($itype)"
|
||||||
|
else
|
||||||
|
if [[ "$itype" == "command" ]]; then
|
||||||
|
rm -f "$COMMANDS_DIR/${installed_name}.md"
|
||||||
|
else
|
||||||
|
rm -rf "${SKILLS_DIR:?}/$installed_name"
|
||||||
|
fi
|
||||||
|
state_remove "$installed_name"
|
||||||
|
ok "Removed: $installed_name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done < <(state_all_names)
|
||||||
|
}
|
||||||
|
|
||||||
|
# ── Main ───────────────────────────────────────────────────────────────────────
|
||||||
|
main() {
|
||||||
|
if [[ "$LIST_ONLY" == true ]]; then
|
||||||
|
info "Available plugins in $REPO_DIR:"
|
||||||
|
local count=0
|
||||||
|
while IFS= read -r json_path; do
|
||||||
|
install_plugin "$json_path"
|
||||||
|
((count++)) || true
|
||||||
|
done < <(find_plugins)
|
||||||
|
echo ""
|
||||||
|
info "Total: $count plugins"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
info "Installing Claude skills from: $REPO_DIR"
|
||||||
|
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be written"
|
||||||
|
[[ -n "$CATEGORY_FILTER" ]] && info "Category filter: $CATEGORY_FILTER"
|
||||||
|
|
||||||
|
local installed=0 skipped=0
|
||||||
|
|
||||||
|
while IFS= read -r json_path; do
|
||||||
|
local before
|
||||||
|
before="$(state_all_names | wc -l || true)"
|
||||||
|
install_plugin "$json_path"
|
||||||
|
local after
|
||||||
|
after="$(state_all_names | wc -l || true)"
|
||||||
|
if [[ "$after" -gt "$before" ]] || [[ "$DRY_RUN" == true ]]; then
|
||||||
|
((installed++)) || true
|
||||||
|
fi
|
||||||
|
done < <(find_plugins)
|
||||||
|
|
||||||
|
cleanup_removed
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
if [[ "$DRY_RUN" == true ]]; then
|
||||||
|
info "Dry run complete. $installed plugins would be installed/updated."
|
||||||
|
else
|
||||||
|
info "Done. $installed plugins installed/updated."
|
||||||
|
info "State saved to: $STATE_FILE"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "biz-contract",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "biz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "biz-ops",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "biz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "biz-plan",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "biz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "finance",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "biz"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "ai-proj",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "core"
|
||||||
}
|
}
|
||||||
|
|||||||
11
skills-core/pm-ask-plugin/.claude-plugin/plugin.json
Normal file
11
skills-core/pm-ask-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "pm-ask-plugin",
|
||||||
|
"description": "基于真实数据的项目问答 /ask。必须引用 MCP/git 真实数据,禁止编造",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "pm-ask",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "core"
|
||||||
|
}
|
||||||
126
skills-core/pm-ask-plugin/skills/SKILL.md
Normal file
126
skills-core/pm-ask-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
---
|
||||||
|
name: pm-ask
|
||||||
|
description: 基于 ai-proj MCP + git log 真实数据的项目问答。禁止编造。当用户说"/ask"、"问一下"、"统计一下"、"最近多少"、"谁做了多少"、"本月进度"等项目相关问题时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# pm-ask Skill — 基于真实数据的项目问答
|
||||||
|
|
||||||
|
借鉴自 devflow-claude `/pm:ask`。源自 REQ-20260416-0017 P1-9。
|
||||||
|
|
||||||
|
## 核心原则(铁律)
|
||||||
|
|
||||||
|
> **必须基于真实数据,禁止编造。**
|
||||||
|
|
||||||
|
- 所有数字必须来自 MCP API / git log / 文件系统的**真实查询**
|
||||||
|
- 必须引用具体 **REQ-XXX / commit-sha / 文件路径**
|
||||||
|
- 数据不足时**明确说明缺失**,不得"大概"、"估计"
|
||||||
|
- **区分事实和推测**:推测必须用 `⚠️ 推测:` 前缀
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
| 用户问题示例 | 数据源 | 工具 |
|
||||||
|
|-------------|--------|------|
|
||||||
|
| "qiudl 最近一个月提交了多少代码?" | git log | `git log --author=qiudl --since='30 days ago'` |
|
||||||
|
| "本月完成了哪些需求?" | ai-proj MCP | `list_requirements --status=completed` |
|
||||||
|
| "当前进行中的需求有多少?分别是?" | MCP | `list_requirements --status=in_progress` |
|
||||||
|
| "REQ-xxx 拆了哪些任务?完成多少?" | MCP | `get_requirement_tasks` |
|
||||||
|
| "backend/services/user.go 最近谁在改?" | git log | `git log --follow -- file` |
|
||||||
|
| "和上月对比,产出是多了还是少了?" | git log + MCP | 组合查询 |
|
||||||
|
| "生成一封客户更新邮件" | MCP + git | 聚合后结构化输出 |
|
||||||
|
|
||||||
|
## 工作流
|
||||||
|
|
||||||
|
### 1. 意图分析(确定数据源)
|
||||||
|
|
||||||
|
关键词 → 数据源映射:
|
||||||
|
|
||||||
|
| 关键词 | 数据源 |
|
||||||
|
|--------|--------|
|
||||||
|
| 需求 / REQ / 需求状态 | ai-proj MCP `list_requirements` / `find_requirement` |
|
||||||
|
| 任务 / todo / 待办 | MCP `list_tasks` / `find_task` |
|
||||||
|
| 提交 / commit / 代码量 / 贡献 | `git log` |
|
||||||
|
| 文件修改 / 变更 | `git log --follow` / `git blame` |
|
||||||
|
| 进度 / 完成率 | MCP `get_project_stats` / `get_requirement_statistics` |
|
||||||
|
| 周报 / 月报 | 组合:MCP + git log + 时间范围过滤 |
|
||||||
|
| 风险 / 停滞 / 延期 | MCP + 时间戳分析 |
|
||||||
|
|
||||||
|
### 2. 执行查询
|
||||||
|
|
||||||
|
**查询前必须声明**:
|
||||||
|
```
|
||||||
|
📊 正在查询数据源:
|
||||||
|
- ai-proj MCP: list_requirements (status=in_progress)
|
||||||
|
- git log: --author=qiudl --since='2026-04-01'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 生成答案(强约束)
|
||||||
|
|
||||||
|
**结构**:
|
||||||
|
```
|
||||||
|
## 直接答案
|
||||||
|
<一句话结论,附数字>
|
||||||
|
|
||||||
|
## 数据来源
|
||||||
|
- <查询语句 1> → <结果摘要>
|
||||||
|
- <查询语句 2> → <结果摘要>
|
||||||
|
|
||||||
|
## 细节
|
||||||
|
<表格 / 列表,每条必有 REQ-XXX 或 commit-sha>
|
||||||
|
|
||||||
|
## ⚠️ 数据缺失(如有)
|
||||||
|
<说明哪些数据无法获取>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 禁止事项
|
||||||
|
|
||||||
|
**不允许的答案:**
|
||||||
|
- "大概 XX 个左右"(必须精确数字或说明"无法精确统计")
|
||||||
|
- "主要在做 XX"(必须列具体 REQ)
|
||||||
|
- "应该是 XX"(推测必须 `⚠️ 推测:` 标记)
|
||||||
|
- "最近进度不错"(必须数据支持)
|
||||||
|
|
||||||
|
**违反时自我纠正**:如果生成答案时发现缺少真实数据引用,重新查询,不要猜测。
|
||||||
|
|
||||||
|
## 输出适配
|
||||||
|
|
||||||
|
根据问题类型自动切换输出格式:
|
||||||
|
|
||||||
|
| 问题类型 | 输出格式 |
|
||||||
|
|---------|---------|
|
||||||
|
| 数量统计 | 数字 + 列表 |
|
||||||
|
| 对比分析 | 表格 |
|
||||||
|
| 进度追踪 | 进度条 / 完成率 |
|
||||||
|
| 历史回顾 | 时间线 |
|
||||||
|
| 生成文档(邮件/报告) | 完整结构化文本 |
|
||||||
|
|
||||||
|
## 与其他 skill 的关系
|
||||||
|
|
||||||
|
| 相关 skill | 分工 |
|
||||||
|
|-----------|------|
|
||||||
|
| `ai-proj` | 执行 MCP 调用(本 skill 的底层) |
|
||||||
|
| `req-workflow` | 需求生命周期管理(本 skill 只读查询) |
|
||||||
|
| `dev-review` | 代码评审(本 skill 提供评审背景数据) |
|
||||||
|
|
||||||
|
## 受众适配(借鉴自 devflow report-generator)
|
||||||
|
|
||||||
|
生成报告时按受众调整:
|
||||||
|
|
||||||
|
| 受众 | 侧重 | 禁用 |
|
||||||
|
|------|------|------|
|
||||||
|
| 高层 | 交付物、里程碑、风险 | 技术细节 |
|
||||||
|
| 客户 | 功能价值、上线时间 | 内部术语 |
|
||||||
|
| 内部团队 | 技术细节、Blocker | - |
|
||||||
|
| 新人 | 背景上下文、术语表 | 假设已知 |
|
||||||
|
|
||||||
|
## Memory 使用规则
|
||||||
|
|
||||||
|
本 skill **仅基于当前数据查询**生成答案。memory 可用于:
|
||||||
|
- 记住用户偏好的输出格式(如"喜欢表格不喜欢列表")
|
||||||
|
- 记住常用的过滤条件(如"默认看 qiudl 的提交")
|
||||||
|
|
||||||
|
**禁止**:用 memory 里的历史数据"缓存"事实类答案(这会导致过时数据)。每次问都要重新查。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- devflow-claude: `plugins/pm/commands/ask.md`
|
||||||
|
- REQ-20260416-0017 P1-9
|
||||||
11
skills-core/pm-risk-plugin/.claude-plugin/plugin.json
Normal file
11
skills-core/pm-risk-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "pm-risk-plugin",
|
||||||
|
"description": "三维度项目风险扫描:需求层/代码层/流程层。当用户说'/risk'、'风险扫描'、'有什么风险'时自动激活",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "pm-risk",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "core"
|
||||||
|
}
|
||||||
154
skills-core/pm-risk-plugin/skills/SKILL.md
Normal file
154
skills-core/pm-risk-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
name: pm-risk
|
||||||
|
description: 三维度项目风险扫描(需求层/代码层/流程层)。当用户说"/risk"、"风险扫描"、"有什么风险"、"哪些需求停滞了"、"分支健康"时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# pm-risk Skill — 三维度风险扫描
|
||||||
|
|
||||||
|
借鉴自 devflow-claude `/pm:risk`。源自 REQ-20260416-0017 P1-10。
|
||||||
|
|
||||||
|
## 命令
|
||||||
|
|
||||||
|
```
|
||||||
|
/risk [--save]
|
||||||
|
```
|
||||||
|
|
||||||
|
- 不加参数:扫描并展示报告
|
||||||
|
- `--save`:保存到思源笔记
|
||||||
|
|
||||||
|
## 扫描维度
|
||||||
|
|
||||||
|
### 维度 1: 需求层
|
||||||
|
|
||||||
|
通过 ai-proj MCP 查询需求状态:
|
||||||
|
|
||||||
|
| 检测项 | 数据源 | 严重 | 警告 | 提示 |
|
||||||
|
|--------|--------|------|------|------|
|
||||||
|
| 需求停滞 | `list_requirements` + `updated_at` | >14 天无更新 | >7 天无更新 | >3 天无更新 |
|
||||||
|
| 草稿滞留 | `list_requirements --status=draft` | >30 天 | >14 天 | >7 天 |
|
||||||
|
| 开发中无提交 | 需求关联分支 + `git log` | >7 天无 commit | >3 天 | - |
|
||||||
|
| 测试中超期 | `list_requirements --delivery_stage=testing` | >14 天 | >7 天 | - |
|
||||||
|
| 已批准未开发 | `list_requirements --status=approved` | >30 天 | >14 天 | - |
|
||||||
|
|
||||||
|
**查询方式**:
|
||||||
|
```
|
||||||
|
mcp__ai-proj__list_requirements(status=in_progress)
|
||||||
|
→ 遍历每个需求的 updated_at,计算距今天数
|
||||||
|
```
|
||||||
|
|
||||||
|
### 维度 2: 代码层
|
||||||
|
|
||||||
|
通过 git 命令分析:
|
||||||
|
|
||||||
|
| 检测项 | 命令 | 严重 | 警告 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| 未合并分支过久 | `git branch -r --no-merged origin/develop` + `git log -1` | >14 天无 commit | >7 天 |
|
||||||
|
| 频繁修复文件 | `git log --diff-filter=M --name-only` 近 30 天 | 同文件 >5 次 fix commit | >3 次 |
|
||||||
|
| 提交频率下降 | 本周 vs 上周 commit 数 | 下降 >70% | 下降 >50% |
|
||||||
|
| 大文件提交 | `git log --diff-filter=A --name-only` | >10MB 文件 | >5MB |
|
||||||
|
|
||||||
|
**查询方式**:
|
||||||
|
```bash
|
||||||
|
# 未合并分支
|
||||||
|
git for-each-ref --sort=-committerdate --format='%(refname:short) %(committerdate:short)' refs/remotes/origin/ | grep -v 'main\|develop\|HEAD'
|
||||||
|
|
||||||
|
# 频繁修复
|
||||||
|
git log --since='30 days ago' --grep='fix' --diff-filter=M --name-only --pretty=format:'' | sort | uniq -c | sort -rn | head -10
|
||||||
|
|
||||||
|
# 提交频率
|
||||||
|
THIS_WEEK=$(git log --since='7 days ago' --oneline | wc -l)
|
||||||
|
LAST_WEEK=$(git log --since='14 days ago' --until='7 days ago' --oneline | wc -l)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 维度 3: 流程层
|
||||||
|
|
||||||
|
通过 MCP + git 交叉分析:
|
||||||
|
|
||||||
|
| 检测项 | 检测方法 | 级别 |
|
||||||
|
|--------|---------|------|
|
||||||
|
| 跳过评审直接开发 | 需求 status 从 draft 直接跳到 in_progress(没有 approved 记录) | 警告 |
|
||||||
|
| PR 无评审直接合并 | Gitea API 查 PR 无 review approve 就 merge | 警告 |
|
||||||
|
| 测试门禁跳过 | delivery_stage 从 dev 跳到 released(没有 testing) | 严重 |
|
||||||
|
| 直接推 main | `git log --first-parent origin/main` 非 merge commit | 严重 |
|
||||||
|
|
||||||
|
## 风险级别定义
|
||||||
|
|
||||||
|
| 级别 | 图标 | 含义 | 响应 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 严重 | 🔴 | 立即关注 | 当天处理 |
|
||||||
|
| 警告 | 🟡 | 近期需处理 | 3 天内处理 |
|
||||||
|
| 提示 | 🔵 | 值得留意 | 下次迭代关注 |
|
||||||
|
|
||||||
|
## 输出模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# 🔍 风险扫描报告
|
||||||
|
|
||||||
|
**扫描时间**: YYYY-MM-DD HH:MM CST
|
||||||
|
**扫描范围**: 项目 ai-proj
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
|
| 维度 | 🔴 严重 | 🟡 警告 | 🔵 提示 |
|
||||||
|
|------|---------|---------|---------|
|
||||||
|
| 需求层 | X | X | X |
|
||||||
|
| 代码层 | X | X | X |
|
||||||
|
| 流程层 | X | X | X |
|
||||||
|
|
||||||
|
## 🔴 严重风险
|
||||||
|
|
||||||
|
### [S1] REQ-20260410-0001 停滞 18 天
|
||||||
|
- **需求**: REQ-20260410-0001 用户积分管理
|
||||||
|
- **状态**: in_progress
|
||||||
|
- **最后更新**: 2026-04-01
|
||||||
|
- **建议**: 联系负责人确认是否阻塞,必要时降级或暂停
|
||||||
|
|
||||||
|
### [S2] ...
|
||||||
|
|
||||||
|
## 🟡 警告
|
||||||
|
|
||||||
|
### [W1] 分支 feat/xxx 14 天未合并
|
||||||
|
- **分支**: feat/old-feature
|
||||||
|
- **最后 commit**: 2026-04-05
|
||||||
|
- **建议**: 确认是否放弃,清理或合并
|
||||||
|
|
||||||
|
## 🔵 提示
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
## ✅ 一切正常的维度
|
||||||
|
|
||||||
|
- (无严重/警告时显示此段)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**下次扫描建议**: 每周一早会前执行 `/risk`
|
||||||
|
```
|
||||||
|
|
||||||
|
## 无风险时的输出
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ 风险扫描完成 — 一切正常
|
||||||
|
|
||||||
|
扫描了 X 个需求、Y 个分支、Z 条提交,未发现风险项。
|
||||||
|
|
||||||
|
下次扫描建议:下周一
|
||||||
|
```
|
||||||
|
|
||||||
|
## 定期执行建议
|
||||||
|
|
||||||
|
- **手动**:每周一早会前 `/risk`
|
||||||
|
- **自动**:可配合 `/loop 7d /risk --save` 定期扫描并保存到思源笔记
|
||||||
|
|
||||||
|
## 与其他 skill 的关系
|
||||||
|
|
||||||
|
| skill | 协作 |
|
||||||
|
|-------|------|
|
||||||
|
| `pm-ask` | risk 发现问题后,用 `/ask` 深入分析 |
|
||||||
|
| `req-workflow` | risk 发现流程违规后,用 `/req` 修正 |
|
||||||
|
| `ai-proj` | 底层 MCP 数据查询 |
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- devflow-claude: `plugins/pm/commands/risk.md`
|
||||||
|
- REQ-20260416-0017 P1-10
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "publish",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "core"
|
||||||
}
|
}
|
||||||
|
|||||||
11
skills-dev/agent-browser-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/agent-browser-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "agent-browser-plugin",
|
||||||
|
"description": "浏览器自动化技能。用于网页交互、E2E冒烟测试、需求验收验证、前端开发验证、截图对比。基于 Vercel agent-browser CLI。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "agent-browser",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
186
skills-dev/agent-browser-plugin/skills/SKILL.md
Normal file
186
skills-dev/agent-browser-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
name: agent-browser
|
||||||
|
description: 浏览器自动化技能。用于网页交互、E2E冒烟测试、需求验收验证、前端开发验证、截图对比。基于 Vercel agent-browser CLI。
|
||||||
|
---
|
||||||
|
|
||||||
|
# agent-browser
|
||||||
|
|
||||||
|
浏览器自动化 CLI,专为 AI Agent 设计。当用户需要与网页交互时使用:导航页面、填写表单、点击按钮、截图、提取数据、测试 Web 应用、自动化浏览器任务。
|
||||||
|
|
||||||
|
当用户提到以下关键词时自动激活:浏览器自动化、打开网页、截图、网页测试、冒烟测试、E2E 验证、页面检查、agent-browser。
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm i -g agent-browser # 安装 CLI
|
||||||
|
agent-browser install # 下载 Chrome
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 核心工作流(snapshot-ref 模式)
|
||||||
|
|
||||||
|
1. **导航** 到 URL
|
||||||
|
2. **snapshot** 获取可交互元素(返回 `@e1`, `@e2` 等引用)
|
||||||
|
3. **交互** 使用元素引用
|
||||||
|
4. **重新 snapshot** DOM 变化后刷新引用
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser open https://example.com/form
|
||||||
|
agent-browser snapshot -i
|
||||||
|
agent-browser fill @e1 "user@example.com"
|
||||||
|
agent-browser click @e3
|
||||||
|
agent-browser wait --load networkidle
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 命令参考
|
||||||
|
|
||||||
|
### 导航与控制
|
||||||
|
- `open <url>` – 打开网页
|
||||||
|
- `goto <url>` – 在会话内跳转
|
||||||
|
- `close` – 关闭浏览器
|
||||||
|
- `wait <condition>` – 等待元素/网络/JS 条件
|
||||||
|
|
||||||
|
### 页面检查
|
||||||
|
- `snapshot -i` – 获取可交互元素及引用(推荐)
|
||||||
|
- `snapshot` – 完整无障碍树
|
||||||
|
- `get text|html|value @e1` – 提取元素内容
|
||||||
|
- `get title|url` – 获取页面标题/URL
|
||||||
|
- `screenshot [path]` – 截图
|
||||||
|
- `screenshot --annotate` – 带元素标注的截图
|
||||||
|
|
||||||
|
### 交互操作
|
||||||
|
- `click @e1` / `dblclick @e1` – 单击/双击
|
||||||
|
- `fill @e1 "text"` – 清空并输入
|
||||||
|
- `type @e1 "text"` – 追加输入
|
||||||
|
- `select @e1 "option"` – 下拉选择
|
||||||
|
- `check @e1` – 勾选/取消勾选
|
||||||
|
- `press Enter` – 按键
|
||||||
|
- `hover @e1` – 悬停
|
||||||
|
- `scroll down 500` – 滚动
|
||||||
|
- `upload @e1 /path` – 上传文件
|
||||||
|
- `drag @e1 @e2` – 拖放
|
||||||
|
|
||||||
|
### 高级功能
|
||||||
|
- `eval 'code'` – 执行 JavaScript
|
||||||
|
- `diff snapshot` – 对比前后页面状态
|
||||||
|
- `set viewport 1920 1080` – 设置视口
|
||||||
|
- `set device "iPhone 14"` – 移动端模拟
|
||||||
|
- `find role <role> click --name "Label"` – 语义定位器
|
||||||
|
- `network mock <pattern> --body '...'` – 网络请求模拟
|
||||||
|
- `cookies get/set/clear` – Cookie 管理
|
||||||
|
- `storage get/set/clear` – localStorage 管理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 认证与会话持久化
|
||||||
|
|
||||||
|
### Auth Vault(推荐)
|
||||||
|
```bash
|
||||||
|
agent-browser auth save myapp
|
||||||
|
agent-browser open https://myapp.com --auth myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 持久化 Profile
|
||||||
|
```bash
|
||||||
|
agent-browser open https://site.com --profile ~/.myapp-profile
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命名会话
|
||||||
|
```bash
|
||||||
|
agent-browser open https://site.com --session-name myapp
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态文件
|
||||||
|
```bash
|
||||||
|
agent-browser open https://site.com --state ./auth.json --save-state
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 命令链式调用
|
||||||
|
|
||||||
|
不需要中间输出时用 `&&` 串联:
|
||||||
|
```bash
|
||||||
|
agent-browser open https://example.com && \
|
||||||
|
agent-browser wait --load networkidle && \
|
||||||
|
agent-browser screenshot page.png
|
||||||
|
```
|
||||||
|
|
||||||
|
需要解析输出(如 snapshot 获取 ref)时分开执行。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与其他技能结合
|
||||||
|
|
||||||
|
### 结合 dev-test(E2E 冒烟测试)
|
||||||
|
|
||||||
|
在 dev-test 的 Gate 4(E2E 冒烟测试)中使用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 打开本地前端
|
||||||
|
agent-browser open http://localhost:3000 --session-name e2e-test
|
||||||
|
|
||||||
|
# 2. 验证页面加载
|
||||||
|
agent-browser snapshot -i
|
||||||
|
agent-browser screenshot /tmp/e2e-home.png
|
||||||
|
|
||||||
|
# 3. 测试登录流程
|
||||||
|
agent-browser fill @e1 "admin@example.com"
|
||||||
|
agent-browser fill @e2 "password"
|
||||||
|
agent-browser click @e3
|
||||||
|
agent-browser wait --load networkidle
|
||||||
|
|
||||||
|
# 4. 验证登录成功
|
||||||
|
agent-browser get title # 应包含 "Dashboard"
|
||||||
|
agent-browser screenshot /tmp/e2e-dashboard.png
|
||||||
|
|
||||||
|
# 5. 清理
|
||||||
|
agent-browser close
|
||||||
|
```
|
||||||
|
|
||||||
|
### 结合 req(需求验收验证)
|
||||||
|
|
||||||
|
PRD 评审后实际操作验证需求是否实现:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 根据 PRD 验收标准逐项检查
|
||||||
|
agent-browser open http://localhost:3000/feature-page
|
||||||
|
agent-browser snapshot -i
|
||||||
|
# 按验收标准操作对应元素...
|
||||||
|
agent-browser screenshot /tmp/req-verify.png
|
||||||
|
```
|
||||||
|
|
||||||
|
### 结合 dev-coding(前端开发即时验证)
|
||||||
|
|
||||||
|
开发完组件后立即打开页面验证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser open http://localhost:3000/new-page
|
||||||
|
agent-browser snapshot -i # 检查渲染的元素
|
||||||
|
agent-browser screenshot --annotate /tmp/dev-check.png # 带标注截图
|
||||||
|
```
|
||||||
|
|
||||||
|
### 结合 ops-tools(部署后验证)
|
||||||
|
|
||||||
|
部署后快速检查页面是否正常:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
agent-browser open https://ai.pipexerp.com
|
||||||
|
agent-browser wait --load networkidle
|
||||||
|
agent-browser get title
|
||||||
|
agent-browser screenshot /tmp/deploy-check.png
|
||||||
|
agent-browser close
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- **Ref 生命周期**:导航或 DOM 变化后引用失效,必须重新 snapshot
|
||||||
|
- **超时**:默认 25 秒,通过 `AGENT_BROWSER_DEFAULT_TIMEOUT` 环境变量调整
|
||||||
|
- **会话隔离**:用 `--session-name` 实现并行自动化
|
||||||
|
- **安全**:支持域名白名单、操作策略、内容边界
|
||||||
|
- **输出格式**:用 `--json` 获取机器可读输出
|
||||||
11
skills-dev/agent-swarm-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/agent-swarm-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"install_name": "agent-swarm",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
406
skills-dev/agent-swarm-plugin/skills/SKILL.md
Normal file
406
skills-dev/agent-swarm-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,406 @@
|
|||||||
|
---
|
||||||
|
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)
|
||||||
11
skills-dev/ai-chat-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/ai-chat-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "ai-chat-plugin",
|
||||||
|
"description": "AI Chat 测试与管理。发送消息测试 AI Chat 工具调用链路,管理工具开关和 Provider 配置,支持 local/staging 环境切换。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "ai-chat",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
537
skills-dev/ai-chat-plugin/skills/SKILL.md
Normal file
537
skills-dev/ai-chat-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,537 @@
|
|||||||
|
---
|
||||||
|
name: ai-chat
|
||||||
|
description: AI Chat 测试与管理。发送消息测试 AI Chat 工具调用链路,管理工具开关和 Provider 配置,支持 local/staging 环境切换。
|
||||||
|
arguments: <subcommand> [args]
|
||||||
|
---
|
||||||
|
|
||||||
|
# AI Chat Skill
|
||||||
|
|
||||||
|
测试和管理 Coolbuy PaaS AI Chat 服务的 Claude Code skill。
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| 命令 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `/ai-chat send <message>` | 发送消息到 AI Chat,实时显示工具调用 + AI 回复 |
|
||||||
|
| `/ai-chat env [local\|staging]` | 切换/查看目标环境(默认 local) |
|
||||||
|
| `/ai-chat tools [category]` | 列出当前环境已注册的工具 |
|
||||||
|
| `/ai-chat config` | 查看 AI 配置(Provider、工具开关等) |
|
||||||
|
| `/ai-chat history` | 显示本次会话的历史消息 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Config
|
||||||
|
|
||||||
|
两套环境,通过 `/ai-chat env` 切换:
|
||||||
|
|
||||||
|
| 环境 | Auth URL | AI URL | 登录账号 |
|
||||||
|
|------|----------|--------|----------|
|
||||||
|
| **local** (默认) | `http://localhost:7089` | `http://localhost:7092` | lining_admin / admin123 |
|
||||||
|
| **staging** | `http://39.105.150.219:7089` | `http://39.105.150.219:7092` | lining_admin / admin123 |
|
||||||
|
|
||||||
|
### 状态文件
|
||||||
|
|
||||||
|
环境状态保存在 `/tmp/ai-chat-state.json`,格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"env": "local",
|
||||||
|
"token": "eyJ...",
|
||||||
|
"token_env": "local",
|
||||||
|
"history": []
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
### /ai-chat env
|
||||||
|
|
||||||
|
**切换或查看当前环境。**
|
||||||
|
|
||||||
|
用法:
|
||||||
|
- `/ai-chat env` — 显示当前环境
|
||||||
|
- `/ai-chat env local` — 切换到本地环境
|
||||||
|
- `/ai-chat env staging` — 切换到 staging 环境
|
||||||
|
|
||||||
|
实现步骤:
|
||||||
|
|
||||||
|
1. 读取 `/tmp/ai-chat-state.json`(不存在则默认 `{"env":"local","history":[]}`)
|
||||||
|
2. 如果提供了参数,更新 `env` 字段并清空 `token`(环境变了 token 失效)
|
||||||
|
3. 写回状态文件
|
||||||
|
4. 输出当前环境信息表格
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### /ai-chat send
|
||||||
|
|
||||||
|
**发送消息到 AI Chat 并实时显示流式响应。**
|
||||||
|
|
||||||
|
用法:`/ai-chat send <message>`
|
||||||
|
|
||||||
|
实现步骤:
|
||||||
|
|
||||||
|
#### Step 1: 读取状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 读取状态文件
|
||||||
|
cat /tmp/ai-chat-state.json 2>/dev/null || echo '{"env":"local","history":[]}'
|
||||||
|
```
|
||||||
|
|
||||||
|
确定环境变量:
|
||||||
|
- **local**: `AUTH_URL=http://localhost:7089`, `AI_URL=http://localhost:7092`
|
||||||
|
- **staging**: `AUTH_URL=http://39.105.150.219:7089`, `AI_URL=http://39.105.150.219:7092`
|
||||||
|
|
||||||
|
#### Step 2: 获取 Token
|
||||||
|
|
||||||
|
如果状态文件中没有 token 或 `token_env` 与当前 `env` 不匹配,执行登录:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST "$AUTH_URL/api/v1/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"lining_admin","password":"admin123"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
从响应中提取 `access_token`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 响应格式
|
||||||
|
# {"access_token":"eyJ...","refresh_token":"...","token_type":"Bearer","expires_in":7200,"user_info":{...}}
|
||||||
|
```
|
||||||
|
|
||||||
|
用 `python3 -c "import json,sys; print(json.load(sys.stdin)['access_token'])"` 提取 token。
|
||||||
|
|
||||||
|
将 token 和 token_env 保存到状态文件。
|
||||||
|
|
||||||
|
#### Step 3: 构造请求并发送 SSE 流
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -N -X POST "$AI_URL/api/v1/ai/chat/stream" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"message\":\"$MSG\",\"history\":$HISTORY}" 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
**重要**: `history` 字段传入之前的会话历史(从状态文件读取),实现多轮对话。
|
||||||
|
|
||||||
|
#### Step 4: 解析 SSE 事件
|
||||||
|
|
||||||
|
用 Python 脚本解析 SSE 流(比 bash while read 更可靠):
|
||||||
|
|
||||||
|
```python
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""解析 AI Chat SSE 流并格式化输出"""
|
||||||
|
import sys, json
|
||||||
|
|
||||||
|
full_content = ""
|
||||||
|
tool_calls = []
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
if not line.startswith("data:"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
data_str = line[5:].strip()
|
||||||
|
if not data_str:
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
event = json.loads(data_str)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
evt_type = event.get("type", "")
|
||||||
|
|
||||||
|
if evt_type == "content":
|
||||||
|
chunk = event.get("content", "")
|
||||||
|
full_content += chunk
|
||||||
|
# 实时输出内容片段
|
||||||
|
sys.stdout.write(chunk)
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
elif evt_type == "tool_call":
|
||||||
|
tc = event.get("tool_call", {})
|
||||||
|
tool_name = tc.get("name", "unknown")
|
||||||
|
tool_args = tc.get("arguments", {})
|
||||||
|
tool_id = tc.get("id", "")
|
||||||
|
tool_calls.append({"id": tool_id, "name": tool_name})
|
||||||
|
# 输出工具调用标记
|
||||||
|
args_str = json.dumps(tool_args, ensure_ascii=False)
|
||||||
|
if len(args_str) > 200:
|
||||||
|
args_str = args_str[:200] + "..."
|
||||||
|
print(f"\n🔧 Tool Call: {tool_name}", file=sys.stderr)
|
||||||
|
print(f" Args: {args_str}", file=sys.stderr)
|
||||||
|
|
||||||
|
elif evt_type == "tool_result":
|
||||||
|
tr = event.get("tool_result", {})
|
||||||
|
tool_name = tr.get("name", "unknown")
|
||||||
|
content = tr.get("content", "")
|
||||||
|
is_error = tr.get("is_error", False)
|
||||||
|
# 截断长结果
|
||||||
|
if len(content) > 500:
|
||||||
|
content = content[:500] + f"... ({len(content)} chars total)"
|
||||||
|
status = "❌ Error" if is_error else "✅ Result"
|
||||||
|
print(f" {status} [{tool_name}]: {content}", file=sys.stderr)
|
||||||
|
|
||||||
|
elif evt_type == "done":
|
||||||
|
usage = event.get("usage") or {}
|
||||||
|
prompt_t = usage.get("prompt_tokens", 0)
|
||||||
|
completion_t = usage.get("completion_tokens", 0)
|
||||||
|
total_t = usage.get("total_tokens", 0)
|
||||||
|
print(f"\n\n--- Done ---", file=sys.stderr)
|
||||||
|
if total_t > 0:
|
||||||
|
print(f"Tokens: {prompt_t} prompt + {completion_t} completion = {total_t} total", file=sys.stderr)
|
||||||
|
if tool_calls:
|
||||||
|
print(f"Tool calls: {len(tool_calls)} ({', '.join(tc['name'] for tc in tool_calls)})", file=sys.stderr)
|
||||||
|
|
||||||
|
elif evt_type == "error":
|
||||||
|
err = event.get("error", "unknown error")
|
||||||
|
print(f"\n❌ Error: {err}", file=sys.stderr)
|
||||||
|
|
||||||
|
# 输出换行
|
||||||
|
print()
|
||||||
|
# 将 full_content 输出到 fd 3 用于状态更新(如果 fd 3 打开)
|
||||||
|
try:
|
||||||
|
with open("/tmp/ai-chat-response.txt", "w") as f:
|
||||||
|
f.write(full_content)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Step 5: 更新会话历史
|
||||||
|
|
||||||
|
发送完成后,将用户消息和 AI 回复追加到状态文件的 `history` 数组中:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{"role": "user", "content": "<用户消息>"},
|
||||||
|
{"role": "assistant", "content": "<AI 完整回复>"}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 完整 bash 执行流程
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
MSG="$1"
|
||||||
|
STATE_FILE="/tmp/ai-chat-state.json"
|
||||||
|
|
||||||
|
# 1. 读取状态
|
||||||
|
if [ -f "$STATE_FILE" ]; then
|
||||||
|
STATE=$(cat "$STATE_FILE")
|
||||||
|
else
|
||||||
|
STATE='{"env":"local","history":[]}'
|
||||||
|
fi
|
||||||
|
|
||||||
|
ENV=$(echo "$STATE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('env','local'))")
|
||||||
|
TOKEN=$(echo "$STATE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('token',''))")
|
||||||
|
TOKEN_ENV=$(echo "$STATE" | python3 -c "import json,sys; print(json.load(sys.stdin).get('token_env',''))")
|
||||||
|
HISTORY=$(echo "$STATE" | python3 -c "import json,sys; print(json.dumps(json.load(sys.stdin).get('history',[])))")
|
||||||
|
|
||||||
|
# 2. 确定 URL
|
||||||
|
if [ "$ENV" = "staging" ]; then
|
||||||
|
AUTH_URL="http://39.105.150.219:7089"
|
||||||
|
AI_URL="http://39.105.150.219:7092"
|
||||||
|
else
|
||||||
|
AUTH_URL="http://localhost:7089"
|
||||||
|
AI_URL="http://localhost:7092"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. 获取 token(如果需要)
|
||||||
|
if [ -z "$TOKEN" ] || [ "$TOKEN_ENV" != "$ENV" ]; then
|
||||||
|
echo "🔐 Logging in to $ENV environment..."
|
||||||
|
LOGIN_RESP=$(curl -s -X POST "$AUTH_URL/api/v1/auth/login" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"username":"lining_admin","password":"admin123"}')
|
||||||
|
|
||||||
|
TOKEN=$(echo "$LOGIN_RESP" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('access_token',d.get('data',{}).get('access_token','')))")
|
||||||
|
|
||||||
|
if [ -z "$TOKEN" ]; then
|
||||||
|
echo "❌ Login failed: $LOGIN_RESP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "✅ Login successful"
|
||||||
|
|
||||||
|
# 更新状态中的 token
|
||||||
|
STATE=$(echo "$STATE" | python3 -c "
|
||||||
|
import json,sys
|
||||||
|
s=json.load(sys.stdin)
|
||||||
|
s['token']='$TOKEN'
|
||||||
|
s['token_env']='$ENV'
|
||||||
|
print(json.dumps(s,ensure_ascii=False))
|
||||||
|
")
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. 发送 SSE 请求并解析
|
||||||
|
echo ""
|
||||||
|
echo "📤 Sending to $ENV: $MSG"
|
||||||
|
echo "---"
|
||||||
|
|
||||||
|
# 转义消息中的特殊字符
|
||||||
|
MSG_JSON=$(python3 -c "import json; print(json.dumps('$MSG'))")
|
||||||
|
|
||||||
|
curl -s -N -X POST "$AI_URL/api/v1/ai/chat/stream" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"message\":$MSG_JSON,\"history\":$HISTORY}" 2>&1 | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
|
||||||
|
full_content = ''
|
||||||
|
tool_calls = []
|
||||||
|
|
||||||
|
for line in sys.stdin:
|
||||||
|
line = line.strip()
|
||||||
|
if not line.startswith('data:'):
|
||||||
|
continue
|
||||||
|
data_str = line[5:].strip()
|
||||||
|
if not data_str:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
event = json.loads(data_str)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
continue
|
||||||
|
|
||||||
|
evt_type = event.get('type', '')
|
||||||
|
|
||||||
|
if evt_type == 'content':
|
||||||
|
chunk = event.get('content', '')
|
||||||
|
full_content += chunk
|
||||||
|
sys.stdout.write(chunk)
|
||||||
|
sys.stdout.flush()
|
||||||
|
elif evt_type == 'tool_call':
|
||||||
|
tc = event.get('tool_call', {})
|
||||||
|
tool_name = tc.get('name', 'unknown')
|
||||||
|
tool_args = tc.get('arguments', {})
|
||||||
|
tool_calls.append({'name': tool_name})
|
||||||
|
args_str = json.dumps(tool_args, ensure_ascii=False)
|
||||||
|
if len(args_str) > 200:
|
||||||
|
args_str = args_str[:200] + '...'
|
||||||
|
print(f'\n🔧 Tool Call: {tool_name}', file=sys.stderr)
|
||||||
|
print(f' Args: {args_str}', file=sys.stderr)
|
||||||
|
elif evt_type == 'tool_result':
|
||||||
|
tr = event.get('tool_result', {})
|
||||||
|
tool_name = tr.get('name', 'unknown')
|
||||||
|
content = tr.get('content', '')
|
||||||
|
is_error = tr.get('is_error', False)
|
||||||
|
if len(content) > 500:
|
||||||
|
content = content[:500] + f'... ({len(content)} chars total)'
|
||||||
|
status = '❌' if is_error else '✅'
|
||||||
|
print(f' {status} [{tool_name}]: {content}', file=sys.stderr)
|
||||||
|
elif evt_type == 'done':
|
||||||
|
usage = event.get('usage') or {}
|
||||||
|
pt = usage.get('prompt_tokens', 0)
|
||||||
|
ct = usage.get('completion_tokens', 0)
|
||||||
|
tt = usage.get('total_tokens', 0)
|
||||||
|
print(f'\n\n--- Done ---', file=sys.stderr)
|
||||||
|
if tt > 0:
|
||||||
|
print(f'Tokens: {pt} prompt + {ct} completion = {tt} total', file=sys.stderr)
|
||||||
|
if tool_calls:
|
||||||
|
names = ', '.join(tc['name'] for tc in tool_calls)
|
||||||
|
print(f'Tool calls: {len(tool_calls)} ({names})', file=sys.stderr)
|
||||||
|
elif evt_type == 'error':
|
||||||
|
err = event.get('error', 'unknown error')
|
||||||
|
print(f'\n❌ Error: {err}', file=sys.stderr)
|
||||||
|
|
||||||
|
print()
|
||||||
|
with open('/tmp/ai-chat-response.txt', 'w') as f:
|
||||||
|
f.write(full_content)
|
||||||
|
"
|
||||||
|
|
||||||
|
# 5. 更新历史
|
||||||
|
RESPONSE=$(cat /tmp/ai-chat-response.txt 2>/dev/null || echo "")
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
state_file = '$STATE_FILE'
|
||||||
|
try:
|
||||||
|
with open(state_file) as f:
|
||||||
|
state = json.load(f)
|
||||||
|
except:
|
||||||
|
state = {'env': 'local', 'history': []}
|
||||||
|
|
||||||
|
msg = $MSG_JSON
|
||||||
|
resp = '''$RESPONSE'''
|
||||||
|
|
||||||
|
state['history'].append({'role': 'user', 'content': msg})
|
||||||
|
if resp:
|
||||||
|
state['history'].append({'role': 'assistant', 'content': resp})
|
||||||
|
state['token'] = '$TOKEN'
|
||||||
|
state['token_env'] = '$ENV'
|
||||||
|
|
||||||
|
with open(state_file, 'w') as f:
|
||||||
|
json.dump(state, f, ensure_ascii=False, indent=2)
|
||||||
|
"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "💬 History: $(echo "$STATE" | python3 -c "import json,sys; h=json.load(sys.stdin).get('history',[]); print(len(h)//2 + 1)") messages in session"
|
||||||
|
```
|
||||||
|
|
||||||
|
**重要注意事项**:
|
||||||
|
- 上面的脚本是逻辑参考,**不要**原样执行。Claude Code 应按步骤逐一执行 bash 命令。
|
||||||
|
- 消息中的引号和特殊字符需要用 python3 json.dumps 转义。
|
||||||
|
- 如果 token 过期(401 响应),自动重新登录。
|
||||||
|
- SSE 超时设置 `--max-time 120`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### /ai-chat tools
|
||||||
|
|
||||||
|
**列出当前环境已注册的工具。**
|
||||||
|
|
||||||
|
用法:
|
||||||
|
- `/ai-chat tools` — 列出所有工具
|
||||||
|
- `/ai-chat tools <category>` — 按分类过滤
|
||||||
|
|
||||||
|
实现步骤:
|
||||||
|
|
||||||
|
1. 读取状态获取环境和 token(必要时先登录)
|
||||||
|
2. 发送请求:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 列出所有工具
|
||||||
|
curl -s "$AI_URL/api/v1/ai/tools" \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
|
||||||
|
# 按分类过滤
|
||||||
|
curl -s "$AI_URL/api/v1/ai/tools?category=order" \
|
||||||
|
-H "Authorization: Bearer $TOKEN"
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 响应格式:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"tools": [
|
||||||
|
{
|
||||||
|
"name": "list_orders",
|
||||||
|
"description": "查询订单列表",
|
||||||
|
"category": "order",
|
||||||
|
"enabled": true,
|
||||||
|
"parameters": {...}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. 按 category 分组,输出表格:
|
||||||
|
|
||||||
|
```
|
||||||
|
📋 AI Tools (142 total)
|
||||||
|
|
||||||
|
order (15 tools)
|
||||||
|
├── list_orders 查询订单列表
|
||||||
|
├── get_order_detail 获取订单详情
|
||||||
|
└── ...
|
||||||
|
|
||||||
|
product (12 tools)
|
||||||
|
├── list_products 查询商品列表
|
||||||
|
└── ...
|
||||||
|
```
|
||||||
|
|
||||||
|
已知工具分类:order, product, sku, inventory, task, brand, requirement, customer, dashboard, distribution, finance, discount, channel, approval, organization, feature_gap
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### /ai-chat config
|
||||||
|
|
||||||
|
**查看 AI 服务配置。**
|
||||||
|
|
||||||
|
实现步骤:
|
||||||
|
|
||||||
|
1. 根据当前环境读取对应配置文件:
|
||||||
|
- **local**: 读取 `ai-service/api/etc/ai-api-local.yaml`
|
||||||
|
- **staging**: SSH 到 staging 读取 `/opt/coolbuy/configs/ai-api.yaml`
|
||||||
|
|
||||||
|
2. 显示关键配置项:
|
||||||
|
- AI Provider 和 Model
|
||||||
|
- 各工具分类的开关状态
|
||||||
|
- API 端口
|
||||||
|
|
||||||
|
3. 输出格式:
|
||||||
|
|
||||||
|
```
|
||||||
|
⚙️ AI Config (local)
|
||||||
|
|
||||||
|
Provider: deepseek
|
||||||
|
Model: deepseek-chat
|
||||||
|
Port: 7092
|
||||||
|
|
||||||
|
Tool Categories:
|
||||||
|
✅ order ✅ product ✅ sku
|
||||||
|
✅ inventory ✅ task ✅ brand
|
||||||
|
✅ requirement ✅ customer ✅ dashboard
|
||||||
|
✅ distribution ✅ finance ✅ discount
|
||||||
|
✅ channel ✅ approval ✅ organization
|
||||||
|
✅ feature_gap
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### /ai-chat history
|
||||||
|
|
||||||
|
**显示当前会话的历史消息。**
|
||||||
|
|
||||||
|
实现步骤:
|
||||||
|
|
||||||
|
1. 读取 `/tmp/ai-chat-state.json` 的 `history` 数组
|
||||||
|
2. 按时间顺序显示:
|
||||||
|
|
||||||
|
```
|
||||||
|
💬 Chat History (3 messages)
|
||||||
|
|
||||||
|
[1] 👤 User: 你好
|
||||||
|
🤖 AI: 你好!我是 AI 助手...
|
||||||
|
|
||||||
|
[2] 👤 User: 搜索组织架构找到大客户部
|
||||||
|
🤖 AI: 我来帮你搜索... (used: search_organizations)
|
||||||
|
|
||||||
|
[3] 👤 User: 最近的订单
|
||||||
|
🤖 AI: 以下是最近的订单列表...
|
||||||
|
```
|
||||||
|
|
||||||
|
3. 如果需要清空历史:`/ai-chat history clear`
|
||||||
|
- 删除状态文件中的 history 数组,重置为空
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SSE Event Format Reference
|
||||||
|
|
||||||
|
AI Chat SSE 流使用 `event: message` + `data: {json}` 格式:
|
||||||
|
|
||||||
|
| type | 数据字段 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `content` | `content: "<text>"` | 增量文本内容 |
|
||||||
|
| `tool_call` | `tool_call: {id, name, arguments}` | AI 请求调用工具 |
|
||||||
|
| `tool_result` | `tool_result: {tool_call_id, name, content, is_error}` | 工具执行结果 |
|
||||||
|
| `done` | `usage: {prompt_tokens, completion_tokens, total_tokens}` (可能为 null), `finish_reason` | 流结束 |
|
||||||
|
| `error` | `error: "<message>"` | 错误 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Token 过期 (401)
|
||||||
|
如果请求返回 401,删除缓存 token 重新登录:
|
||||||
|
```bash
|
||||||
|
# 清除 token 强制重新登录
|
||||||
|
python3 -c "
|
||||||
|
import json
|
||||||
|
with open('/tmp/ai-chat-state.json') as f: s=json.load(f)
|
||||||
|
s['token']=''
|
||||||
|
with open('/tmp/ai-chat-state.json','w') as f: json.dump(s,f)
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 连接失败
|
||||||
|
- **local**: 确认本地服务已启动 (`./scripts/start_dev.sh`)
|
||||||
|
- **staging**: 确认 staging 服务运行中 (`ssh coolbuy-staging "docker ps | grep ai-service"`)
|
||||||
|
|
||||||
|
### SSE 流中断
|
||||||
|
- 检查 AI 服务日志
|
||||||
|
- local: 查看终端输出
|
||||||
|
- staging: `ssh coolbuy-staging "docker logs coolbuy-ai-service --tail 50"`
|
||||||
|
|
||||||
|
### 消息中包含特殊字符
|
||||||
|
务必用 `python3 -c "import json; print(json.dumps(msg))"` 转义消息内容,避免 JSON 解析失败。
|
||||||
11
skills-dev/db-migration-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/db-migration-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "db-migration-plugin",
|
||||||
|
"description": "数据库变更方案插件。Migration 脚本生成、数据迁移策略、回滚方案。挂载在 design 阶段,涉及数据库变更时激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "db-migration",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
105
skills-dev/db-migration-plugin/skills/SKILL.md
Normal file
105
skills-dev/db-migration-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
name: db-migration
|
||||||
|
description: 数据库变更方案插件。Migration 脚本生成、数据迁移策略、回滚方案。挂载在 design 阶段,涉及数据库变更时由 req-design 推荐激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 数据库变更方案插件 (db-migration)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
当需求涉及数据库结构变更时使用,确保变更安全、可回滚。
|
||||||
|
|
||||||
|
**触发条件**:
|
||||||
|
- 新增/修改/删除表或字段
|
||||||
|
- 数据迁移(旧数据转换)
|
||||||
|
- 索引优化
|
||||||
|
|
||||||
|
## Migration 规范
|
||||||
|
|
||||||
|
### 文件命名
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/migrations/YYYYMMDDHHMMSS_description.up.sql # 正向迁移
|
||||||
|
backend/migrations/YYYYMMDDHHMMSS_description.down.sql # 回滚迁移
|
||||||
|
```
|
||||||
|
|
||||||
|
### 安全规则
|
||||||
|
|
||||||
|
| 操作 | 风险等级 | 注意事项 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| ADD COLUMN (nullable) | 低 | 安全,无锁表 |
|
||||||
|
| ADD COLUMN (NOT NULL + DEFAULT) | 中 | PG 12+ 不锁表,旧版本锁表 |
|
||||||
|
| DROP COLUMN | 高 | 确认无代码引用,先标记废弃 |
|
||||||
|
| ALTER COLUMN TYPE | 高 | 可能锁表,大表慎用 |
|
||||||
|
| ADD INDEX | 中 | 使用 CONCURRENTLY 避免锁表 |
|
||||||
|
| DROP TABLE | 极高 | 必须确认无依赖 |
|
||||||
|
|
||||||
|
### Migration 模板
|
||||||
|
|
||||||
|
**新增表**:
|
||||||
|
```sql
|
||||||
|
-- up.sql
|
||||||
|
CREATE TABLE IF NOT EXISTS xxx (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
tenant_id BIGINT NOT NULL,
|
||||||
|
-- 业务字段
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
status VARCHAR(50) NOT NULL DEFAULT 'active',
|
||||||
|
-- 审计字段
|
||||||
|
created_by BIGINT,
|
||||||
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||||
|
deleted_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_xxx_tenant_id ON xxx(tenant_id);
|
||||||
|
CREATE INDEX idx_xxx_deleted_at ON xxx(deleted_at);
|
||||||
|
|
||||||
|
-- down.sql
|
||||||
|
DROP TABLE IF EXISTS xxx;
|
||||||
|
```
|
||||||
|
|
||||||
|
**新增字段**:
|
||||||
|
```sql
|
||||||
|
-- up.sql
|
||||||
|
ALTER TABLE xxx ADD COLUMN yyy VARCHAR(255);
|
||||||
|
-- 如果需要索引
|
||||||
|
CREATE INDEX CONCURRENTLY idx_xxx_yyy ON xxx(yyy);
|
||||||
|
|
||||||
|
-- down.sql
|
||||||
|
DROP INDEX IF EXISTS idx_xxx_yyy;
|
||||||
|
ALTER TABLE xxx DROP COLUMN IF EXISTS yyy;
|
||||||
|
```
|
||||||
|
|
||||||
|
**数据迁移**:
|
||||||
|
```sql
|
||||||
|
-- up.sql
|
||||||
|
-- 1. 先添加新字段
|
||||||
|
ALTER TABLE xxx ADD COLUMN new_field VARCHAR(255);
|
||||||
|
|
||||||
|
-- 2. 迁移数据
|
||||||
|
UPDATE xxx SET new_field = old_field WHERE new_field IS NULL;
|
||||||
|
|
||||||
|
-- 3. 添加约束(数据迁移完成后)
|
||||||
|
ALTER TABLE xxx ALTER COLUMN new_field SET NOT NULL;
|
||||||
|
|
||||||
|
-- down.sql
|
||||||
|
ALTER TABLE xxx ALTER COLUMN new_field DROP NOT NULL;
|
||||||
|
ALTER TABLE xxx DROP COLUMN IF EXISTS new_field;
|
||||||
|
```
|
||||||
|
|
||||||
|
## 大表变更策略
|
||||||
|
|
||||||
|
当表数据量 > 100 万行时:
|
||||||
|
|
||||||
|
1. **添加索引**:必须使用 `CREATE INDEX CONCURRENTLY`
|
||||||
|
2. **修改字段类型**:分步执行(新增列→迁移数据→切换引用→删除旧列)
|
||||||
|
3. **添加 NOT NULL**:先添加 DEFAULT,再 SET NOT NULL
|
||||||
|
4. **数据迁移**:分批处理,每批 1000-10000 行
|
||||||
|
|
||||||
|
## 回滚检查
|
||||||
|
|
||||||
|
每个 Migration 必须有可执行的 down.sql:
|
||||||
|
- [ ] down.sql 存在且语法正确
|
||||||
|
- [ ] down.sql 可以完全撤销 up.sql 的变更
|
||||||
|
- [ ] down.sql 不会丢失业务数据(除非是 DROP TABLE)
|
||||||
11
skills-dev/defect-analysis-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/defect-analysis-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "defect-analysis-plugin",
|
||||||
|
"description": "系统性设计缺陷分析。对需求方案/代码架构进行多维度检查,发现隐藏的技术风险和设计漏洞。当用户提到缺陷检查、方案审查、设计审计时自动激活。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "defect-analysis",
|
||||||
|
"install_type": "command",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
159
skills-dev/defect-analysis-plugin/skills/SKILL.md
Normal file
159
skills-dev/defect-analysis-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
---
|
||||||
|
name: defect-analysis
|
||||||
|
description: 系统性设计缺陷分析。对需求方案/代码架构进行多维度检查,发现隐藏的技术风险和设计漏洞。当用户提到缺陷检查、方案审查、设计审计时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 设计缺陷分析 Skill(通用版)
|
||||||
|
|
||||||
|
你是资深架构审计师。对给定的需求方案或代码实现,执行系统性的多维度缺陷检查,**反复迭代直到收敛**(连续一轮无新发现即停止)。
|
||||||
|
|
||||||
|
## 检查维度(按严重度排序)
|
||||||
|
|
||||||
|
### 1. 致命级:架构不可行
|
||||||
|
- **异步/同步冲突**:异步操作被当作同步使用?长时间操作阻塞了请求?
|
||||||
|
- **框架限制**:v-html 无法绑定事件、WebSocket/SSE 超时、API 轮数限制
|
||||||
|
- **数据格式不匹配**:前后端约定的 ID 格式/字段名/序列化方式不一致?
|
||||||
|
- **循环依赖**:模块 A 内部调 B,B 又依赖 A 的结果?嵌套调用超时?
|
||||||
|
|
||||||
|
### 2. 高级:运行时崩溃
|
||||||
|
- **资源生命周期**:DB session/连接/文件句柄在回调中过期?
|
||||||
|
- **并发冲突**:多个异步操作同时修改共享状态?用户操作和自动流程冲突?
|
||||||
|
- **超时/死锁**:链式调用累计超时?轮询无限等待?重试风暴?
|
||||||
|
- **内存泄漏**:大数据未释放?事件监听器未清理?闭包持有旧引用?
|
||||||
|
|
||||||
|
### 3. 中级:数据错误
|
||||||
|
- **状态覆盖**:多次回调覆盖同一变量?最后一次覆盖前面的?
|
||||||
|
- **上下文丢失**:对话/会话截断导致关键信息丢失?
|
||||||
|
- **参数传递断裂**:A 组件的输出无法完整传递给 B 组件?
|
||||||
|
- **类型不安全**:JSON.parse 可能失败?nullable 字段未处理?双重编码?
|
||||||
|
- **初始化缺失**:变量未赋初值?首次使用时为 undefined/NaN?
|
||||||
|
|
||||||
|
### 4. 低级:体验/维护问题
|
||||||
|
- **重复触发**:watcher/callback 多次触发同一操作?
|
||||||
|
- **维护成本**:硬编码路径/行号/ID 需要手动同步?
|
||||||
|
- **XSS/注入**:用户输入或外部输出被直接渲染为 HTML?
|
||||||
|
- **状态清理**:组件卸载/页面切换时未清理进行中的请求/定时器?
|
||||||
|
|
||||||
|
## 检查流程
|
||||||
|
|
||||||
|
1. **读取方案描述**(需求文档或代码)
|
||||||
|
2. **画数据流图**(从用户操作 → 前端 → API → 后端 → DB/外部服务 → 返回)
|
||||||
|
3. **沿数据流逐节点检查**:每个节点问 5 个问题:
|
||||||
|
- 输入从哪来?可能为 null/异常吗?
|
||||||
|
- 输出给谁?接收方能处理所有情况吗?
|
||||||
|
- 耗时多久?会超时吗?
|
||||||
|
- 资源(session/连接/监听器)何时释放?
|
||||||
|
- 并发执行 N 次会怎样?
|
||||||
|
4. **检查边界**:
|
||||||
|
- 前后端交界(API 格式/认证/超时/序列化)
|
||||||
|
- 同步/异步交界(await/callback/轮询/SSE)
|
||||||
|
- 组件生命周期交界(mount/unmount/路由切换)
|
||||||
|
- AI/LLM 输出交界(结构化 vs 自由文本,幻觉风险,token 限制)
|
||||||
|
5. **从用户旅程检查**:
|
||||||
|
- 首次使用(服务未就绪?数据为空?)
|
||||||
|
- 正常使用(N 次重复操作后状态累积?)
|
||||||
|
- 异常使用(断网/超时/并发/快速切换)
|
||||||
|
- 边界数据(空列表/超大数据/特殊字符)
|
||||||
|
|
||||||
|
## 迭代收敛规则
|
||||||
|
|
||||||
|
- 每轮检查一个维度,输出发现的缺陷列表
|
||||||
|
- 如果某轮发现 0 个新缺陷 → **收敛,停止**
|
||||||
|
- 如果 5 轮后仍有新发现 → 继续,最多 10 轮
|
||||||
|
- 每个缺陷标注严重度和轮次
|
||||||
|
|
||||||
|
## 输出格式
|
||||||
|
|
||||||
|
对每个缺陷:
|
||||||
|
|
||||||
|
```
|
||||||
|
### 缺陷 #N: {标题}({致命/高/中/低})
|
||||||
|
|
||||||
|
**问题**:{一句话描述}
|
||||||
|
**场景**:{触发条件}
|
||||||
|
**后果**:{不修复会怎样}
|
||||||
|
**解决**:{具体方案}
|
||||||
|
**验收**:- [ ] {如何确认已修复}
|
||||||
|
```
|
||||||
|
|
||||||
|
最后输出汇总表:
|
||||||
|
|
||||||
|
```
|
||||||
|
| 轮次 | 维度 | 缺陷数 | 关键发现 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | 架构 | N | ... |
|
||||||
|
| ... | ... | ... | ... |
|
||||||
|
| K | 收敛 | 0 | 无新发现 |
|
||||||
|
```
|
||||||
|
|
||||||
|
## 端到端验证方法论
|
||||||
|
|
||||||
|
设计方案发现的缺陷可能在实际运行时不存在,反之亦然。对关键功能执行以下分层验证:
|
||||||
|
|
||||||
|
### 层 1:单元验证(后端隔离测试)
|
||||||
|
直接调用目标函数,绕过 API/前端,确认核心逻辑可用:
|
||||||
|
```python
|
||||||
|
# 示例:验证 AI 工具是否正常返回数据
|
||||||
|
docker exec app python3 -c "
|
||||||
|
import asyncio, json
|
||||||
|
from app.services.ai_tools import ai_tool_registry
|
||||||
|
from app.models.base import async_session_factory
|
||||||
|
async def test():
|
||||||
|
async with async_session_factory() as db:
|
||||||
|
result = await ai_tool_registry.execute('tool_name', {args}, user_id=1, db=db)
|
||||||
|
print(json.loads(result))
|
||||||
|
asyncio.run(test())
|
||||||
|
"
|
||||||
|
```
|
||||||
|
**如果这层失败**:代码逻辑错误或依赖缺失。
|
||||||
|
|
||||||
|
### 层 2:AI 行为验证(LLM 是否正确调用工具)
|
||||||
|
直接调用 AI 非流式接口,验证 LLM 是否输出了预期的工具调用标签:
|
||||||
|
```python
|
||||||
|
# 示例:验证 DeepSeek 是否输出 [TOOL_CALL]
|
||||||
|
result = await ai_gateway.chat([
|
||||||
|
{"role": "system", "content": SYSTEM_PROMPT},
|
||||||
|
{"role": "user", "content": "分析回测 LB-xxx"}
|
||||||
|
])
|
||||||
|
print("[TOOL_CALL] found:", "[TOOL_CALL]" in result["content"])
|
||||||
|
```
|
||||||
|
**如果这层失败**:SYSTEM_PROMPT 不够强,LLM 不遵循指令。加"必须"/"绝不能"等强制词。
|
||||||
|
|
||||||
|
### 层 3:SSE 流式验证(前后端数据管道)
|
||||||
|
用 curl 模拟前端 SSE 请求,检查事件流格式:
|
||||||
|
```bash
|
||||||
|
curl -N "http://localhost:8000/api/ai/chat" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{"message":"测试","model":"v3"}' | head -20
|
||||||
|
```
|
||||||
|
检查是否有 `type: "tool_call"` 和 `type: "tool_result"` 事件。
|
||||||
|
**如果这层失败**:SSE 流解析/工具执行/事件格式问题。
|
||||||
|
|
||||||
|
### 层 4:前端渲染验证(浏览器实际效果)
|
||||||
|
打开 F12 → Network → 找到 SSE 请求 → EventStream 选项卡:
|
||||||
|
- 有 `tool_call` 事件?→ 后端正常
|
||||||
|
- 有 `tool_result` 事件?→ 工具执行正常
|
||||||
|
- 页面渲染了结果?→ 前端正常
|
||||||
|
**如果这层失败**:前端缓存(Cmd+Shift+R)、v-html 渲染、事件委托问题。
|
||||||
|
|
||||||
|
### 层 5:部署验证(CI/CD + 远端环境)
|
||||||
|
```bash
|
||||||
|
# 检查 CI 绿否
|
||||||
|
gh run list --limit 1 --branch main
|
||||||
|
# 检查远端容器是否加载了新代码
|
||||||
|
ssh server "docker exec app grep 'key_function' /app/path/to/file.py"
|
||||||
|
# 检查远端日志
|
||||||
|
ssh server "docker logs app 2>&1 | tail -20"
|
||||||
|
```
|
||||||
|
**如果这层失败**:PR 未合并、CI 失败、Docker 缓存旧镜像、.env 缺配置。
|
||||||
|
|
||||||
|
### 常见的"设计没问题但实际不工作"的原因
|
||||||
|
|
||||||
|
| 症状 | 通常原因 | 排查方法 |
|
||||||
|
|---|---|---|
|
||||||
|
| AI 不调用工具 | SYSTEM_PROMPT 用"可以"而非"必须" | 层 2 验证 |
|
||||||
|
| 工具返回空 | DB 中无数据 / 权限隔离 user_id 不匹配 | 层 1 验证 |
|
||||||
|
| 前端无反应 | 浏览器缓存旧 JS / SSE 事件未解析 | 层 4 + Cmd+Shift+R |
|
||||||
|
| 远端不生效 | PR 未合并 / Docker 用了旧镜像 | 层 5 验证 |
|
||||||
|
| 数据格式错 | 双重 JSON 编码 / 字段名不一致 | 层 3 验证 |
|
||||||
|
| 按钮点不了 | v-html 无法绑 Vue 事件 | 层 4 + 事件委托 |
|
||||||
11
skills-dev/deploy-rollback-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/deploy-rollback-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "deploy-rollback-plugin",
|
||||||
|
"description": "回滚方案插件。部署后发现问题时的回滚策略、数据修复、灰度回退。挂载在 deploy 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "deploy-rollback",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
102
skills-dev/deploy-rollback-plugin/skills/SKILL.md
Normal file
102
skills-dev/deploy-rollback-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
---
|
||||||
|
name: deploy-rollback
|
||||||
|
description: 回滚方案插件。部署后发现问题时的回滚策略和执行步骤。挂载在 deploy 阶段,部署出问题时激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 回滚方案插件 (deploy-rollback)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
当生产部署后发现问题时,提供结构化的回滚决策和执行步骤。
|
||||||
|
|
||||||
|
**触发条件**:
|
||||||
|
- 部署后健康检查失败
|
||||||
|
- 部署后用户报告问题
|
||||||
|
- 部署后监控告警
|
||||||
|
|
||||||
|
## 回滚决策树
|
||||||
|
|
||||||
|
```
|
||||||
|
问题发现
|
||||||
|
├── 服务完全不可用?
|
||||||
|
│ └── YES → 立即回滚(紧急)
|
||||||
|
├── 核心功能异常?
|
||||||
|
│ └── YES → 评估影响范围 → 回滚或热修复
|
||||||
|
├── 非核心功能异常?
|
||||||
|
│ └── 评估修复时间
|
||||||
|
│ ├── < 30 分钟 → 热修复
|
||||||
|
│ └── > 30 分钟 → 回滚
|
||||||
|
└── 性能下降?
|
||||||
|
├── 严重(>5x) → 回滚
|
||||||
|
└── 轻微(<2x) → 监控 + 排期修复
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚类型
|
||||||
|
|
||||||
|
### 1. Docker 镜像回滚(最常用)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看历史镜像
|
||||||
|
docker images | grep ai-proj
|
||||||
|
|
||||||
|
# 回滚到上一版本
|
||||||
|
cd deploy
|
||||||
|
# 修改 docker-compose.prod.yml 中的镜像 tag
|
||||||
|
docker compose -f docker-compose.prod.yml up -d
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
curl -s http://localhost:8080/health | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 数据库回滚
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 执行 down migration
|
||||||
|
cd backend
|
||||||
|
migrate -path migrations -database "$DB_URL" down 1
|
||||||
|
|
||||||
|
# 验证表结构
|
||||||
|
psql -U user -d db -c "\d affected_table"
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:数据库回滚可能导致数据丢失,必须先评估影响。
|
||||||
|
|
||||||
|
### 3. 配置回滚
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 恢复旧配置
|
||||||
|
git checkout HEAD~1 -- deploy/config/
|
||||||
|
docker compose -f docker-compose.prod.yml restart
|
||||||
|
```
|
||||||
|
|
||||||
|
## 回滚检查清单
|
||||||
|
|
||||||
|
- [ ] 确认问题现象和影响范围
|
||||||
|
- [ ] 通知相关人员(用户需知道在维护中)
|
||||||
|
- [ ] 执行回滚操作
|
||||||
|
- [ ] 验证服务恢复正常
|
||||||
|
- [ ] 验证数据完整性
|
||||||
|
- [ ] 记录回滚原因和过程
|
||||||
|
- [ ] 创建修复任务
|
||||||
|
|
||||||
|
## 回滚记录模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 回滚记录
|
||||||
|
|
||||||
|
**时间**: YYYY-MM-DD HH:mm
|
||||||
|
**触发原因**: [问题描述]
|
||||||
|
**影响范围**: [受影响的功能/用户]
|
||||||
|
**回滚类型**: Docker 镜像 / 数据库 / 配置
|
||||||
|
**回滚操作**: [具体步骤]
|
||||||
|
**恢复确认**: [验证结果]
|
||||||
|
**根因分析**: [问题根因]
|
||||||
|
**修复计划**: [后续修复方案]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 预防措施
|
||||||
|
|
||||||
|
- 部署前确保 Migration 有 down.sql
|
||||||
|
- 部署前确保 Docker 保留上一版本镜像
|
||||||
|
- 大变更使用灰度发布
|
||||||
|
- 监控关键指标(错误率、延迟、CPU/内存)
|
||||||
11
skills-dev/dev-android-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-android-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-android-plugin",
|
||||||
|
"description": "Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-android",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
54
skills-dev/dev-android-plugin/skills/SKILL.md
Normal file
54
skills-dev/dev-android-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
name: dev-android
|
||||||
|
description: Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。当涉及 Android 开发任务时按需加载。
|
||||||
|
---
|
||||||
|
|
||||||
|
# Android 开发插件 (dev-android)
|
||||||
|
|
||||||
|
## 架构:MVVM + Hilt
|
||||||
|
|
||||||
|
```
|
||||||
|
android-app/app/src/main/
|
||||||
|
├── java/com/project/
|
||||||
|
│ ├── ui/ # Compose 屏幕 + 组件
|
||||||
|
│ ├── data/ # API + Repository + 本地存储
|
||||||
|
│ ├── domain/ # 业务逻辑
|
||||||
|
│ └── di/ # Hilt 依赖注入
|
||||||
|
└── res/ # 资源文件
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@HiltViewModel
|
||||||
|
class TaskViewModel @Inject constructor(
|
||||||
|
private val taskRepository: TaskRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _tasks = MutableStateFlow<List<Task>>(emptyList())
|
||||||
|
val tasks: StateFlow<List<Task>> = _tasks.asStateFlow()
|
||||||
|
|
||||||
|
fun fetchTasks() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
taskRepository.getTasks()
|
||||||
|
.collect { _tasks.value = it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun TaskListScreen(viewModel: TaskViewModel = hiltViewModel()) {
|
||||||
|
val tasks by viewModel.tasks.collectAsState()
|
||||||
|
LazyColumn {
|
||||||
|
items(tasks) { task -> TaskItem(task = task) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew assembleDebug # Debug 构建
|
||||||
|
./gradlew assembleRelease # Release 构建
|
||||||
|
./gradlew test # 测试
|
||||||
|
```
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "dev-arch",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
}
|
}
|
||||||
|
|||||||
11
skills-dev/dev-cicd-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-cicd-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-cicd-plugin",
|
||||||
|
"description": "Plugin for dev-cicd",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-cicd",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
599
skills-dev/dev-cicd-plugin/skills/SKILL.md
Normal file
599
skills-dev/dev-cicd-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,599 @@
|
|||||||
|
---
|
||||||
|
name: dev-cicd
|
||||||
|
description: CI/CD 流水线设计、优化与排查。适配 Gitea Actions + Go/Swift/Next.js/Docker 栈。当用户提到 CI、CD、流水线、pipeline、workflow、构建失败、runner 相关任务时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# CI/CD 流水线技能 (dev-cicd)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
管理 Gitea Actions CI/CD 流水线的设计、优化和故障排查。适配技术栈:
|
||||||
|
- **Git**: Gitea (self-hosted, GitHub Actions YAML 兼容)
|
||||||
|
- **Backend**: Go (Gin + GORM)
|
||||||
|
- **iOS**: Swift 6 + SwiftUI + TCA
|
||||||
|
- **Web**: Next.js (React)
|
||||||
|
- **Container**: Docker + Docker Compose
|
||||||
|
- **Registry**: Aliyun ACR
|
||||||
|
- **Runners**: self-hosted (Linux) + macos-arm64 (iOS)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 命令参考
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `/cicd analyze` | 分析当前 workflow 找优化点 |
|
||||||
|
| `/cicd troubleshoot` | 诊断流水线失败原因 |
|
||||||
|
| `/cicd template [go\|ios\|web\|docker]` | 生成 workflow 模板 |
|
||||||
|
| `/cicd status` | 查看最近 workflow 运行状态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Pipeline 设计
|
||||||
|
|
||||||
|
### 1.1 Monorepo 路径过滤
|
||||||
|
|
||||||
|
仓库包含多个子项目,用 `paths` 只触发相关构建:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .gitea/workflows/ci-cd.yml — Go + Web + Docker
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop, main]
|
||||||
|
paths:
|
||||||
|
- 'gateway/**'
|
||||||
|
- 'web/**'
|
||||||
|
- 'docker/**'
|
||||||
|
- 'scripts/**'
|
||||||
|
|
||||||
|
# .gitea/workflows/ios-testflight.yml — iOS 独立
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop, main]
|
||||||
|
paths:
|
||||||
|
- 'ios/**'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.2 Pipeline 结构原则
|
||||||
|
|
||||||
|
```
|
||||||
|
快速反馈优先:
|
||||||
|
1. 静态检查 (lint/vet) — 秒级
|
||||||
|
2. 单元测试 (test) — 1-5 分钟
|
||||||
|
3. 构建 (build) — 2-10 分钟
|
||||||
|
4. 集成测试 (可选) — 5-15 分钟
|
||||||
|
5. 发布 (deploy) — 5-15 分钟
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Go 后端模板
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
run: |
|
||||||
|
cd ${{ github.workspace }}
|
||||||
|
if [ -d .git ]; then
|
||||||
|
git fetch --depth 1 origin ${{ github.ref_name }}
|
||||||
|
git reset --hard origin/${{ github.ref_name }}
|
||||||
|
else
|
||||||
|
git clone --depth 1 --branch ${{ github.ref_name }} \
|
||||||
|
http://xiaoqu:${{ secrets.REPO_TOKEN }}@localhost:3000/<org>/<repo>.git .
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Go Vet
|
||||||
|
run: cd gateway && go vet ./...
|
||||||
|
|
||||||
|
- name: Go Test
|
||||||
|
run: cd gateway && go test ./... -count=1 -timeout 120s
|
||||||
|
|
||||||
|
- name: Go Build
|
||||||
|
run: cd gateway && go build ./cmd/gateway/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 iOS 模板
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
jobs:
|
||||||
|
ios:
|
||||||
|
runs-on: macos-arm64
|
||||||
|
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
run: git clone --depth 1 --branch ${{ github.ref_name }} <repo-url> .
|
||||||
|
|
||||||
|
- name: xcodegen
|
||||||
|
run: /opt/homebrew/bin/xcodegen generate
|
||||||
|
working-directory: ios
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: |
|
||||||
|
set -o pipefail
|
||||||
|
swift test 2>&1 | tee /tmp/test.log | tail -20
|
||||||
|
working-directory: ios
|
||||||
|
|
||||||
|
- name: Deploy TestFlight
|
||||||
|
env:
|
||||||
|
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||||
|
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
|
||||||
|
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
|
||||||
|
run: ./scripts/ios-testflight.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.5 Web (Next.js) 模板
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Web Install
|
||||||
|
run: cd web && npm ci --legacy-peer-deps
|
||||||
|
|
||||||
|
- name: Web Build
|
||||||
|
run: cd web && npm run build
|
||||||
|
|
||||||
|
- name: Docker Build Web
|
||||||
|
run: |
|
||||||
|
docker build -t $REGISTRY/$WEB_IMAGE:${{ github.sha }} \
|
||||||
|
-t $REGISTRY/$WEB_IMAGE:latest ./web
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.6 单 Job vs 多 Job
|
||||||
|
|
||||||
|
| 场景 | 选择 | 原因 |
|
||||||
|
|------|------|------|
|
||||||
|
| Runner capacity=1 | 单 Job | 多 Job 串行 + 多次 checkout = 更慢 |
|
||||||
|
| 多 Runner 可用 | 多 Job + needs | 并行加速 |
|
||||||
|
| 不同 OS (Linux+macOS) | 分 Workflow | 不同 runner label |
|
||||||
|
|
||||||
|
**当前推荐**:Linux runner 单 Job(Go+Web+Docker),macOS runner 单 Job(iOS)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 优化
|
||||||
|
|
||||||
|
### 2.1 浅克隆
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 首次 clone
|
||||||
|
git clone --depth 1 --branch ${{ github.ref_name }} <url> .
|
||||||
|
|
||||||
|
# 增量 fetch
|
||||||
|
git fetch --depth 1 origin ${{ github.ref_name }}
|
||||||
|
git reset --hard origin/${{ github.ref_name }}
|
||||||
|
```
|
||||||
|
|
||||||
|
**效果**:仓库含大量二进制文件时,clone 时间从 30s+ 降到 3-5s。
|
||||||
|
|
||||||
|
**注意**:需要 push 时先 `git fetch --unshallow`。
|
||||||
|
|
||||||
|
### 2.2 依赖缓存
|
||||||
|
|
||||||
|
Gitea Actions 不支持 `actions/cache`,但 self-hosted runner 可利用本地磁盘:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Go modules — runner 上全局缓存
|
||||||
|
env:
|
||||||
|
GOMODCACHE: /opt/runner-cache/go/mod
|
||||||
|
GOCACHE: /opt/runner-cache/go/build
|
||||||
|
|
||||||
|
# npm — 利用 node_modules 持久化
|
||||||
|
# self-hosted runner 的 workspace 在两次运行间保留
|
||||||
|
- run: |
|
||||||
|
if [ -f web/node_modules/.cache-hash ] && \
|
||||||
|
[ "$(cat web/node_modules/.cache-hash)" = "$(md5sum web/package-lock.json | cut -d' ' -f1)" ]; then
|
||||||
|
echo "npm cache hit, skip install"
|
||||||
|
else
|
||||||
|
cd web && npm ci --legacy-peer-deps
|
||||||
|
md5sum package-lock.json | cut -d' ' -f1 > node_modules/.cache-hash
|
||||||
|
fi
|
||||||
|
|
||||||
|
# SPM — Xcode 自动缓存到 DerivedData,self-hosted runner 保留
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 并发取消
|
||||||
|
|
||||||
|
避免同一分支多次 push 排队等待:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 条件跳过
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 跳过 CI Bot 的自动提交
|
||||||
|
if: "!contains(github.event.head_commit.message, '[skip ci]')"
|
||||||
|
|
||||||
|
# 只在 develop 分支部署
|
||||||
|
if: github.ref == 'refs/heads/develop'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 构建产物复用
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Build once, use in deploy
|
||||||
|
- name: Build
|
||||||
|
run: go build -o /tmp/gateway ./cmd/gateway/
|
||||||
|
|
||||||
|
- name: Docker Build
|
||||||
|
run: |
|
||||||
|
# 用已编译的二进制,不在 Docker 内重新编译
|
||||||
|
cp /tmp/gateway docker/
|
||||||
|
docker build -f docker/gateway.prebuilt.Dockerfile -t $IMAGE .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 Docker Context 瘦身
|
||||||
|
|
||||||
|
**问题**:`docker build` 会将整个 context 目录发送到 daemon。缺少 `.dockerignore` 时,`node_modules`(数百 MB)、`.next/`、`.git/` 等全部传入,导致 `transferring context: 768MB` 耗时 30s+。
|
||||||
|
|
||||||
|
**诊断**:
|
||||||
|
```bash
|
||||||
|
# 检查 context 大小(模拟 docker build 发送量)
|
||||||
|
du -sh --exclude=.git <project-dir>
|
||||||
|
|
||||||
|
# 检查是否有 .dockerignore
|
||||||
|
cat <project-dir>/.dockerignore 2>/dev/null || echo "缺少 .dockerignore!"
|
||||||
|
```
|
||||||
|
|
||||||
|
**`/cicd analyze` 必查项**:对每个有 Dockerfile 的目录检查 `.dockerignore` 是否存在。缺失则告警。
|
||||||
|
|
||||||
|
**标准 .dockerignore 模板**:
|
||||||
|
|
||||||
|
```
|
||||||
|
# Node.js
|
||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
.turbo
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Common
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env*
|
||||||
|
*.md
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
```
|
||||||
|
|
||||||
|
**效果**:Web 项目 context 从 768MB → ~10MB,Docker build 加速 10x。
|
||||||
|
|
||||||
|
**经验教训**:`.gitignore` 不等于 `.dockerignore`。Git 忽略的文件可能在 runner workspace 中存在(如 self-hosted runner 保留的 `node_modules` 缓存),docker build 会把它们全部打包传入。每个有 Dockerfile 的子目录**必须有 `.dockerignore`**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 故障排查
|
||||||
|
|
||||||
|
### 3.1 决策树
|
||||||
|
|
||||||
|
```
|
||||||
|
Pipeline 失败
|
||||||
|
├── Workflow 没触发
|
||||||
|
│ ├── 检查 paths 过滤 → 改动不在匹配路径下
|
||||||
|
│ ├── 检查 branch 过滤 → 分支名不匹配
|
||||||
|
│ ├── 检查 [skip ci] → commit message 含跳过标记
|
||||||
|
│ └── Runner 离线 → Gitea Admin > Runners 检查状态
|
||||||
|
│
|
||||||
|
├── Checkout 失败
|
||||||
|
│ ├── "Authentication failed" → REPO_TOKEN secret 过期/无效
|
||||||
|
│ ├── "Connection refused :3000" → Gitea 服务未运行
|
||||||
|
│ └── Checkout 很慢 → 加 --depth 1 浅克隆
|
||||||
|
│
|
||||||
|
├── Go 构建失败
|
||||||
|
│ ├── "module not found" → GOPROXY 设置 / go mod tidy
|
||||||
|
│ ├── "cannot find package" → go.sum 不完整
|
||||||
|
│ └── "go: version mismatch" → runner 上 Go 版本与 go.mod 不匹配
|
||||||
|
│
|
||||||
|
├── iOS 构建失败
|
||||||
|
│ ├── "Macro must be enabled" → 加 -skipMacroValidation
|
||||||
|
│ ├── "cannot find type" → xcodegen generate 未运行
|
||||||
|
│ ├── "errSecInternalComponent" → unlock-keychain + set-key-partition-list
|
||||||
|
│ ├── "No signing certificate" → Xcode > Accounts 登录下载证书
|
||||||
|
│ ├── "Redundant Binary Upload" → 递增 CURRENT_PROJECT_VERSION
|
||||||
|
│ └── "Missing required icon" → Assets.xcassets 缺 1024x1024 icon
|
||||||
|
│
|
||||||
|
├── Docker 构建失败/慢
|
||||||
|
│ ├── "Cannot connect to daemon" → Docker Desktop 未启动
|
||||||
|
│ ├── "unauthorized" / "denied" → docker login 凭据过期 或 ACR namespace 缺失
|
||||||
|
│ ├── "no space left" → docker system prune
|
||||||
|
│ ├── "transferring context: XXX MB" 很慢 → 缺少 .dockerignore(node_modules 被传入)
|
||||||
|
│ ├── build 成功但 push denied → 镜像路径缺 namespace(registry/namespace/image)
|
||||||
|
│ ├── docker compose pull 超时 → 不带参数会拉 Docker Hub 上的 postgres/redis,只拉业务镜像
|
||||||
|
│ └── docker compose up -d 也会 pull → 加 `--no-deps gateway web` 只重启业务容器
|
||||||
|
│
|
||||||
|
└── 部署失败
|
||||||
|
├── "Connection refused" (SSH) → 目标服务器 SSH 端口/密钥
|
||||||
|
├── "health check failed" → 应用启动慢,增加重试等待
|
||||||
|
├── "port already in use" → docker compose down 先停旧容器
|
||||||
|
├── "no such service: xxx" → 服务器 compose 与 CI 配置不一致
|
||||||
|
├── health check 失败但容器在跑 → curl URL 的端口与实际服务端口不匹配
|
||||||
|
├── --no-deps 跳过了 nginx → health check 走 port 80 但 nginx 未启动
|
||||||
|
├── gateway 无端口映射 → prod compose 不暴露端口,用 docker exec 检查
|
||||||
|
└── nginx crash "upstream not allowed" → nginx.conf mount 到 /etc/nginx/nginx.conf 覆盖主配置,改 /etc/nginx/conf.d/default.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 常见错误速查
|
||||||
|
|
||||||
|
| 错误 | 原因 | 修复 |
|
||||||
|
|------|------|------|
|
||||||
|
| `errSecInternalComponent` | SSH 会话无法访问 Keychain | `security unlock-keychain` + `set-key-partition-list` |
|
||||||
|
| `Macro "X" must be enabled` | Swift Macros 安全限制 | `-skipMacroValidation` |
|
||||||
|
| `cannot find type 'Foo'` | xcodeproj 未包含新文件 | `xcodegen generate` |
|
||||||
|
| `Redundant Binary Upload` | build number 重复 | 递增 `CURRENT_PROJECT_VERSION` |
|
||||||
|
| `Cloud signing permission error` | API Key 权限不足或 Issuer ID 错误 | 用手动签名 + 本地 profile |
|
||||||
|
| `HTTP 401 Unauthorized` (ASC API) | JWT 缺少 `kid` header | `headers={"kid": KEY_ID}` |
|
||||||
|
| `No profiles for bundle id` | 无 distribution profile | 在 Apple Developer 创建并安装 |
|
||||||
|
| `transferring context: 768MB` | 缺 .dockerignore | 创建 .dockerignore 排除 node_modules/.next/.git |
|
||||||
|
| `denied: requested access` (push) | ACR 镜像路径缺 namespace | registry/**namespace**/image |
|
||||||
|
| `docker compose pull` 超时 | 拉了 Docker Hub 的 postgres/redis | `docker compose pull gateway web` 只拉业务镜像 |
|
||||||
|
| `docker compose up -d` 也超时 | up 隐含 pull 所有 service | `docker compose up -d --no-deps gateway web` |
|
||||||
|
| health check 失败但容器在跑 | curl URL 端口 ≠ 服务端口 | 检查 nginx(80) vs gateway(8080),直接 `curl :8080/health` |
|
||||||
|
| `--no-deps` 后 nginx 没启动 | nginx 被 no-deps 跳过 | 显式加 `--no-deps gateway web nginx` |
|
||||||
|
| `no such service: xxx` | 服务器 compose 缺 service | SSH 检查实际 compose 文件 |
|
||||||
|
| gateway healthy 但 curl 不通 | prod compose 无端口映射 | `docker exec <container> wget -q -O- localhost:8080/health` |
|
||||||
|
| nginx `upstream not allowed` | nginx.conf mount 到 /etc/nginx/nginx.conf | 改 mount 到 `/etc/nginx/conf.d/default.conf` |
|
||||||
|
| `missing icon file 120x120` | 无 App Icon asset | 创建 Assets.xcassets + AppIcon |
|
||||||
|
| `UIInterfaceOrientation` iPad | 缺 iPad 方向声明 | 四方向 + `UIRequiresFullScreen` |
|
||||||
|
|
||||||
|
### 3.3 调试技巧
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看 Gitea runner 状态
|
||||||
|
curl -s -H "Authorization: token <TOKEN>" \
|
||||||
|
http://<gitea>/api/v1/repos/<org>/<repo>/actions/runners
|
||||||
|
|
||||||
|
# 查看最近 workflow 运行
|
||||||
|
curl -s -H "Authorization: token <TOKEN>" \
|
||||||
|
http://<gitea>/api/v1/repos/<org>/<repo>/actions/runs?limit=5
|
||||||
|
|
||||||
|
# 本地模拟 CI 环境
|
||||||
|
# Go
|
||||||
|
docker run -v $(pwd):/app -w /app golang:1.25 go build ./cmd/gateway/
|
||||||
|
|
||||||
|
# iOS — 只能在 macOS 上
|
||||||
|
ssh bjwework "cd ~/workspace/xiaoqu-ai/ios && swift test"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 安全
|
||||||
|
|
||||||
|
### 4.1 Secrets 管理
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过 Gitea API 配置 secrets(不要手动编辑 workflow 文件)
|
||||||
|
curl -X PUT -H "Authorization: token <ADMIN_TOKEN>" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
"http://<gitea>/api/v1/repos/<org>/<repo>/actions/secrets/<NAME>" \
|
||||||
|
-d '{"data": "<VALUE>"}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**必需 Secrets 清单**:
|
||||||
|
|
||||||
|
| Secret | 用途 | 轮换周期 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| `REPO_TOKEN` | Git clone 认证 | 按需 |
|
||||||
|
| `ACR_USERNAME` / `ACR_PASSWORD` | Docker 镜像推送 | 90 天 |
|
||||||
|
| `SSH_PRIVATE_KEY` | 服务器部署 | 按需 |
|
||||||
|
| `KEYCHAIN_PASSWORD` | macOS 签名解锁 | 改密码时 |
|
||||||
|
| `ASC_KEY_ID` / `ASC_ISSUER_ID` | App Store Connect | 按需 |
|
||||||
|
| `FEISHU_WEBHOOK` | 通知 | 不过期 |
|
||||||
|
|
||||||
|
### 4.2 防泄漏检查清单
|
||||||
|
|
||||||
|
- [ ] `.gitignore` 包含 `.env`、`*.p8`、`*.pem`、`*.mobileprovision`
|
||||||
|
- [ ] Workflow 中无硬编码密码/token(全走 `${{ secrets.* }}`)
|
||||||
|
- [ ] 脚本用 `${VAR:?error}` 强制要求环境变量(不用默认值暴露凭据)
|
||||||
|
- [ ] Docker 镜像不包含 `.env` 文件(Dockerfile 有 `.dockerignore`)
|
||||||
|
- [ ] Git remote URL 不含 token(用 secrets 注入)
|
||||||
|
|
||||||
|
### 4.3 提交前检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 扫描即将提交的文件是否含密钥
|
||||||
|
git diff --cached --name-only | xargs grep -lE \
|
||||||
|
'(PRIVATE KEY|password|secret|token|apikey)' 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 监控
|
||||||
|
|
||||||
|
### 5.1 查看 Pipeline 状态
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 最近运行
|
||||||
|
curl -s -H "Authorization: token <TOKEN>" \
|
||||||
|
"http://<gitea>/api/v1/repos/<org>/<repo>/actions/runs?limit=5" | \
|
||||||
|
python3 -c "
|
||||||
|
import json, sys
|
||||||
|
for r in json.load(sys.stdin).get('workflow_runs', []):
|
||||||
|
print(f\"{r['id']} | {r['display_title'][:40]} | {r['status']} | {r['conclusion']}\")
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 飞书通知模板
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# 成功/失败通知(在 workflow 最后一步 if: always())
|
||||||
|
- name: Notify
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
STATUS="${{ job.status }}"
|
||||||
|
EMOJI=$([ "$STATUS" = "success" ] && echo "✅" || echo "❌")
|
||||||
|
COLOR=$([ "$STATUS" = "success" ] && echo "green" || echo "red")
|
||||||
|
cat > /tmp/notify.json << EOF
|
||||||
|
{
|
||||||
|
"msg_type": "interactive",
|
||||||
|
"card": {
|
||||||
|
"header": {
|
||||||
|
"title": {"tag": "plain_text", "content": "$EMOJI <App> $STATUS"},
|
||||||
|
"template": "$COLOR"
|
||||||
|
},
|
||||||
|
"elements": [{
|
||||||
|
"tag": "div",
|
||||||
|
"text": {"tag": "lark_md", "content": "**分支**: ${{ github.ref_name }}\n**提交**: ${{ github.sha }}\n**触发**: ${{ github.event.head_commit.message }}"}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
curl -s -X POST "${{ secrets.FEISHU_WEBHOOK }}" \
|
||||||
|
-H "Content-Type: application/json" -d @/tmp/notify.json || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.3 构建时间追踪
|
||||||
|
|
||||||
|
在 workflow 首尾加时间戳:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
steps:
|
||||||
|
- name: Start Timer
|
||||||
|
run: echo "BUILD_START=$(date +%s)" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
# ... 构建步骤 ...
|
||||||
|
|
||||||
|
- name: Report Duration
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
DURATION=$(( $(date +%s) - $BUILD_START ))
|
||||||
|
echo "Build duration: ${DURATION}s"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Runner 管理
|
||||||
|
|
||||||
|
### 6.1 Runner 类型
|
||||||
|
|
||||||
|
| Runner | 标签 | 用途 | 位置 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| xiaoqu-runner | `self-hosted` | Go + Web + Docker | 阿里云 39.104.65.241 |
|
||||||
|
| bjwework-macos | `macos-arm64` | iOS + Swift | Tailscale 100.69.230.116 |
|
||||||
|
|
||||||
|
### 6.2 新增 Runner
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 获取注册 token
|
||||||
|
curl -s -H "Authorization: token <ADMIN_TOKEN>" \
|
||||||
|
"http://<gitea>/api/v1/repos/<org>/<repo>/actions/runners/registration-token"
|
||||||
|
|
||||||
|
# 2. 注册
|
||||||
|
./act_runner register --no-interactive \
|
||||||
|
--instance http://<gitea> \
|
||||||
|
--token <TOKEN> \
|
||||||
|
--name <NAME> \
|
||||||
|
--labels <LABEL>:host
|
||||||
|
|
||||||
|
# 3. 启动(macOS 用 launchd)
|
||||||
|
launchctl load ~/Library/LaunchAgents/com.gitea.act-runner.plist
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Runner 健康检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 runner 进程
|
||||||
|
ssh bjwework "launchctl list | grep act-runner"
|
||||||
|
|
||||||
|
# 检查 runner 日志
|
||||||
|
ssh bjwework "tail -20 ~/act_runner/runner.log"
|
||||||
|
|
||||||
|
# 检查 Gitea 上的 runner 状态
|
||||||
|
curl -s -H "Authorization: token <TOKEN>" \
|
||||||
|
"http://<gitea>/api/v1/repos/<org>/<repo>/actions/runners" | \
|
||||||
|
python3 -c "import json,sys; [print(f\"{r['name']} | {r['status']}\") for r in json.load(sys.stdin)]"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Workflow 模板生成
|
||||||
|
|
||||||
|
### `/cicd analyze` 检查清单
|
||||||
|
|
||||||
|
执行时自动扫描以下项目:
|
||||||
|
|
||||||
|
1. **Workflow YAML** — 语法检查、路径过滤、并发取消、[skip ci]
|
||||||
|
2. **Docker context** — 每个有 Dockerfile 的目录是否有 `.dockerignore`(**必查**)
|
||||||
|
3. **Secrets** — workflow 中是否有硬编码凭据、路径
|
||||||
|
4. **缓存** — 是否利用了依赖缓存(npm/Go/SPM)
|
||||||
|
5. **浅克隆** — checkout 是否用了 `--depth 1`
|
||||||
|
6. **镜像命名** — ACR/registry 路径是否包含 namespace
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 快速扫描命令
|
||||||
|
echo "=== .dockerignore 检查 ==="
|
||||||
|
find . -name Dockerfile -exec sh -c 'DIR=$(dirname "{}"); [ -f "$DIR/.dockerignore" ] && echo "✅ $DIR" || echo "❌ $DIR 缺少 .dockerignore"' \;
|
||||||
|
|
||||||
|
echo "=== 硬编码凭据检查 ==="
|
||||||
|
grep -rn 'password\|secret\|token' .gitea/workflows/ | grep -v 'secrets\.' | grep -v '#'
|
||||||
|
```
|
||||||
|
|
||||||
|
### `/cicd template go`
|
||||||
|
|
||||||
|
生成 Go 后端 CI workflow,含 vet → test → build → docker → deploy。
|
||||||
|
|
||||||
|
### `/cicd template ios`
|
||||||
|
|
||||||
|
生成 iOS TestFlight workflow,含 xcodegen → test → archive → upload → notify。
|
||||||
|
|
||||||
|
### `/cicd template web`
|
||||||
|
|
||||||
|
生成 Next.js CI workflow,含 install → build → docker → deploy。
|
||||||
|
|
||||||
|
### `/cicd template docker`
|
||||||
|
|
||||||
|
生成 Docker multi-service build+push workflow,含 ACR 登录 → 多镜像构建 → SSH 部署。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. CD 部署前验证清单
|
||||||
|
|
||||||
|
**每次修改 deploy 步骤前必须逐项确认:**
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 服务器 compose 有哪些 service?
|
||||||
|
→ ssh <server> "docker compose -f <file> config --services"
|
||||||
|
|
||||||
|
2. CI deploy 启动了哪些 service?
|
||||||
|
→ grep "up -d" .gitea/workflows/ci-cd.yml
|
||||||
|
|
||||||
|
3. health check URL 指向哪个端口?
|
||||||
|
→ grep "curl.*health" .gitea/workflows/ci-cd.yml
|
||||||
|
|
||||||
|
4. 该端口由哪个 service 服务?
|
||||||
|
→ port 80 = nginx, port 8080 = gateway, port 3001 = web
|
||||||
|
|
||||||
|
5. 该 service 是否在 deploy 启动列表中?
|
||||||
|
→ 如果 health check 走 nginx:80,deploy 必须包含 nginx
|
||||||
|
|
||||||
|
6. 基础服务(postgres/redis)是否已运行?
|
||||||
|
→ docker compose ps 检查,不要在 CI 中重启它们
|
||||||
|
|
||||||
|
7. Docker Hub 可达吗?
|
||||||
|
→ 国内服务器必须配镜像源,或只拉 ACR 镜像
|
||||||
|
```
|
||||||
|
|
||||||
|
**部署命令标准模板:**
|
||||||
|
```bash
|
||||||
|
# 只拉业务镜像(不触碰 Docker Hub)
|
||||||
|
docker compose -f docker-compose.prod.yml pull gateway web
|
||||||
|
|
||||||
|
# 只重启业务容器 + nginx(不动 postgres/redis)
|
||||||
|
docker compose -f docker-compose.prod.yml up -d --no-deps gateway web nginx
|
||||||
|
|
||||||
|
# 直接检查 gateway 端口(不依赖 nginx)
|
||||||
|
sleep 10
|
||||||
|
curl -sf http://localhost:8080/health
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 与其他技能的关系
|
||||||
|
|
||||||
|
| 技能 | 协作点 |
|
||||||
|
|------|--------|
|
||||||
|
| `dev-deploy` | `/deploy ios` 执行 TestFlight 部署,`/deploy docker` 执行容器部署 |
|
||||||
|
| `dev-coding` | 开发完成后触发 CI |
|
||||||
|
| `req` | `/req deploy` 项目级批量部署 |
|
||||||
|
| `pull-request` | PR 触发 CI 检查 |
|
||||||
|
| `req-test-gate` | CI 中的测试门禁 |
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "dev-coding-plugin",
|
"name": "dev-coding-plugin",
|
||||||
"description": "Plugin for dev-coding",
|
"description": "软件编码开发技能。Go 后端 + Vue/React 前端编码实现,集成 ai-proj 任务管理。",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "dev-coding",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,24 @@
|
|||||||
---
|
---
|
||||||
name: dev-coding
|
name: dev-coding
|
||||||
description: 软件编码开发技能。用于代码编写、功能实现、代码审查、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。支持 Go、Vue、React、iOS、Android、小程序等全栈开发。
|
description: 软件编码开发技能。用于代码编写、功能实现、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。核心支持 Go 后端 + Vue/React 前端开发。
|
||||||
---
|
---
|
||||||
|
|
||||||
# 软件编码开发 Skill (dev-coding)
|
# 软件编码开发 Skill (dev-coding)
|
||||||
|
|
||||||
## ⚠️ REQ 任务自动工作流
|
|
||||||
|
|
||||||
**当收到 REQ 任务(包含 REQ-YYYYMMDD-XXXX)需要开发时,必须严格按以下顺序执行:**
|
|
||||||
|
|
||||||
1. **读取 ticket** — 从 ai-proj 获取需求详情和关联文档
|
|
||||||
```
|
|
||||||
mcp__ai-proj-dev__get_detailed_task_info (通过 REQ 号查找)
|
|
||||||
mcp__ai-proj-dev__get_task_document (如果有 PRD 文档)
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **进入 Plan Mode** — 调用 `EnterPlanMode` 工具
|
|
||||||
- 分析需求,探索代码库,设计实现方案
|
|
||||||
- 输出实现计划(涉及的文件、改动范围、测试策略)
|
|
||||||
- 等待用户审批后再开始编码
|
|
||||||
|
|
||||||
3. **执行计划** — 用户批准后按计划编码 + 写测试
|
|
||||||
|
|
||||||
**禁止跳过 plan mode 直接编码。**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
|
|
||||||
本技能用于软件编码开发工作,支持多种项目类型:
|
本技能用于软件编码实现,核心覆盖:
|
||||||
- Go 后端 (Gin + GORM)
|
- Go 后端 (Gin + GORM)
|
||||||
- Vue 3 / React 前端
|
- Vue 3 / React 前端
|
||||||
- iOS (Swift/SwiftUI)
|
|
||||||
- Android (Kotlin/Jetpack Compose)
|
|
||||||
- PDA 应用
|
|
||||||
- MCP 桥接服务
|
|
||||||
- 微服务架构
|
|
||||||
|
|
||||||
核心集成 **ai-proj CLI** 进行任务管理。
|
集成 **ai-proj CLI** 进行任务管理。
|
||||||
|
|
||||||
|
**不包含**(由其他技能/插件负责):
|
||||||
|
- 开发设计(API 契约、任务拆分)→ `req-design`
|
||||||
|
- 代码评审 → `dev-review`(批次2)
|
||||||
|
- iOS 开发 → `dev-ios`(插件)
|
||||||
|
- Android 开发 → `dev-android`(插件)
|
||||||
|
- MCP 开发 → `dev-mcp`(插件)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -84,11 +65,11 @@ ai-proj task append-doc --id <taskId> --content "实现说明"
|
|||||||
|
|
||||||
### 当前项目生态
|
### 当前项目生态
|
||||||
|
|
||||||
| 项目 | 类型 | 后端 | 前端 | 移动端 |
|
| 项目 | 类型 | 后端 | 前端 |
|
||||||
|------|------|------|------|--------|
|
|------|------|------|------|
|
||||||
| TWMS | 仓储物流 | Go+Gin+MySQL | Vue 3 | - |
|
| TWMS | 仓储物流 | Go+Gin+MySQL | Vue 3 |
|
||||||
| AI-Proj | 项目管理 | Go+Gin+PostgreSQL | React 18 | iOS+Android |
|
| AI-Proj | 项目管理 | Go+Gin+PostgreSQL | React 18 |
|
||||||
| DICIAI | 进销存SaaS | Go+Gin+MySQL | Vue 3 | Android PDA |
|
| DICIAI | 进销存SaaS | Go+Gin+MySQL | Vue 3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -349,309 +330,6 @@ npm run test:e2e
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## iOS 开发 (Swift/SwiftUI)
|
|
||||||
|
|
||||||
### 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
AI-Proj-iOS/
|
|
||||||
├── Core/ # 核心服务
|
|
||||||
│ ├── Network/ # 网络层
|
|
||||||
│ ├── Storage/ # 本地存储
|
|
||||||
│ └── Auth/ # 认证
|
|
||||||
├── Features/ # 功能模块
|
|
||||||
│ ├── Dashboard/
|
|
||||||
│ ├── Tasks/
|
|
||||||
│ └── Settings/
|
|
||||||
├── Models/ # 数据模型
|
|
||||||
└── UI/ # UI 组件
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代码规范
|
|
||||||
|
|
||||||
```swift
|
|
||||||
// MVVM 架构
|
|
||||||
class TaskViewModel: ObservableObject {
|
|
||||||
@Published var tasks: [Task] = []
|
|
||||||
@Published var isLoading = false
|
|
||||||
|
|
||||||
private let taskService: TaskServiceProtocol
|
|
||||||
|
|
||||||
init(taskService: TaskServiceProtocol = TaskService()) {
|
|
||||||
self.taskService = taskService
|
|
||||||
}
|
|
||||||
|
|
||||||
func fetchTasks() async {
|
|
||||||
isLoading = true
|
|
||||||
defer { isLoading = false }
|
|
||||||
|
|
||||||
do {
|
|
||||||
tasks = try await taskService.getTasks()
|
|
||||||
} catch {
|
|
||||||
// 错误处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SwiftUI 视图
|
|
||||||
struct TaskListView: View {
|
|
||||||
@StateObject private var viewModel = TaskViewModel()
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
List(viewModel.tasks) { task in
|
|
||||||
TaskRow(task: task)
|
|
||||||
}
|
|
||||||
.task {
|
|
||||||
await viewModel.fetchTasks()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 构建命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Xcode 构建
|
|
||||||
xcodebuild -scheme AI-Proj-iOS -configuration Debug
|
|
||||||
|
|
||||||
# 测试
|
|
||||||
xcodebuild test -scheme AI-Proj-iOS
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常见问题排查
|
|
||||||
|
|
||||||
#### SwiftLint 沙盒错误
|
|
||||||
|
|
||||||
**问题描述**:
|
|
||||||
构建时出现错误:
|
|
||||||
```
|
|
||||||
Sandbox: swiftlint(xxxx) deny(1) file-read-data /path/to/.swiftlint.yml
|
|
||||||
```
|
|
||||||
|
|
||||||
**原因**:
|
|
||||||
Xcode 15+ 默认启用 User Script Sandboxing,限制脚本访问文件系统。
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
|
|
||||||
方案 1 - 修改项目配置(推荐):
|
|
||||||
1. 打开 Xcode → 选择项目 → Build Settings
|
|
||||||
2. 搜索 "User Script Sandboxing"
|
|
||||||
3. 将 `ENABLE_USER_SCRIPT_SANDBOXING` 设置为 `NO`
|
|
||||||
|
|
||||||
方案 2 - 命令行构建时禁用:
|
|
||||||
```bash
|
|
||||||
xcodebuild -scheme AI-Proj-iOS -configuration Debug \
|
|
||||||
ENABLE_USER_SCRIPT_SANDBOXING=NO
|
|
||||||
```
|
|
||||||
|
|
||||||
方案 3 - 直接修改 project.pbxproj:
|
|
||||||
```bash
|
|
||||||
sed -i '' 's/ENABLE_USER_SCRIPT_SANDBOXING = YES/ENABLE_USER_SCRIPT_SANDBOXING = NO/g' \
|
|
||||||
AI-Proj-iOS.xcodeproj/project.pbxproj
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Personal Development Team 功能限制
|
|
||||||
|
|
||||||
**问题描述**:
|
|
||||||
使用免费 Personal Team 签名时报错:
|
|
||||||
```
|
|
||||||
Cannot create iOS App Development provisioning profile...
|
|
||||||
Personal development teams do not support the Associated Domains,
|
|
||||||
Push Notifications and App Groups capabilities.
|
|
||||||
```
|
|
||||||
|
|
||||||
**原因**:
|
|
||||||
Personal Team(免费账户)不支持以下 Entitlements:
|
|
||||||
- Associated Domains (`com.apple.developer.associated-domains`)
|
|
||||||
- Push Notifications (`aps-environment`)
|
|
||||||
- App Groups (`com.apple.security.application-groups`)
|
|
||||||
|
|
||||||
**解决方案**:
|
|
||||||
|
|
||||||
1. 从 Entitlements 文件中移除不支持的功能:
|
|
||||||
|
|
||||||
```xml
|
|
||||||
<!-- AI-Proj-iOS.entitlements -->
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
||||||
<plist version="1.0">
|
|
||||||
<dict>
|
|
||||||
<!-- 仅保留 Personal Team 支持的功能 -->
|
|
||||||
<key>keychain-access-groups</key>
|
|
||||||
<array>
|
|
||||||
<string>$(AppIdentifierPrefix)com.yourcompany.app</string>
|
|
||||||
</array>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Personal Team 支持的功能:
|
|
||||||
- Keychain Access Groups ✓
|
|
||||||
- In-App Purchase ✓
|
|
||||||
- Game Center ✓
|
|
||||||
|
|
||||||
3. 需要付费 Apple Developer Program 的功能:
|
|
||||||
- Push Notifications ✗
|
|
||||||
- Associated Domains ✗
|
|
||||||
- App Groups ✗
|
|
||||||
- CloudKit ✗
|
|
||||||
- Sign in with Apple ✗
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Android 开发 (Kotlin)
|
|
||||||
|
|
||||||
### 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
android-app/app/src/main/
|
|
||||||
├── java/com/project/
|
|
||||||
│ ├── ui/ # UI 层
|
|
||||||
│ │ ├── screens/ # Compose 屏幕
|
|
||||||
│ │ └── components/ # 可复用组件
|
|
||||||
│ ├── data/ # 数据层
|
|
||||||
│ │ ├── api/ # 网络接口
|
|
||||||
│ │ ├── repository/ # 仓库模式
|
|
||||||
│ │ └── local/ # 本地存储
|
|
||||||
│ ├── domain/ # 业务逻辑
|
|
||||||
│ └── di/ # 依赖注入
|
|
||||||
└── res/ # 资源文件
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代码规范
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
// Hilt 依赖注入
|
|
||||||
@HiltViewModel
|
|
||||||
class TaskViewModel @Inject constructor(
|
|
||||||
private val taskRepository: TaskRepository
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val _tasks = MutableStateFlow<List<Task>>(emptyList())
|
|
||||||
val tasks: StateFlow<List<Task>> = _tasks.asStateFlow()
|
|
||||||
|
|
||||||
fun fetchTasks() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
taskRepository.getTasks()
|
|
||||||
.collect { _tasks.value = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jetpack Compose
|
|
||||||
@Composable
|
|
||||||
fun TaskListScreen(
|
|
||||||
viewModel: TaskViewModel = hiltViewModel()
|
|
||||||
) {
|
|
||||||
val tasks by viewModel.tasks.collectAsState()
|
|
||||||
|
|
||||||
LazyColumn {
|
|
||||||
items(tasks) { task ->
|
|
||||||
TaskItem(task = task)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 构建命令
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 构建 Debug
|
|
||||||
./gradlew assembleDebug
|
|
||||||
|
|
||||||
# 构建 Release
|
|
||||||
./gradlew assembleRelease
|
|
||||||
|
|
||||||
# 测试
|
|
||||||
./gradlew test
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## PDA 应用开发
|
|
||||||
|
|
||||||
### 特点
|
|
||||||
|
|
||||||
- Android 原生开发
|
|
||||||
- 扫码枪集成
|
|
||||||
- 离线优先
|
|
||||||
- 简洁 UI
|
|
||||||
|
|
||||||
### 常见功能
|
|
||||||
|
|
||||||
```kotlin
|
|
||||||
// 扫码处理
|
|
||||||
class ScanReceiver : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
val barcode = intent.getStringExtra("SCAN_BARCODE")
|
|
||||||
// 处理扫码结果
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 离线存储
|
|
||||||
@Entity(tableName = "inventory")
|
|
||||||
data class Inventory(
|
|
||||||
@PrimaryKey val id: Long,
|
|
||||||
val barcode: String,
|
|
||||||
val quantity: Int,
|
|
||||||
@ColumnInfo(name = "sync_status")
|
|
||||||
val syncStatus: SyncStatus = SyncStatus.PENDING
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## MCP 桥接开发
|
|
||||||
|
|
||||||
### 项目结构
|
|
||||||
|
|
||||||
```
|
|
||||||
mcp-task-bridge/
|
|
||||||
├── index.ts # 入口
|
|
||||||
├── task-service.ts # 任务服务
|
|
||||||
├── document-service.ts # 文档服务
|
|
||||||
├── base-client.ts # HTTP 基类
|
|
||||||
├── types.ts # 类型定义
|
|
||||||
└── token-storage.ts # Token 管理
|
|
||||||
```
|
|
||||||
|
|
||||||
### 代码规范
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// 服务类模式
|
|
||||||
export class TaskService extends BaseClient {
|
|
||||||
async createTask(
|
|
||||||
title: string,
|
|
||||||
projectId: number = 1,
|
|
||||||
options: CreateTaskOptions = {}
|
|
||||||
): Promise<ApiResponse<Task>> {
|
|
||||||
try {
|
|
||||||
const response = await this.makeRequest<Task>(
|
|
||||||
'POST',
|
|
||||||
`/projects/${projectId}/tasks`,
|
|
||||||
{ title, project_id: projectId, ...options }
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.success) {
|
|
||||||
return {
|
|
||||||
success: true,
|
|
||||||
data: response.data,
|
|
||||||
message: `✅ 任务 "${title}" 创建成功`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return response;
|
|
||||||
} catch (error: any) {
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `创建任务失败: ${error.message}`
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 通用开发规范
|
## 通用开发规范
|
||||||
|
|
||||||
### API 响应格式
|
### API 响应格式
|
||||||
@@ -695,13 +373,6 @@ try {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message);
|
message.error(error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Swift
|
|
||||||
do {
|
|
||||||
let result = try await service.fetch()
|
|
||||||
} catch {
|
|
||||||
// 处理错误
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -808,3 +479,66 @@ fi
|
|||||||
4. **小步提交** - 频繁提交,每次做一件事
|
4. **小步提交** - 频繁提交,每次做一件事
|
||||||
5. **测试覆盖** - 核心逻辑必须有测试
|
5. **测试覆盖** - 核心逻辑必须有测试
|
||||||
6. **文档同步** - 代码变更同步更新文档
|
6. **文档同步** - 代码变更同步更新文档
|
||||||
|
|
||||||
|
## 相关技能
|
||||||
|
|
||||||
|
| 技能 | 用途 |
|
||||||
|
|------|------|
|
||||||
|
| `req-design` | 开发设计(API 契约、任务拆分)— dev-coding 的输入 |
|
||||||
|
| `dev-test` | 测试与质量门禁 |
|
||||||
|
| `dev-review` | 代码评审(五视角扫描)|
|
||||||
|
| `dev-ios` | iOS 开发(插件,按需加载)|
|
||||||
|
| `dev-android` | Android 开发(插件,按需加载)|
|
||||||
|
| `dev-mcp` | MCP bridge 开发(插件,按需加载)|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CLAUDE.md 架构检查机制(REQ-20260416-0017 P0-5)
|
||||||
|
|
||||||
|
**原则:本 skill 不硬编码任何项目的架构细节,从项目 CLAUDE.md 读取**。
|
||||||
|
|
||||||
|
### 为什么
|
||||||
|
|
||||||
|
同一套 skill 要支持多个技术栈(Go+Gin / React+AntD / Vue+Element / Python+FastAPI)。如果把分层、命名、目录结构写死在 SKILL.md 里,跨项目就会冲突。
|
||||||
|
|
||||||
|
devflow-claude 的做法(借鉴):skill 只管**流程和模板**,项目架构由 CLAUDE.md 的 "Architecture" / "项目架构" 章节定义。
|
||||||
|
|
||||||
|
### 执行前检查
|
||||||
|
|
||||||
|
开始编码任务前,skill 先检查项目根 `CLAUDE.md`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 检查 CLAUDE.md 是否含架构关键词
|
||||||
|
if [ -f "CLAUDE.md" ]; then
|
||||||
|
if grep -qiE "(架构|分层|目录结构|tech stack|architecture|project structure)" CLAUDE.md; then
|
||||||
|
echo "✅ 检测到项目架构信息"
|
||||||
|
else
|
||||||
|
echo "⚠️ CLAUDE.md 缺少架构描述"
|
||||||
|
echo " dev-coding 需要架构信息来生成准确的文件路径和分层顺序"
|
||||||
|
echo ""
|
||||||
|
echo " 📋 建议操作:"
|
||||||
|
echo " - 查看预置架构片段: ai-proj-helper/skills-dev/dev-coding-plugin/templates/claude-md-snippets/"
|
||||||
|
echo " - 选择匹配技术栈的片段,补充到 CLAUDE.md 的 '## Architecture' 章节"
|
||||||
|
echo ""
|
||||||
|
echo " ⚠️ 继续执行,但生成的文件路径可能不够准确"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "⚠️ 未找到项目 CLAUDE.md,建议创建"
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
### 架构片段模板库
|
||||||
|
|
||||||
|
位于 `skills-dev/dev-coding-plugin/templates/claude-md-snippets/`:
|
||||||
|
|
||||||
|
| 文件 | 适用场景 |
|
||||||
|
|------|---------|
|
||||||
|
| `go-gin-gorm.md` | Go + Gin + GORM 后端(ai-proj backend 风格) |
|
||||||
|
| `react-antd.md` | React + TypeScript + Ant Design(ai-proj frontend 风格) |
|
||||||
|
| `vue-element.md` | Vue 3 + Element Plus(coolbuy-paas 风格) |
|
||||||
|
| `mcp-typescript.md` | MCP bridge TypeScript(mcp-task-bridge 风格) |
|
||||||
|
| `generic.md` | 通用空白骨架 |
|
||||||
|
|
||||||
|
### 非阻断原则
|
||||||
|
|
||||||
|
架构信息缺失时**仅警告不阻止**。用户仍可继续,但会被告知"生成的建议可能不够准确"。
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<!-- 复制此片段到项目根 CLAUDE.md 的 "## Architecture" 章节,按实际情况填写 -->
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 技术栈
|
||||||
|
|
||||||
|
- **语言**: _TODO_
|
||||||
|
- **框架**: _TODO_
|
||||||
|
- **数据库**: _TODO_
|
||||||
|
- **缓存**: _TODO_
|
||||||
|
- **部署**: _TODO_
|
||||||
|
|
||||||
|
### 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
project-root/
|
||||||
|
├── ???/ # _TODO: 说明_
|
||||||
|
├── ???/ # _TODO_
|
||||||
|
└── ???/
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分层 / 模块规则
|
||||||
|
|
||||||
|
1. _TODO: 依赖方向_
|
||||||
|
2. _TODO: 允许/禁止的跨层调用_
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 约定 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| _TODO_ | _TODO_ | _TODO_ |
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
_TODO_
|
||||||
|
|
||||||
|
### 日志
|
||||||
|
|
||||||
|
_TODO_
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
_TODO_
|
||||||
|
|
||||||
|
### 其他关键约定
|
||||||
|
|
||||||
|
- _TODO_
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<!-- 复制此片段到项目根 CLAUDE.md 的 "## Architecture" 章节 -->
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 分层结构(Go + Gin + GORM)
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── routes/ # HTTP 路由定义(按模块拆分)
|
||||||
|
├── handlers/ # 请求解析 + 响应组装(薄层,不含业务)
|
||||||
|
├── services/ # 业务逻辑(事务、组合、校验)
|
||||||
|
├── models/ # GORM 数据模型
|
||||||
|
├── database/ # Repository 层(SQL、查询)
|
||||||
|
├── middleware/ # 认证、CORS、日志、限流
|
||||||
|
├── migrations/ # SQL 迁移文件
|
||||||
|
└── utils/ # 通用工具(密码、签名等)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 分层规则(强制)
|
||||||
|
|
||||||
|
1. **请求流向**:Route → Handler → Service → Database → Models
|
||||||
|
2. **Handler 禁止直接访问 database**:必须走 Service 层
|
||||||
|
3. **Service 禁止调用 Handler 或 Route**:单向依赖
|
||||||
|
4. **Model 仅定义结构 + GORM tag**:不含业务方法
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 约定 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 文件名 | snake_case | `user_service.go` |
|
||||||
|
| 包名 | lowercase | `services`, `handlers` |
|
||||||
|
| 导出函数/类型 | PascalCase | `CreateUser`, `UserRepository` |
|
||||||
|
| 内部函数 | camelCase | `validatePassword` |
|
||||||
|
| 常量 | SCREAMING_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
||||||
|
|
||||||
|
### 错误处理
|
||||||
|
|
||||||
|
- 使用 `errors.New()` 或自定义 error type
|
||||||
|
- Handler 层统一返回 `{"code": X, "msg": "...", "data": ...}`
|
||||||
|
- Service 层返回原始 error,由 Handler 转换
|
||||||
|
|
||||||
|
### 日志
|
||||||
|
|
||||||
|
- 使用结构化 log:`log.WithField("user_id", uid).Info("...")`
|
||||||
|
- 禁用 `fmt.Println` / `print`
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
- 单元测试文件名:`xxx_test.go`
|
||||||
|
- 使用 `testify/assert`
|
||||||
|
- Mock 用 `testify/mock` 或 `gomock`
|
||||||
|
|
||||||
|
### 依赖检查
|
||||||
|
|
||||||
|
- **新 handler 禁止直接 `import database/`**:需走 Service 层
|
||||||
|
- `./scripts/check-architecture.sh check` 作为 CI 门禁
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<!-- 复制此片段到项目根 CLAUDE.md 的 "## Architecture" 章节 -->
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 目录结构(MCP Bridge - TypeScript)
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp-task-bridge/
|
||||||
|
├── src/
|
||||||
|
│ ├── tools/ # MCP tool 定义(每个工具一个文件)
|
||||||
|
│ ├── resources/ # MCP resources(若有)
|
||||||
|
│ ├── prompts/ # MCP prompts(若有)
|
||||||
|
│ ├── client/ # 后端 REST API 客户端
|
||||||
|
│ ├── utils/ # 工具函数
|
||||||
|
│ └── index.ts # 入口
|
||||||
|
├── tests/
|
||||||
|
└── dist/ # 编译产物(不提交)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 工具定义规范
|
||||||
|
|
||||||
|
每个 MCP tool 一个文件:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// tools/create-task.ts
|
||||||
|
export const createTaskTool: Tool = {
|
||||||
|
name: 'create_task',
|
||||||
|
description: '...',
|
||||||
|
inputSchema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: { ... },
|
||||||
|
required: [...]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function handleCreateTask(args) { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 后端 API 调用
|
||||||
|
|
||||||
|
- 所有 REST 请求通过 `src/client/api.ts` 统一封装
|
||||||
|
- 认证头由 client 自动附加(不在 tool 里处理)
|
||||||
|
- 错误统一转成 MCP error response
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 约定 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| MCP tool name | snake_case | `create_task`, `list_requirements` |
|
||||||
|
| 文件名 | kebab-case | `create-task.ts` |
|
||||||
|
| 函数名 | camelCase | `handleCreateTask` |
|
||||||
|
| Tool 变量 | camelCase + `Tool` | `createTaskTool` |
|
||||||
|
|
||||||
|
### 构建与部署
|
||||||
|
|
||||||
|
- `npm run build` → `dist/`
|
||||||
|
- **修改代码后必须重新 build**:`pkill -f mcp-task-bridge/dist/index.js` 重启 MCP server
|
||||||
|
- 不能直接运行 TypeScript 源码
|
||||||
|
|
||||||
|
### 环境配置
|
||||||
|
|
||||||
|
- `dev` 环境:`ai-proj-dev` MCP server
|
||||||
|
- `prod` 环境:`ai-proj-prod` MCP server
|
||||||
|
- 禁止跨环境传数据(dev 需求不能关联 prod 任务)
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
- Jest + ts-jest
|
||||||
|
- 集成测试模拟真实 MCP 协议
|
||||||
|
|
||||||
|
### 常见错误
|
||||||
|
|
||||||
|
- **Rule 1**: MCP 端点必须 `/api/v1/mcp/` 前缀
|
||||||
|
- **Rule 2**: 修改后必须 rebuild + 重启
|
||||||
|
- **Rule 3**: 环境隔离(dev / prod)
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
<!-- 复制此片段到项目根 CLAUDE.md 的 "## Architecture" 章节 -->
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 目录结构(React + TypeScript + Ant Design)
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
├── pages/ # 页面级组件(路由对应)
|
||||||
|
├── components/ # 可复用 UI 组件
|
||||||
|
├── services/ # API 客户端(Axios 封装)
|
||||||
|
├── hooks/ # 自定义 React Hooks
|
||||||
|
├── contexts/ # Context Providers(auth, timer 等)
|
||||||
|
├── utils/ # 工具函数(auth, validation, date 等)
|
||||||
|
├── types/ # TypeScript 类型定义
|
||||||
|
└── config/ # Feature flags, 性能配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
|
||||||
|
| 状态类型 | 方案 |
|
||||||
|
|---------|------|
|
||||||
|
| 服务器状态 | React Query (TanStack Query) |
|
||||||
|
| 全局状态 | Context API |
|
||||||
|
| 本地状态 | useState / useReducer |
|
||||||
|
| 表单状态 | Ant Design Form |
|
||||||
|
|
||||||
|
**禁止**:Redux / MobX(本项目不使用)
|
||||||
|
|
||||||
|
### 路由
|
||||||
|
|
||||||
|
- React Router v6
|
||||||
|
- 路由定义集中在 `src/routes/`
|
||||||
|
- 懒加载:`const Page = lazy(() => import(...))`
|
||||||
|
|
||||||
|
### API 调用
|
||||||
|
|
||||||
|
- 使用 `services/` 下的封装函数,不要在组件里直接 `axios.get`
|
||||||
|
- 响应类型必须有 TypeScript interface
|
||||||
|
- 错误统一由 axios 拦截器处理
|
||||||
|
|
||||||
|
### 样式
|
||||||
|
|
||||||
|
- Ant Design 组件 + CSS Module
|
||||||
|
- 禁止内联 `style={{ ... }}` 用于复杂样式
|
||||||
|
- 全局变量走 CSS Variables
|
||||||
|
|
||||||
|
### Modal 安全规则(重要)
|
||||||
|
|
||||||
|
`Modal.success/info/warning/error` 是非阻塞调用,后续 UI 操作必须放在 `onOk` 回调中:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
// WRONG
|
||||||
|
Modal.success({ title: '成功' });
|
||||||
|
setNextModalOpen(true); // 立即执行,两个 modal 冲突
|
||||||
|
|
||||||
|
// CORRECT
|
||||||
|
Modal.success({
|
||||||
|
title: '成功',
|
||||||
|
onOk: () => setNextModalOpen(true),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 约定 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 组件文件 | PascalCase | `UserProfile.tsx` |
|
||||||
|
| Hook 文件 | camelCase | `useAuth.ts` |
|
||||||
|
| 工具文件 | kebab-case | `date-utils.ts` |
|
||||||
|
| 组件名 | PascalCase | `UserProfile` |
|
||||||
|
| Hook 名 | `use` 前缀 | `useAuth` |
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
- 单测:Jest + React Testing Library
|
||||||
|
- E2E:Playwright
|
||||||
|
- 测试文件:`xxx.test.tsx` 与源文件同目录
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
<!-- 复制此片段到项目根 CLAUDE.md 的 "## Architecture" 章节 -->
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### 目录结构(Vue 3 + TypeScript + Element Plus)
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── views/ # 页面级组件(路由对应)
|
||||||
|
├── components/ # 可复用组件
|
||||||
|
├── api/ # API 封装
|
||||||
|
├── stores/ # Pinia stores
|
||||||
|
├── composables/ # 组合式函数(use* hooks)
|
||||||
|
├── utils/ # 工具函数
|
||||||
|
├── types/ # TypeScript 类型定义
|
||||||
|
└── router/ # Vue Router 配置
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
|
||||||
|
- **Pinia**(官方推荐)
|
||||||
|
- 每个业务模块一个 store:`stores/user.ts`、`stores/order.ts`
|
||||||
|
- 禁止直接在组件里写持久状态
|
||||||
|
|
||||||
|
### 路由
|
||||||
|
|
||||||
|
- Vue Router 4
|
||||||
|
- 路由守卫统一在 `router/guards.ts`
|
||||||
|
- 懒加载:`component: () => import('@/views/...')`
|
||||||
|
|
||||||
|
### Composition API
|
||||||
|
|
||||||
|
- **强制使用 `<script setup>`**,禁止 Options API
|
||||||
|
- Props 用 `defineProps<T>()`,Emits 用 `defineEmits<T>()`
|
||||||
|
|
||||||
|
### API 调用
|
||||||
|
|
||||||
|
- `api/` 下按模块划分:`api/user.ts`、`api/order.ts`
|
||||||
|
- 每个函数返回类型明确
|
||||||
|
- 错误由 axios 拦截器统一处理
|
||||||
|
|
||||||
|
### 命名规范
|
||||||
|
|
||||||
|
| 类型 | 约定 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 组件文件 | PascalCase | `UserProfile.vue` |
|
||||||
|
| Composable | camelCase + use 前缀 | `useAuth.ts` |
|
||||||
|
| Store | camelCase | `useUserStore` |
|
||||||
|
| API 文件 | kebab-case | `user-api.ts` |
|
||||||
|
| 工具函数 | camelCase | `formatDate` |
|
||||||
|
|
||||||
|
### 样式
|
||||||
|
|
||||||
|
- SCSS + Element Plus 主题
|
||||||
|
- scoped style(避免全局污染)
|
||||||
|
- 全局变量走 SCSS 变量或 CSS Variables
|
||||||
|
|
||||||
|
### 国际化
|
||||||
|
|
||||||
|
- 使用 vue-i18n
|
||||||
|
- 消息文件:`src/locales/zh.json` / `en.json`
|
||||||
|
- 禁止硬编码文本:用 `t('path.to.key')`
|
||||||
|
|
||||||
|
### 测试
|
||||||
|
|
||||||
|
- 单测:Vitest + Vue Test Utils
|
||||||
|
- E2E:Playwright / Cypress
|
||||||
11
skills-dev/dev-commit-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-commit-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-commit-plugin",
|
||||||
|
"description": "智能 /commit 命令:分支保护 + 自动建功能分支 + Conventional Commits 生成",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-commit",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
186
skills-dev/dev-commit-plugin/skills/SKILL.md
Normal file
186
skills-dev/dev-commit-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
---
|
||||||
|
name: dev-commit
|
||||||
|
description: 智能 git commit — 分支保护 + 自动建功能分支 + Conventional Commits 生成。当用户说"提交代码"、"commit"、"/commit"、"保存修改"时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# dev-commit Skill — 智能提交
|
||||||
|
|
||||||
|
借鉴自 devflow-claude `/req:commit`。源自 REQ-20260416-0017 P0-6。
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
|
||||||
|
**用户说"/commit" 或 "提交代码" 时执行:**
|
||||||
|
|
||||||
|
1. **分支合规检查**
|
||||||
|
2. **自动建功能分支**(若在保护分支)
|
||||||
|
3. **Conventional Commits 生成**
|
||||||
|
4. **自动关联 REQ-XXX**
|
||||||
|
|
||||||
|
## 流程详解
|
||||||
|
|
||||||
|
### 1. 分支合规检查(强制)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
CURRENT_BRANCH=$(git symbolic-ref --short HEAD)
|
||||||
|
|
||||||
|
# 保护分支名单
|
||||||
|
PROTECTED_BRANCHES="main master develop production"
|
||||||
|
|
||||||
|
for b in $PROTECTED_BRANCHES; do
|
||||||
|
if [ "$CURRENT_BRANCH" = "$b" ]; then
|
||||||
|
IS_PROTECTED=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
**铁律**:**绝对禁止**在 main / develop / master / production 分支上直接 commit。
|
||||||
|
|
||||||
|
### 2. 保护分支上有改动 → 自动建功能分支
|
||||||
|
|
||||||
|
```
|
||||||
|
检测到 main/develop 有未提交改动:
|
||||||
|
|
||||||
|
推荐方案:
|
||||||
|
1. 自动建分支 feat/xxx 并切换过去(推荐)
|
||||||
|
2. 取消本次 commit,手动切分支
|
||||||
|
3. 临时 stash 后切分支
|
||||||
|
|
||||||
|
【默认选 1】
|
||||||
|
```
|
||||||
|
|
||||||
|
**分支命名自动推断:**
|
||||||
|
- 从对话上下文:如果刚在讨论 "REQ-20260416-0017" → `feat/REQ-20260416-0017-summary`
|
||||||
|
- 从文件变更:扫描 staged files 的路径 → 推断模块(如 `backend/services/user/` → `feat/user-xxx`)
|
||||||
|
- 从 commit message 意图:如果意图是 fix → `fix/xxx`
|
||||||
|
|
||||||
|
**前缀规则:**
|
||||||
|
- `feat/` — 新功能
|
||||||
|
- `fix/` — bug 修复
|
||||||
|
- `chore/` — 杂项(依赖升级、CI 调整等)
|
||||||
|
- `refactor/` — 重构
|
||||||
|
- `docs/` — 文档
|
||||||
|
|
||||||
|
### 3. Conventional Commits 生成
|
||||||
|
|
||||||
|
**格式:**
|
||||||
|
```
|
||||||
|
<type>(<scope>): <description> [(REQ-XXX)] [closes #N]
|
||||||
|
```
|
||||||
|
|
||||||
|
**type:**
|
||||||
|
- feat - 新功能
|
||||||
|
- fix - 修复
|
||||||
|
- chore - 杂项
|
||||||
|
- refactor - 重构
|
||||||
|
- docs - 文档
|
||||||
|
- test - 测试
|
||||||
|
- perf - 性能
|
||||||
|
- style - 格式
|
||||||
|
|
||||||
|
**scope:**
|
||||||
|
- 从修改的文件路径推断:`backend/services/user/` → `user`
|
||||||
|
- `frontend/src/pages/login/` → `login`
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```
|
||||||
|
feat(mcp): 新增 set_requirement_reviewers 工具 (REQ-20260415-0023)
|
||||||
|
fix(frontend): 403 权限重载死循环
|
||||||
|
chore(cicd): 精简 CI/CD 流程,移除 Staging 环境 (REQ-20260415-0004) closes #242
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. REQ-XXX 自动关联
|
||||||
|
|
||||||
|
**查找顺序:**
|
||||||
|
1. 当前分支名:`feat/REQ-20260416-0017-xxx` → 提取 `REQ-20260416-0017`
|
||||||
|
2. 最近对话中提到的 REQ ID
|
||||||
|
3. MCP 查询当前用户的"进行中"需求(如只有一个,直接用)
|
||||||
|
|
||||||
|
**分支名含 `-iN` 时追加 `closes #N`:**
|
||||||
|
```
|
||||||
|
feat/REQ-xxx-i42 + commit → commit message 自动加 "closes #42"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 提交确认
|
||||||
|
|
||||||
|
提交前展示预览:
|
||||||
|
```
|
||||||
|
即将执行:
|
||||||
|
分支: feat/mcp-set-reviewers
|
||||||
|
Files: backend/mcp/tools/set_reviewers.go, mcp-task-bridge/src/tools/set-reviewers.ts
|
||||||
|
Message:
|
||||||
|
feat(mcp): 新增 set_requirement_reviewers 工具 (REQ-20260415-0023)
|
||||||
|
|
||||||
|
确认提交?(y/n)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Issue 集成规范(REQ-20260416-0017 P1-13)
|
||||||
|
|
||||||
|
借鉴 devflow-claude 的 issue 关联机制。
|
||||||
|
|
||||||
|
### 分支名 `-iN` 后缀
|
||||||
|
|
||||||
|
当任务/需求来自 Gitea/GitHub issue 时,分支名末尾追加 `-iN`:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat/REQ-20260416-0017-user-points-i12 ← issue #12
|
||||||
|
fix/login-token-expired-i5 ← issue #5
|
||||||
|
```
|
||||||
|
|
||||||
|
**规则**:
|
||||||
|
- `-iN` 仅当 issue 关联存在时追加
|
||||||
|
- `N` 为纯数字(不带 `#`)
|
||||||
|
- 位于分支名最末尾
|
||||||
|
|
||||||
|
### commit message 自动追加 `closes #N`
|
||||||
|
|
||||||
|
当分支名含 `-iN` 后缀时,commit message 末尾自动追加 `closes #N`:
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(user): 实现积分规则管理 (REQ-20260416-0017) closes #12
|
||||||
|
fix(auth): 修复 token 过期未刷新 closes #5
|
||||||
|
```
|
||||||
|
|
||||||
|
**效果**:PR 合并后 Gitea/GitHub 自动关闭关联 issue。
|
||||||
|
|
||||||
|
### Issue 编号读取优先级
|
||||||
|
|
||||||
|
| 优先级 | 来源 | 说明 |
|
||||||
|
|-------|------|------|
|
||||||
|
| 1 | `--from-issue=#N` 参数 | 用户显式指定 |
|
||||||
|
| 2 | 分支名 `-iN` 后缀 | 自动解析 `(-i(\d+))$` |
|
||||||
|
| 3 | MCP 需求文档关联的 issue | (预留) |
|
||||||
|
|
||||||
|
### 不触发的情况
|
||||||
|
|
||||||
|
- 分支名不含 `-iN` → 不追加 `closes`
|
||||||
|
- 用户显式说"不关联 issue" → 跳过
|
||||||
|
|
||||||
|
## 与 ai-proj 集成
|
||||||
|
|
||||||
|
- **查询当前需求**:通过 MCP `mcp__ai-proj__find_requirement` 或 `list_requirements` 找 user 进行中的
|
||||||
|
- **commit 后可选**:调用 `mcp__ai-proj__update_task` 更新关联任务进度
|
||||||
|
|
||||||
|
## 排除项
|
||||||
|
|
||||||
|
本 skill **不做**:
|
||||||
|
- 推送远程(留给 `/pr` 或 `git push`)
|
||||||
|
- 创建 PR(留给 pull-request skill)
|
||||||
|
- 代码评审(留给 dev-review skill)
|
||||||
|
|
||||||
|
职责边界清晰,防止单命令膨胀。
|
||||||
|
|
||||||
|
## 风险控制
|
||||||
|
|
||||||
|
1. **保护分支改动不得 commit** — 强制拦截
|
||||||
|
2. **message 必须用 Conventional Commits** — 后续 changelog 依赖
|
||||||
|
3. **REQ-XXX 关联尽量自动推断** — 但推断不出不阻断
|
||||||
|
|
||||||
|
## 安装方式
|
||||||
|
|
||||||
|
本 skill 自动随 ai-proj-helper marketplace 加载。用户说"/commit" 即激活。
|
||||||
|
|
||||||
|
## 参考
|
||||||
|
|
||||||
|
- devflow-claude: `plugins/req/commands/commit.md`
|
||||||
|
- REQ-20260416-0017 P0-6
|
||||||
11
skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-deploy-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-deploy-plugin",
|
||||||
|
"description": "Plugin for dev-deploy",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-deploy",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
776
skills-dev/dev-deploy-plugin/skills/SKILL.md
Normal file
776
skills-dev/dev-deploy-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,776 @@
|
|||||||
|
---
|
||||||
|
name: dev-deploy
|
||||||
|
description: 应用部署技能。支持 iOS TestFlight、Docker 容器等多平台部署。当用户提到部署、发布、TestFlight、上架、build、archive 相关任务时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 应用部署 Skill (dev-deploy)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
管理应用从构建到发布的完整部署流程,支持多平台:
|
||||||
|
- **iOS**: TestFlight 内测 / App Store 发布
|
||||||
|
- **Docker**: Staging / Production 容器部署
|
||||||
|
|
||||||
|
集成 ai-proj 任务系统进行部署记录和需求阶段推进。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 命令参考
|
||||||
|
|
||||||
|
| 命令 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `/deploy ios` | iOS TestFlight 部署 |
|
||||||
|
| `/deploy docker [staging\|prod]` | Docker 容器部署 |
|
||||||
|
| `/deploy status` | 查看部署状态 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## iOS TestFlight 部署
|
||||||
|
|
||||||
|
### 前置条件
|
||||||
|
|
||||||
|
| 项目 | 要求 |
|
||||||
|
|------|------|
|
||||||
|
| 构建机器 | macOS + Xcode(通过 SSH 访问) |
|
||||||
|
| 签名证书 | Apple Distribution 证书已安装在 Keychain |
|
||||||
|
| Provisioning Profile | App Store Distribution profile 已安装 |
|
||||||
|
| API Key | App Store Connect API Key (.p8) |
|
||||||
|
| sshpass | 本机安装用于非交互 SSH(`brew install hudochenkov/sshpass/sshpass`) |
|
||||||
|
| xcodegen | 构建机器安装用于从 project.yml 生成 xcodeproj |
|
||||||
|
|
||||||
|
### 完整部署流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. git push → 代码推送到远程仓库
|
||||||
|
2. SSH 连接构建机 → git pull 拉取最新代码
|
||||||
|
3. xcodebuild archive → 无签名构建 Archive
|
||||||
|
4. xcodebuild -exportArchive → Distribution 签名 + 上传 TestFlight
|
||||||
|
5. ASC API 补全 → 合规信息 + 测试说明 + build 关联版本
|
||||||
|
6. 验证 → 确认 TestFlight 状态为 IN_BETA_TESTING
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1: SSH 连接构建机
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 sshpass 进行非交互 SSH
|
||||||
|
sshpass -p '<password>' ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no <user>@<host> '<command>'
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键经验**:
|
||||||
|
- SSH 远程 codesign 需要先**解锁 Keychain**,否则报 `errSecInternalComponent`
|
||||||
|
- 还需要 `set-key-partition-list` 授权 codesign 访问密钥
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 必须在每次 SSH 会话开头执行
|
||||||
|
security unlock-keychain -p "<password>" ~/Library/Keychains/login.keychain-db
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "<password>" ~/Library/Keychains/login.keychain-db
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: 拉取代码
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <repo-path> && git pull origin develop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3: Archive(无签名)
|
||||||
|
|
||||||
|
**关键经验**:Archive 阶段**不要签名**。原因:
|
||||||
|
- xcodebuild CLI 签名参数会泄漏到 SPM 依赖的 targets,导致 "does not support provisioning profiles" 错误
|
||||||
|
- 正确做法是 archive 时禁用签名,在 export 阶段单独签名
|
||||||
|
|
||||||
|
```bash
|
||||||
|
xcodebuild archive \
|
||||||
|
-project XiaoquCRM.xcodeproj \
|
||||||
|
-scheme XiaoquCRM \
|
||||||
|
-destination 'generic/platform=iOS' \
|
||||||
|
-configuration Release \
|
||||||
|
-archivePath ~/Desktop/XiaoquCRM.xcarchive \
|
||||||
|
-skipMacroValidation \
|
||||||
|
CODE_SIGNING_ALLOWED=NO \
|
||||||
|
CODE_SIGNING_REQUIRED=NO
|
||||||
|
```
|
||||||
|
|
||||||
|
**常见错误及解决**:
|
||||||
|
|
||||||
|
| 错误 | 原因 | 解决 |
|
||||||
|
|------|------|------|
|
||||||
|
| `Macro "X" must be enabled` | Swift Macros 安全限制 | 加 `-skipMacroValidation` |
|
||||||
|
| `cannot find type 'AdminFeature'` | xcodeproj 未包含新文件 | 运行 `xcodegen generate` 重新生成 |
|
||||||
|
| SPM 依赖报签名错误 | 签名参数泄漏到依赖 | Archive 用 `CODE_SIGNING_ALLOWED=NO` |
|
||||||
|
|
||||||
|
### Step 4: Export + 上传 TestFlight
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# ExportOptions.plist(提前创建在构建机上)
|
||||||
|
cat > /tmp/ExportOptions.plist << EOF
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||||
|
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>method</key>
|
||||||
|
<string>app-store-connect</string>
|
||||||
|
<key>destination</key>
|
||||||
|
<string>upload</string>
|
||||||
|
<key>teamID</key>
|
||||||
|
<string>{TEAM_ID}</string>
|
||||||
|
<key>signingStyle</key>
|
||||||
|
<string>manual</string>
|
||||||
|
<key>signingCertificate</key>
|
||||||
|
<string>Apple Distribution</string>
|
||||||
|
<key>provisioningProfiles</key>
|
||||||
|
<dict>
|
||||||
|
<key>{BUNDLE_ID}</key>
|
||||||
|
<string>{PROFILE_NAME}</string>
|
||||||
|
</dict>
|
||||||
|
<key>manageAppVersionAndBuildNumber</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Export + Upload
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath ~/Desktop/XiaoquCRM.xcarchive \
|
||||||
|
-exportOptionsPlist /tmp/ExportOptions.plist \
|
||||||
|
-exportPath ~/Desktop/XiaoquCRM-export \
|
||||||
|
-authenticationKeyPath {API_KEY_PATH} \
|
||||||
|
-authenticationKeyID {KEY_ID} \
|
||||||
|
-authenticationKeyIssuerID {ISSUER_ID}
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键经验**:
|
||||||
|
|
||||||
|
| 问题 | 教训 |
|
||||||
|
|------|------|
|
||||||
|
| `errSecInternalComponent` | SSH 远程签名前必须 `unlock-keychain` + `set-key-partition-list` |
|
||||||
|
| `No signing certificate "iOS Distribution"` | 机器上没装 Distribution 证书,需在 Xcode > Accounts 登录 Apple ID 下载 |
|
||||||
|
| `Redundant Binary Upload` | build number 重复,需要在 project.yml 递增 `CURRENT_PROJECT_VERSION` |
|
||||||
|
| `Missing required icon file` | 需要 Assets.xcassets/AppIcon.appiconset 含 1024x1024 PNG |
|
||||||
|
| `UIInterfaceOrientation` iPad 错误 | 必须声明 iPad 四方向支持,或设置 `UIRequiresFullScreen=true` |
|
||||||
|
| `Cloud signing permission error` | API Key 权限不够或 Issuer ID 错误;改用手动签名 + 本地 profile |
|
||||||
|
|
||||||
|
### Step 5: ASC API 补全 TestFlight 信息
|
||||||
|
|
||||||
|
上传成功后,需要通过 App Store Connect API 补全三项信息,否则测试者收不到通知或无法安装:
|
||||||
|
|
||||||
|
#### 5.1 生成 JWT Token
|
||||||
|
|
||||||
|
```python
|
||||||
|
import jwt, time
|
||||||
|
key = open("AuthKey_XXXXXX.p8").read()
|
||||||
|
token = jwt.encode(
|
||||||
|
{"iss": "{ISSUER_ID}", "iat": int(time.time()),
|
||||||
|
"exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1"},
|
||||||
|
key, algorithm="ES256",
|
||||||
|
headers={"kid": "{KEY_ID}"} # ← 必须包含 kid!
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
**关键经验**:JWT 必须包含 `headers={"kid": KEY_ID}`,否则 401 认证失败。还需要安装 `cryptography` 库支持 ES256。
|
||||||
|
|
||||||
|
#### 5.2 设置出口合规
|
||||||
|
|
||||||
|
```
|
||||||
|
PATCH /v1/builds/{build_id}
|
||||||
|
{"data": {"type": "builds", "id": "{build_id}",
|
||||||
|
"attributes": {"usesNonExemptEncryption": false}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
不设置此项,build 会卡在 "Missing Compliance" 状态,内部测试者无法安装。
|
||||||
|
|
||||||
|
#### 5.3 填写测试说明 (whatsNew)
|
||||||
|
|
||||||
|
```
|
||||||
|
# 先获取 localization ID
|
||||||
|
GET /v1/builds/{build_id}/betaBuildLocalizations
|
||||||
|
|
||||||
|
# 更新 whatsNew
|
||||||
|
PATCH /v1/betaBuildLocalizations/{loc_id}
|
||||||
|
{"data": {"type": "betaBuildLocalizations", "id": "{loc_id}",
|
||||||
|
"attributes": {"whatsNew": "更新内容..."}}}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 5.4 关联 Build 到 App Store 版本
|
||||||
|
|
||||||
|
**关键经验**:App Store Connect 页面的 App Icon 来自关联的 build。如果没有把 build 关联到 App Store 版本,图标显示为空。
|
||||||
|
|
||||||
|
```
|
||||||
|
# 关联 build 到版本
|
||||||
|
PATCH /v1/appStoreVersions/{version_id}/relationships/build
|
||||||
|
{"data": {"type": "builds", "id": "{build_id}"}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 6: 验证部署状态
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 检查 build 状态
|
||||||
|
GET /v1/builds/{build_id}?include=buildBetaDetail
|
||||||
|
|
||||||
|
# 期望结果:
|
||||||
|
# processingState: VALID
|
||||||
|
# internalBuildState: IN_BETA_TESTING
|
||||||
|
# usesNonExemptEncryption: false
|
||||||
|
```
|
||||||
|
|
||||||
|
### 一键部署脚本模板
|
||||||
|
|
||||||
|
将以上步骤整合为单次 SSH 调用:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sshpass -p '<password>' ssh -o PreferredAuthentications=password \
|
||||||
|
-o PubkeyAuthentication=no <user>@<host> '
|
||||||
|
# 0. Keychain
|
||||||
|
security unlock-keychain -p "<password>" ~/Library/Keychains/login.keychain-db
|
||||||
|
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "<password>" ~/Library/Keychains/login.keychain-db 2>/dev/null
|
||||||
|
|
||||||
|
# 1. Pull
|
||||||
|
cd <repo> && git pull origin develop
|
||||||
|
|
||||||
|
# 2. Archive
|
||||||
|
cd ios && rm -rf ~/Desktop/App.xcarchive ~/Desktop/App-export
|
||||||
|
xcodebuild archive -project App.xcodeproj -scheme App \
|
||||||
|
-destination "generic/platform=iOS" -configuration Release \
|
||||||
|
-archivePath ~/Desktop/App.xcarchive \
|
||||||
|
-skipMacroValidation CODE_SIGNING_ALLOWED=NO CODE_SIGNING_REQUIRED=NO \
|
||||||
|
2>&1 | tail -1
|
||||||
|
|
||||||
|
# 3. Export + Upload
|
||||||
|
xcodebuild -exportArchive \
|
||||||
|
-archivePath ~/Desktop/App.xcarchive \
|
||||||
|
-exportOptionsPlist /tmp/ExportOptions.plist \
|
||||||
|
-exportPath ~/Desktop/App-export \
|
||||||
|
-authenticationKeyPath <key_path> \
|
||||||
|
-authenticationKeyID <key_id> \
|
||||||
|
-authenticationKeyIssuerID <issuer_id> \
|
||||||
|
2>&1 | grep -E "Upload|EXPORT|error:" | tail -5
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## iOS 部署检查清单
|
||||||
|
|
||||||
|
部署前逐项确认:
|
||||||
|
|
||||||
|
- [ ] build number 已递增(`CURRENT_PROJECT_VERSION` in project.yml)
|
||||||
|
- [ ] `xcodegen generate` 已运行(新文件已包含在 xcodeproj 中)
|
||||||
|
- [ ] 代码已 push 到远程仓库
|
||||||
|
- [ ] 构建机可 SSH 访问
|
||||||
|
- [ ] Assets.xcassets 包含 1024x1024 App Icon
|
||||||
|
- [ ] Info.plist 包含 iPad 四方向支持
|
||||||
|
- [ ] Distribution 证书已安装在构建机 Keychain
|
||||||
|
|
||||||
|
部署后逐项确认:
|
||||||
|
|
||||||
|
- [ ] Archive 成功
|
||||||
|
- [ ] Export + Upload 成功
|
||||||
|
- [ ] 合规信息已设置(usesNonExemptEncryption)
|
||||||
|
- [ ] 测试说明已填写(whatsNew)
|
||||||
|
- [ ] Build 已关联到 App Store 版本
|
||||||
|
- [ ] TestFlight 状态为 IN_BETA_TESTING
|
||||||
|
- [ ] 测试者收到更新通知
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Docker Staging/Production 部署
|
||||||
|
|
||||||
|
### 架构概览
|
||||||
|
|
||||||
|
```
|
||||||
|
develop push → Build Image → Push ACR → SSH Deploy (staging) → Health Check
|
||||||
|
main push → Build Image → Push ACR → 人工审批 → SSH Deploy (prod) → Health Check
|
||||||
|
```
|
||||||
|
|
||||||
|
| 组件 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 服务器 | 39.104.87.246(阿里云 ECS) |
|
||||||
|
| Registry | Aliyun ACR: `crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com` |
|
||||||
|
| 镜像 | `xiaoqu-gateway`, `xiaoqu-web` |
|
||||||
|
| SSH Key | `~/.ssh/xiaoqu.pem` |
|
||||||
|
| 部署方式 | Docker Compose |
|
||||||
|
|
||||||
|
### 完整部署流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 本地构建镜像 → docker build -t <image>:<tag>
|
||||||
|
2. 推送到 ACR → docker push <registry>/<image>:<tag>
|
||||||
|
3. SSH 到服务器 → docker compose pull + up -d
|
||||||
|
4. 健康检查 → curl /health
|
||||||
|
5. 通知 → 飞书 Webhook 发送部署结果
|
||||||
|
```
|
||||||
|
|
||||||
|
### Staging 部署(develop 分支自动触发)
|
||||||
|
|
||||||
|
Push 到 `develop` 分支自动触发 staging 部署。流程:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 构建镜像(tag 用 commit SHA 前 8 位)
|
||||||
|
TAG=$(git rev-parse --short=8 HEAD)
|
||||||
|
REGISTRY=crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com
|
||||||
|
|
||||||
|
docker build -t $REGISTRY/xiaoqu-gateway:$TAG -f gateway/Dockerfile .
|
||||||
|
docker build -t $REGISTRY/xiaoqu-web:$TAG -f web/Dockerfile .
|
||||||
|
|
||||||
|
# 2. 推送到 ACR
|
||||||
|
docker push $REGISTRY/xiaoqu-gateway:$TAG
|
||||||
|
docker push $REGISTRY/xiaoqu-web:$TAG
|
||||||
|
|
||||||
|
# 3. SSH 部署
|
||||||
|
ssh -i ~/.ssh/xiaoqu.pem root@39.104.87.246 "
|
||||||
|
cd /opt/xiaoqu/staging
|
||||||
|
export IMAGE_TAG=$TAG
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
"
|
||||||
|
|
||||||
|
# 4. 健康检查
|
||||||
|
sleep 10
|
||||||
|
curl -sf http://39.104.87.246:8080/health || echo 'Health check failed!'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production 部署(手动审批)
|
||||||
|
|
||||||
|
Production 部署需要人工确认,不会自动触发:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 build-and-push 脚本
|
||||||
|
./scripts/build-and-push.sh prod --detect --deploy --wait --verify
|
||||||
|
|
||||||
|
# 或手动执行:
|
||||||
|
TAG=v1.2.3 # 使用语义化版本号
|
||||||
|
REGISTRY=crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com
|
||||||
|
|
||||||
|
# 构建 + 推送
|
||||||
|
docker build -t $REGISTRY/xiaoqu-gateway:$TAG -f gateway/Dockerfile .
|
||||||
|
docker build -t $REGISTRY/xiaoqu-web:$TAG -f web/Dockerfile .
|
||||||
|
docker push $REGISTRY/xiaoqu-gateway:$TAG
|
||||||
|
docker push $REGISTRY/xiaoqu-web:$TAG
|
||||||
|
|
||||||
|
# 部署(生产环境目录)
|
||||||
|
ssh -i ~/.ssh/xiaoqu.pem root@39.104.87.246 "
|
||||||
|
cd /opt/xiaoqu/production
|
||||||
|
export IMAGE_TAG=$TAG
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
"
|
||||||
|
|
||||||
|
# 验证
|
||||||
|
curl -sf http://39.104.87.246/health && echo 'Production deploy OK'
|
||||||
|
```
|
||||||
|
|
||||||
|
### build-and-push 脚本模板
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# scripts/build-and-push.sh
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ENV=${1:-staging}
|
||||||
|
REGISTRY=crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com
|
||||||
|
SERVER=39.104.87.246
|
||||||
|
SSH_KEY=~/.ssh/xiaoqu.pem
|
||||||
|
IMAGES=(xiaoqu-gateway xiaoqu-web)
|
||||||
|
|
||||||
|
# 确定 tag
|
||||||
|
if [ "$ENV" = "prod" ]; then
|
||||||
|
TAG=${2:-$(git describe --tags --abbrev=0)}
|
||||||
|
else
|
||||||
|
TAG=$(git rev-parse --short=8 HEAD)
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Deploying to $ENV with tag $TAG ==="
|
||||||
|
|
||||||
|
# 构建
|
||||||
|
for img in "${IMAGES[@]}"; do
|
||||||
|
echo "Building $img..."
|
||||||
|
docker build -t $REGISTRY/$img:$TAG -f ${img#xiaoqu-}/Dockerfile .
|
||||||
|
done
|
||||||
|
|
||||||
|
# 推送
|
||||||
|
for img in "${IMAGES[@]}"; do
|
||||||
|
echo "Pushing $img..."
|
||||||
|
docker push $REGISTRY/$img:$TAG
|
||||||
|
done
|
||||||
|
|
||||||
|
# 部署
|
||||||
|
DEPLOY_DIR=/opt/xiaoqu/$ENV
|
||||||
|
ssh -i $SSH_KEY root@$SERVER "
|
||||||
|
cd $DEPLOY_DIR
|
||||||
|
export IMAGE_TAG=$TAG
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d --remove-orphans
|
||||||
|
"
|
||||||
|
|
||||||
|
# 健康检查(重试 3 次)
|
||||||
|
echo "Waiting for health check..."
|
||||||
|
for i in 1 2 3; do
|
||||||
|
sleep 5
|
||||||
|
if curl -sf http://$SERVER/health > /dev/null 2>&1; then
|
||||||
|
echo "✓ Health check passed"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $i failed, retrying..."
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "✗ Health check failed after 3 attempts"
|
||||||
|
exit 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Compose 示例
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# docker-compose.yml
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
gateway:
|
||||||
|
image: crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com/xiaoqu-gateway:${IMAGE_TAG:-latest}
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
- DATABASE_URL=postgres://...
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com/xiaoqu-web:${IMAGE_TAG:-latest}
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
gateway:
|
||||||
|
condition: service_healthy
|
||||||
|
restart: unless-stopped
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署前健康检查
|
||||||
|
|
||||||
|
部署前进行预检,避免部署失败浪费时间。
|
||||||
|
|
||||||
|
### iOS 预检
|
||||||
|
|
||||||
|
```bash
|
||||||
|
preflight_ios() {
|
||||||
|
local errors=0
|
||||||
|
|
||||||
|
# 检查 Distribution 证书
|
||||||
|
if ! security find-identity -v -p codesigning | grep -q "Apple Distribution"; then
|
||||||
|
echo "ERROR: Apple Distribution 证书未安装"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 Provisioning Profile 有效期
|
||||||
|
local profile_dir="$HOME/Library/MobileDevice/Provisioning Profiles"
|
||||||
|
if [ -d "$profile_dir" ]; then
|
||||||
|
for profile in "$profile_dir"/*.mobileprovision; do
|
||||||
|
local expiry
|
||||||
|
expiry=$(security cms -D -i "$profile" 2>/dev/null | plutil -extract ExpirationDate raw - 2>/dev/null)
|
||||||
|
if [ -n "$expiry" ]; then
|
||||||
|
local expiry_epoch
|
||||||
|
expiry_epoch=$(date -j -f "%Y-%m-%dT%H:%M:%SZ" "$expiry" "+%s" 2>/dev/null)
|
||||||
|
local now_epoch
|
||||||
|
now_epoch=$(date "+%s")
|
||||||
|
if [ "$expiry_epoch" -lt "$now_epoch" ]; then
|
||||||
|
echo "WARNING: Profile 已过期: $(basename "$profile")"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
else
|
||||||
|
echo "ERROR: Provisioning Profiles 目录不存在"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 API Key
|
||||||
|
if [ ! -f "${API_KEY_PATH:-/dev/null}" ]; then
|
||||||
|
echo "ERROR: ASC API Key (.p8) 文件不存在: $API_KEY_PATH"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 Xcode
|
||||||
|
if ! xcode-select -p > /dev/null 2>&1; then
|
||||||
|
echo "ERROR: Xcode Command Line Tools 未安装"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $errors -gt 0 ]; then
|
||||||
|
echo "iOS 预检失败: $errors 个问题"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "iOS 预检通过"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker 预检
|
||||||
|
|
||||||
|
```bash
|
||||||
|
preflight_docker() {
|
||||||
|
local errors=0
|
||||||
|
|
||||||
|
# 检查 Docker daemon
|
||||||
|
if ! docker info > /dev/null 2>&1; then
|
||||||
|
echo "ERROR: Docker daemon 未运行"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 ACR registry 可达
|
||||||
|
local registry=crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com
|
||||||
|
if ! docker login $registry --username dummy --password dummy 2>&1 | grep -qv "connection refused"; then
|
||||||
|
# login 会失败但不应该是 connection refused
|
||||||
|
echo "WARNING: ACR registry 可能不可达(将在 push 时验证)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查 SSH 连通性
|
||||||
|
if ! ssh -i ~/.ssh/xiaoqu.pem -o ConnectTimeout=5 -o BatchMode=yes root@39.104.87.246 "echo ok" > /dev/null 2>&1; then
|
||||||
|
echo "ERROR: 无法 SSH 连接到部署服务器 39.104.87.246"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查服务器磁盘空间
|
||||||
|
local disk_usage
|
||||||
|
disk_usage=$(ssh -i ~/.ssh/xiaoqu.pem root@39.104.87.246 "df -h / | tail -1 | awk '{print \$5}' | tr -d '%'" 2>/dev/null)
|
||||||
|
if [ -n "$disk_usage" ] && [ "$disk_usage" -gt 85 ]; then
|
||||||
|
echo "WARNING: 服务器磁盘使用率 ${disk_usage}%(建议清理 docker system prune)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 检查本地磁盘空间
|
||||||
|
local local_disk
|
||||||
|
local_disk=$(df -h . | tail -1 | awk '{print $5}' | tr -d '%')
|
||||||
|
if [ "$local_disk" -gt 90 ]; then
|
||||||
|
echo "ERROR: 本地磁盘使用率 ${local_disk}%,空间不足"
|
||||||
|
((errors++))
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ $errors -gt 0 ]; then
|
||||||
|
echo "Docker 预检失败: $errors 个问题"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "Docker 预检通过"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 回滚策略
|
||||||
|
|
||||||
|
### iOS TestFlight 回滚
|
||||||
|
|
||||||
|
TestFlight **无法真正回滚**已安装的版本,但有以下应急手段:
|
||||||
|
|
||||||
|
| 手段 | 说明 | API |
|
||||||
|
|------|------|-----|
|
||||||
|
| 停止分发 | 将 build 从测试中移除,用户不再收到更新 | `PATCH /v1/builds/{id}` 设置 `expired: true` |
|
||||||
|
| 过期 build | 强制过期有问题的 build | 同上 |
|
||||||
|
| 紧急热修 | 构建新版本覆盖上线 | 常规部署流程 |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通过 ASC API 停止分发某个 build
|
||||||
|
curl -X PATCH "https://api.appstoreconnect.apple.com/v1/builds/$BUILD_ID" \
|
||||||
|
-H "Authorization: Bearer $JWT_TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"data":{"type":"builds","id":"'$BUILD_ID'","attributes":{"expired":true}}}'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker 回滚
|
||||||
|
|
||||||
|
Docker 回滚相对简单,拉取上一个正常版本的镜像重新部署即可:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. 确定上一个正常的 tag
|
||||||
|
PREVIOUS_TAG=<previous-good-tag>
|
||||||
|
REGISTRY=crpi-q4nnuivosic0zc98.cn-beijing.personal.cr.aliyuncs.com
|
||||||
|
|
||||||
|
# 2. 在服务器上回滚
|
||||||
|
ssh -i ~/.ssh/xiaoqu.pem root@39.104.87.246 "
|
||||||
|
cd /opt/xiaoqu/production # 或 /opt/xiaoqu/staging
|
||||||
|
export IMAGE_TAG=$PREVIOUS_TAG
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
"
|
||||||
|
|
||||||
|
# 3. 验证回滚成功
|
||||||
|
curl -sf http://39.104.87.246/health && echo 'Rollback OK'
|
||||||
|
```
|
||||||
|
|
||||||
|
### 数据库回滚注意事项
|
||||||
|
|
||||||
|
| 场景 | 策略 |
|
||||||
|
|------|------|
|
||||||
|
| 可逆 migration(加列、加表) | 部署回滚后数据库无需回滚,旧代码忽略新列 |
|
||||||
|
| 不可逆 migration(删列、改类型) | **必须先回滚 migration 再回滚代码**,否则旧代码报错 |
|
||||||
|
| 数据 migration | 评估是否需要补偿脚本,建议 migration 前做备份快照 |
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 数据库 migration 回滚示例(如果使用 golang-migrate)
|
||||||
|
ssh -i ~/.ssh/xiaoqu.pem root@39.104.87.246 "
|
||||||
|
docker compose exec gateway migrate -path /migrations -database \$DATABASE_URL down 1
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署监控
|
||||||
|
|
||||||
|
### Post-deploy 健康检查模式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 通用部署后验证函数
|
||||||
|
post_deploy_verify() {
|
||||||
|
local url=$1
|
||||||
|
local max_retries=${2:-5}
|
||||||
|
local interval=${3:-10}
|
||||||
|
|
||||||
|
echo "Verifying deployment at $url ..."
|
||||||
|
for i in $(seq 1 $max_retries); do
|
||||||
|
local status
|
||||||
|
status=$(curl -sf -o /dev/null -w "%{http_code}" "$url" 2>/dev/null || echo "000")
|
||||||
|
if [ "$status" = "200" ]; then
|
||||||
|
echo "Health check passed (attempt $i/$max_retries)"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
echo "Attempt $i/$max_retries: status=$status, retrying in ${interval}s..."
|
||||||
|
sleep $interval
|
||||||
|
done
|
||||||
|
echo "Health check FAILED after $max_retries attempts"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
post_deploy_verify "http://39.104.87.246/health" 5 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 飞书通知模板
|
||||||
|
|
||||||
|
部署完成后通过飞书 Webhook 发送通知:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 部署成功通知
|
||||||
|
send_feishu_deploy_notification() {
|
||||||
|
local env=$1 # staging / production
|
||||||
|
local version=$2 # 版本号或 tag
|
||||||
|
local status=$3 # success / failure
|
||||||
|
local detail=$4 # 额外说明
|
||||||
|
|
||||||
|
local WEBHOOK_URL="<飞书群 Webhook 地址>"
|
||||||
|
|
||||||
|
if [ "$status" = "success" ]; then
|
||||||
|
local color="green"
|
||||||
|
local emoji="✅"
|
||||||
|
else
|
||||||
|
local color="red"
|
||||||
|
local emoji="❌"
|
||||||
|
fi
|
||||||
|
|
||||||
|
curl -s -X POST "$WEBHOOK_URL" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"msg_type": "interactive",
|
||||||
|
"card": {
|
||||||
|
"header": {
|
||||||
|
"title": {"tag": "plain_text", "content": "'"$emoji"' 部署通知 - '"$env"'"},
|
||||||
|
"template": "'"$color"'"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{"tag": "div", "text": {"tag": "lark_md", "content": "**环境**: '"$env"'\n**版本**: '"$version"'\n**状态**: '"$status"'\n**时间**: '"$(date '+%Y-%m-%d %H:%M:%S')"'\n**详情**: '"$detail"'"}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 使用示例
|
||||||
|
send_feishu_deploy_notification "production" "v1.2.3" "success" "Gateway + Web 部署完成"
|
||||||
|
send_feishu_deploy_notification "staging" "abc12345" "failure" "Health check 超时"
|
||||||
|
```
|
||||||
|
|
||||||
|
### iOS TestFlight 构建状态监控
|
||||||
|
|
||||||
|
通过 ASC API 持续监控 build 处理状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 监控 TestFlight build 处理状态
|
||||||
|
monitor_testflight_build() {
|
||||||
|
local build_id=$1
|
||||||
|
local jwt_token=$2
|
||||||
|
local max_wait=600 # 最长等待 10 分钟
|
||||||
|
local elapsed=0
|
||||||
|
|
||||||
|
while [ $elapsed -lt $max_wait ]; do
|
||||||
|
local response
|
||||||
|
response=$(curl -s "https://api.appstoreconnect.apple.com/v1/builds/$build_id" \
|
||||||
|
-H "Authorization: Bearer $jwt_token")
|
||||||
|
|
||||||
|
local state
|
||||||
|
state=$(echo "$response" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['attributes']['processingState'])" 2>/dev/null)
|
||||||
|
|
||||||
|
echo "[$(date '+%H:%M:%S')] Build $build_id: $state"
|
||||||
|
|
||||||
|
case "$state" in
|
||||||
|
VALID)
|
||||||
|
echo "Build 处理完成,可用于测试"
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
FAILED|INVALID)
|
||||||
|
echo "Build 处理失败: $state"
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
PROCESSING)
|
||||||
|
sleep 30
|
||||||
|
((elapsed+=30))
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
sleep 15
|
||||||
|
((elapsed+=15))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Build 处理超时(${max_wait}s)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与需求工作流集成
|
||||||
|
|
||||||
|
部署完成后更新需求状态:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推进到 released
|
||||||
|
ai-proj req advance --id <req_id> --to released
|
||||||
|
|
||||||
|
# 创建部署任务并关联
|
||||||
|
ai-proj task create --title "【部署】TestFlight 发布: {需求标题}"
|
||||||
|
ai-proj req link --id <req_id> --task-ids <task_id>
|
||||||
|
|
||||||
|
# 附加部署文档
|
||||||
|
ai-proj task append-doc --id <task_id> --content "部署记录..."
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 经验教训汇总
|
||||||
|
|
||||||
|
### iOS TestFlight 部署的 10 个坑
|
||||||
|
|
||||||
|
| # | 坑 | 解决方案 |
|
||||||
|
|---|-----|---------|
|
||||||
|
| 1 | SSH 远程 codesign 失败 | `unlock-keychain` + `set-key-partition-list` |
|
||||||
|
| 2 | SPM 依赖报签名错误 | Archive 阶段 `CODE_SIGNING_ALLOWED=NO`,Export 阶段签名 |
|
||||||
|
| 3 | Swift Macros 被拒 | `-skipMacroValidation` |
|
||||||
|
| 4 | xcodeproj 缺文件 | 新增源文件后必须 `xcodegen generate` |
|
||||||
|
| 5 | 无 Distribution 证书 | Xcode > Accounts 登录 Apple ID 自动下载 |
|
||||||
|
| 6 | build number 冲突 | 每次部署前递增 `CURRENT_PROJECT_VERSION` |
|
||||||
|
| 7 | 缺 App Icon | Assets.xcassets + AppIcon.appiconset + 1024x1024 PNG |
|
||||||
|
| 8 | iPad 方向验证失败 | 声明四方向或 `UIRequiresFullScreen=true` |
|
||||||
|
| 9 | ASC API 401 | JWT 必须包含 `kid` header + 正确的 Issuer ID |
|
||||||
|
| 10 | App Store 图标为空 | 需将 build 关联到 App Store 版本(PATCH relationships/build) |
|
||||||
|
| 11 | SSH 长连接断开 | xcodebuild 3-4 分钟无输出,Tailscale 断连。用 nohup 后台执行 |
|
||||||
|
| 12 | xcodegen 后 cwd 错乱 | `cd ios && xcodegen && cd ..` 失败时不回退。用 subshell `(cd ios && xcodegen)` |
|
||||||
11
skills-dev/dev-integration-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-integration-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-integration-plugin",
|
||||||
|
"description": "前后端联调技能。API 契约验证、联调报告、纯后端需求自动跳过。对应 req 流程 integration 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-integration",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
154
skills-dev/dev-integration-plugin/skills/SKILL.md
Normal file
154
skills-dev/dev-integration-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
---
|
||||||
|
name: dev-integration
|
||||||
|
description: 前后端联调技能。API 契约验证、接口对接、联调报告生成。对应 req 流程 integration 阶段。纯后端需求自动跳过。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 前后端联调 Skill (dev-integration)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本技能用于前后端联调阶段,确保实际实现与 API 契约一致。
|
||||||
|
|
||||||
|
**核心价值**:发现契约偏差(字段名不一致、类型不匹配、缺少错误码处理)比测试阶段更早、修复成本更低。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技能间契约
|
||||||
|
|
||||||
|
| 上游 | 本技能输入 | 本技能输出 | 下游 |
|
||||||
|
|------|-----------|-----------|------|
|
||||||
|
| dev-coding | 已实现的前后端代码 + API 契约(来自 req-design) | 联调报告(通过/不通过 + 问题列表) | dev-test |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 自动跳过条件
|
||||||
|
|
||||||
|
以下情况 integration 阶段**自动通过**,无需执行联调:
|
||||||
|
|
||||||
|
| 条件 | 原因 |
|
||||||
|
|------|------|
|
||||||
|
| 需求只有后端 implementation 任务(无前端任务) | 没有前端对接,无需联调 |
|
||||||
|
| 需求只有前端 implementation 任务(无后端任务) | 使用已有 API,无需联调 |
|
||||||
|
| 需求无 implementation 任务(纯 skill/ops/doc) | 非代码需求 |
|
||||||
|
|
||||||
|
**检测方法**:
|
||||||
|
```
|
||||||
|
get_requirement_tasks → 检查 linkRole=implementation 的任务标题
|
||||||
|
含【开发-后端】和【开发-前端】→ 需要联调
|
||||||
|
仅含一端 → 自动跳过
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 检查是否需要联调
|
||||||
|
├── 获取 implementation 任务列表
|
||||||
|
├── 判断是否有前后端双端任务
|
||||||
|
└── 仅单端 → 自动跳过,生成跳过说明
|
||||||
|
|
||||||
|
2. 获取 API 契约
|
||||||
|
├── 从 req-design 文档中提取 API 契约
|
||||||
|
└── 无契约 → 从代码反推接口定义
|
||||||
|
|
||||||
|
3. 契约验证
|
||||||
|
├── 后端实际接口 vs 契约定义
|
||||||
|
│ ├── URL 路径是否一致
|
||||||
|
│ ├── 请求/响应字段名是否一致
|
||||||
|
│ ├── 字段类型是否匹配
|
||||||
|
│ └── 错误码是否完整
|
||||||
|
├── 前端调用 vs 契约定义
|
||||||
|
│ ├── API service 调用路径是否正确
|
||||||
|
│ ├── 请求参数是否完整
|
||||||
|
│ └── 响应处理是否覆盖所有错误码
|
||||||
|
└── 前端 ↔ 后端一致性
|
||||||
|
├── 字段命名一致(camelCase vs snake_case 转换)
|
||||||
|
└── 分页参数格式一致
|
||||||
|
|
||||||
|
4. 功能对接验证
|
||||||
|
├── 前端表单字段 vs 后端 binding 规则
|
||||||
|
├── 前端列表列 vs 后端响应字段
|
||||||
|
└── 前端状态流转 vs 后端状态机
|
||||||
|
|
||||||
|
5. 生成联调报告
|
||||||
|
├── 契约一致性结果
|
||||||
|
├── 发现的偏差列表
|
||||||
|
└── 结论:通过/不通过
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 联调报告模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 联调报告 — {需求标题}
|
||||||
|
|
||||||
|
**日期**: YYYY-MM-DD
|
||||||
|
**API 契约来源**: {req-design 文档 / 代码反推}
|
||||||
|
|
||||||
|
### 契约验证结果
|
||||||
|
|
||||||
|
| # | 接口 | 契约 | 后端实际 | 前端调用 | 结果 |
|
||||||
|
|---|------|------|---------|---------|------|
|
||||||
|
| 1 | POST /api/v1/xxx | ✅ 已定义 | ✅ 一致 | ✅ 一致 | PASS |
|
||||||
|
| 2 | GET /api/v1/xxx | ✅ 已定义 | ⚠️ 字段名不一致 | ✅ 一致 | FAIL |
|
||||||
|
|
||||||
|
### 发现的偏差
|
||||||
|
|
||||||
|
| # | 类型 | 接口 | 描述 | 影响 | 建议 |
|
||||||
|
|---|------|------|------|------|------|
|
||||||
|
| 1 | 字段名不一致 | GET /api/v1/xxx | 契约定义 `created_at`,后端返回 `createTime` | 前端解析失败 | 统一为 `created_at` |
|
||||||
|
|
||||||
|
### 结论
|
||||||
|
|
||||||
|
**{通过 / 不通过}**
|
||||||
|
|
||||||
|
{如不通过,列出必须修复的偏差编号}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 插件支持
|
||||||
|
|
||||||
|
| 插件 | 触发条件 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `api-contract-verify` | 有 API 变更 | 自动化契约验证(未来) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与 ai-proj 集成
|
||||||
|
|
||||||
|
### req 流程内
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建联调任务(如需要)
|
||||||
|
mcp__ai-proj__create_task({ title: "【联调】前后端对接: {需求标题}" })
|
||||||
|
mcp__ai-proj__link_tasks_to_requirement({
|
||||||
|
requirementId, taskIds: [taskId], linkRole: "implementation"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 附加联调报告
|
||||||
|
mcp__ai-proj__create-and-attach({
|
||||||
|
taskId, title: "联调报告", content: "<报告内容>"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自动跳过时
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 记录跳过原因
|
||||||
|
mcp__ai-proj__create-and-attach({
|
||||||
|
taskId: <设计任务ID>,
|
||||||
|
content: "## 联调阶段\n\n自动跳过:仅后端变更,无前端对接。"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **契约先行** — API 契约是联调的基准,没有契约就先补
|
||||||
|
2. **字段级验证** — 不只检查接口是否通,要检查每个字段名、类型、格式
|
||||||
|
3. **错误码覆盖** — 前端必须处理契约中定义的所有错误码
|
||||||
|
4. **snake_case 转换** — Go 后端用 snake_case,前端用 camelCase,确认自动转换正确
|
||||||
11
skills-dev/dev-ios-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-ios-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-ios-plugin",
|
||||||
|
"description": "iOS 开发插件。Swift/SwiftUI + MVVM 架构、TestFlight 部署、Xcode 构建。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-ios",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
90
skills-dev/dev-ios-plugin/skills/SKILL.md
Normal file
90
skills-dev/dev-ios-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
name: dev-ios
|
||||||
|
description: iOS 开发插件。Swift/SwiftUI + MVVM 架构,TestFlight 部署。当涉及 iOS 开发任务时按需加载。
|
||||||
|
---
|
||||||
|
|
||||||
|
# iOS 开发插件 (dev-ios)
|
||||||
|
|
||||||
|
## 架构:SwiftUI + MVVM
|
||||||
|
|
||||||
|
```
|
||||||
|
AI-Proj-iOS/
|
||||||
|
├── Core/ # 核心层
|
||||||
|
│ ├── Architecture/ # AppCoordinator, AppState
|
||||||
|
│ ├── Components/ # 通用 UI 组件
|
||||||
|
│ ├── Config.swift # 配置
|
||||||
|
│ ├── Services/ # APIEndpoints, AuthService, NetworkService, DIContainer
|
||||||
|
│ ├── Theme/ # 主题配置
|
||||||
|
│ └── Utilities/ # 设备适配
|
||||||
|
├── Features/ # 功能模块(MVVM)
|
||||||
|
│ └── Requirements/ # 示例:List/Detail View + ViewModel
|
||||||
|
├── Models/ # 数据模型 + DTOs
|
||||||
|
└── Resources/ # Assets
|
||||||
|
```
|
||||||
|
|
||||||
|
**开发顺序**:Model → DTO → APIEndpoints → ServiceProtocols → ViewModel → View
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
|
||||||
|
```swift
|
||||||
|
@MainActor
|
||||||
|
class TaskViewModel: ObservableObject {
|
||||||
|
@Published private(set) var tasks: [Task] = []
|
||||||
|
@Published private(set) var isLoading = false
|
||||||
|
@Published var error: String?
|
||||||
|
|
||||||
|
private let taskService: TaskServiceProtocol
|
||||||
|
|
||||||
|
init(taskService: TaskServiceProtocol) {
|
||||||
|
self.taskService = taskService
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadTasks() async {
|
||||||
|
guard !isLoading else { return }
|
||||||
|
isLoading = true
|
||||||
|
defer { isLoading = false }
|
||||||
|
|
||||||
|
do {
|
||||||
|
tasks = try await taskService.fetchTasks()
|
||||||
|
} catch {
|
||||||
|
self.error = error.localizedDescription
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**规则**:
|
||||||
|
- ViewModel 使用 `@MainActor`
|
||||||
|
- Published 属性用 `private(set)`
|
||||||
|
- 使用协议依赖注入
|
||||||
|
- `async/await` 而非 completion handler
|
||||||
|
- `guard` 提前返回,`defer` 确保状态重置
|
||||||
|
|
||||||
|
## 命名规范
|
||||||
|
|
||||||
|
| 类型 | 规范 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 文件/类 | 大驼峰 | `ManualListView.swift` |
|
||||||
|
| 协议 | 大驼峰 + Protocol | `ManualServiceProtocol` |
|
||||||
|
| 函数/变量 | 小驼峰 | `loadManuals()`, `isLoading` |
|
||||||
|
| 枚举 case | 小驼峰 | `case draft` |
|
||||||
|
|
||||||
|
## 构建与部署
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 构建
|
||||||
|
xcodebuild -scheme AI-Proj-iOS -configuration Debug
|
||||||
|
|
||||||
|
# 测试
|
||||||
|
xcodebuild test -scheme AI-Proj-iOS
|
||||||
|
|
||||||
|
# TestFlight 部署详见 memory: testflight-deploy.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### SwiftLint 沙盒错误
|
||||||
|
Xcode 15+ 默认启用 User Script Sandboxing → Build Settings → `ENABLE_USER_SCRIPT_SANDBOXING = NO`
|
||||||
|
|
||||||
|
### Personal Team 功能限制
|
||||||
|
免费账户不支持 Push Notifications / Associated Domains / App Groups → 从 Entitlements 中移除
|
||||||
11
skills-dev/dev-mcp-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-mcp-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-mcp-plugin",
|
||||||
|
"description": "MCP Bridge 开发插件。TypeScript MCP 服务开发、Token 管理、HTTP 客户端模式。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-mcp",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
59
skills-dev/dev-mcp-plugin/skills/SKILL.md
Normal file
59
skills-dev/dev-mcp-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
---
|
||||||
|
name: dev-mcp
|
||||||
|
description: MCP Bridge 开发插件。TypeScript MCP 服务开发,Token 管理,HTTP 客户端模式。当涉及 mcp-task-bridge 开发时按需加载。
|
||||||
|
---
|
||||||
|
|
||||||
|
# MCP Bridge 开发插件 (dev-mcp)
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
mcp-task-bridge/
|
||||||
|
├── index.ts # 入口,MCP server 注册
|
||||||
|
├── task-service.ts # 任务服务
|
||||||
|
├── document-service.ts # 文档服务
|
||||||
|
├── requirement-service.ts # 需求服务
|
||||||
|
├── base-client.ts # HTTP 基类(认证、重试)
|
||||||
|
├── types.ts # 类型定义
|
||||||
|
└── token-storage.ts # Token 持久化
|
||||||
|
```
|
||||||
|
|
||||||
|
## 代码规范
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
export class TaskService extends BaseClient {
|
||||||
|
async createTask(
|
||||||
|
title: string,
|
||||||
|
projectId: number = 1,
|
||||||
|
options: CreateTaskOptions = {}
|
||||||
|
): Promise<ApiResponse<Task>> {
|
||||||
|
try {
|
||||||
|
const response = await this.makeRequest<Task>(
|
||||||
|
'POST',
|
||||||
|
`/projects/${projectId}/tasks`,
|
||||||
|
{ title, project_id: projectId, ...options }
|
||||||
|
);
|
||||||
|
return response.success
|
||||||
|
? { success: true, data: response.data, message: `✅ 任务创建成功` }
|
||||||
|
: response;
|
||||||
|
} catch (error: any) {
|
||||||
|
return { success: false, error: `创建任务失败: ${error.message}` };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 关键规则
|
||||||
|
|
||||||
|
1. **MCP endpoint 前缀**:所有 MCP 专用后端接口必须包含 `/mcp/` 前缀
|
||||||
|
2. **修改后重新构建**:`npm run build` → `pkill -f "mcp-task-bridge/dist/index.js"`
|
||||||
|
3. **环境一致性**:不要跨环境混用数据(dev/staging/prod)
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev # 开发(hot reload)
|
||||||
|
npm run build # 编译 TypeScript
|
||||||
|
npm test # 快速测试
|
||||||
|
npm run test:integration # 集成测试
|
||||||
|
```
|
||||||
11
skills-dev/dev-pda-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-pda-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-pda-plugin",
|
||||||
|
"description": "PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先模式。按需加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-pda",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
49
skills-dev/dev-pda-plugin/skills/SKILL.md
Normal file
49
skills-dev/dev-pda-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: dev-pda
|
||||||
|
description: PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先。当涉及 PDA/手持终端开发时按需加载。
|
||||||
|
---
|
||||||
|
|
||||||
|
# PDA 应用开发插件 (dev-pda)
|
||||||
|
|
||||||
|
## 特点
|
||||||
|
|
||||||
|
- Android 原生开发(Kotlin)
|
||||||
|
- 扫码枪硬件集成
|
||||||
|
- 离线优先(本地 Room DB + 同步队列)
|
||||||
|
- 简洁 UI(大按钮、大字体、适配小屏幕)
|
||||||
|
|
||||||
|
## 扫码集成
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class ScanReceiver : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val barcode = intent.getStringExtra("SCAN_BARCODE")
|
||||||
|
// 处理扫码结果
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 离线存储
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Entity(tableName = "inventory")
|
||||||
|
data class Inventory(
|
||||||
|
@PrimaryKey val id: Long,
|
||||||
|
val barcode: String,
|
||||||
|
val quantity: Int,
|
||||||
|
@ColumnInfo(name = "sync_status")
|
||||||
|
val syncStatus: SyncStatus = SyncStatus.PENDING
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class SyncStatus { PENDING, SYNCED, FAILED }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 离线同步策略
|
||||||
|
|
||||||
|
```
|
||||||
|
操作 → 写入本地 DB (PENDING)
|
||||||
|
↓
|
||||||
|
网络可用 → 批量上传 → 成功 → 标记 SYNCED
|
||||||
|
↓ 失败
|
||||||
|
标记 FAILED → 下次重试
|
||||||
|
```
|
||||||
11
skills-dev/dev-review-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-review-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-review-plugin",
|
||||||
|
"description": "代码评审技能。五视角对抗性扫描法(攻击者/泄露者/并发者/边界者/依赖者),CR 报告生成,独立于 req 工作流可单独使用。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-review",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
280
skills-dev/dev-review-plugin/skills/SKILL.md
Normal file
280
skills-dev/dev-review-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,280 @@
|
|||||||
|
---
|
||||||
|
name: dev-review
|
||||||
|
description: 代码评审技能。五视角对抗性扫描法,用于 PR 代码审查、安全评审、质量检查。当执行 /req cr 或独立 PR review 时自动激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 代码评审 Skill (dev-review)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
独立的代码评审技能,核心方法论是**五视角对抗性扫描法**。
|
||||||
|
|
||||||
|
**适用场景**:
|
||||||
|
- `/req cr [REQ-ID]` — 需求流程中的代码评审阶段
|
||||||
|
- 独立 PR review — 不绑定需求的代码审查
|
||||||
|
- 安全评审 — 专项安全扫描
|
||||||
|
|
||||||
|
**核心原则**:实现阶段关注"怎么让它跑通",评审阶段关注**"怎么让它出错"**。AI 必须切换到对抗性思维。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技能间契约
|
||||||
|
|
||||||
|
| 上游 | 本技能输入 | 本技能输出 | 下游 |
|
||||||
|
|------|-----------|-----------|------|
|
||||||
|
| dev-coding | PR diff + 开发设计文档 | CR 报告(五视角扫描 + 发现汇总 + 结论) | dev-test |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 确定评审范围
|
||||||
|
├── git diff 获取变更文件列表
|
||||||
|
├── 统计文件数、行数
|
||||||
|
└── 排除 test 文件(单独审查)
|
||||||
|
|
||||||
|
2. 读取变更代码
|
||||||
|
├── 逐个读取变更<E58F98><E69BB4><EFBFBD>件源码
|
||||||
|
├── 理解业务上下文
|
||||||
|
└── 参考开发设计文档(如有)
|
||||||
|
|
||||||
|
3. 五视角扫描(核心)
|
||||||
|
├── 攻击<E694BB><E587BB><EFBFBD>视角
|
||||||
|
├── 泄露者视角
|
||||||
|
├── 并发者视角
|
||||||
|
├── 边界者视角
|
||||||
|
└── 依赖者视角
|
||||||
|
|
||||||
|
4. 加载项目检查清单(如有)
|
||||||
|
└── review-checklist 插件
|
||||||
|
|
||||||
|
5. 生成 CR 报告
|
||||||
|
├── 变更概要
|
||||||
|
├── 五视角扫描结果
|
||||||
|
├── 发现汇总表
|
||||||
|
└── 结论(通过/有条件通过/需修改)
|
||||||
|
|
||||||
|
6. 创建评审任务(req 流程内)
|
||||||
|
├── ai-proj task create【代码评审】
|
||||||
|
├── 关联需求(linkRole=code_review)
|
||||||
|
└── 附加 CR 报告文档
|
||||||
|
|
||||||
|
7. 处理发现
|
||||||
|
├── Critical/High → 创建修复任务
|
||||||
|
└── Medium/Low → 记录建议
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五视角对抗性扫描法
|
||||||
|
|
||||||
|
### 总览
|
||||||
|
|
||||||
|
| 视角 | 思维模式 | 核心问题 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| **1. 攻击者** | "我怎么绕过/<2F><><EFBFBD>用它?" | 跨租户泄露、越权访问、参数注入、重放攻击 |
|
||||||
|
| **2. 泄露者** | "它暴露了什么不该暴露的?" | 错误信息泄露、日志敏感数据、响应内部细节 |
|
||||||
|
| **3. 并发者** | "两个请求同时来会怎样?" | 竞态条件、双重扣款、幂等性缺失、锁粒度 |
|
||||||
|
| **4. 边界者** | "极端输入会怎样?" | 空值/零值/负值/超长字符串、类型溢出、分页越界 |
|
||||||
|
| **5. 依赖者** | "外部服务挂了会怎样?" | 超时处理、重试策略、降级方案、连接泄露 |
|
||||||
|
|
||||||
|
### 视角1:攻击者(多租户安全)
|
||||||
|
|
||||||
|
**思维模式**:我是恶意用户,如何绕过权限获取他人数据。
|
||||||
|
|
||||||
|
扫描清单:
|
||||||
|
- [ ] 所有 Store/Repository 层查询是否带 `tenant_id` 过滤?
|
||||||
|
- [ ] 通过 ID 直接查询的方法是否校验归属?
|
||||||
|
- [ ] 用户只能操作自己的数据?(consumer_id 校验)
|
||||||
|
- [ ] URL/请求参数是否有注入风险?(SQL、URL、命令注入)
|
||||||
|
- [ ] 外部输入是否直接拼接到查询/URL?(应使用参数化查询或编码)
|
||||||
|
- [ ] 批量操作是否逐条校验权限?(不能只校验第一条)
|
||||||
|
- [ ] 文件上传是否有类型/大小限制?
|
||||||
|
|
||||||
|
**典型发现示例**:
|
||||||
|
```
|
||||||
|
file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
|
||||||
|
攻击者可通过遍<EFBFBD><EFBFBD> ID 获取其他租户数据。
|
||||||
|
建议:添加 WHERE tenant_id = ? 条件。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 视角2:泄露者(信息安全)
|
||||||
|
|
||||||
|
**思维模式**:我是安全审计员,检查每个出口是否泄露了不该泄露的信息。
|
||||||
|
|
||||||
|
扫描清单:
|
||||||
|
- [ ] 错误消息是否泄露业务状态?(如"手机号未注册"暴露用户存在性)
|
||||||
|
- [ ] 日志是否打印了密码、token、密钥、身份证号?
|
||||||
|
- [ ] 响应是否包含不必要的内部字段?(如内部 ID、数据库字段名、堆栈跟踪)
|
||||||
|
- [ ] panic recover 后是否返回了内部错误详情?
|
||||||
|
- [ ] 导出/下载功能是否过滤了敏感字段?
|
||||||
|
|
||||||
|
### 视角3:并发者(数据一致性)
|
||||||
|
|
||||||
|
**思维模式**:两个用户同时操作同一条数据,会发生什么。
|
||||||
|
|
||||||
|
扫描清单:
|
||||||
|
- [ ] 涉及金额/库存变更是否使用事务 + 悲观锁/乐观锁?
|
||||||
|
- [ ] 关键操作是否有幂等保护?(bizNo 唯一索引、幂等键)
|
||||||
|
- [ ] 全局状态(如进程内计数器、缓存)重启后是否安全?
|
||||||
|
- [ ] 是否有 TOCTOU(检查-使用)竞态?(先查状态再操作,中间被修改)
|
||||||
|
- [ ] 并发创建是否会产生重复数据?(唯一约束)
|
||||||
|
|
||||||
|
### 视角4:边界者(健壮性)
|
||||||
|
|
||||||
|
**思维模<E7BBB4><E6A8A1>**:用最极端的输入来测试系统的承受能力。
|
||||||
|
|
||||||
|
扫描清单:
|
||||||
|
- [ ] 必填参数是否有 `binding:"required"` 校验?
|
||||||
|
- [ ] 数值参数是否有范围校验?(min/max,防止负数、溢出)
|
||||||
|
- [ ] 字符串是否有长度限制?(防止超长输入消耗内存)
|
||||||
|
- [ ] 分页参数是否有默认值和上限?(page_size 不能为 0 或 999999)
|
||||||
|
- [ ] 数组参数是否有长度限制?(批量操作不能传 10 万条)
|
||||||
|
- [ ] 空数组/空字符串是否正确处理?(不应触发不必要的数据库操作)
|
||||||
|
- [ ] 除零错误?百分比计算分母为 0?
|
||||||
|
|
||||||
|
### 视角5:依赖者(可靠性)
|
||||||
|
|
||||||
|
**思维模式**:外部服务全部挂掉,系统还能正常运行吗。
|
||||||
|
|
||||||
|
扫描清单:
|
||||||
|
- [ ] HTTP 客户端是否设置超时?(connect/read/write timeout)
|
||||||
|
- [ ] 外部 API 调用失败是否有合理的错误处理?(不能直接 panic)
|
||||||
|
- [ ] 是否有重试策略?重试是否有退避?是否幂等安全?
|
||||||
|
- [ ] 数据库连接池配置是否合理?(max idle/max open/lifetime)
|
||||||
|
- [ ] Redis 不可用时是否有降级方案?(缓存穿透到数据库)
|
||||||
|
- [ ] token 过期/刷新逻辑是否正确?(access vs refresh 不同策略)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CR 报告模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 代<><E4BBA3>评审报告 — {需求标题/PR 标题}
|
||||||
|
|
||||||
|
**日期**: YYYY-MM-DD
|
||||||
|
**评审范围**: {N} 个文件, {M} 行变更
|
||||||
|
**评审人**: AI (dev-review)
|
||||||
|
|
||||||
|
### 变更概要
|
||||||
|
{1-3 句描述本次变更的目的和范围}
|
||||||
|
|
||||||
|
### 五视角扫描结果
|
||||||
|
|
||||||
|
#### 1. 攻击者视角
|
||||||
|
{扫描发现,或 "未发现问题"}
|
||||||
|
|
||||||
|
#### 2. 泄露者视角
|
||||||
|
{扫描发现,或 "未发现问题"}
|
||||||
|
|
||||||
|
#### 3. 并发者视角
|
||||||
|
{扫描发现,或 "未发现问题"}
|
||||||
|
|
||||||
|
#### 4. 边界者视角
|
||||||
|
{扫描发现,或 "未发现问题"}
|
||||||
|
|
||||||
|
#### 5. 依赖者视角
|
||||||
|
{扫描发现,或 "未发现问题"}
|
||||||
|
|
||||||
|
### 审查发现汇总
|
||||||
|
|
||||||
|
| # | 严重度 | 文件:行号 | <20><><EFBFBD>角 | 描述 | 建议 |
|
||||||
|
|---|--------|----------|------|------|------|
|
||||||
|
| 1 | {Critical/High/Medium/Low} | {file:line} | {视角} | {问题} | {建议} |
|
||||||
|
|
||||||
|
### 统计
|
||||||
|
|
||||||
|
| 严重度 | 数量 |
|
||||||
|
|--------|------|
|
||||||
|
| Critical | 0 |
|
||||||
|
| High | 0 |
|
||||||
|
| Medium | 0 |
|
||||||
|
| Low | 0 |
|
||||||
|
|
||||||
|
### 结论
|
||||||
|
|
||||||
|
**{通过 / 有条件通过 / 需修改}**
|
||||||
|
|
||||||
|
{结论说明:如果有 Critical/High 必须修复后重新评审}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 严重度定义
|
||||||
|
|
||||||
|
| 严重度 | 含义 | 处理方式 |
|
||||||
|
|--------|------|---------|
|
||||||
|
| **Critical** | 安全漏洞、数据泄露、资金风险 | 必须修复,阻断合并 |
|
||||||
|
| **High** | 数据一致性风险、业务逻辑错误 | 必须修复,阻断合并 |
|
||||||
|
| **Medium** | 边界未处理、缺少校验、性能隐患 | 建议修复,不阻断 |
|
||||||
|
| **Low** | 代码风格、命名优化、文档补充 | 可选修复 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CR 报告质量门禁
|
||||||
|
|
||||||
|
`/req next` 从 review 阶段推进时,检查 CR 报告质量:
|
||||||
|
|
||||||
|
| 检查项 | 标准 |
|
||||||
|
|--------|------|
|
||||||
|
| 文档存在 | CR 任务有附加文档 |
|
||||||
|
| 字数 | ≥ 500 字 |
|
||||||
|
| 代码引用 | 含 `file:line` 格式的引用 |
|
||||||
|
| 五视角扫描 | 含全部 5 个视角章节 |
|
||||||
|
| 结论章节 | 含明确的通过/不通过结论 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 插件支持
|
||||||
|
|
||||||
|
| 插件 | 触发条件 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `review-checklist` | 每次 CR | 加载项目特定检查清单 |
|
||||||
|
| `figma-design-qa` | 有设计稿 | 设计还原度对比 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与 ai-proj 集成
|
||||||
|
|
||||||
|
### req 流程内(/req cr)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 1. 确认 implementation 任务已完成
|
||||||
|
mcp__ai-proj__get_requirement_tasks({ requirementId })
|
||||||
|
// 检查所有 linkRole=implementation 的任务状态
|
||||||
|
|
||||||
|
// 2. 创建 CR 任务
|
||||||
|
mcp__ai-proj__create_task({ title: "【代码评审】CR: {需求标题}" })
|
||||||
|
mcp__ai-proj__link_tasks_to_requirement({
|
||||||
|
requirementId, taskIds: [crTaskId], linkRole: "code_review"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. 附加 CR 报告
|
||||||
|
mcp__ai-proj__create-and-attach({
|
||||||
|
taskId: crTaskId,
|
||||||
|
title: "代码评审报告",
|
||||||
|
content: "<CR <20><><EFBFBD>告 Markdown>"
|
||||||
|
})
|
||||||
|
|
||||||
|
// 4. High/Critical 发现 → 创建修复任务
|
||||||
|
mcp__ai-proj__create_task({ title: "【修复】{问题描述}" })
|
||||||
|
mcp__ai-proj__link_tasks_to_requirement({
|
||||||
|
requirementId, taskIds: [fixTaskId], linkRole: "implementation"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 独立 PR review
|
||||||
|
|
||||||
|
不需要 ai-proj 集成,直接输出 CR 报告到对话。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **先理解再审查** — 读完所有变更<E58F98><E69BB4><EFBFBD>件后再开始扫描,避免断章取义
|
||||||
|
2. **对抗性思维** — 切换到"怎么让它出错"的心态,不是"怎么让它跑通"
|
||||||
|
3. **证据驱动** — 每个发现必须引用具体的 `file:line`
|
||||||
|
4. **严重度准确** — 不要所有问题都标 High,按实际影响分级
|
||||||
|
5. **建议可操作** — 每个发现必须附带具体修复建议
|
||||||
|
6. **关注变更** — 评审范围是 diff,不要对未变更的代码提意见(除非变更引入了对旧代码的新风险)
|
||||||
11
skills-dev/dev-scaffold-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/dev-scaffold-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "dev-scaffold-plugin",
|
||||||
|
"description": "模块脚手架插件。新建模块时自动生成分层代码骨架(Model/Repository/Service/Handler/Route)。挂载在 dev 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "dev-scaffold",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
78
skills-dev/dev-scaffold-plugin/skills/SKILL.md
Normal file
78
skills-dev/dev-scaffold-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
---
|
||||||
|
name: dev-scaffold
|
||||||
|
description: 模块脚手架插件。新建模块时自动生成分层代码骨架。挂载在 dev 阶段,新建模块时激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 模块脚手架插件 (dev-scaffold)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
当需要新建一个完整模块时,自动生成分层代码骨架,避免手动创建大量样板文件。
|
||||||
|
|
||||||
|
**触发条件**:
|
||||||
|
- 需求需要新建数据库表 + 完整 CRUD
|
||||||
|
- 开发设计文档中有"新增"类型的文件
|
||||||
|
|
||||||
|
## Go 后端脚手架
|
||||||
|
|
||||||
|
输入模块名(如 `manual`),生成以下文件:
|
||||||
|
|
||||||
|
```
|
||||||
|
backend/
|
||||||
|
├── models/manual.go # GORM 模型
|
||||||
|
├── database/manual_repository.go # Repository
|
||||||
|
├── services/manual_service.go # Service
|
||||||
|
├── handlers/manual_handler.go # Handler
|
||||||
|
├── routes/manual_routes.go # Route
|
||||||
|
└── migrations/YYYYMMDDHHMMSS_create_manual.up.sql # Migration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 生成规则
|
||||||
|
|
||||||
|
**Model** (`models/{name}.go`):
|
||||||
|
- struct 定义 + GORM tags
|
||||||
|
- TableName() 方法
|
||||||
|
- 标准字段:ID, TenantID, CreatedBy, CreatedAt, UpdatedAt, DeletedAt
|
||||||
|
|
||||||
|
**Repository** (`database/{name}_repository.go`):
|
||||||
|
- interface 定义
|
||||||
|
- Create, GetByID, List(分页), Update, Delete 方法
|
||||||
|
- 所有查询带 tenant_id 过滤
|
||||||
|
|
||||||
|
**Service** (`services/{name}_service.go`):
|
||||||
|
- interface 定义
|
||||||
|
- 注入 Repository
|
||||||
|
- 基础 CRUD + 业务校验
|
||||||
|
|
||||||
|
**Handler** (`handlers/{name}_handler.go`):
|
||||||
|
- Create, Get, List, Update, Delete 方法
|
||||||
|
- 请求参数绑定 + 验证
|
||||||
|
- 统一错误处理
|
||||||
|
|
||||||
|
**Route** (`routes/{name}_routes.go`):
|
||||||
|
- RESTful 路由注册
|
||||||
|
- Auth 中间件
|
||||||
|
|
||||||
|
**Migration** (`migrations/YYYYMMDDHHMMSS_create_{name}.up.sql`):
|
||||||
|
- CREATE TABLE + 标准字段 + 索引
|
||||||
|
|
||||||
|
## React 前端脚手架
|
||||||
|
|
||||||
|
输入模块名(如 `Manual`),生成:
|
||||||
|
|
||||||
|
```
|
||||||
|
frontend/src/
|
||||||
|
├── types/manual.ts # TypeScript 类型
|
||||||
|
├── services/manualService.ts # API Service
|
||||||
|
├── pages/ManualListPage.tsx # 列表页
|
||||||
|
└── pages/ManualDetailPage.tsx # 详情页(可选)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
```
|
||||||
|
用户: "新建 manual 模块的脚手架"
|
||||||
|
AI: 根据 req-design 的变更文件清单,生成所有骨架文件
|
||||||
|
```
|
||||||
|
|
||||||
|
**注意**:脚手架只生成骨架,具体业务逻辑需在骨架基础上补充。
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "dev-test",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
|
|||||||
| `ios-testing.md` | iOS 测试 (XCTest + Swift Concurrency) |
|
| `ios-testing.md` | iOS 测试 (XCTest + Swift Concurrency) |
|
||||||
| `android-testing.md` | Android 测试 (JUnit + Espresso + Compose) |
|
| `android-testing.md` | Android 测试 (JUnit + Espresso + Compose) |
|
||||||
| `e2e-testing.md` | E2E Playwright:API Mock 冒烟测试(无后端)+ 全链路集成测试 |
|
| `e2e-testing.md` | E2E Playwright:API Mock 冒烟测试(无后端)+ 全链路集成测试 |
|
||||||
|
| `templates/go-integration-test.md` | Go 集成测试模板(多步骤 API 流程、中间件验证、租户隔离) |
|
||||||
|
| `templates/pdv-smoke-spec.md` | PDV 部署后验收 Playwright 模板(页面可达、菜单可见、API 连通) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -49,6 +51,7 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
|
|||||||
| E2E (Mock 冒烟) | `npm run test:e2e:smoke-mock` | `e2e-testing.md` §API Mock |
|
| E2E (Mock 冒烟) | `npm run test:e2e:smoke-mock` | `e2e-testing.md` §API Mock |
|
||||||
| E2E (全链路) | `npm run test:e2e` | `e2e-testing.md` §全链路 |
|
| E2E (全链路) | `npm run test:e2e` | `e2e-testing.md` §全链路 |
|
||||||
| E2E (Coolbuy PaaS) | `make e2e` | `e2e-testing.md` §Coolbuy |
|
| E2E (Coolbuy PaaS) | `make e2e` | `e2e-testing.md` §Coolbuy |
|
||||||
|
| E2E (部署后验收 PDV) | `E2E_BASE_URL=<url> npx playwright test e2e/pdv/` | §PDV |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -141,3 +144,74 @@ ai-proj task append-doc --id <taskId> --content "# 测试报告
|
|||||||
7. **Mock 仅限 Handler 层** - handler 层可以 mock biz 接口 + httptest
|
7. **Mock 仅限 Handler 层** - handler 层可以 mock biz 接口 + httptest
|
||||||
8. **E2E 冒烟测试必须用 API Mock** - E2E 门禁不能依赖后端,否则形同虚设。用 `page.route()` 拦截 API,见 `e2e-testing.md`。质量门禁流程(Gates 1-5)定义在 `req-test-gate` 技能中
|
8. **E2E 冒烟测试必须用 API Mock** - E2E 门禁不能依赖后端,否则形同虚设。用 `page.route()` 拦截 API,见 `e2e-testing.md`。质量门禁流程(Gates 1-5)定义在 `req-test-gate` 技能中
|
||||||
9. **李宁测试用例** - Excel 导出见 `coolbuy-legacy` 技能的 `test-cases-excel.md`
|
9. **李宁测试用例** - Excel 导出见 `coolbuy-legacy` 技能的 `test-cases-excel.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 部署后 E2E 验收 (PDV — Post-Deploy Verification)
|
||||||
|
|
||||||
|
部署后验收是独立于 TG4 的 E2E 冒烟模式,在 `/req deploy` 健康检查通过后执行。
|
||||||
|
|
||||||
|
### PDV vs TG4 区别
|
||||||
|
|
||||||
|
| 维度 | TG4 (开发阶段 E2E 冒烟) | PDV (部署后验收) |
|
||||||
|
|------|------------------------|-----------------|
|
||||||
|
| **触发时机** | `/req test` Gate 4 | `/req deploy` 步骤 6 |
|
||||||
|
| **环境** | 本地开发环境,API Mock | 真实部署环境 (staging/prod) |
|
||||||
|
| **目的** | 验证前端逻辑、UI 渲染 | 验证功能入口可达、基本可用 |
|
||||||
|
| **API** | `page.route()` 拦截 | 真实后端 API |
|
||||||
|
| **范围** | 回归冒烟 | 仅新功能可达性 |
|
||||||
|
|
||||||
|
### PDV 检查项
|
||||||
|
|
||||||
|
| 检查项 | 说明 | 示例 |
|
||||||
|
|--------|------|------|
|
||||||
|
| **页面可达** | 需求涉及的前端页面返回 200 | `/okr/my`, `/okr/team` |
|
||||||
|
| **菜单可见** | 新增菜单项在侧栏中出现 | OKR 菜单对目标用户角色可见 |
|
||||||
|
| **基础渲染** | 页面无白屏/JS 报错 | 页面有预期的标题/组件 |
|
||||||
|
| **API 连通** | 关键 API 带 token 调用返回正常 | `GET /api/v1/okr/objectives` 返回 200 |
|
||||||
|
|
||||||
|
### PDV 不做什么
|
||||||
|
|
||||||
|
- 不做完整回归测试(那是 TG4 的事)
|
||||||
|
- 不测试复杂业务流程(如多步骤表单提交)
|
||||||
|
- 不替代手动验收
|
||||||
|
- 只做「功能入口可达 + 基本可用」的冒烟验证
|
||||||
|
|
||||||
|
### PDV 执行方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
E2E_BASE_URL=<部署环境URL> npx playwright test e2e/pdv/ --project=chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
### PDV Spec 生成规则
|
||||||
|
|
||||||
|
AI 根据需求变更范围动态生成 Playwright spec,模板见 `templates/pdv-smoke-spec.md`。生成流程:
|
||||||
|
|
||||||
|
1. 从需求关联任务提取前端变更范围(页面路由、菜单项、API 端点)
|
||||||
|
2. 登录测试账号(使用 storageState 或手动登录)
|
||||||
|
3. 验证新增菜单项可见(检查 `.ant-menu` 包含目标文本)
|
||||||
|
4. 导航到新页面,验证非白屏(title 不含 error/500/404)
|
||||||
|
5. 调用关键 API,验证返回状态码 < 500
|
||||||
|
6. 每步截图保存为证据
|
||||||
|
|
||||||
|
### PDV 结果判定
|
||||||
|
|
||||||
|
- **全部 PASS** → 继续推进到 released
|
||||||
|
- **任一 FAIL** → 阻断推进,在部署文档记录失败项,通知修复
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TG2 集成测试检测
|
||||||
|
|
||||||
|
### 模板映射
|
||||||
|
|
||||||
|
| 变更范围 | 测试输出位置 | 模板 |
|
||||||
|
|----------|-------------|------|
|
||||||
|
| 单个 handler 或 service | `*_test.go` (同目录) | `go-testing.md` |
|
||||||
|
| handlers/ + middleware/ + routes/ (同一功能) | `tests/{feature}_integration_test.go` | `templates/go-integration-test.md` |
|
||||||
|
|
||||||
|
### 检测规则
|
||||||
|
|
||||||
|
若 git diff 显示同一功能的 `handlers/`、`middleware/`、`routes/` 文件均有变更(通过命名模式识别,如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go`),则除单元测试外**额外生成** `backend/tests/` 下的集成测试。
|
||||||
|
|
||||||
|
识别方式:提取文件名中的功能前缀(如 `impersonation`),若在三个目录中均出现,则触发集成测试生成。
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
# E2E 测试 (Playwright)
|
# E2E 测试 (Playwright)
|
||||||
|
|
||||||
## 两种 E2E 测试模式
|
## 三种 E2E 测试模式
|
||||||
|
|
||||||
| 模式 | 后端依赖 | 速度 | 适用场景 | 门禁阶段 |
|
| 模式 | 后端依赖 | 速度 | 适用场景 | 门禁阶段 |
|
||||||
|------|---------|------|---------|---------|
|
|------|---------|------|---------|---------|
|
||||||
| **API Mock 冒烟测试** | ❌ 无需后端 | 快(<30s) | UI 布局、路由、菜单、权限隔离 | E2E 冒烟门禁 |
|
| **API Mock 冒烟测试** | ❌ 无需后端 | 快(<30s) | UI 布局、路由、菜单、权限隔离 | TG4 E2E 冒烟门禁 |
|
||||||
| **全链路集成测试** | ✅ 需完整后端+DB | 慢(分钟级) | CRUD 业务流程、数据持久化 | 手动/CI |
|
| **全链路集成测试** | ✅ 需完整后端+DB | 慢(分钟级) | CRUD 业务流程、数据持久化 | 手动/CI |
|
||||||
|
| **部署后验收 (PDV)** | ✅ 真实部署环境 | 中(<2min) | 功能入口可达、菜单可见、API 连通 | `/req deploy` 步骤 6 |
|
||||||
|
|
||||||
**⚠️ 关键原则:E2E 冒烟门禁必须使用 API Mock 模式,不依赖后端。** 依赖后端的 E2E 在开发机上经常跑不通(后端没启动、DB 未初始化),导致门禁形同虚设。
|
**⚠️ 关键原则:E2E 冒烟门禁必须使用 API Mock 模式,不依赖后端。** 依赖后端的 E2E 在开发机上经常跑不通(后端没启动、DB 未初始化),导致门禁形同虚设。
|
||||||
|
|
||||||
|
> **PDV 与 TG4 的区别**:TG4 在开发阶段用 API Mock 验证前端逻辑;PDV 在部署后用真实环境验证功能可达性。详见 `SKILL.md` §PDV 章节。
|
||||||
|
|
||||||
> **与 req-test-gate 的关系**:本文档定义 E2E 测试的**执行技术**(怎么写 mock、怎么跑)。质量门禁流程(Gates 0-5、scope 分级、文档持久化)定义在 `req-test-gate` 技能中。
|
> **与 req-test-gate 的关系**:本文档定义 E2E 测试的**执行技术**(怎么写 mock、怎么跑)。质量门禁流程(Gates 0-5、scope 分级、文档持久化)定义在 `req-test-gate` 技能中。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
# Go 集成测试模板
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
当 git diff 显示同一功能的 **handlers/ + middleware/ + routes/** 文件均有变更时,除单元测试外应额外生成集成测试。
|
||||||
|
|
||||||
|
适用于:
|
||||||
|
- 多步骤 API 流程(登录→操作→验证→退出)
|
||||||
|
- 中间件拦截验证(权限、限流、模拟状态限制)
|
||||||
|
- 租户隔离 / 数据隔离验证
|
||||||
|
- 跨模块交互(handler ↔ middleware ↔ repository)
|
||||||
|
|
||||||
|
## 检测规则
|
||||||
|
|
||||||
|
通过命名模式识别同一功能的跨文件变更:
|
||||||
|
|
||||||
|
```
|
||||||
|
handlers/{feature}_handler.go
|
||||||
|
middleware/{feature}_middleware.go
|
||||||
|
routes/{feature}_routes.go
|
||||||
|
```
|
||||||
|
|
||||||
|
例如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go` 同时变更 → 生成 `tests/impersonation_integration_test.go`。
|
||||||
|
|
||||||
|
## 生成规则
|
||||||
|
|
||||||
|
1. 测试文件放在 `backend/tests/` 目录(与 `test_helpers.go` 同包)
|
||||||
|
2. 使用 `SetupTestApp` + `defer TeardownTestApp` 初始化真实路由和数据库
|
||||||
|
3. 每个测试完全自包含:独立创建/清理数据
|
||||||
|
4. 异步持久化操作(goroutine 写 DB)需 `time.Sleep(200-500ms)` 后再验证
|
||||||
|
|
||||||
|
## 代码骨架
|
||||||
|
|
||||||
|
```go
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFeature_Scenario(t *testing.T) {
|
||||||
|
testApp := SetupTestApp(t)
|
||||||
|
defer TeardownTestApp(t, testApp)
|
||||||
|
|
||||||
|
// === Setup: 创建测试数据 ===
|
||||||
|
// 使用 test_helpers.go 中的 helper 函数
|
||||||
|
sysAdmin := CreateTestSystemUser(t, testApp, "admin_scenario")
|
||||||
|
// tenantID := CreateTestTenant(t, testApp, "scenario_tenant")
|
||||||
|
// enterprise := CreateTestEnterpriseWithTenant(t, testApp, "Corp", "CODE", tenantID)
|
||||||
|
// tenantAdmin := CreateTestTenantAdmin(t, testApp, "tadmin", enterprise.ID, tenantID)
|
||||||
|
|
||||||
|
// === Cleanup: 按外键约束顺序清理 ===
|
||||||
|
defer CleanupImpersonationTestData(t, testApp,
|
||||||
|
[]int{sysAdmin.ID},
|
||||||
|
[]int{}, // enterpriseIDs
|
||||||
|
[]int64{}, // tenantIDs
|
||||||
|
)
|
||||||
|
|
||||||
|
// === 状态变量:跨步骤传递 ===
|
||||||
|
var token string
|
||||||
|
|
||||||
|
t.Run("Step1_InitialAction", func(t *testing.T) {
|
||||||
|
w := MakeAuthenticatedRequest(t, testApp, http.MethodPost,
|
||||||
|
"/api/v1/...",
|
||||||
|
map[string]string{"key": "value"},
|
||||||
|
sysAdmin,
|
||||||
|
)
|
||||||
|
require.Equal(t, http.StatusOK, w.Code, "Step1 failed: %s", w.Body.String())
|
||||||
|
|
||||||
|
var resp map[string]interface{}
|
||||||
|
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
|
||||||
|
// 提取后续步骤需要的数据
|
||||||
|
token = resp["data"].(map[string]interface{})["token"].(string)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Step2_VerifyState", func(t *testing.T) {
|
||||||
|
if token == "" {
|
||||||
|
t.Skip("Step1 failed")
|
||||||
|
}
|
||||||
|
// 使用 Step1 产出的 token 继续验证
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StepN_AsyncVerification", func(t *testing.T) {
|
||||||
|
// 异步写入的数据需要等待
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
// 然后验证 DB 数据
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4 种必测场景
|
||||||
|
|
||||||
|
| 场景类型 | 说明 | 示例 |
|
||||||
|
|----------|------|------|
|
||||||
|
| **Happy Path** | 完整正常流程 | 开始模拟 → 访问 API → 查状态 → 退出 → 确认恢复 |
|
||||||
|
| **权限拒绝** | 无权限用户尝试操作 | 普通用户尝试模拟 → 403 |
|
||||||
|
| **隔离验证** | 跨租户/跨企业数据隔离 | tenant2 admin 访问 tenant1 企业 → 403 |
|
||||||
|
| **边界条件** | 输入校验、状态冲突 | 原因太短 → 400;已在模拟中再次模拟 → 403 |
|
||||||
|
|
||||||
|
## Helper 函数参考
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 来自 test_helpers.go
|
||||||
|
SetupTestApp(t) // 初始化完整应用(路由 + DB)
|
||||||
|
CreateTestSystemUser(t, app, username) // 系统管理员 + JWT
|
||||||
|
CreateTestTenant(t, app, name) // 创建租户,返回 int64 ID
|
||||||
|
CreateTestEnterpriseWithTenant(t, app, name, code, tenantID) // 带租户的企业
|
||||||
|
CreateTestTenantAdmin(t, app, username, enterpriseID, tenantID...) // 租户管理员
|
||||||
|
CreateTestEnterpriseUser(t, app, username, enterpriseID) // 普通企业用户
|
||||||
|
ExtractImpersonationToken(t, response) // 从模拟响应提取 token
|
||||||
|
MakeAuthenticatedRequest(t, app, method, path, body, user) // 发送认证请求
|
||||||
|
CleanupImpersonationTestData(t, app, userIDs, enterpriseIDs, tenantIDs) // 按FK顺序清理
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
- **Token 链式传递**:模拟 API 返回新 token,后续请求必须用新 token
|
||||||
|
- **异步持久化**:handler 用 goroutine 写 session/audit,测试需 Sleep 后再查
|
||||||
|
- **数据隔离**:每个测试用唯一的 username/code,避免测试间干扰
|
||||||
|
- **清理顺序**:外键约束要求先删子表再删父表
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# PDV Smoke Spec 模板
|
||||||
|
|
||||||
|
部署后验收 (Post-Deploy Verification) Playwright 测试模板。AI 根据需求变更范围,基于此模板动态生成验收 spec。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 指定部署环境 URL 执行
|
||||||
|
E2E_BASE_URL=https://staging.example.com npx playwright test e2e/pdv/ --project=chromium
|
||||||
|
```
|
||||||
|
|
||||||
|
## Spec 模板结构
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
test.describe('PDV: {需求标题}', () => {
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
// 方式 1: 使用 storageState(推荐,需预先保存登录状态)
|
||||||
|
// test.use({ storageState: 'e2e/.auth/user.json' });
|
||||||
|
|
||||||
|
// 方式 2: 手动登录
|
||||||
|
await page.goto(`${BASE_URL}/login`);
|
||||||
|
await page.fill('input[name="username"]', '{测试账号}');
|
||||||
|
await page.fill('input[name="password"]', '{测试密码}');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForURL('**/dashboard/**');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('菜单可见性: {菜单名}', async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}/`);
|
||||||
|
await page.waitForSelector('.ant-menu');
|
||||||
|
|
||||||
|
// 检查侧栏包含新菜单项
|
||||||
|
const menu = page.locator('.ant-menu');
|
||||||
|
await expect(menu).toContainText('{菜单名}');
|
||||||
|
|
||||||
|
// 截图证据
|
||||||
|
await page.screenshot({ path: 'e2e-results/pdv-menu-{菜单名}.png', fullPage: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('页面可达: {页面路由}', async ({ page }) => {
|
||||||
|
const response = await page.goto(`${BASE_URL}{页面路由}`);
|
||||||
|
|
||||||
|
// 验证 HTTP 状态
|
||||||
|
expect(response?.status()).toBeLessThan(400);
|
||||||
|
|
||||||
|
// 验证非白屏 — title 不含错误关键词
|
||||||
|
await expect(page).not.toHaveTitle(/error|500|404|not found/i);
|
||||||
|
|
||||||
|
// 验证页面有核心内容(非空白)
|
||||||
|
await expect(page.locator('{核心选择器}')).toBeVisible({ timeout: 10000 });
|
||||||
|
|
||||||
|
// 检查无 JS 报错(通过 console error 监听)
|
||||||
|
const errors: string[] = [];
|
||||||
|
page.on('console', msg => {
|
||||||
|
if (msg.type() === 'error') errors.push(msg.text());
|
||||||
|
});
|
||||||
|
await page.waitForTimeout(2000);
|
||||||
|
expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
|
||||||
|
|
||||||
|
// 截图证据
|
||||||
|
await page.screenshot({ path: 'e2e-results/pdv-page-{页面名}.png', fullPage: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API 连通: {接口描述}', async ({ request }) => {
|
||||||
|
// 需要带认证 token 调用
|
||||||
|
const resp = await request.get(`${BASE_URL}/api/v1/{路径}`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer {token}',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 验证非 5xx 错误
|
||||||
|
expect(resp.status()).toBeLessThan(500);
|
||||||
|
|
||||||
|
// 可选:验证响应结构
|
||||||
|
// const body = await resp.json();
|
||||||
|
// expect(body).toHaveProperty('data');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 占位符说明
|
||||||
|
|
||||||
|
| 占位符 | 含义 | 来源 |
|
||||||
|
|--------|------|------|
|
||||||
|
| `{需求标题}` | 需求名称 | `ai-proj req get --id <id>` |
|
||||||
|
| `{菜单名}` | 新增的菜单文本 | 从需求关联的前端任务中提取 |
|
||||||
|
| `{页面路由}` | 新增/变更的前端路由 | 从前端路由配置或 PRD 提取 |
|
||||||
|
| `{核心选择器}` | 页面核心内容的 CSS 选择器 | 如 `h1`, `.page-title`, `[data-testid="xxx"]` |
|
||||||
|
| `{测试账号}` / `{测试密码}` | 测试环境登录凭据 | 环境配置 |
|
||||||
|
| `{token}` | API 认证 token | 登录后获取 |
|
||||||
|
| `{接口描述}` / `{路径}` | 关键 API 端点 | 从后端路由或 PRD 提取 |
|
||||||
|
| `{页面名}` | 截图文件名标识 | 自定义 |
|
||||||
|
|
||||||
|
## 生成示例(OKR 功能)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { test, expect } from '@playwright/test';
|
||||||
|
|
||||||
|
const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:3000';
|
||||||
|
|
||||||
|
test.describe('PDV: OKR 团队/对齐/设置/评分功能', () => {
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}/login`);
|
||||||
|
await page.fill('input[name="username"]', 'testuser');
|
||||||
|
await page.fill('input[name="password"]', 'TestPass123');
|
||||||
|
await page.click('button[type="submit"]');
|
||||||
|
await page.waitForURL('**/dashboard/**');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('菜单可见性: OKR', async ({ page }) => {
|
||||||
|
await page.goto(`${BASE_URL}/`);
|
||||||
|
await page.waitForSelector('.ant-menu');
|
||||||
|
await expect(page.locator('.ant-menu')).toContainText('OKR');
|
||||||
|
await page.screenshot({ path: 'e2e-results/pdv-menu-okr.png' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('页面可达: /okr/my', async ({ page }) => {
|
||||||
|
const response = await page.goto(`${BASE_URL}/okr/my`);
|
||||||
|
expect(response?.status()).toBeLessThan(400);
|
||||||
|
await expect(page).not.toHaveTitle(/error|500|404/i);
|
||||||
|
await expect(page.locator('h1, .page-title')).toBeVisible({ timeout: 10000 });
|
||||||
|
await page.screenshot({ path: 'e2e-results/pdv-page-okr-my.png', fullPage: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('页面可达: /okr/team', async ({ page }) => {
|
||||||
|
const response = await page.goto(`${BASE_URL}/okr/team`);
|
||||||
|
expect(response?.status()).toBeLessThan(400);
|
||||||
|
await expect(page).not.toHaveTitle(/error|500|404/i);
|
||||||
|
await expect(page.locator('h1, .page-title')).toBeVisible({ timeout: 10000 });
|
||||||
|
await page.screenshot({ path: 'e2e-results/pdv-page-okr-team.png', fullPage: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('API 连通: OKR objectives', async ({ request }) => {
|
||||||
|
const resp = await request.get(`${BASE_URL}/api/v1/okr/objectives`);
|
||||||
|
expect(resp.status()).toBeLessThan(500);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
```
|
||||||
11
skills-dev/executing-plans-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/executing-plans-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "executing-plans-plugin",
|
||||||
|
"description": "Use when you have a written implementation plan to execute in a separate session with review checkpoints.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "executing-plans",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
96
skills-dev/executing-plans-plugin/skills/SKILL.md
Normal file
96
skills-dev/executing-plans-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
---
|
||||||
|
name: executing-plans
|
||||||
|
description: Use when you have a written implementation plan to execute in a separate session with review checkpoints
|
||||||
|
---
|
||||||
|
|
||||||
|
# Executing Plans
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Load plan, review critically, create branch, execute tasks in batches, report for review between batches.
|
||||||
|
|
||||||
|
**Core principle:** Batch execution with checkpoints for architect review.
|
||||||
|
|
||||||
|
**Announce at start:** "I'm using the executing-plans skill to implement this plan."
|
||||||
|
|
||||||
|
## The Process
|
||||||
|
|
||||||
|
### Step 1: Load and Review Plan
|
||||||
|
1. Read plan file
|
||||||
|
2. Review critically - identify any questions or concerns about the plan
|
||||||
|
3. If concerns: Raise them with your human partner before starting
|
||||||
|
4. If no concerns: Proceed to branch setup
|
||||||
|
|
||||||
|
### Step 2: Setup Branch
|
||||||
|
**Before any implementation, ensure proper branch isolation.**
|
||||||
|
|
||||||
|
1. Check if already on a feature branch for this task
|
||||||
|
2. If not, use `/pr start` to create one:
|
||||||
|
```bash
|
||||||
|
/pr start <type> <REQ-id> <name>
|
||||||
|
# Example: /pr start feature REQ-123 user-login
|
||||||
|
```
|
||||||
|
3. If no REQ-id available, ask user or create branch manually:
|
||||||
|
```bash
|
||||||
|
git fetch origin
|
||||||
|
git checkout -b <type>/<descriptive-name> origin/main
|
||||||
|
```
|
||||||
|
4. Confirm branch is ready before proceeding
|
||||||
|
|
||||||
|
**Branch types:** `feature`, `fix`, `refactor`
|
||||||
|
|
||||||
|
### Step 3: Create Tasks and Execute Batch
|
||||||
|
**Default: First 3 tasks**
|
||||||
|
|
||||||
|
1. Create TodoWrite tasks from plan
|
||||||
|
2. For each task in batch:
|
||||||
|
- Mark as in_progress
|
||||||
|
- Follow each step exactly (plan has bite-sized steps)
|
||||||
|
- Run verifications as specified
|
||||||
|
- Mark as completed
|
||||||
|
|
||||||
|
### Step 4: Report
|
||||||
|
When batch complete:
|
||||||
|
- Show what was implemented
|
||||||
|
- Show verification output
|
||||||
|
- Say: "Ready for feedback."
|
||||||
|
|
||||||
|
### Step 5: Continue
|
||||||
|
Based on feedback:
|
||||||
|
- Apply changes if needed
|
||||||
|
- Execute next batch
|
||||||
|
- Repeat until complete
|
||||||
|
|
||||||
|
### Step 6: Complete Development
|
||||||
|
|
||||||
|
After all tasks complete and verified:
|
||||||
|
- Announce: "I'm using the finishing-a-development-branch skill to complete this work."
|
||||||
|
- **REQUIRED SUB-SKILL:** Use superpowers:finishing-a-development-branch
|
||||||
|
- Follow that skill to verify tests, present options, execute choice
|
||||||
|
|
||||||
|
## When to Stop and Ask for Help
|
||||||
|
|
||||||
|
**STOP executing immediately when:**
|
||||||
|
- Hit a blocker mid-batch (missing dependency, test fails, instruction unclear)
|
||||||
|
- Plan has critical gaps preventing starting
|
||||||
|
- You don't understand an instruction
|
||||||
|
- Verification fails repeatedly
|
||||||
|
|
||||||
|
**Ask for clarification rather than guessing.**
|
||||||
|
|
||||||
|
## When to Revisit Earlier Steps
|
||||||
|
|
||||||
|
**Return to Review (Step 1) when:**
|
||||||
|
- Partner updates the plan based on your feedback
|
||||||
|
- Fundamental approach needs rethinking
|
||||||
|
|
||||||
|
**Don't force through blockers** - stop and ask.
|
||||||
|
|
||||||
|
## Remember
|
||||||
|
- Review plan critically first
|
||||||
|
- **Create feature branch before implementation**
|
||||||
|
- Follow plan steps exactly
|
||||||
|
- Don't skip verifications
|
||||||
|
- Reference skills when plan says to
|
||||||
|
- Between batches: just report and wait
|
||||||
|
- Stop when blocked, don't guess
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "finishing-branch-plugin",
|
||||||
|
"description": "Use when implementation is complete and all tests pass - verifies and creates PR.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "finishing-a-development-branch",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
104
skills-dev/finishing-branch-plugin/skills/SKILL.md
Normal file
104
skills-dev/finishing-branch-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
---
|
||||||
|
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
|
||||||
11
skills-dev/frontend-design-plugin/.claude-plugin/plugin.json
Normal file
11
skills-dev/frontend-design-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend-design-plugin",
|
||||||
|
"description": "Create distinctive, production-grade frontend interfaces with high design quality. Generates creative, polished code that avoids generic AI aesthetics.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "frontend-design",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
695
skills-dev/frontend-design-plugin/skills/SKILL.md
Normal file
695
skills-dev/frontend-design-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,695 @@
|
|||||||
|
---
|
||||||
|
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 配置完整
|
||||||
|
- [ ] 包含组件文档描述
|
||||||
|
- [ ] 交互状态可测试
|
||||||
|
- [ ] 响应式展示
|
||||||
|
|
||||||
|
### 视觉质量检查
|
||||||
|
|
||||||
|
- [ ] 字体选择有特色
|
||||||
|
- [ ] 配色方案协调
|
||||||
|
- [ ] 动画流畅自然
|
||||||
|
- [ ] 间距一致
|
||||||
|
- [ ] 暗色主题支持
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "pull-request",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "review-checklist-plugin",
|
||||||
|
"description": "项目级代码评审检查清单。按项目积累的特定检查项,挂载在 dev-review 下自动加载。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "review-checklist",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "dev"
|
||||||
|
}
|
||||||
42
skills-dev/review-checklist-plugin/checklists/ai-proj.md
Normal file
42
skills-dev/review-checklist-plugin/checklists/ai-proj.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# AI-Proj 代码评审检查清单
|
||||||
|
|
||||||
|
## 后端(Go + Gin + GORM)
|
||||||
|
|
||||||
|
### 分层架构
|
||||||
|
- [ ] Handler 是否直接 import `database/` 包?— 禁止,必须通过 Service 层。教训:架构退化导致循环依赖
|
||||||
|
- [ ] 新 Handler 是否在 routes/ 中注册?— 遗漏会导致 404
|
||||||
|
- [ ] MCP 专用 endpoint 是否包含 `/mcp/` 前缀?— 教训:缺少前缀导致 MCP bridge 404
|
||||||
|
|
||||||
|
### 数据库
|
||||||
|
- [ ] 新增 Migration 文件名是否符合 `YYYYMMDDHHMMSS_xxx.up.sql` 格式?
|
||||||
|
- [ ] Migration 是否有对应的 `.down.sql`?
|
||||||
|
- [ ] GORM 查询是否带 `tenant_id` 过滤?(多租户安全)
|
||||||
|
- [ ] 软删除查询是否正确使用 `Unscoped()`?— 误用导致查不到已删除数据或查出已删除数据
|
||||||
|
|
||||||
|
### 认证与权限
|
||||||
|
- [ ] 新 API 是否配置了 Auth 中间件?— 遗漏导致未授权访问
|
||||||
|
- [ ] JWT token 类型是否区分 access/refresh?— 教训:token 混用导致安全漏洞
|
||||||
|
- [ ] bcrypt cost 是否使用 12?— 教训:默认 cost 10 导致登录失败
|
||||||
|
|
||||||
|
### Redis
|
||||||
|
- [ ] Redis key 是否有 TTL?— 缺少 TTL 导致内存泄露
|
||||||
|
- [ ] Redis 不可用时是否降级到数据库?
|
||||||
|
|
||||||
|
## 前端(React 18 + Ant Design)
|
||||||
|
|
||||||
|
### Modal 安全
|
||||||
|
- [ ] `Modal.success/info/warning/error` 之后是否有立即执行的 UI 操作?— 必须放在 `onOk` 回调中。教训:两个 Modal 同时弹出互相遮挡
|
||||||
|
|
||||||
|
### 状态管理
|
||||||
|
- [ ] React Query 的 queryKey 是否正确包含所有依赖参数?— 缺少导致缓存错误
|
||||||
|
- [ ] 列表页分页是否正确重置 page?— 教训:筛选条件变更后 page 未重置导致空页
|
||||||
|
|
||||||
|
### 类型安全
|
||||||
|
- [ ] 是否有 `any` 类型?— 应使用具体类型
|
||||||
|
- [ ] API 响应是否有 TypeScript 接口定义?
|
||||||
|
|
||||||
|
## 通用
|
||||||
|
|
||||||
|
- [ ] `.env` <20><>凭据文件是否被意外加入 git?
|
||||||
|
- [ ] 是否有硬编码的 URL/IP/端口?— 应使用配置
|
||||||
|
- [ ] 错误日志是否包含足够的上下文信息?(user_id, tenant_id, request_id)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Coolbuy PaaS(酷采3.0)代码评审检查清单
|
||||||
|
|
||||||
|
## 后端(Go + Gin + MySQL)
|
||||||
|
|
||||||
|
### 多租户
|
||||||
|
- [ ] 所有查询是否带 `tenant_id` 和 `enterprise_id` 过滤?
|
||||||
|
- [ ] 跨租户数据操作是否被阻止?
|
||||||
|
|
||||||
|
### 数据迁移
|
||||||
|
- [ ] 从酷采2.0迁移的字段映射是否正确?(varchar ID → bigint ID)
|
||||||
|
- [ ] 迁移脚本是否处理了酷采2.0<EFBFBD><EFBFBD><EFBFBD>软删除标记(is_delete → deleted_at)?
|
||||||
|
|
||||||
|
## 前端(Vue 3 + Ant Design Vue)
|
||||||
|
|
||||||
|
### i18n
|
||||||
|
- [ ] 新增文案是否使用 `$t()` 国际化?— 不允许硬编码中文
|
||||||
|
- [ ] i18n key 是否在 zh-CN 和 en-US 都有定义?
|
||||||
|
|
||||||
|
### 权限
|
||||||
|
- [ ] 按钮/菜单是否有权限控制(v-permission 指令)?
|
||||||
23
skills-dev/review-checklist-plugin/checklists/general.md
Normal file
23
skills-dev/review-checklist-plugin/checklists/general.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 通用代码评审检查清单
|
||||||
|
|
||||||
|
适用于所有项目,补充五视角扫描法。
|
||||||
|
|
||||||
|
## API 设计
|
||||||
|
- [ ] RESTful 命名是否规范?(复数名词、无动词)
|
||||||
|
- [ ] 分页参数是否有默认值和上限?
|
||||||
|
- [ ] 响应格式是否统一?(code/message/data)
|
||||||
|
|
||||||
|
## 错误处理
|
||||||
|
- [ ] 错误是否被正确传播?(不要吞掉错误)
|
||||||
|
- [ ] 用户可见的错误消息是否友好?(不暴露技术细节)
|
||||||
|
- [ ] 是否有 panic recover 兜底?
|
||||||
|
|
||||||
|
## 性能
|
||||||
|
- [ ] 列表查询是否有分页?(不允许无限制查询)
|
||||||
|
- [ ] N+1 查询问题?(循环内查数据库)
|
||||||
|
- [ ] 是否有不必要的全表扫描?(缺少索引)
|
||||||
|
|
||||||
|
## 可维护性
|
||||||
|
- [ ] 魔法数字是否提取为常量?
|
||||||
|
- [ ] 复杂业务逻辑是否有注释说明?
|
||||||
|
- [ ] 函数是否过长?(超过 100 行考虑拆分)
|
||||||
49
skills-dev/review-checklist-plugin/skills/SKILL.md
Normal file
49
skills-dev/review-checklist-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
---
|
||||||
|
name: review-checklist
|
||||||
|
description: 项目级代码评审检查清单。按项目积累特定检查项,挂载在 dev-review 下自动加载。每次 CR 时触发。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 代码评审检查清单插件 (review-checklist)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本插件为 `dev-review` 提供**项目特定的检查清单**,补充五视角扫描法之外的项目级经验。
|
||||||
|
|
||||||
|
检查清单按项目独立维护,每个项目文件记录该项目踩过的坑和必查项。
|
||||||
|
|
||||||
|
## 使用方式
|
||||||
|
|
||||||
|
1. `dev-review` 执行五视角扫描时,自动加载当前项目的检查清单
|
||||||
|
2. 扫描完成后,逐条检查清单项
|
||||||
|
3. 检查结果附加到 CR 报告的「项目检查清单」章节
|
||||||
|
|
||||||
|
## 检查清单文件
|
||||||
|
|
||||||
|
```
|
||||||
|
review-checklist-plugin/
|
||||||
|
├── skills/SKILL.md # 本文件
|
||||||
|
└── checklists/
|
||||||
|
├── ai-proj.md # AI-Proj 项目清单
|
||||||
|
├── coolbuy-paas.md # 酷采3.0 项目清单
|
||||||
|
└── general.md # 通用清单(所有项目适用)
|
||||||
|
```
|
||||||
|
|
||||||
|
## 如何添加检查项
|
||||||
|
|
||||||
|
当 CR 中发现了一个**项目特有**的问题模式,且未来可能复发时:
|
||||||
|
|
||||||
|
1. 打开对应项目的检查清单文件
|
||||||
|
2. 添加条目,格式:`- [ ] {检查项} — 教训:{来源}`
|
||||||
|
3. 标注严重度和适用范围
|
||||||
|
|
||||||
|
**不要添加**五视角扫描已覆盖的通用安全/并发/边界问题。
|
||||||
|
|
||||||
|
## CR 报告附加章节
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 项目检查清单({项目名})
|
||||||
|
|
||||||
|
| # | 检查项 | 结果 | 说明 |
|
||||||
|
|---|--------|------|------|
|
||||||
|
| 1 | {检查项} | ✅/❌/N/A | {说明} |
|
||||||
|
```
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "data-excel",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,5 +10,8 @@
|
|||||||
"name": "doubao-voice",
|
"name": "doubao-voice",
|
||||||
"path": "./skills/SKILL.md"
|
"path": "./skills/SKILL.md"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"install_name": "doubao-voice",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "feishu-bitable",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "feishu-docx",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "feishu",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "siyuan",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,128 +5,35 @@ description: 思源笔记 API 集成。通过自然语言创建、编辑、搜
|
|||||||
|
|
||||||
# 思源笔记 API 集成 Skill
|
# 思源笔记 API 集成 Skill
|
||||||
|
|
||||||
## 服务配置
|
## 环境变量配置(必须)
|
||||||
|
|
||||||
### 阿里云生产环境 (推荐)
|
本 skill 通过环境变量读取思源笔记连接信息,不同用户/组织配置不同的值。
|
||||||
|
|
||||||
| 配置项 | 值 |
|
**在 `~/.claude/settings.json` 中配置:**
|
||||||
|--------|-----|
|
|
||||||
| 服务地址 | `https://siyuan.pipexerp.com` (HTTPS) |
|
|
||||||
| HTTP访问 | `http://47.93.23.182` (重定向到HTTPS) |
|
|
||||||
| SSH 别名 | `siyuan` |
|
|
||||||
| 访问码 | `SiYuan@2026` |
|
|
||||||
| 版本 | **3.1.11** (2026-02-16 升级) |
|
|
||||||
| 部署位置 | 阿里云 Ubuntu 24.04 (Docker) |
|
|
||||||
| 容器名称 | `siyuan` |
|
|
||||||
| 镜像 | `b3log/siyuan:v3.1.11` |
|
|
||||||
| 数据目录 | `/opt/siyuan/workspace` |
|
|
||||||
| 反向代理 | Caddy (HTTPS + SSL证书) |
|
|
||||||
| SSL证书 | Let's Encrypt (自动续期) |
|
|
||||||
|
|
||||||
### Tailscale 内网环境 (备用)
|
```json
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"SIYUAN_URL": "${SIYUAN_URL}",
|
||||||
|
"SIYUAN_TOKEN": "your-api-token-here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| 配置项 | 值 |
|
或在项目级 `.claude/settings.json` 中配置(优先级更高)。
|
||||||
|--------|-----|
|
|
||||||
| 服务地址 | `http://47.93.23.182` (Tailscale) |
|
**检查配置是否就绪:** 执行任何思源操作前,先确认环境变量已设置:
|
||||||
| 局域网地址 | `http://192.168.1.50:6806` |
|
|
||||||
| API Token | `nfnycjb1g8vbexb2` |
|
```bash
|
||||||
| 版本 | 3.1.5 |
|
echo "URL: ${SIYUAN_URL:-未配置}"
|
||||||
| 部署位置 | 飞牛OS (Docker) |
|
echo "TOKEN: ${SIYUAN_TOKEN:+已配置}"
|
||||||
|
```
|
||||||
|
|
||||||
|
如果未配置,提示用户在 `~/.claude/settings.json` 的 `env` 中添加 `SIYUAN_URL` 和 `SIYUAN_TOKEN`。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 服务器管理 (阿里云)
|
## 🔐 保存<E4BF9D><E5AD98><EFBFBD>禁:敏感<E6958F><E6849F>息脱敏
|
||||||
|
|
||||||
### SSH 连接
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 快捷连接
|
|
||||||
ssh siyuan
|
|
||||||
|
|
||||||
# 完整连接命令
|
|
||||||
ssh -i /Users/donglinlai/Downloads/siyuan.pem root@47.93.23.182
|
|
||||||
```
|
|
||||||
|
|
||||||
### Docker 管理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看容器状态
|
|
||||||
ssh siyuan "docker ps | grep siyuan"
|
|
||||||
|
|
||||||
# 查看日志
|
|
||||||
ssh siyuan "docker logs -f siyuan --tail 100"
|
|
||||||
|
|
||||||
# 重启服务
|
|
||||||
ssh siyuan "docker restart siyuan"
|
|
||||||
|
|
||||||
# 停止服务
|
|
||||||
ssh siyuan "docker stop siyuan"
|
|
||||||
|
|
||||||
# 启动服务
|
|
||||||
ssh siyuan "docker start siyuan"
|
|
||||||
|
|
||||||
# 升级到特定版本(推荐)
|
|
||||||
ssh siyuan "docker stop siyuan && docker rm siyuan"
|
|
||||||
ssh siyuan "docker pull b3log/siyuan:v3.1.11"
|
|
||||||
ssh siyuan "docker run -d --name siyuan --restart=always \
|
|
||||||
-p 127.0.0.1:6806:6806 \
|
|
||||||
-v /opt/siyuan/workspace:/siyuan/workspace \
|
|
||||||
-e LANG=en_US.UTF-8 \
|
|
||||||
b3log/siyuan:v3.1.11 \
|
|
||||||
--workspace=/siyuan/workspace \
|
|
||||||
--accessAuthCode=SiYuan@2026"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据备份与恢复
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看数据目录大小
|
|
||||||
ssh siyuan "du -sh /opt/siyuan/workspace"
|
|
||||||
|
|
||||||
# 备份数据到服务器
|
|
||||||
ssh siyuan "tar -czf /root/siyuan-backup-\$(date +%Y%m%d).tar.gz /opt/siyuan/workspace"
|
|
||||||
|
|
||||||
# 下载备份到本地
|
|
||||||
scp siyuan:/root/siyuan-backup-*.tar.gz ~/Downloads/
|
|
||||||
|
|
||||||
# 恢复数据(先停止容器)
|
|
||||||
ssh siyuan "docker stop siyuan && \
|
|
||||||
tar -xzf /root/siyuan-backup-YYYYMMDD.tar.gz -C / && \
|
|
||||||
docker start siyuan"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 访问测试
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 测试 HTTPS 访问
|
|
||||||
curl -I https://siyuan.pipexerp.com
|
|
||||||
|
|
||||||
# 测试认证 API
|
|
||||||
curl -s -X POST https://siyuan.pipexerp.com/api/system/version | jq .
|
|
||||||
|
|
||||||
# 浏览器访问
|
|
||||||
open https://siyuan.pipexerp.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Caddy 反向代理管理
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 查看 Caddy 状态
|
|
||||||
ssh siyuan "systemctl status caddy"
|
|
||||||
|
|
||||||
# 重启 Caddy
|
|
||||||
ssh siyuan "systemctl restart caddy"
|
|
||||||
|
|
||||||
# 查看 Caddy 配置
|
|
||||||
ssh siyuan "cat /etc/caddy/Caddyfile"
|
|
||||||
|
|
||||||
# 查看 SSL 证书
|
|
||||||
ssh siyuan "journalctl -u caddy | grep certificate"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔐 保存门禁:敏感信息脱敏
|
|
||||||
|
|
||||||
**保存任何内容到思源笔记前,必须先完成脱敏处理。**
|
**保存任何内容到思源笔记前,必须先完成脱敏处理。**
|
||||||
|
|
||||||
@@ -201,8 +108,8 @@ api.upsert_doc(notebook_id, "/文档路径", content)
|
|||||||
所有 API 请求使用 POST 方法,需携带 Token:
|
所有 API 请求使用 POST 方法,需携带 Token:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/xxx \
|
curl -X POST ${SIYUAN_URL}/api/xxx \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"param": "value"}'
|
-d '{"param": "value"}'
|
||||||
```
|
```
|
||||||
@@ -239,8 +146,8 @@ curl -X POST https://siyuan.pipexerp.com/api/xxx \
|
|||||||
#### 列出所有笔记本
|
#### 列出所有笔记本
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/lsNotebooks \
|
curl -X POST ${SIYUAN_URL}/api/notebook/lsNotebooks \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{}'
|
-d '{}'
|
||||||
```
|
```
|
||||||
@@ -248,8 +155,8 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/lsNotebooks \
|
|||||||
#### 创建笔记本
|
#### 创建笔记本
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/createNotebook \
|
curl -X POST ${SIYUAN_URL}/api/notebook/createNotebook \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"name": "笔记本名称"}'
|
-d '{"name": "笔记本名称"}'
|
||||||
```
|
```
|
||||||
@@ -257,8 +164,8 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/createNotebook \
|
|||||||
#### 删除笔记本
|
#### 删除笔记本
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/removeNotebook \
|
curl -X POST ${SIYUAN_URL}/api/notebook/removeNotebook \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"notebook": "笔记本ID"}'
|
-d '{"notebook": "笔记本ID"}'
|
||||||
```
|
```
|
||||||
@@ -276,22 +183,22 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/removeNotebook \
|
|||||||
```bash
|
```bash
|
||||||
# 1. 先查询是否存在(按路径精确匹配)
|
# 1. 先查询是否存在(按路径精确匹配)
|
||||||
DOC_PATH="/网络管理/家庭Tailscale网络"
|
DOC_PATH="/网络管理/家庭Tailscale网络"
|
||||||
EXISTING=$(curl -s -X POST https://siyuan.pipexerp.com/api/query/sql \
|
EXISTING=$(curl -s -X POST ${SIYUAN_URL}/api/query/sql \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"stmt\": \"SELECT id FROM blocks WHERE type='d' AND hpath='${DOC_PATH}' LIMIT 1\"}" \
|
-d "{\"stmt\": \"SELECT id FROM blocks WHERE type='d' AND hpath='${DOC_PATH}' LIMIT 1\"}" \
|
||||||
| jq -r '.data[0].id // empty')
|
| jq -r '.data[0].id // empty')
|
||||||
|
|
||||||
if [ -n "$EXISTING" ]; then
|
if [ -n "$EXISTING" ]; then
|
||||||
# 2a. 存在则更新
|
# 2a. 存在则更新
|
||||||
curl -s -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
curl -s -X POST ${SIYUAN_URL}/api/block/updateBlock \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d "{\"id\": \"${EXISTING}\", \"dataType\": \"markdown\", \"data\": \"# 更新的内容\"}"
|
-d "{\"id\": \"${EXISTING}\", \"dataType\": \"markdown\", \"data\": \"# 更新的内容\"}"
|
||||||
else
|
else
|
||||||
# 2b. 不存在则创建
|
# 2b. 不存在则创建
|
||||||
curl -s -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
curl -s -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"notebook": "笔记本ID", "path": "/网络管理/家庭Tailscale网络", "markdown": "# 新内容"}'
|
-d '{"notebook": "笔记本ID", "path": "/网络管理/家庭Tailscale网络", "markdown": "# 新内容"}'
|
||||||
fi
|
fi
|
||||||
@@ -300,8 +207,8 @@ fi
|
|||||||
#### 创建文档 (仅新建时使用)
|
#### 创建文档 (仅新建时使用)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
curl -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"notebook": "笔记本ID",
|
"notebook": "笔记本ID",
|
||||||
@@ -313,8 +220,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
|||||||
#### 获取文档内容
|
#### 获取文档内容
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/getDoc \
|
curl -X POST ${SIYUAN_URL}/api/filetree/getDoc \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"id": "文档ID"}'
|
-d '{"id": "文档ID"}'
|
||||||
```
|
```
|
||||||
@@ -322,8 +229,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/getDoc \
|
|||||||
#### 删除文档
|
#### 删除文档
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/removeDoc \
|
curl -X POST ${SIYUAN_URL}/api/filetree/removeDoc \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"notebook": "笔记本ID", "path": "/文档路径"}'
|
-d '{"notebook": "笔记本ID", "path": "/文档路径"}'
|
||||||
```
|
```
|
||||||
@@ -331,8 +238,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/removeDoc \
|
|||||||
#### 重命名文档
|
#### 重命名文档
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/renameDoc \
|
curl -X POST ${SIYUAN_URL}/api/filetree/renameDoc \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"notebook": "笔记本ID", "path": "/旧路径", "title": "新标题"}'
|
-d '{"notebook": "笔记本ID", "path": "/旧路径", "title": "新标题"}'
|
||||||
```
|
```
|
||||||
@@ -344,8 +251,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/renameDoc \
|
|||||||
#### 插入块
|
#### 插入块
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/block/insertBlock \
|
curl -X POST ${SIYUAN_URL}/api/block/insertBlock \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"dataType": "markdown",
|
"dataType": "markdown",
|
||||||
@@ -357,8 +264,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/insertBlock \
|
|||||||
#### 更新块
|
#### 更新块
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
curl -X POST ${SIYUAN_URL}/api/block/updateBlock \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"dataType": "markdown",
|
"dataType": "markdown",
|
||||||
@@ -370,8 +277,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
|||||||
#### 删除块
|
#### 删除块
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/block/deleteBlock \
|
curl -X POST ${SIYUAN_URL}/api/block/deleteBlock \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"id": "块ID"}'
|
-d '{"id": "块ID"}'
|
||||||
```
|
```
|
||||||
@@ -383,8 +290,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/deleteBlock \
|
|||||||
#### 全文搜索
|
#### 全文搜索
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/search/fullTextSearchBlock \
|
curl -X POST ${SIYUAN_URL}/api/search/fullTextSearchBlock \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"query": "搜索关键词",
|
"query": "搜索关键词",
|
||||||
@@ -395,8 +302,8 @@ curl -X POST https://siyuan.pipexerp.com/api/search/fullTextSearchBlock \
|
|||||||
#### SQL 查询
|
#### SQL 查询
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/query/sql \
|
curl -X POST ${SIYUAN_URL}/api/query/sql \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{
|
-d '{
|
||||||
"stmt": "SELECT * FROM blocks WHERE content LIKE '\''%关键词%'\'' LIMIT 10"
|
"stmt": "SELECT * FROM blocks WHERE content LIKE '\''%关键词%'\'' LIMIT 10"
|
||||||
@@ -410,8 +317,8 @@ curl -X POST https://siyuan.pipexerp.com/api/query/sql \
|
|||||||
#### 导出 Markdown
|
#### 导出 Markdown
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl -X POST https://siyuan.pipexerp.com/api/export/exportMdContent \
|
curl -X POST ${SIYUAN_URL}/api/export/exportMdContent \
|
||||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"id": "文档ID"}'
|
-d '{"id": "文档ID"}'
|
||||||
```
|
```
|
||||||
@@ -421,13 +328,16 @@ curl -X POST https://siyuan.pipexerp.com/api/export/exportMdContent \
|
|||||||
## Python 封装
|
## Python 封装
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import os
|
||||||
import requests
|
import requests
|
||||||
from typing import Optional, Dict, Any
|
from typing import Optional, Dict, Any
|
||||||
|
|
||||||
class SiYuanAPI:
|
class SiYuanAPI:
|
||||||
"""思源笔记 API 封装"""
|
"""思源笔记 API 封装"""
|
||||||
|
|
||||||
def __init__(self, base_url: str = "https://siyuan.pipexerp.com", token: str = "nfnycjb1g8vbexb2"):
|
def __init__(self, base_url: str = None, token: str = None):
|
||||||
|
base_url = base_url or os.environ.get("SIYUAN_URL", "")
|
||||||
|
token = token or os.environ.get("SIYUAN_TOKEN", "")
|
||||||
self.base_url = base_url
|
self.base_url = base_url
|
||||||
self.headers = {
|
self.headers = {
|
||||||
"Authorization": f"Token {token}",
|
"Authorization": f"Token {token}",
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "siyuan-to-feishu",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "wecom",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "integration"
|
||||||
}
|
}
|
||||||
|
|||||||
11
skills-req/req-audit-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-audit-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "req-audit-plugin",
|
||||||
|
"description": "部署后审计。运行时日志检查 + 静态缺陷分析 + 设计偏移检测。可独立调用或由 /req done 自动触发。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "req-audit",
|
||||||
|
"install_type": "command",
|
||||||
|
"dir_category": "req"
|
||||||
|
}
|
||||||
105
skills-req/req-audit-plugin/skills/SKILL.md
Normal file
105
skills-req/req-audit-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
name: req-audit
|
||||||
|
description: 部署后审计。运行时日志检查 + 静态缺陷分析 + 设计偏移检测。可独立调用或由 /req done 自动触发。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 部署后审计 (audit)
|
||||||
|
|
||||||
|
对本次部署执行三维度审计:运行时行为、代码缺陷、设计偏移。
|
||||||
|
|
||||||
|
## 执行流程
|
||||||
|
|
||||||
|
### 2a. 运行时检查
|
||||||
|
|
||||||
|
检查部署后是否有新增错误。
|
||||||
|
|
||||||
|
**优先 SSH**:
|
||||||
|
```bash
|
||||||
|
ssh -o ConnectTimeout=3 ${EC2_USER}@${EC2_HOST} \
|
||||||
|
"docker logs ${APP_CONTAINER} --since 10m 2>&1 | grep -i 'error\|panic\|fatal\|traceback'"
|
||||||
|
```
|
||||||
|
|
||||||
|
**降级 1:CI 日志**:
|
||||||
|
```bash
|
||||||
|
RUN_ID=$(gh run list --repo ${OWNER}/${REPO} --limit 1 --json databaseId -q '.[0].databaseId')
|
||||||
|
gh run view ${RUN_ID} --repo ${OWNER}/${REPO} --log 2>&1 | grep -i 'error\|panic\|fatal'
|
||||||
|
```
|
||||||
|
|
||||||
|
**降级 2:N/A + 警告**:
|
||||||
|
```
|
||||||
|
⚠️ 无法获取运行时日志(SSH 不可达 + CI 日志无异常信息),2a 标记为 N/A。
|
||||||
|
运行时问题可能未被发现,建议手动检查服务器日志。
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2b. 静态分析
|
||||||
|
|
||||||
|
调用现有 `/defect-analysis` command,传入变更文件:
|
||||||
|
|
||||||
|
```
|
||||||
|
对以下变更文件执行缺陷分析:
|
||||||
|
{变更文件列表}
|
||||||
|
|
||||||
|
重点关注:运行时行为(不是合并前 CR 的重复,而是部署后复查)
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:缺陷清单(按致命/高/中/低分级)
|
||||||
|
|
||||||
|
### 2c. 设计偏移检测
|
||||||
|
|
||||||
|
1. 读取需求的 PRD 文档(linkRole=prd 的任务文档)
|
||||||
|
2. 读取本次变更的源码
|
||||||
|
3. AI 对比分析:
|
||||||
|
|
||||||
|
```
|
||||||
|
请对比以下 PRD 功能点和实际代码实现:
|
||||||
|
|
||||||
|
PRD 功能点:
|
||||||
|
{从 PRD 提取的功能清单}
|
||||||
|
|
||||||
|
实际代码变更:
|
||||||
|
{变更文件的关键逻辑}
|
||||||
|
|
||||||
|
检查:
|
||||||
|
- 遗漏的功能(PRD 有但代码没实现)
|
||||||
|
- 多做的功能(代码有但 PRD 没提)
|
||||||
|
- 实现方式与 PRD 描述不一致
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:偏移项列表
|
||||||
|
|
||||||
|
### 合并报告
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 部署后审计报告
|
||||||
|
|
||||||
|
### 2a 运行时检查
|
||||||
|
| 检查项 | 结果 | 详情 |
|
||||||
|
| 新增错误 | ✅ 无 / ❌ 有 N 条 | ... |
|
||||||
|
|
||||||
|
### 2b 静态分析
|
||||||
|
| 缺陷 | 严重度 | 描述 |
|
||||||
|
(来自 defect-analysis 输出)
|
||||||
|
|
||||||
|
### 2c 设计偏移
|
||||||
|
| 偏移项 | 类型 | 说明 |
|
||||||
|
|
||||||
|
### 结论
|
||||||
|
- 致命/高级缺陷: N 个 → {阻断/通过}
|
||||||
|
- 中/低级缺陷: N 个 → 已创建 backlog
|
||||||
|
- 设计偏移: N 项 → {建议处理方式}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 缺陷分级处理
|
||||||
|
|
||||||
|
| 级别 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 致命 | 阻断归档 + 回滚建议 + `ai-proj task create` 创建修复任务并关联需求 |
|
||||||
|
| 高级 | 阻断归档 + 回滚建议 + 创建修复任务 |
|
||||||
|
| 中级 | 警告不阻断 + 创建 backlog 任务 |
|
||||||
|
| 低级 | 记录到报告,不创建任务 |
|
||||||
|
|
||||||
|
## 任务关联
|
||||||
|
|
||||||
|
- linkRole: `code_review`
|
||||||
|
- 任务标题: `【审计】部署后审计: {需求标题}`
|
||||||
|
- 报告附加到任务文档
|
||||||
11
skills-req/req-compare-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-compare-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "req-compare-plugin",
|
||||||
|
"description": "对比式需求分析插件。系统平移、竞品借鉴、版本升级时的参考对象对比分析。挂载在 analysis 阶段。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "req-compare",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
|
}
|
||||||
210
skills-req/req-compare-plugin/skills/SKILL.md
Normal file
210
skills-req/req-compare-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,210 @@
|
|||||||
|
---
|
||||||
|
name: req-compare
|
||||||
|
description: 对比式需求分析插件。系统平移、竞品借鉴、版本升级时使用参考对象对比法编写 PRD。挂载在 analysis 阶段,需要对比分析时由 req-prd 推荐激活。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 对比式需求分析插件 (req-compare)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
当进行**系统平移、功能迁移、竞品借鉴**时,使用对比分析法编写 PRD,确保新系统功能完整且有所改进。
|
||||||
|
|
||||||
|
**触发条件**:
|
||||||
|
- 用户提到"从 XX 系统迁移"、"参考 XX 功能"、"平移"、"借鉴"
|
||||||
|
- req-prd 检测到需求涉及参考系统
|
||||||
|
|
||||||
|
## 适用场景
|
||||||
|
|
||||||
|
| 场景 | 说明 | 示例 |
|
||||||
|
|------|------|------|
|
||||||
|
| 系统平移 | 旧系统迁移到新技术栈 | 酷采2.0 → 酷采3.0 |
|
||||||
|
| 功能借鉴 | 参考竞品功能设计 | 参考飞书设计协作功能 |
|
||||||
|
| 版本升级 | 基于当前版本优化 | V1.0 → V2.0 重构 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对比分析工作流
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 确定参考对象
|
||||||
|
├── 识别参考系统(可以是多个)
|
||||||
|
├── 获取访问权限(测试环境、源代码)
|
||||||
|
└── 明确对比目标
|
||||||
|
|
||||||
|
2. 参考对象分析
|
||||||
|
├── 功能调研(前端页面操作)
|
||||||
|
├── 业务数据分析(核心实体、字段含义)
|
||||||
|
├── 业务逻辑分析(规则、流转、校验)
|
||||||
|
└── 用户体验分析
|
||||||
|
|
||||||
|
3. 对比分析
|
||||||
|
├── 功能对比表(保留/优化/新增/废弃)
|
||||||
|
├── 业务数据对比(实体映射、新增数据项)
|
||||||
|
├── 用户体验对比
|
||||||
|
└── 非功能需求对比(性能、安全)
|
||||||
|
|
||||||
|
4. PRD 编写
|
||||||
|
├── 背景说明(明确参考来源)
|
||||||
|
├── 功能需求(标注来源与变更)
|
||||||
|
├── 业务数据需求(实体、字段、规则)
|
||||||
|
└── 非功能需求(性能、安全、兼容性)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 对比式 PRD 模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# [功能模块名称] PRD
|
||||||
|
|
||||||
|
## 1. 文档概述
|
||||||
|
|
||||||
|
### 1.1 文档信息
|
||||||
|
| 项目 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 文档名称 | [模块名称] 需求文档 |
|
||||||
|
| 版本 | V1.0 |
|
||||||
|
| 创建日期 | [日期] |
|
||||||
|
| **需求来源** | **[参考系统名称] 平移/借鉴** |
|
||||||
|
| **参考系统** | **[参考系统访问地址]** |
|
||||||
|
|
||||||
|
### 1.2 背景说明
|
||||||
|
本需求文档基于 **[参考系统]** 的 [模块名称] 功能分析,将其平移至 [目标系统]。
|
||||||
|
|
||||||
|
**参考系统信息**:
|
||||||
|
- 系统地址:[URL]
|
||||||
|
- 技术栈:[技术栈描述]
|
||||||
|
- 源码位置:[源码路径](如有)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 参考系统分析
|
||||||
|
|
||||||
|
### 2.1 功能截图
|
||||||
|
[插入参考系统功能截图]
|
||||||
|
|
||||||
|
### 2.2 业务数据(参考系统)
|
||||||
|
| 数据实体 | 核心字段 | 业务含义 |
|
||||||
|
|----------|----------|----------|
|
||||||
|
| [实体名] | [字段列表] | [业务说明] |
|
||||||
|
|
||||||
|
### 2.3 核心功能(参考系统)
|
||||||
|
| 功能 | 用户操作 | 业务规则 |
|
||||||
|
|------|----------|----------|
|
||||||
|
| [功能名] | [操作描述] | [规则说明] |
|
||||||
|
|
||||||
|
### 2.4 业务逻辑(参考系统)
|
||||||
|
- 核心业务规则摘要
|
||||||
|
- 数据校验规则
|
||||||
|
- 状态流转逻辑
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 功能对比分析
|
||||||
|
|
||||||
|
### 3.1 功能对比表
|
||||||
|
| 序号 | 功能 | 参考系统 | 目标系统 | 变更类型 | 说明 |
|
||||||
|
|------|------|----------|----------|----------|------|
|
||||||
|
| 1 | [功能1] | ✅ | ✅ | 保留 | 直接平移 |
|
||||||
|
| 2 | [功能2] | ✅ | ✅+ | 优化 | [优化内容] |
|
||||||
|
| 3 | [功能3] | ❌ | ✅ | 新增 | [新增原因] |
|
||||||
|
| 4 | [功能4] | ✅ | ❌ | 废弃 | [废弃原因] |
|
||||||
|
|
||||||
|
### 3.2 业务数据对比
|
||||||
|
| 数据项 | 参考系统 | 目标系统 | 变更 | 说明 |
|
||||||
|
|--------|----------|----------|------|------|
|
||||||
|
| [数据项] | [描述] | [描述] | 保留/优化/新增/废弃 | [说明] |
|
||||||
|
|
||||||
|
### 3.3 非功能需求对比
|
||||||
|
| 维度 | 参考系统 | 目标系统要求 |
|
||||||
|
|------|----------|-------------|
|
||||||
|
| 性能 | [现状] | [目标] |
|
||||||
|
| 安全 | [现状] | [目标] |
|
||||||
|
| 用户体验 | [现状] | [目标] |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 目标系统设计
|
||||||
|
|
||||||
|
### 4.1 功能清单
|
||||||
|
| 序号 | 功能 | 优先级 | 来源 | 说明 |
|
||||||
|
|------|------|--------|------|------|
|
||||||
|
| 1 | [功能] | P0 | 平移 | 从参考系统平移 |
|
||||||
|
|
||||||
|
### 4.2 业务数据需求(目标系统)
|
||||||
|
| 数据实体 | 核心字段 | 业务规则 | 来源 |
|
||||||
|
|----------|----------|----------|------|
|
||||||
|
| [实体名] | [字段列表] | [校验/约束] | 平移/新增 |
|
||||||
|
|
||||||
|
### 4.3 业务规则
|
||||||
|
- [ ] 规则1(沿用参考系统)
|
||||||
|
- [ ] 规则2(优化调整)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 上线优先级
|
||||||
|
1. [P0 功能] — 核心路径
|
||||||
|
2. [P1 功能] — 重要但可后续迭代
|
||||||
|
3. [P2 功能] — 优化项
|
||||||
|
|
||||||
|
## 6. 注意事项
|
||||||
|
- 参考系统中 [xxx] 逻辑需要特别注意
|
||||||
|
- 新系统中需改进 [xxx] 问题
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 参考对象分析方法
|
||||||
|
|
||||||
|
### 1. 前端功能调研
|
||||||
|
- 访问参考系统,截图记录页面布局和交互流程
|
||||||
|
- 记录用户操作路径(CRUD、搜索、筛选等)
|
||||||
|
- 标注交互细节(表单校验、提示信息、异常处理)
|
||||||
|
|
||||||
|
### 2. 业务逻辑调研
|
||||||
|
- 梳理核心业务规则和状态流转
|
||||||
|
- 记录数据校验规则
|
||||||
|
- 标注业务异常处理方式
|
||||||
|
|
||||||
|
> **技术层分析**(代码结构、数据库表结构、API 接口)请在 design 阶段使用 `req-design` 技能完成。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 竞品分析模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# [竞品名称] 分析
|
||||||
|
|
||||||
|
## 1. 产品概述
|
||||||
|
- 定位:
|
||||||
|
- 核心功能:
|
||||||
|
- 目标用户:
|
||||||
|
|
||||||
|
## 2. 功能对比
|
||||||
|
| 功能 | 我们 | 竞品A | 竞品B |
|
||||||
|
|------|------|-------|-------|
|
||||||
|
| 功能1 | ✅/❌/部分 | ... | ... |
|
||||||
|
|
||||||
|
## 3. 优劣势分析
|
||||||
|
### 优势
|
||||||
|
1. ...
|
||||||
|
|
||||||
|
### 劣势
|
||||||
|
1. ...
|
||||||
|
|
||||||
|
## 4. 可借鉴点
|
||||||
|
- ...
|
||||||
|
|
||||||
|
## 5. 差异化策略
|
||||||
|
- ...
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **先调研再写 PRD** — 充分理解参考系统后再动笔
|
||||||
|
2. **功能对比表必填** — 明确每个功能的保留/优化/新增/废弃决策
|
||||||
|
3. **标注来源** — 每个功能需求标注是"平移"还是"新增"
|
||||||
|
4. **记录废弃原因** — 参考系统有但不做的功能,必须记录原因
|
||||||
|
5. **不抄技术实现** — 对比的是业务功能,不是代码结构
|
||||||
11
skills-req/req-design-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-design-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "req-design-plugin",
|
||||||
|
"description": "需求开发设计技能。PRD 到开发设计的转换:API 契约、数据模型变更、任务拆分、风险评估。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "req-design",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
|
}
|
||||||
476
skills-req/req-design-plugin/skills/SKILL.md
Normal file
476
skills-req/req-design-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,476 @@
|
|||||||
|
---
|
||||||
|
name: req-design
|
||||||
|
description: 需求开发设计技能。用于 PRD 到开发设计的转换:API 契约定义、数据模型变更方案、变更文件清单、开发任务拆分、技术风险评估。当用户执行 /req doc 或需要编写开发设计文档时自动激活。
|
||||||
|
arguments: <REQ-ID>
|
||||||
|
---
|
||||||
|
|
||||||
|
# 需求开发设计 Skill (req-design)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
本技能用于将 PRD 文档转换为**开发设计文档**,是产品需求(req-prd)和编码实现(dev-coding)之间的桥梁。
|
||||||
|
|
||||||
|
**核心输出**:
|
||||||
|
- API 契约定义(前后端对齐)
|
||||||
|
- 数据模型变更方案(新增/修改表和字段)
|
||||||
|
- 变更文件清单(标注层级和改动类型)
|
||||||
|
- 开发任务拆分(SMART 原则,2-4h 粒度)
|
||||||
|
- 技术风险评估
|
||||||
|
|
||||||
|
**不包含**(由其他技能负责):
|
||||||
|
- 具体代码实现 → `dev-coding`
|
||||||
|
- 代码规范和编码模式 → `dev-coding`
|
||||||
|
- 深度架构设计 → `dev-arch`(插件)
|
||||||
|
- 数据库迁移细节 → `db-migration`(插件)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技能间契约
|
||||||
|
|
||||||
|
| 上游 | 本技能输入 | 本技能输出 | 下游 |
|
||||||
|
|------|-----------|-----------|------|
|
||||||
|
| req-prd | PRD 文档(用户故事、业务规则、验收标准) | 开发设计文档 | dev-coding |
|
||||||
|
|
||||||
|
**输出格式要求**:
|
||||||
|
- API 契约:B 级格式(见下方标准)
|
||||||
|
- 变更清单:按文件列出,标注层级和改动类型
|
||||||
|
- 任务拆分:每任务 2-4h,SMART 原则
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作流程
|
||||||
|
|
||||||
|
```
|
||||||
|
0. ⚠️ 需求验证(防浪费,5-10 分钟)
|
||||||
|
├── 现有 UI 检查:功能是否已存在?
|
||||||
|
├── 数据检查:字段/API 是否已有?
|
||||||
|
└── 价值验证:痛点明确?有更简方案?
|
||||||
|
|
||||||
|
1. 获取需求信息
|
||||||
|
├── mcp__ai-proj__get_requirement
|
||||||
|
└── mcp__ai-proj__get_requirement_tasks(找 PRD 任务)
|
||||||
|
|
||||||
|
2. 分析 PRD 文档
|
||||||
|
├── 提取功能点和业务规则
|
||||||
|
├── 识别数据实体和关系
|
||||||
|
└── 确定非功能需求(性能、安全)
|
||||||
|
|
||||||
|
3. 探索代码库
|
||||||
|
├── 搜索相关现有代码(Grep/Glob/Explore)
|
||||||
|
├── 识别修改文件和可复用代码
|
||||||
|
└── 分析依赖关系
|
||||||
|
|
||||||
|
4. 设计 API 契约
|
||||||
|
├── 定义新增/修改接口(B 级格式)
|
||||||
|
├── 定义请求/响应结构
|
||||||
|
└── 定义错误码
|
||||||
|
|
||||||
|
5. 设计数据模型变更
|
||||||
|
├── 新增/修改表和字段
|
||||||
|
├── 索引设计
|
||||||
|
└── 数据迁移方案(如需要)
|
||||||
|
|
||||||
|
6. 生成变更文件清单
|
||||||
|
├── 后端:Model → Repository → Service → Handler → Route → Migration
|
||||||
|
├── 前端:Types → Services → Components → Pages
|
||||||
|
└── 标注每个文件的改动类型(新增/修改/删除)
|
||||||
|
|
||||||
|
7. 拆分开发任务
|
||||||
|
├── 按模块/文件拆分子任务
|
||||||
|
└── mcp__ai-proj__create_subtask
|
||||||
|
|
||||||
|
8. 技术风险评估
|
||||||
|
├── 影响分析(兼容性、性能)
|
||||||
|
└── 回退方案
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 需求验证(第 0 步)
|
||||||
|
|
||||||
|
> **教训**:不先验证需求,可能花几小时实现冗余功能。
|
||||||
|
|
||||||
|
**验证清单**(5-10 分钟):
|
||||||
|
|
||||||
|
1. **现有功能检查**(最重要)
|
||||||
|
- 打开相关页面,目视检查功能是否已存在
|
||||||
|
- 检查数据库是否已有相关字段
|
||||||
|
- 检查 API 是否已返回所需数据
|
||||||
|
|
||||||
|
2. **价值验证**
|
||||||
|
- 解决什么具体痛点?
|
||||||
|
- 有无更简单的替代方案?
|
||||||
|
- 投入产出比是否合理?
|
||||||
|
|
||||||
|
3. **设计审查**
|
||||||
|
- UI 设计是否合理?不重复、不混乱?
|
||||||
|
- 是否符合现有设计规范?
|
||||||
|
|
||||||
|
**通过验证后再开始设计!**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 契约格式标准(B 级)
|
||||||
|
|
||||||
|
所有新增/修改 API 必须使用以下格式定义契约,作为前后端对齐的桥梁:
|
||||||
|
|
||||||
|
```
|
||||||
|
### POST /api/v1/enterprises
|
||||||
|
描述: 创建企业
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
- name: string (required) — 企业名称,最长 100 字符
|
||||||
|
- code: string (required) — 企业编码,唯一,正则 ^[A-Z0-9-]+$
|
||||||
|
- contact_email: string (optional) — 联系邮箱
|
||||||
|
|
||||||
|
响应 200:
|
||||||
|
- id: int
|
||||||
|
- name: string
|
||||||
|
- code: string
|
||||||
|
- created_at: datetime
|
||||||
|
|
||||||
|
错误:
|
||||||
|
- 400: 参数校验失败(name/code 为空或格式错误)
|
||||||
|
- 409: 编码已存在
|
||||||
|
- 403: 无权限创建企业
|
||||||
|
```
|
||||||
|
|
||||||
|
**B 级规则**:
|
||||||
|
- 列出所有字段、类型、必填/可选
|
||||||
|
- 标注校验规则(长度、格式、唯一性)
|
||||||
|
- 列出所有可能的错误码和含义
|
||||||
|
- 不需要 JSON 示例(A 级才需要)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 开发设计文档模板
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# [需求标题] 开发设计文档
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
|
||||||
|
### 1.1 需求信息
|
||||||
|
| 项目 | 内容 |
|
||||||
|
|------|------|
|
||||||
|
| 需求编号 | REQ-YYYYMMDD-XXXX |
|
||||||
|
| 需求标题 | [标题] |
|
||||||
|
| 优先级 | high/medium/low |
|
||||||
|
| 预估工时 | Xh |
|
||||||
|
|
||||||
|
### 1.2 功能点清单
|
||||||
|
- [ ] 功能点1:[描述]
|
||||||
|
- [ ] 功能点2:[描述]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 代码库分析
|
||||||
|
|
||||||
|
### 2.1 相关现有代码
|
||||||
|
| 文件路径 | 层级 | 当前功能 | 改动类型 |
|
||||||
|
|---------|------|----------|---------|
|
||||||
|
| `backend/models/xxx.go` | Model | 数据模型 | 新增字段 |
|
||||||
|
| `backend/services/xxx_service.go` | Service | 业务逻辑 | 新增方法 |
|
||||||
|
|
||||||
|
### 2.2 可复用代码
|
||||||
|
- [描述已有的可参考实现]
|
||||||
|
|
||||||
|
### 2.3 依赖关系
|
||||||
|
- [需要注意的调用链和依赖]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. API 契约
|
||||||
|
|
||||||
|
### 3.1 新增接口
|
||||||
|
|
||||||
|
### POST /api/v1/xxx
|
||||||
|
描述: [功能描述]
|
||||||
|
|
||||||
|
请求体:
|
||||||
|
- field1: type (required) — 说明
|
||||||
|
- field2: type (optional) — 说明
|
||||||
|
|
||||||
|
响应 200:
|
||||||
|
- field1: type
|
||||||
|
- field2: type
|
||||||
|
|
||||||
|
错误:
|
||||||
|
- 400: [说明]
|
||||||
|
- 404: [说明]
|
||||||
|
|
||||||
|
### 3.2 修改接口
|
||||||
|
[列出需要修改的现有接口及变更内容]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 数据模型变更
|
||||||
|
|
||||||
|
### 4.1 新增表
|
||||||
|
| 表名 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| xxx | [用途] |
|
||||||
|
|
||||||
|
**字段设计**:
|
||||||
|
| 字段 | 类型 | 约束 | 说明 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| id | BIGSERIAL | PK | 主键 |
|
||||||
|
| tenant_id | BIGINT | NOT NULL, INDEX | 租户ID |
|
||||||
|
| name | VARCHAR(255) | NOT NULL | 名称 |
|
||||||
|
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | 创建时间 |
|
||||||
|
|
||||||
|
**索引**:
|
||||||
|
| 索引名 | 字段 | 类型 | 说明 |
|
||||||
|
|--------|------|------|------|
|
||||||
|
| idx_xxx_tenant | tenant_id | B-tree | 租户查询 |
|
||||||
|
|
||||||
|
### 4.2 修改表
|
||||||
|
| 表名 | 变更 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| xxx | 新增字段 yyy | [用途] |
|
||||||
|
|
||||||
|
### 4.3 数据迁移(如需要)
|
||||||
|
[迁移方案描述]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 变更文件清单
|
||||||
|
|
||||||
|
### 后端
|
||||||
|
| 文件 | 层级 | 改动类型 | 改动说明 |
|
||||||
|
|------|------|---------|---------|
|
||||||
|
| `models/xxx.go` | Model | 修改 | 新增字段 |
|
||||||
|
| `database/xxx_repository.go` | Repository | 修改 | 新增查询方法 |
|
||||||
|
| `services/xxx_service.go` | Service | 修改 | 新增业务方法 |
|
||||||
|
| `handlers/xxx_handler.go` | Handler | 修改 | 新增接口 |
|
||||||
|
| `routes/xxx_routes.go` | Route | 修改 | 注册新路由 |
|
||||||
|
| `migrations/YYYYMMDD_xxx.up.sql` | Migration | 新增 | 表结构变更 |
|
||||||
|
|
||||||
|
### 前端
|
||||||
|
| 文件 | 层级 | 改动类型 | 改动说明 |
|
||||||
|
|------|------|---------|---------|
|
||||||
|
| `types/xxx.ts` | Types | 修改 | 新增接口定义 |
|
||||||
|
| `services/xxxService.ts` | Service | 修改 | 新增 API 调用 |
|
||||||
|
| `pages/XxxPage.tsx` | Page | 修改 | 新增功能 UI |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 开发任务拆分
|
||||||
|
|
||||||
|
### 6.1 任务列表
|
||||||
|
| # | 任务标题 | 预估工时 | 依赖 |
|
||||||
|
|---|---------|---------|------|
|
||||||
|
| 1 | [后端] Model + Migration | 1h | - |
|
||||||
|
| 2 | [后端] Repository + Service | 2h | #1 |
|
||||||
|
| 3 | [后端] Handler + Route | 2h | #2 |
|
||||||
|
| 4 | [前端] Types + Service | 1h | #3 |
|
||||||
|
| 5 | [前端] 页面开发 | 3h | #4 |
|
||||||
|
| 6 | [测试] 单元测试 | 2h | #3, #5 |
|
||||||
|
|
||||||
|
### 6.2 实施顺序
|
||||||
|
[按依赖关系排列的开发步骤]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 技术风险评估
|
||||||
|
|
||||||
|
### 7.1 影响分析
|
||||||
|
| 改动类型 | 影响范围 | 风险等级 | 应对措施 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| [描述] | [范围] | 高/中/低 | [措施] |
|
||||||
|
|
||||||
|
### 7.2 风险清单
|
||||||
|
- [ ] 数据库:是否需要数据迁移?是否影响数据完整性?
|
||||||
|
- [ ] API:是否破坏兼容性?是否需要版本管理?
|
||||||
|
- [ ] 前端:是否影响现有页面?是否有性能瓶颈?
|
||||||
|
- [ ] 业务:是否影响核心流程?是否需要灰度?
|
||||||
|
|
||||||
|
### 7.3 回退方案
|
||||||
|
[如果上线出问题,如何回退]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 注意事项
|
||||||
|
[特殊说明、边界情况、兼容性要求]
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 任务拆分规则
|
||||||
|
|
||||||
|
### SMART 原则
|
||||||
|
|
||||||
|
- **Specific**(具体):明确要修改哪个文件/模块
|
||||||
|
- **Measurable**(可衡量):清晰的完成标准
|
||||||
|
- **Achievable**(可实现):单个任务 2-4 小时完成
|
||||||
|
- **Relevant**(相关):与需求直接相关
|
||||||
|
- **Time-bound**(有时限):预估工时
|
||||||
|
|
||||||
|
### 拆分粒度
|
||||||
|
|
||||||
|
- ✅ 好的粒度:`[Handler] 修改 manual_handler.go 添加发布接口(2h)`
|
||||||
|
- ❌ 太粗:`[后端] 实现所有后端功能`
|
||||||
|
- ❌ 太细:`[Handler] 添加 import 语句`
|
||||||
|
|
||||||
|
### 按文件拆分(推荐)
|
||||||
|
- 每个文件对应一个子任务
|
||||||
|
- 格式:`[层级] 修改 文件名 简要说明`
|
||||||
|
|
||||||
|
### 按功能模块拆分(大改动)
|
||||||
|
- 将相关文件归为一个模块
|
||||||
|
- 格式:`[模块] 功能描述`
|
||||||
|
|
||||||
|
### 标准开发顺序
|
||||||
|
|
||||||
|
```
|
||||||
|
第一阶段:数据层
|
||||||
|
├── Model 层:定义数据结构
|
||||||
|
├── Migration:创建/修改表
|
||||||
|
└── Repository 层:实现数据访问
|
||||||
|
|
||||||
|
第二阶段:业务层
|
||||||
|
├── Service 层:实现业务逻辑
|
||||||
|
└── 编写单元测试
|
||||||
|
|
||||||
|
第三阶段:接口层
|
||||||
|
├── Handler 层:实现 HTTP 接口
|
||||||
|
├── Route 层:注册路由
|
||||||
|
└── API 文档(Swagger)
|
||||||
|
|
||||||
|
第四阶段:前端层
|
||||||
|
├── Types:定义 TypeScript 接口
|
||||||
|
├── Services:封装 API 调用
|
||||||
|
├── Components:开发可复用组件
|
||||||
|
└── Pages:实现业务页面
|
||||||
|
|
||||||
|
第五阶段:测试与优化
|
||||||
|
├── 后端单元测试
|
||||||
|
├── 前端组件测试
|
||||||
|
└── E2E 测试
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术风险评估框架
|
||||||
|
|
||||||
|
### 影响分析矩阵
|
||||||
|
|
||||||
|
| 改动类型 | 影响范围 | 风险等级 | 应对措施 |
|
||||||
|
|---------|---------|---------|---------|
|
||||||
|
| 新增字段(非必填) | 低 | 低 | 添加 Migration,向后兼容 |
|
||||||
|
| 修改字段类型 | 中 | 中 | 数据迁移脚本,充分测试 |
|
||||||
|
| 删除字段 | 高 | 高 | 确认无依赖,先标记废弃 |
|
||||||
|
| 新增 API 接口 | 低 | 低 | 接口设计评审 |
|
||||||
|
| 修改 API 接口 | 中 | 中 | 保留旧接口,添加版本号 |
|
||||||
|
| 删除 API 接口 | 高 | 高 | 确认无调用,发布公告 |
|
||||||
|
| 新增前端页面 | 低 | 低 | 路由冲突检查 |
|
||||||
|
| 修改核心组件 | 高 | 高 | 充分测试所有使用场景 |
|
||||||
|
|
||||||
|
### 性能影响评估
|
||||||
|
|
||||||
|
| 维度 | 评估项 |
|
||||||
|
|------|--------|
|
||||||
|
| 数据库 | 查询复杂度、索引使用、数据量、并发影响 |
|
||||||
|
| API | 响应时间预期、QPS 预估、缓存策略、限流需求 |
|
||||||
|
| 前端 | 首屏加载影响、渲染性能、内存占用 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 与 ai-proj 集成
|
||||||
|
|
||||||
|
### 文档创建
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 创建开发设计文档并关联任务
|
||||||
|
mcp__ai-proj__create-and-attach({
|
||||||
|
taskId: <设计任务ID>,
|
||||||
|
title: "开发设计文档 - [需求标题]",
|
||||||
|
content: "<使用上方模板生成的内容>"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 子任务拆分
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
mcp__ai-proj__create_subtask({
|
||||||
|
parentId: <开发任务ID>,
|
||||||
|
title: "[Handler] 修改 manual_handler.go 添加发布接口"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 进度更新
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
mcp__ai-proj__update_task_document({
|
||||||
|
taskId: <设计任务ID>,
|
||||||
|
content: "<更新后的设计文档>"
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 插件触发
|
||||||
|
|
||||||
|
| 插件 | 触发条件 | 说明 |
|
||||||
|
|------|---------|------|
|
||||||
|
| `dev-arch` | 新模块/新表/复杂架构 | 深度架构设计 |
|
||||||
|
| `db-migration` | 涉及数据库变更 | 数据库迁移方案 |
|
||||||
|
|
||||||
|
当检测到以上条件时,建议用户启用对应插件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 最佳实践
|
||||||
|
|
||||||
|
1. **先验证再设计** — 5 分钟验证省 3 小时返工
|
||||||
|
2. **API 契约先行** — 前后端对齐的桥梁,先定义再实现
|
||||||
|
3. **复用优于新建** — 充分探索代码库,识别可复用代码
|
||||||
|
4. **风险前置** — 在设计阶段识别风险,而非编码时才发现
|
||||||
|
5. **粒度适中** — 任务拆分 2-4h,不要太粗也不要太细
|
||||||
|
6. **文档可操作** — 开发者可直接按设计文档实施
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常见问题
|
||||||
|
|
||||||
|
### Q1: 如何确定是否需要新增数据库表?
|
||||||
|
检查:新数据是否与现有实体有独立生命周期?是否需要独立查询?是否 1:N 或 N:N 关系?任一满足则新增表。
|
||||||
|
|
||||||
|
### Q2: 什么时候需要创建子任务?
|
||||||
|
修改 3 个以上文件 / 预估工时 > 4h / 涉及多个层级 → 创建子任务。
|
||||||
|
|
||||||
|
### Q3: API 契约需要多详细?
|
||||||
|
B 级:字段 + 类型 + 必填/可选 + 校验规则 + 错误码。不需要 JSON 示例。
|
||||||
|
|
||||||
|
### Q4: 设计文档和 PRD 的边界?
|
||||||
|
PRD 说「做什么」(用户故事、业务规则、验收标准),设计文档说「怎么做」(API、数据模型、文件清单)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Memory 隔离规则(强制,源自 devflow-claude 借鉴)
|
||||||
|
|
||||||
|
**规则:本 skill 产出的设计文档禁止受 auto-memory 影响结构和字段定义。**
|
||||||
|
|
||||||
|
### 禁止行为
|
||||||
|
1. 不得用 memory 里的历史 API 契约填充当前设计(避免张冠李戴)
|
||||||
|
2. 不得根据 memory 偏好省略"变更文件清单"/"数据模型变更"等章节
|
||||||
|
3. 不得读取 `~/.claude/projects/*/memory/` 生成 API 字段或 SQL
|
||||||
|
|
||||||
|
### 允许行为
|
||||||
|
- memory 可影响交互风格、命令推荐
|
||||||
|
- memory 可记住"用户偏好 RESTful 而非 RPC" 这类**偏好**,但字段定义必须基于当前需求
|
||||||
|
|
||||||
|
### Why
|
||||||
|
设计文档是开发契约,字段/接口/表结构错一个字就是 bug。memory 污染会让 AI 脑补出"看起来像但不对"的字段。必须严格基于当前 PRD + 代码现状生成。
|
||||||
|
|
||||||
|
**参考**:devflow-claude `plugins/req/commands/_common.md` 同名规则。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 变更记录
|
||||||
|
|
||||||
|
| 版本 | 日期 | 变更内容 |
|
||||||
|
|------|------|----------|
|
||||||
|
| V1.0 | 2026-01-26 | 初始版本(原名 req-dev) |
|
||||||
|
| V2.0 | 2026-04-06 | 重构为 req-design:移除架构指南和编码规范,聚焦 API 契约 + 任务拆分 |
|
||||||
|
| V2.1 | 2026-04-16 | 新增 Memory 隔离规则(REQ-20260416-0017) |
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "req-dev-plugin",
|
"name": "req-dev-plugin",
|
||||||
"description": "Plugin for req-dev",
|
"description": "[已废弃] 请使用 req-design-plugin。需求开发设计功能已迁移。",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"deprecated": true,
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "req-dev",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
}
|
}
|
||||||
|
|||||||
11
skills-req/req-lookback-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-lookback-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "req-lookback-plugin",
|
||||||
|
"description": "回归测试。部署后自动验证变更涉及的功能是否正常。可独立调用或由 /req done 自动触发。",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "req-lookback",
|
||||||
|
"install_type": "command",
|
||||||
|
"dir_category": "req"
|
||||||
|
}
|
||||||
87
skills-req/req-lookback-plugin/skills/SKILL.md
Normal file
87
skills-req/req-lookback-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
---
|
||||||
|
name: req-lookback
|
||||||
|
description: 回归测试。部署后自动验证变更涉及的功能是否正常。可独立调用或由 /req done 自动触发。
|
||||||
|
---
|
||||||
|
|
||||||
|
# 回归测试 (lookback)
|
||||||
|
|
||||||
|
对本次部署变更的功能执行自动化验证。
|
||||||
|
|
||||||
|
## 执行流程
|
||||||
|
|
||||||
|
### 1. 获取变更文件列表
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 优先从最近合并的 PR 获取(覆盖 merge 多 commit 场景)
|
||||||
|
gh pr list --state merged --base main --limit 1 --json number,files
|
||||||
|
# 回退:git diff
|
||||||
|
git diff HEAD~1..HEAD --name-only
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 自动发现验证项(动态推断)
|
||||||
|
|
||||||
|
读取变更文件,按类型推断需要验证什么:
|
||||||
|
|
||||||
|
| 文件类型 | 推断方式 | 验证命令 |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| `*.vue` / `*.tsx` | grep router 配置,提取对应路由路径 | `curl -sf -o /dev/null -w "%{http_code}" ${SERVER}${ROUTE}` |
|
||||||
|
| `app/api/*.py` | grep 路由装饰器 `@router.get/post`,提取 API 路径 | `curl -sf -o /dev/null -w "%{http_code}" ${SERVER}/api${PATH}` |
|
||||||
|
| `app/services/*.py` | 找到引用该 service 的 api 文件,提取关联 API | 同上 |
|
||||||
|
| `nginx.conf` | 提取 location 块 | `curl` 各路径检查状态码 |
|
||||||
|
| `docker-compose.yml` | 提取服务列表 | `docker ps` 检查容器状态 |
|
||||||
|
| `alembic/*.py` | 提取表名 | `psql -c "\d table_name"` |
|
||||||
|
| `*.md` / `*.txt` / `SKILL.md` | 纯文档 | **N/A(自动通过)** |
|
||||||
|
| `*.css` / `*.scss` | 纯样式 | 轻量验证:页面可达即可 |
|
||||||
|
|
||||||
|
### 3. 执行验证
|
||||||
|
|
||||||
|
**远端模式**(SSH 可达时):
|
||||||
|
```bash
|
||||||
|
# 3 秒超时检测
|
||||||
|
ssh -o ConnectTimeout=3 ${EC2_USER}@${EC2_HOST} "echo ok" 2>/dev/null
|
||||||
|
# 成功 → 执行完整验证(docker ps + curl + docker logs)
|
||||||
|
```
|
||||||
|
|
||||||
|
**降级模式**(SSH 不可达时):
|
||||||
|
```bash
|
||||||
|
# 检查最近 CI run 是否成功
|
||||||
|
gh run list --repo ${OWNER}/${REPO} --limit 1 --json conclusion
|
||||||
|
# conclusion=success → PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 输出报告
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## 回归测试报告
|
||||||
|
|
||||||
|
### 变更范围
|
||||||
|
| 文件 | 类型 | 推断的验证项 |
|
||||||
|
|
||||||
|
### 测试结果
|
||||||
|
| 测试项 | 方式 | 预期 | 实际 | 状态 |
|
||||||
|
|
||||||
|
### 结论: ✅ PASS / ❌ FAIL / N/A
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 失败处理
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ 回归测试失败:{失败项描述}
|
||||||
|
|
||||||
|
建议操作:
|
||||||
|
1. [回滚] git revert HEAD~1 + /req deploy(推荐,影响最小)
|
||||||
|
2. [修复] 创建 hotfix 分支修复后重新部署
|
||||||
|
3. [忽略] 标记为已知问题,继续归档
|
||||||
|
|
||||||
|
输入 1/2/3:
|
||||||
|
```
|
||||||
|
|
||||||
|
- 选 1 → 执行 `git revert`,提示用户运行 `/req deploy`
|
||||||
|
- 选 2 → 创建 hotfix 任务(ai-proj create_task),阻断归档
|
||||||
|
- 选 3 → 记录到报告,继续
|
||||||
|
|
||||||
|
## 任务关联
|
||||||
|
|
||||||
|
- linkRole: `test`
|
||||||
|
- 任务标题: `【回归】回归测试: {需求标题}`
|
||||||
|
- 报告附加到任务文档
|
||||||
@@ -4,5 +4,8 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "req",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ analysis → design → dev → review → testing → [待部署池] → releas
|
|||||||
- **操作前先确认实际 ID** — 从 URL 提取 ID(如 `/requirements/897` → ID=897)
|
- **操作前先确认实际 ID** — 从 URL 提取 ID(如 `/requirements/897` → ID=897)
|
||||||
- **务实路线关闭也必须补全关联任务** — 每个进度条阶段需创建关联任务
|
- **务实路线关闭也必须补全关联任务** — 每个进度条阶段需创建关联任务
|
||||||
- **阶段内容门禁(防空转)** — `/req next` 时检查关键阶段任务是否有实质内容
|
- **阶段内容门禁(防空转)** — `/req next` 时检查关键阶段任务是否有实质内容
|
||||||
|
- **Harness 环境检测** — `/req dev` 启动开发前,快速检测项目基础设施(`.husky/` 存在?`scripts/check-*.sh` 存在?)。若两者都不存在(Level 0),输出一行提示:「项目无质量护栏,建议先运行 `/harness init`」。检测结果不阻塞开发,仅提示
|
||||||
|
|
||||||
## 禁止直接调用(必须走命令流程)
|
## 禁止直接调用(必须走命令流程)
|
||||||
|
|
||||||
@@ -86,27 +87,45 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
1. /req new → 创建需求 (draft)
|
1. /req new → 创建需求 (draft)
|
||||||
2. 需求讨论 → 输出结论摘要,用户确认
|
2. 需求讨论 → 输出结论摘要,用户确认
|
||||||
3. req-prd 技能 → 编写 PRD(创建【评审】PRD 任务)
|
3. req-prd 技能 → 编写 PRD(创建【评审】PRD 任务)
|
||||||
|
3.5 /req prototype → 生成 Stitch 原型(可选,有 UI 时推荐)
|
||||||
4. /req submit → 提交评审 (pending),含 4 道门禁
|
4. /req submit → 提交评审 (pending),含 4 道门禁
|
||||||
5. /req review pass → 评审通过 (approved, delivery_stage=analysis)
|
5. /req review pass → 评审通过 (approved, delivery_stage=analysis)
|
||||||
6. /req next → 推进到 design/dev 阶段
|
6. /req next → 推进到 design/dev 阶段
|
||||||
7. /req dev → 启动开发(创建【开发】任务, delivery_stage=dev)
|
7. /req dev → 启动开发(创建【开发】任务, delivery_stage=dev)
|
||||||
8. /req next → 推进到 review 阶段
|
7.5 /req ci → CI 质量门禁检查与自动修复(PR 提交后执行)
|
||||||
9. /req cr → 代码评审(创建【代码评审】任务)
|
8. /req next → 推进到 review(需 quality_gate 证据)
|
||||||
|
9. /req cr → 代码评审 + 约定检查 + bug约定建议(⭐ harness 自动)
|
||||||
10. /req next → 推进到 testing 阶段
|
10. /req next → 推进到 testing 阶段
|
||||||
11. /req test → 测试验收(5-Gate 流程)
|
11. /req test → 测试验收(Gate 0B 约定检查 ⭐ + Gate 1-5)
|
||||||
12. /req deploy → 批量部署(DG1-DG6 Deploy Gates)
|
12. /req deploy → 批量部署(build-and-push.sh → Jenkins ai-proj)
|
||||||
13. /req done → 归档 (archived) + git commit + push
|
13. /req done → 归档 (archived) + git commit + push
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> ⭐ 标记的步骤是 Harness Engineering 自动嵌入的,无需手动调用。
|
||||||
|
|
||||||
**5 阶段文档**:
|
**5 阶段文档**:
|
||||||
|
|
||||||
| 阶段 | 文档名 | 技能 |
|
| 阶段 | 文档名 | 技能 |
|
||||||
|------|--------|------|
|
|------|--------|------|
|
||||||
| PRD | 01-PRD.md | `req-prd` |
|
| PRD | 01-PRD.md | `req-prd` |
|
||||||
| 测试 | 02-测试报告.md | 自动生成 |
|
| 测试 | 02-测试报告.md | 自动生成 |
|
||||||
| 发布 | 03-发布记录.md | 自动生成 |
|
| 发布 | 03-发布记录.md | 自动生成(含 PDV 验收章节) |
|
||||||
| 归档 | 04-生命周期总结.md | 自动生成 |
|
| 归档 | 04-生命周期总结.md | 自动生成 |
|
||||||
|
|
||||||
|
**03-发布记录.md 中 PDV 章节模板**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 部署后验收 (PDV)
|
||||||
|
| 检查项 | 结果 | 截图 |
|
||||||
|
|--------|------|------|
|
||||||
|
| 页面可达: /path/to/page | ✅ PASS | [截图] |
|
||||||
|
| 菜单可见: {菜单名} | ✅ PASS | [截图] |
|
||||||
|
| 基础渲染: 无白屏/JS 报错 | ✅ PASS | [截图] |
|
||||||
|
| API: /api/v1/{路径} | ✅ PASS | — |
|
||||||
|
|
||||||
|
PDV 结论: ✅ 全部通过 / ❌ {N}项失败,阻断推进
|
||||||
|
```
|
||||||
|
|
||||||
## 任务命名规范
|
## 任务命名规范
|
||||||
|
|
||||||
| linkRole | 前缀 | 示例 |
|
| linkRole | 前缀 | 示例 |
|
||||||
@@ -117,6 +136,7 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
| code_review | 【代码评审】 | 【代码评审】CR: 代码审查 |
|
| code_review | 【代码评审】 | 【代码评审】CR: 代码审查 |
|
||||||
| test | 【测试】 | 【测试】集成测试验证 |
|
| test | 【测试】 | 【测试】集成测试验证 |
|
||||||
| deploy | 【部署】 | 【部署】部署到 staging |
|
| deploy | 【部署】 | 【部署】部署到 staging |
|
||||||
|
| verification | 【验收】 | 【验收】PDV: OKR 功能部署验收 |
|
||||||
| documentation | 【文档】 | 【文档】API 文档更新 |
|
| documentation | 【文档】 | 【文档】API 文档更新 |
|
||||||
|
|
||||||
## 阶段内容门禁(防空转)
|
## 阶段内容门禁(防空转)
|
||||||
@@ -131,6 +151,24 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
|
|
||||||
未通过 → AskUserQuestion(补充文档 or 强制跳过+记录原因)
|
未通过 → AskUserQuestion(补充文档 or 强制跳过+记录原因)
|
||||||
|
|
||||||
|
### 部署门禁(推进到 released 前)
|
||||||
|
|
||||||
|
`/req deploy` 推进 `released` 前,必须通过 3 道门禁:
|
||||||
|
|
||||||
|
```
|
||||||
|
Deploy Gate 1: 健康检查 ── /health 返回 200,服务存活
|
||||||
|
Deploy Gate 2: PDV 验收 ── linkRole=verification 的【验收】任务存在且 completed
|
||||||
|
Deploy Gate 3: 证据完整 ── 验收任务有文档,含检查项表格 + E2E 截图 + 通过/失败结论
|
||||||
|
```
|
||||||
|
|
||||||
|
**PDV 验收任务生命周期**:
|
||||||
|
1. `/req deploy` 步骤 6 自动创建:`ai-proj task create --title "【验收】PDV: {需求标题}"`
|
||||||
|
2. 关联需求:`ai-proj req link --id <req_id> --task-ids <task_id> --link-role verification`
|
||||||
|
3. 执行 Playwright PDV 冒烟测试
|
||||||
|
4. 附加验收文档(检查项表格 + 截图证据 + 结论):`ai-proj task append-doc --id <task_id>`
|
||||||
|
5. **全部通过** → `ai-proj task complete --id <task_id>` → 门禁放行
|
||||||
|
6. **任一失败** → 任务保持 in_progress,阻断推进,报告失败项
|
||||||
|
|
||||||
### CR 五视角扫描法
|
### CR 五视角扫描法
|
||||||
|
|
||||||
**核心原则**:实现阶段关注"怎么让它跑通",评审阶段关注"怎么让它出错"。AI 必须**切换到对抗性思维**,逐一用以下 5 个视角扫描代码。
|
**核心原则**:实现阶段关注"怎么让它跑通",评审阶段关注"怎么让它出错"。AI 必须**切换到对抗性思维**,逐一用以下 5 个视角扫描代码。
|
||||||
@@ -231,6 +269,18 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
|
|
||||||
**`/req split [REQ-ID]`** — 拆分为开发任务(后端/前端/测试)并关联
|
**`/req split [REQ-ID]`** — 拆分为开发任务(后端/前端/测试)并关联
|
||||||
|
|
||||||
|
### 原型设计
|
||||||
|
|
||||||
|
**`/req prototype [REQ-ID]`** — 基于 PRD 生成 Stitch 原型:
|
||||||
|
1. 读取 PRD 文档,提取 UI 相关描述
|
||||||
|
2. 创建 Stitch 项目 + 生成页面(调用 req-prototype 子技能)
|
||||||
|
3. 截图回填 PRD「4.2 界面原型」章节
|
||||||
|
- 参数:`--device [desktop|mobile|tablet]`,`--model [pro|flash]`
|
||||||
|
|
||||||
|
**`/req prototype edit [REQ-ID] --prompt "..."`** — 修改已有原型
|
||||||
|
|
||||||
|
**`/req prototype variant [REQ-ID]`** — 生成设计变体供选择
|
||||||
|
|
||||||
### 提交评审
|
### 提交评审
|
||||||
|
|
||||||
**`/req submit [REQ-ID]`** ⭐ — 从 draft 到 pending 的唯一合法路径:
|
**`/req submit [REQ-ID]`** ⭐ — 从 draft 到 pending 的唯一合法路径:
|
||||||
@@ -258,6 +308,10 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
5. 自动创建目标阶段建议任务
|
5. 自动创建目标阶段建议任务
|
||||||
6. 展示:「✅ 已创建: #XXXX {任务名}。如需撤销请说明」
|
6. 展示:「✅ 已创建: #XXXX {任务名}。如需撤销请说明」
|
||||||
|
|
||||||
|
**dev → review 门禁**:
|
||||||
|
- `pr_created`: PR 已创建
|
||||||
|
- `quality_gate`: CI 质量门禁通过记录(质量门禁未通过时提示:「CI 质量门禁未通过,请先运行 `/req ci`」)
|
||||||
|
|
||||||
**阶段顺序**:`analysis → design → dev → review → testing → [待部署池] → released`
|
**阶段顺序**:`analysis → design → dev → review → testing → [待部署池] → released`
|
||||||
|
|
||||||
**`/req resume [REQ-ID]`** — 会话断点恢复:`ai-proj req get --id <id>` 获取 delivery_stage + 任务状态 + 建议操作
|
**`/req resume [REQ-ID]`** — 会话断点恢复:`ai-proj req get --id <id>` 获取 delivery_stage + 任务状态 + 建议操作
|
||||||
@@ -274,24 +328,73 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
|||||||
2. `git diff` / `find` 确定变更范围(文件数、行数)
|
2. `git diff` / `find` 确定变更范围(文件数、行数)
|
||||||
3. 读取所有变更文件源码(非 test 文件)
|
3. 读取所有变更文件源码(非 test 文件)
|
||||||
4. **五视角扫描**:逐一用攻击者/泄露者/并发者/边界者/依赖者视角审查
|
4. **五视角扫描**:逐一用攻击者/泄露者/并发者/边界者/依赖者视角审查
|
||||||
5. `ai-proj task create` 创建【代码评审】任务并关联需求(linkRole=code_review)
|
5. **约定检查**:运行所有 `scripts/check-*.sh --ci`,将结果写入 CR 报告
|
||||||
6. `ai-proj create-and-attach` 附加 CR 报告文档(必须含五视角扫描结果)
|
6. **⭐ Bug 约定建议**(bug 类需求自动触发:category=bug,或标题含 fix/修复/bug,或描述含根因/复现步骤):
|
||||||
7. 展示发现摘要,AskUserQuestion 确认是否创建 bug 修复任务
|
- 分析本次 bug 的根因模式
|
||||||
8. 若有 High/Critical 发现 → `ai-proj task create` 创建关联修复任务
|
- 判断可检测性:能写 grep 找到同类问题?(规则见 convention-flow.md「可检测性判断」)
|
||||||
|
- 不可检测 → 仅在 CR 报告记录根因,跳过
|
||||||
|
- 可检测 → 检查已有 `scripts/check-*.sh` 是否已覆盖 → 已覆盖则跳过
|
||||||
|
- 扫描代码库统计同类模式数量 N
|
||||||
|
- AskUserQuestion(N=0: 防未来复发;N>0: 防恶化+逐步清理)
|
||||||
|
- 是 → 执行 convention flow → 产出物**单独 commit**(`chore: 建立 {name} 约定`)
|
||||||
|
7. `ai-proj task create` 创建【代码评审】任务并关联需求(linkRole=code_review)
|
||||||
|
8. `ai-proj create-and-attach` 附加 CR 报告文档(必须含五视角扫描结果 + 约定检查结果)
|
||||||
|
9. 展示发现摘要,AskUserQuestion 确认是否创建 bug 修复任务
|
||||||
|
10. 若有 High/Critical 发现 → `ai-proj task create` 创建关联修复任务
|
||||||
|
|
||||||
|
**`/req ci [REQ-ID]`** — CI 质量门禁检查与自动修复:
|
||||||
|
1. `check-ci-status.sh` 检查当前分支 CI 状态
|
||||||
|
- 通过 → 写入 `quality_gate` 门禁证据(`gate-callback.sh`)→ 完成
|
||||||
|
- 运行中 → `--wait` 等待最多 5 分钟
|
||||||
|
- 失败 → 进入自动修复循环
|
||||||
|
2. 自动修复循环(最多 3 轮):
|
||||||
|
a. `fetch-ci-logs.sh --failed-only` 获取失败 job 日志
|
||||||
|
b. AI 诊断根因(见错误模式目录)
|
||||||
|
c. 自动修复代码
|
||||||
|
d. 本地验证(`go vet ./...` / `npx tsc --noEmit`)
|
||||||
|
e. `git commit && git push`
|
||||||
|
f. `check-ci-status.sh --trigger --wait` 等待新 CI
|
||||||
|
g. 通过 → 写入门禁证据 → 完成
|
||||||
|
h. 仍失败 → 下一轮(超过 3 轮 → AskUserQuestion 是否继续)
|
||||||
|
- 参数:`--trigger` 手动触发 CI,`--wait` 等待完成,`--no-fix` 仅诊断不修复
|
||||||
|
|
||||||
|
**错误模式目录**:
|
||||||
|
|
||||||
|
| 失败 Job | 错误模式 | 修复策略 |
|
||||||
|
|----------|---------|---------|
|
||||||
|
| architecture-gate | 架构 ratchet 违规 | 读取违规文件,用 service 层替代直接 database import |
|
||||||
|
| backend-lint (go vet) | `file.go:42: unreachable code` | 读取文件,修复具体问题 |
|
||||||
|
| backend-lint (gofmt) | `Files need gofmt:` | 运行 `gofmt -s -w` |
|
||||||
|
| backend-lint (go test) | `--- FAIL: TestXxx` | 读取测试和实现,修复根因 |
|
||||||
|
| frontend-lint (ESLint) | `warnings N > 4600` | 对比 main,只修复新增警告 |
|
||||||
|
| frontend-lint (Prettier) | `unformatted N > 1000` | `npx prettier --write` 变更文件 |
|
||||||
|
| frontend-lint (TS) | `errors N > 60` | 对比 main,只修复新增类型错误 |
|
||||||
|
|
||||||
**`/req test [REQ-ID]`** — 测试(前置:代码评审通过),遵循 dev-test 技能的 5-Gate 流程
|
**`/req test [REQ-ID]`** — 测试(前置:代码评审通过),遵循 dev-test 技能的 5-Gate 流程
|
||||||
|
|
||||||
**`/req deploy [--project <name>] [--env <env>]`** — 项目级批量部署:
|
**`/req deploy [--project <name>] [--env production]`** — 项目级批量部署(Staging 已自动化,push develop 即触发):
|
||||||
1. 收集待部署需求(delivery_stage=testing + 全 test 任务 completed)
|
1. 收集待部署需求(delivery_stage=testing + 全 test 任务 completed)
|
||||||
2. AskUserQuestion 确认范围
|
2. AskUserQuestion 确认范围
|
||||||
3. `ai-proj task create` 创建部署批次任务
|
3. `ai-proj task create` 创建【部署】批次任务(linkRole=deploy)
|
||||||
4. Deploy Gates DG1-DG6:staging 部署 → 冒烟测试 → 回归 → 生产部署
|
4. 部署前检查(变更检测、数据库迁移)
|
||||||
5. `ai-proj task append-doc` 记录部署文档
|
5. 执行 `./scripts/build-and-push.sh prod --detect --deploy --wait --verify`
|
||||||
6. `ai-proj req advance --id <id> --to released` 批量推进
|
6. **Deploy Gate 1: 健康检查** — `/health` 返回 200
|
||||||
|
7. **Deploy Gate 2+3: PDV 验收**(为每个需求创建独立验收任务):
|
||||||
|
a. `ai-proj task create --title "【验收】PDV: {需求标题}"` 创建验收任务
|
||||||
|
b. `ai-proj req link --id <req_id> --task-ids <task_id> --link-role verification` 关联需求
|
||||||
|
c. 从需求关联任务中提取前端变更范围(页面路由、菜单项、关键 API)
|
||||||
|
d. 生成验收检查清单(页面可达 + 菜单可见 + 基础渲染 + API 连通)
|
||||||
|
e. 用 Playwright 对部署环境执行冒烟测试:`E2E_BASE_URL=<部署环境URL> npx playwright test e2e/pdv/ --project=chromium`
|
||||||
|
f. 收集截图证据
|
||||||
|
g. `ai-proj task append-doc --id <task_id>` 附加验收文档(检查项表格 + 截图 + 结论)
|
||||||
|
h. **全部通过** → `ai-proj task complete --id <task_id>` → Gate 2+3 放行
|
||||||
|
i. **任一失败** → 任务保持 in_progress,**阻断推进**,报告失败项
|
||||||
|
8. `ai-proj task append-doc` 记录【部署】任务文档(构建日志 + 健康检查 + PDV 结果摘要)
|
||||||
|
9. `ai-proj req advance --id <id> --to released` 批量推进(仅 Gate 1-3 全部通过的需求)
|
||||||
|
|
||||||
**`/req done [REQ-ID]`** — 类型化归档门禁 + git commit + push + `ai-proj req archive --id <id>`:
|
**`/req done [REQ-ID]`** — 类型化归档门禁 + git commit + push + `ai-proj req archive --id <id>`:
|
||||||
- **推断类型**:有 implementation → code;无 implementation 有 prd/test → skill;仅 deploy → ops
|
- **推断类型**:有 implementation → code;无 implementation 有 prd/test → skill;仅 deploy → ops
|
||||||
- **code 检查**:delivery_stage=released + deploy 任务完成 + 部署文档 + 所有任务完成
|
- **code 检查**:delivery_stage=released + deploy 任务完成 + verification 任务完成(PDV 通过) + 部署文档 + 所有任务完成
|
||||||
- **skill 检查**:delivery_stage≥testing + 所有任务完成
|
- **skill 检查**:delivery_stage≥testing + 所有任务完成
|
||||||
- **ops 检查**:deploy 任务完成 + 所有任务完成
|
- **ops 检查**:deploy 任务完成 + 所有任务完成
|
||||||
|
|
||||||
@@ -317,8 +420,10 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
|||||||
| design | 【评审】技术设计: {需求标题} | design |
|
| design | 【评审】技术设计: {需求标题} | design |
|
||||||
| dev | 【开发-后端】{需求标题}、【开发-前端】{需求标题} | implementation |
|
| dev | 【开发-后端】{需求标题}、【开发-前端】{需求标题} | implementation |
|
||||||
| review | 【代码评审】{需求标题} | code_review |
|
| review | 【代码评审】{需求标题} | code_review |
|
||||||
|
| review | 【约定】{约定名称}(bug 类需求,convention flow 自动创建) | documentation |
|
||||||
| testing | 【测试】集成测试: {需求标题} | test |
|
| testing | 【测试】集成测试: {需求标题} | test |
|
||||||
| deploy | 由 `/req deploy` 批量创建 | deploy |
|
| deploy | 由 `/req deploy` 批量创建 | deploy |
|
||||||
|
| released | 【验收】PDV: {需求标题}(`/req deploy` 自动创建) | verification |
|
||||||
|
|
||||||
## 测试环境流程
|
## 测试环境流程
|
||||||
|
|
||||||
@@ -337,7 +442,8 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
|||||||
| implementation | 【开发】 | dev | 50% |
|
| implementation | 【开发】 | dev | 50% |
|
||||||
| code_review | 【代码评审】 | review | 5% |
|
| code_review | 【代码评审】 | review | 5% |
|
||||||
| test | 【测试】 | testing | 15% |
|
| test | 【测试】 | testing | 15% |
|
||||||
| deploy | 【部署】 | staging/released | 10% |
|
| deploy | 【部署】 | staging/released | 5% |
|
||||||
|
| verification | 【验收】 | released | 5% |
|
||||||
| documentation | 【文档】 | any | 5% |
|
| documentation | 【文档】 | any | 5% |
|
||||||
|
|
||||||
## 标准任务结构
|
## 标准任务结构
|
||||||
@@ -350,7 +456,9 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
|||||||
├── 🔍 review → 【代码评审】CR: {需求标题} (linkRole: code_review)
|
├── 🔍 review → 【代码评审】CR: {需求标题} (linkRole: code_review)
|
||||||
├── 🧪 testing → 【测试】集成测试: {需求标题} (linkRole: test)
|
├── 🧪 testing → 【测试】集成测试: {需求标题} (linkRole: test)
|
||||||
├── 🚀 staging → 【部署】部署到 staging (linkRole: deploy)
|
├── 🚀 staging → 【部署】部署到 staging (linkRole: deploy)
|
||||||
└── 🏁 released → 【部署】部署到 prod (linkRole: deploy)
|
└── 🏁 released
|
||||||
|
├── 【部署】部署到 prod (linkRole: deploy)
|
||||||
|
└── 【验收】PDV: {需求标题} (linkRole: verification)
|
||||||
```
|
```
|
||||||
|
|
||||||
## ai-proj CLI 速查
|
## ai-proj CLI 速查
|
||||||
@@ -374,187 +482,13 @@ ai-proj req tasks --id <id> # 查看关联任务
|
|||||||
|
|
||||||
**开发阶段**: `backlog`, `analysis`, `design`, `dev`, `review`, `integration`, `testing`, `staging`, `released`
|
**开发阶段**: `backlog`, `analysis`, `design`, `dev`, `review`, `integration`, `testing`, `staging`, `released`
|
||||||
|
|
||||||
**linkRole**: `prd`, `design`, `implementation`, `code_review`, `test`, `deploy`, `regression`, `documentation`
|
**linkRole**: `prd`, `design`, `implementation`, `code_review`, `test`, `deploy`, `verification`, `regression`, `documentation`
|
||||||
|
|
||||||
## 详细阶段管理命令
|
|
||||||
|
|
||||||
### `/req phase [REQ-ID]`
|
|
||||||
查看需求当前开发阶段和任务状态。
|
|
||||||
|
|
||||||
**执行逻辑**:
|
|
||||||
1. `get_requirement(id)` → 获取 `delivery_stage`
|
|
||||||
2. `get_requirement_tasks(id)` → 获取所有关联任务
|
|
||||||
3. 按 linkRole 分组展示,标注当前阶段任务完成度
|
|
||||||
|
|
||||||
**输出示例**:
|
|
||||||
```
|
|
||||||
REQ-20260218-0013 | 阶段: dev (开发)
|
|
||||||
────────────────────────────────────
|
|
||||||
✅ analysis (评审)
|
|
||||||
✅ #6035 【评审】PRD: 需求全生命周期阶段化管理
|
|
||||||
▶ dev (开发) ← 当前阶段
|
|
||||||
🔄 #6042 【开发-后端】实现阶段管理 API [in_progress]
|
|
||||||
⬜ #6043 【开发-前端】阶段可视化组件 [todo]
|
|
||||||
⬜ review (代码评审)
|
|
||||||
⬜ testing (测试)
|
|
||||||
```
|
|
||||||
|
|
||||||
### `/req resume [REQ-ID]`
|
|
||||||
从上次中断的位置恢复工作。**会话被截断后的首选命令**。
|
|
||||||
|
|
||||||
**执行逻辑**:
|
|
||||||
1. 若无 REQ-ID → `list_requirements(status=approved)` 找最近活跃需求
|
|
||||||
2. `get_requirement(id)` → 获取 delivery_stage
|
|
||||||
3. `get_requirement_tasks(id)` → 获取所有关联任务及状态
|
|
||||||
4. 汇总展示恢复上下文
|
|
||||||
|
|
||||||
**输出格式**:
|
|
||||||
```
|
|
||||||
📋 恢复工作上下文
|
|
||||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
||||||
需求: REQ-20260218-0016 | 需求标题
|
|
||||||
状态: approved | 阶段: dev (开发)
|
|
||||||
|
|
||||||
📊 任务进度: 2/4 完成
|
|
||||||
✅ #6051 【开发-后端】实现 API
|
|
||||||
🔄 #6053 【开发-Skill】xxx [in_progress]
|
|
||||||
⬜ #6054 【开发-Skill】xxx [todo]
|
|
||||||
|
|
||||||
▶ 建议操作:
|
|
||||||
1. 继续任务 #6053(当前 in_progress)
|
|
||||||
2. 执行 /req next 推进阶段
|
|
||||||
```
|
|
||||||
|
|
||||||
### `/req regression [--module <name>] [--mode <mode>]`
|
|
||||||
**运行回归测试**。系统级持续质量活动。
|
|
||||||
|
|
||||||
- `--module` 指定模块,不指定则按影响域推断
|
|
||||||
- `--mode`:`impact`(影响域,默认)| `critical`(关键路径)| `full`(全量)
|
|
||||||
|
|
||||||
**执行逻辑**:
|
|
||||||
1. 确定范围:指定 module → 直接运行;未指定 → `git diff` + `module-deps.json` 推断
|
|
||||||
2. 运行 `regression-suite/suites/` 下对应脚本
|
|
||||||
3. 对比上次运行结果,标记回归(上次 PASS + 本次 FAIL)
|
|
||||||
4. 有回归 → AskUserQuestion「是否创建 bug 需求?」
|
|
||||||
|
|
||||||
## 辅助命令
|
|
||||||
|
|
||||||
**`/req link [REQ-ID] --task-id [TASK-ID]`** — 关联任务到需求
|
|
||||||
|
|
||||||
**`/req decompose [REQ-ID]`** — 分解需求为任务,自动加阶段前缀
|
|
||||||
|
|
||||||
**`/req generate-tasks [REQ-ID]`** — 根据 PRD 自动生成开发任务,带【开发】前缀
|
|
||||||
|
|
||||||
**`/req priority [REQ-ID] [low|medium|high]`** — 设置需求优先级
|
|
||||||
|
|
||||||
**`/req stats [REQ-ID|--all]`** — 查看需求统计信息
|
|
||||||
|
|
||||||
## 阶段任务 Checklist 模板
|
|
||||||
|
|
||||||
`/req next` 推进到新阶段时,通过 `create_stage_task` 自动创建建议任务:
|
|
||||||
|
|
||||||
### analysis(评审)
|
|
||||||
```
|
|
||||||
☐ 【评审】PRD: {需求标题} (linkRole: prd)
|
|
||||||
☐ 【评审】技术设计: {需求标题} (linkRole: design) [可选]
|
|
||||||
```
|
|
||||||
|
|
||||||
### design(设计)
|
|
||||||
```
|
|
||||||
☐ 【设计】技术方案: {需求标题} (linkRole: design)
|
|
||||||
```
|
|
||||||
|
|
||||||
### dev(开发)
|
|
||||||
```
|
|
||||||
☐ 【开发-后端】{需求标题} (linkRole: implementation)
|
|
||||||
☐ 【开发-前端】{需求标题} (linkRole: implementation) [可选]
|
|
||||||
```
|
|
||||||
|
|
||||||
### review(代码评审)
|
|
||||||
```
|
|
||||||
☐ 【代码评审】{需求标题} (linkRole: code_review)
|
|
||||||
```
|
|
||||||
|
|
||||||
### testing(测试)
|
|
||||||
```
|
|
||||||
☐ 【测试】单元测试: {需求标题} (linkRole: test)
|
|
||||||
☐ 【测试】集成测试: {需求标题} (linkRole: test) [可选]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 部署(由 `/req deploy` 批量触发)
|
|
||||||
```
|
|
||||||
testing 完成 → 进入「待部署池」→ /req deploy 创建批次任务
|
|
||||||
```
|
|
||||||
|
|
||||||
**按需求类型的最低测试标准**:
|
|
||||||
|
|
||||||
| 需求类型 | 最低测试要求 |
|
|
||||||
|----------|-------------|
|
|
||||||
| **code** | `go test` 或前端测试通过 + API 验证 + 截图验证 |
|
|
||||||
| **skill** | 关键词一致性 + 规则无歧义 + 边界情况(至少 3 项) |
|
|
||||||
| **ops** | 部署命令可执行 + 健康检查通过 |
|
|
||||||
|
|
||||||
## MCP 工具映射
|
|
||||||
|
|
||||||
| 命令 | MCP 工具 |
|
|
||||||
|------|----------|
|
|
||||||
| `/req` | `list_requirements` |
|
|
||||||
| `/req new` | `create_requirement` |
|
|
||||||
| `/req phase` | `get_requirement` + `get_requirement_tasks` |
|
|
||||||
| `/req next` | `advance_delivery_stage` + `create_stage_task` |
|
|
||||||
| `/req resume` | `get_requirement` + `get_requirement_tasks` |
|
|
||||||
| `/req dev` | `get_requirement` + `get_requirement_tasks` + `start_task_with_timer` |
|
|
||||||
| `/req cr` | `get_requirement_tasks` + Git diff |
|
|
||||||
| `/req test` | `req-test-gate` 5-Gate 流程 |
|
|
||||||
| `/req deploy` | Deploy Gates DG1-DG6 |
|
|
||||||
| `/req regression` | `git diff` + 回归套件 |
|
|
||||||
| `/req done` | 类型推断 + 检查清单 + `archive_requirement` |
|
|
||||||
| `/req review` | `submit_requirement` |
|
|
||||||
| `/req review pass` | `approve_requirement` |
|
|
||||||
| `/req link` | `link_tasks_to_requirement` |
|
|
||||||
|
|
||||||
## 环境选择
|
|
||||||
|
|
||||||
> **重要**:正式需求应直接在**生产环境** (`mcp__ai-proj-prod__*`) 创建。
|
|
||||||
> 仅在功能测试、流程验证时使用开发环境 (`mcp__ai-proj-dev__*`)。
|
|
||||||
> 开发环境同步到生产时,`due_date`、`reviewer_id`、`allow_self_approve` 等字段会丢失,需手动补充。
|
|
||||||
|
|
||||||
## Hook 自动同步
|
|
||||||
|
|
||||||
需求操作自动触发同步,无需手动执行:
|
|
||||||
|
|
||||||
| 事件 | 触发操作 |
|
|
||||||
|------|----------|
|
|
||||||
| `requirement.created` | 同步到远程 |
|
|
||||||
| `requirement.approved` | 同步需求和任务 |
|
|
||||||
| `task.completed` | 同步任务状态 |
|
|
||||||
| `requirement.archived` | 最终同步 + 思源笔记 |
|
|
||||||
|
|
||||||
**手动同步**:
|
|
||||||
```typescript
|
|
||||||
mcp__ai-proj__sync_requirement_to_remote(requirementId)
|
|
||||||
mcp__ai-proj__batch_sync_tasks_to_remote(taskIds)
|
|
||||||
```
|
|
||||||
|
|
||||||
**同步已知限制**:
|
|
||||||
| 字段 | 同步支持 | 说明 |
|
|
||||||
|------|----------|------|
|
|
||||||
| title, description, status, priority | ✅ | 正常同步 |
|
|
||||||
| **due_date** | ❌ | 不会同步,需手动补充 |
|
|
||||||
| reviewer_id | ❌ | 不会同步 |
|
|
||||||
| allow_self_approve | ❌ | 不会同步 |
|
|
||||||
|
|
||||||
## 思源笔记集成
|
|
||||||
|
|
||||||
归档时自动同步 5 阶段文档到思源笔记:
|
|
||||||
- 路径:`需求管理/REQ-XXXX/`
|
|
||||||
- 文档:01-PRD.md ~ 05-生命周期总结.md
|
|
||||||
- 手动同步:`/req sync-siyuan [REQ-ID]`
|
|
||||||
|
|
||||||
## 相关技能
|
## 相关技能
|
||||||
|
|
||||||
| 技能 | 用途 |
|
| 技能 | 用途 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `req-prd` | PRD 文档编写 + 评审方法论 |
|
| `req-prd` | PRD 文档编写 + 评审方法论 |
|
||||||
| `req-test-gate` | 测试 5-Gate + Deploy Gates |
|
| `req-prototype` | Stitch 原型生成 + 迭代 |
|
||||||
| `req-dev` | PRD 到代码转换、开发计划 |
|
| `dev-test` | 测试 + 质量门禁 |
|
||||||
|
| `req-test-gate` (harness) | 工程约束方法论。Gate 0-2 的约定检查由项目本地脚本定义,方法论见 `/harness` 命令 |
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "req-prd-plugin",
|
"name": "req-prd-plugin",
|
||||||
"description": "Plugin for req-prd",
|
"description": "产品需求设计技能。PRD 文档编写、需求分析、用户故事、对比式分析。纯产品视角,不含技术实现。",
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "qiudl"
|
"name": "qiudl"
|
||||||
}
|
},
|
||||||
|
"install_name": "req-prd",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: req-prd
|
name: req-prd
|
||||||
description: 产品设计与需求管理。用于 PRD 文档编写、需求分析、用户故事创建、功能设计和原型规划、PRD 评审。当用户提到产品设计、PRD、需求文档、功能规划、用户故事、PRD 评审相关任务时自动激活。
|
description: 产品设计与需求管理。用于 PRD 文档编写、需求分析、用户故事创建、功能设计和原型规划。当用户提到产品设计、PRD、需求文档、功能规划、用户故事相关任务时自动激活。
|
||||||
---
|
---
|
||||||
|
|
||||||
# 产品需求设计 Skill (req-prd)
|
# 产品需求设计 Skill (req-prd)
|
||||||
@@ -9,248 +9,37 @@ description: 产品设计与需求管理。用于 PRD 文档编写、需求分
|
|||||||
|
|
||||||
本技能用于辅助产品设计和需求管理工作,包括:
|
本技能用于辅助产品设计和需求管理工作,包括:
|
||||||
- PRD 文档编写与管理
|
- PRD 文档编写与管理
|
||||||
- **参考对象对比式 PRD 编写**(核心能力)
|
|
||||||
- 需求分析与优先级排序
|
- 需求分析与优先级排序
|
||||||
- 用户故事创建
|
- 用户故事创建
|
||||||
- 功能设计与规划
|
- 功能设计与规划
|
||||||
- 与 ai-proj 任务系统集成
|
- 与 ai-proj 任务系统集成
|
||||||
|
|
||||||
---
|
**插件扩展**:
|
||||||
|
- `req-compare` — 对比式 PRD 编写(系统平移/竞品借鉴时激活)
|
||||||
|
- `req-prototype` — UI 原型生成
|
||||||
|
|
||||||
## 参考对象对比式 PRD 编写
|
## 客户原话原则(REQ-20260416-0017 P1-8)
|
||||||
|
|
||||||
当进行**系统平移、功能迁移、竞品借鉴**时,应采用对比分析法编写 PRD,确保新系统功能完整且有所改进。
|
**编写 PRD 时必须包含「客户原始诉求」章节(模板 1.4),保留客户/业务方原话,不做 AI 加工。**
|
||||||
|
|
||||||
### 适用场景
|
**为什么**:
|
||||||
|
- 产品经理转述会失真(借鉴自 devflow-claude 的"客户场景"设计)
|
||||||
|
- 后续争议追溯时有据可查
|
||||||
|
- 团队成员看到原话能建立同理心
|
||||||
|
|
||||||
| 场景 | 说明 | 示例 |
|
**填写规范**:
|
||||||
|------|------|------|
|
- 原话用 Markdown `> 引用块` 包裹,区分于 AI 加工内容
|
||||||
| 系统平移 | 旧系统迁移到新技术栈 | 酷采2.0 → 酷采3.0 |
|
- 标注提出人、时间、出处(会议/邮件/聊天)
|
||||||
| 功能借鉴 | 参考竞品功能设计 | 参考飞书设计协作功能 |
|
- 特殊约束(时间/合规/预算)必须保留
|
||||||
| 版本升级 | 基于当前版本优化 | V1.0 → V2.0 重构 |
|
|
||||||
|
|
||||||
### 对比分析工作流
|
|
||||||
|
|
||||||
```
|
|
||||||
1. 确定参考对象
|
|
||||||
├── 识别参考系统(可以是多个)
|
|
||||||
├── 获取访问权限(测试环境、源代码)
|
|
||||||
└── 明确对比目标
|
|
||||||
|
|
||||||
2. 参考对象分析
|
|
||||||
├── 功能调研(前端页面操作)
|
|
||||||
├── 数据模型分析(数据库表结构)
|
|
||||||
├── 业务逻辑分析(后端代码)
|
|
||||||
└── API 接口分析
|
|
||||||
|
|
||||||
3. 对比分析
|
|
||||||
├── 功能对比表(保留/优化/新增/废弃)
|
|
||||||
├── 数据模型对比(字段映射、新增字段)
|
|
||||||
├── 技术架构对比
|
|
||||||
└── 用户体验对比
|
|
||||||
|
|
||||||
4. PRD 编写
|
|
||||||
├── 背景说明(明确参考来源)
|
|
||||||
├── 功能需求(标注来源与变更)
|
|
||||||
├── 数据设计(标注字段来源)
|
|
||||||
└── 实现建议
|
|
||||||
```
|
|
||||||
|
|
||||||
### 对比式 PRD 模板
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# [功能模块名称] PRD
|
|
||||||
|
|
||||||
## 1. 文档概述
|
|
||||||
|
|
||||||
### 1.1 文档信息
|
|
||||||
| 项目 | 内容 |
|
|
||||||
|------|------|
|
|
||||||
| 文档名称 | [模块名称] 需求文档 |
|
|
||||||
| 版本 | V1.0 |
|
|
||||||
| 创建日期 | [日期] |
|
|
||||||
| **需求来源** | **[参考系统名称] 平移/借鉴** |
|
|
||||||
| **参考系统** | **[参考系统访问地址]** |
|
|
||||||
|
|
||||||
### 1.2 背景说明
|
|
||||||
本需求文档基于 **[参考系统]** 的 [模块名称] 功能分析,将其平移至 [目标系统]。
|
|
||||||
|
|
||||||
**参考系统信息**:
|
|
||||||
- 系统地址:[URL]
|
|
||||||
- 技术栈:[技术栈描述]
|
|
||||||
- 源码位置:[源码路径](如有)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 参考系统分析
|
## 对比式 PRD 编写
|
||||||
|
|
||||||
### 2.1 功能截图
|
> 系统平移、竞品借鉴、版本升级时,使用 `req-compare` 插件进行对比分析。
|
||||||
[插入参考系统功能截图]
|
> 该插件包含完整的对比工作流、对比式 PRD 模板和竞品分析模板。
|
||||||
|
|
||||||
### 2.2 数据模型(参考系统)
|
**触发方式**:当需求涉及参考系统时,req-prd 自动推荐激活 req-compare 插件。
|
||||||
```sql
|
|
||||||
-- 参考系统表结构
|
|
||||||
CREATE TABLE [表名] (
|
|
||||||
-- 从源代码/数据库提取
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.3 API 接口(参考系统)
|
|
||||||
| 接口 | 方法 | 说明 |
|
|
||||||
|------|------|------|
|
|
||||||
| /api/xxx | GET/POST | [描述] |
|
|
||||||
|
|
||||||
### 2.4 业务逻辑(参考系统)
|
|
||||||
- 核心业务规则摘要
|
|
||||||
- 数据校验规则
|
|
||||||
- 状态流转逻辑
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. 功能对比分析
|
|
||||||
|
|
||||||
### 3.1 功能对比表
|
|
||||||
| 序号 | 功能 | 参考系统 | 目标系统 | 变更类型 | 说明 |
|
|
||||||
|------|------|----------|----------|----------|------|
|
|
||||||
| 1 | [功能1] | ✅ | ✅ | 保留 | 直接平移 |
|
|
||||||
| 2 | [功能2] | ✅ | ✅+ | 优化 | [优化内容] |
|
|
||||||
| 3 | [功能3] | ❌ | ✅ | 新增 | [新增原因] |
|
|
||||||
| 4 | [功能4] | ✅ | ❌ | 废弃 | [废弃原因] |
|
|
||||||
|
|
||||||
### 3.2 数据模型对比
|
|
||||||
| 参考系统字段 | 目标系统字段 | 类型 | 变更 | 说明 |
|
|
||||||
|--------------|--------------|------|------|------|
|
|
||||||
| id (varchar) | id (bigint) | PK | 优化 | 改用自增ID |
|
|
||||||
| company_id | tenant_id | FK | 重命名 | 统一租户字段 |
|
|
||||||
| -- | created_by | bigint | 新增 | 审计字段 |
|
|
||||||
|
|
||||||
### 3.3 技术架构对比
|
|
||||||
| 层次 | 参考系统 | 目标系统 |
|
|
||||||
|------|----------|----------|
|
|
||||||
| 后端框架 | [如: Spring Boot] | [如: Go Gin] |
|
|
||||||
| 前端框架 | [如: Vue 2] | [如: React 18] |
|
|
||||||
| 数据库 | [如: MySQL] | [如: PostgreSQL] |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. 目标系统设计
|
|
||||||
|
|
||||||
### 4.1 功能清单
|
|
||||||
| 序号 | 功能 | 优先级 | 来源 | 说明 |
|
|
||||||
|------|------|--------|------|------|
|
|
||||||
| 1 | [功能] | P0 | 平移 | 从参考系统平移 |
|
|
||||||
|
|
||||||
### 4.2 数据模型(目标系统)
|
|
||||||
```sql
|
|
||||||
-- 目标系统表结构(基于对比分析设计)
|
|
||||||
CREATE TABLE [表名] (
|
|
||||||
id BIGINT PRIMARY KEY,
|
|
||||||
-- 字段设计...
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 API 设计(目标系统)
|
|
||||||
| 接口 | 方法 | 说明 | 参考接口 |
|
|
||||||
|------|------|------|----------|
|
|
||||||
| /api/v1/xxx | GET | [描述] | 参考 /api/xxx |
|
|
||||||
|
|
||||||
### 4.4 业务规则
|
|
||||||
- [ ] 规则1(沿用参考系统)
|
|
||||||
- [ ] 规则2(优化调整)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. 实现建议
|
|
||||||
|
|
||||||
### 5.1 开发顺序
|
|
||||||
1. 数据模型迁移
|
|
||||||
2. 后端 API 实现
|
|
||||||
3. 前端页面开发
|
|
||||||
4. 数据迁移脚本
|
|
||||||
|
|
||||||
### 5.2 注意事项
|
|
||||||
- 参考系统中 [xxx] 逻辑需要特别注意
|
|
||||||
- 新系统中需改进 [xxx] 问题
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 参考对象分析工具
|
|
||||||
|
|
||||||
#### 1. 前端分析
|
|
||||||
```bash
|
|
||||||
# 启动浏览器调试模式(macOS)
|
|
||||||
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
|
|
||||||
--remote-debugging-port=9222 \
|
|
||||||
--user-data-dir=/tmp/chrome-debug
|
|
||||||
|
|
||||||
# 访问参考系统,截图、分析交互
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 2. 后端代码分析
|
|
||||||
```bash
|
|
||||||
# 搜索相关模型
|
|
||||||
grep -r "class.*Model" --include="*.java" /path/to/legacy/
|
|
||||||
|
|
||||||
# 搜索相关控制器
|
|
||||||
grep -r "@Controller\|@RestController" --include="*.java" /path/to/legacy/
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 3. 数据库分析
|
|
||||||
```sql
|
|
||||||
-- 查看表结构
|
|
||||||
SHOW CREATE TABLE table_name;
|
|
||||||
|
|
||||||
-- 查看字段注释
|
|
||||||
SELECT COLUMN_NAME, COLUMN_COMMENT
|
|
||||||
FROM information_schema.COLUMNS
|
|
||||||
WHERE TABLE_NAME = 'table_name';
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 示例:酷采3.0标签管理模块
|
|
||||||
|
|
||||||
以下是基于酷采2.0平移的标签管理模块对比分析示例:
|
|
||||||
|
|
||||||
#### 参考系统(酷采2.0)
|
|
||||||
|
|
||||||
**数据模型**:
|
|
||||||
```sql
|
|
||||||
-- 酷采2.0 prd_product_label 表
|
|
||||||
CREATE TABLE `prd_product_label` (
|
|
||||||
`id` varchar(64) NOT NULL,
|
|
||||||
`label_name` varchar(256) DEFAULT NULL,
|
|
||||||
`company_id` varchar(64) DEFAULT NULL,
|
|
||||||
`input_user_id` varchar(64) DEFAULT NULL,
|
|
||||||
`input_user_name` varchar(64) DEFAULT NULL,
|
|
||||||
`input_time` datetime DEFAULT NULL,
|
|
||||||
`update_user_id` varchar(64) DEFAULT NULL,
|
|
||||||
`update_user_name` varchar(64) DEFAULT NULL,
|
|
||||||
`update_time` datetime DEFAULT NULL,
|
|
||||||
`version` bigint(20) DEFAULT 0,
|
|
||||||
`is_delete` tinyint(1) DEFAULT 0,
|
|
||||||
`status` tinyint(1) DEFAULT 1,
|
|
||||||
`remark` varchar(512) DEFAULT NULL,
|
|
||||||
`sort_no` int(11) DEFAULT 0,
|
|
||||||
PRIMARY KEY (`id`)
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
**代码位置**:
|
|
||||||
- 后端: `cool_lining/module-provider/.../dao/model/prd/PrdProductLabel.java`
|
|
||||||
- 前端: `ln_admin/src/views/module/prd/product_label/`
|
|
||||||
|
|
||||||
#### 目标系统(酷采3.0)设计
|
|
||||||
|
|
||||||
**数据模型对比**:
|
|
||||||
| 酷采2.0 | 酷采3.0 | 变更 |
|
|
||||||
|---------|---------|------|
|
|
||||||
| id (varchar) | id (bigint) | 改用自增ID |
|
|
||||||
| company_id | tenant_id | 统一租户标识 |
|
|
||||||
| input_user_id | created_by | 简化字段命名 |
|
|
||||||
| input_time | created_at | 统一时间字段 |
|
|
||||||
| is_delete | deleted_at | 改用软删除时间戳 |
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -275,6 +64,20 @@ CREATE TABLE `prd_product_label` (
|
|||||||
|------|--------|--------|----------|
|
|------|--------|--------|----------|
|
||||||
| ... | ... | ... | ... |
|
| ... | ... | ... | ... |
|
||||||
|
|
||||||
|
### 1.4 客户原始诉求 ⭐ 强制保留
|
||||||
|
|
||||||
|
> **重要**:记录客户/业务方提出需求时的**原始描述**,**不做 AI 加工、不总结、不转述**。
|
||||||
|
> 保留原话是为了后续溯源"我们当初为什么做这个"有据可查,避免产品经理转述失真。
|
||||||
|
|
||||||
|
- **场景1**(提出人:xxx / 时间:yyyy-mm-dd):
|
||||||
|
> "原始描述引用..."
|
||||||
|
- **场景2**:
|
||||||
|
> "原始描述引用..."
|
||||||
|
|
||||||
|
**补充信息**(可选):
|
||||||
|
- 会议/邮件/聊天记录链接
|
||||||
|
- 客户特殊约束(如"必须在 Q2 前上线")
|
||||||
|
|
||||||
## 2. 用户分析
|
## 2. 用户分析
|
||||||
### 2.1 目标用户
|
### 2.1 目标用户
|
||||||
[用户画像描述]
|
[用户画像描述]
|
||||||
@@ -304,7 +107,11 @@ CREATE TABLE `prd_product_label` (
|
|||||||
[流程图或步骤描述]
|
[流程图或步骤描述]
|
||||||
|
|
||||||
### 4.2 界面原型
|
### 4.2 界面原型
|
||||||
[原型链接或描述]
|
|
||||||
|
> 使用 `/req prototype [REQ-ID]` 基于 PRD 自动生成 Stitch 原型。
|
||||||
|
> 生成后截图将自动回填到此章节。
|
||||||
|
|
||||||
|
[执行 `/req prototype` 后自动填充]
|
||||||
|
|
||||||
## 5. 技术要求
|
## 5. 技术要求
|
||||||
### 5.1 性能要求
|
### 5.1 性能要求
|
||||||
@@ -329,12 +136,49 @@ CREATE TABLE `prd_product_label` (
|
|||||||
### 6.2 灰度策略
|
### 6.2 灰度策略
|
||||||
[灰度发布计划]
|
[灰度发布计划]
|
||||||
|
|
||||||
## 7. 风险评估
|
## 7. 验收标准 ⭐ 强制包含 VP 三件套
|
||||||
|
|
||||||
|
> **规则(源自 REQ-20260421-0002)**:每条 AC 必须附带 VP-Data / VP-Steps / VP-Pass,缺一项评审不通过。
|
||||||
|
|
||||||
|
### AC1: [验收条件标题]
|
||||||
|
|
||||||
|
**目标**:[一句话描述期望结果]
|
||||||
|
|
||||||
|
**VP-Data(前置测试数据)**:
|
||||||
|
- 环境:localhost / production(二选一,明确注明)
|
||||||
|
- 数据:[字段、值、状态,例如:需求状态=approved,关联任务 3 个,task_project_id 非空]
|
||||||
|
- 建数据方式:[curl localhost:8080/... 或 MCP 工具,禁止混用]
|
||||||
|
|
||||||
|
**VP-Steps(验证步骤)**:
|
||||||
|
1. [工具 + 操作,例如:agent-browser open http://localhost:3000/xxx]
|
||||||
|
2. [检查指标,例如:eval `document.querySelector('.xxx').textContent`]
|
||||||
|
3. [确认值,例如:返回值包含"期望字符串"]
|
||||||
|
|
||||||
|
**VP-Pass(通过判定)**:
|
||||||
|
- ✅ [具体期望值,例如:eval 返回数组长度 = 3]
|
||||||
|
- ✅ [第二个判定条件]
|
||||||
|
- ❌ [明确的不通过条件,例如:仅靠代码分析得出结论 = 不通过]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### AC2: [第二条验收条件]
|
||||||
|
|
||||||
|
**目标**:...
|
||||||
|
|
||||||
|
**VP-Data**:...
|
||||||
|
|
||||||
|
**VP-Steps**:...
|
||||||
|
|
||||||
|
**VP-Pass**:
|
||||||
|
- ✅ ...
|
||||||
|
- ❌ ...
|
||||||
|
|
||||||
|
## 8. 风险评估
|
||||||
| 风险 | 影响 | 概率 | 应对措施 |
|
| 风险 | 影响 | 概率 | 应对措施 |
|
||||||
|------|------|------|----------|
|
|------|------|------|----------|
|
||||||
| ... | 高/中/低 | 高/中/低 | ... |
|
| ... | 高/中/低 | 高/中/低 | ... |
|
||||||
|
|
||||||
## 8. 附录
|
## 9. 附录
|
||||||
- 相关文档链接
|
- 相关文档链接
|
||||||
- 参考资料
|
- 参考资料
|
||||||
```
|
```
|
||||||
@@ -365,6 +209,81 @@ CREATE TABLE `prd_product_label` (
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 需求粒度判断(REQ-20260416-0017 P1-12)
|
||||||
|
|
||||||
|
**创建需求前,AI 必须先对标题做粒度预判。** 借鉴 devflow-claude `/req:split`。
|
||||||
|
|
||||||
|
### 核心问题
|
||||||
|
|
||||||
|
> **"这个需求完成后,用户能感知到一个完整的功能变化吗?"**
|
||||||
|
> - 能 → 粒度合适
|
||||||
|
> - 不能(太大或太小)→ 需调整
|
||||||
|
|
||||||
|
### 粒度参考表
|
||||||
|
|
||||||
|
| 标题示例 | 粒度 | 建议 |
|
||||||
|
|---------|------|------|
|
||||||
|
| "用户积分系统"(含规则+查询+兑换+排行) | 太大 | 拆为 4 个需求 |
|
||||||
|
| "用户积分-积分规则管理"(含 CRUD+校验) | 合适 | 直接创建 |
|
||||||
|
| "用户积分-新增积分接口"(仅一个 API) | 太小 | 合并到功能级需求,或用任务(task) |
|
||||||
|
| "用户积分-新增 model 层"(按技术层拆) | 错误 | 按功能拆,不按技术层拆 |
|
||||||
|
|
||||||
|
### AI 自动检测规则
|
||||||
|
|
||||||
|
**标题过宽信号**(建议拆分):
|
||||||
|
- 含"系统"/"模块"/"平台"/"管理"等宏观词
|
||||||
|
- 描述中功能点 > 5 个
|
||||||
|
- 预估涉及文件 > 15 个
|
||||||
|
|
||||||
|
**标题过窄信号**(建议合并或改 task):
|
||||||
|
- 含"新增XX接口"/"修改XX字段"/"加一个按钮"
|
||||||
|
- 单个 CRUD 操作
|
||||||
|
- 预估涉及文件 ≤ 2 个
|
||||||
|
|
||||||
|
**错误拆分信号**(按技术层拆了):
|
||||||
|
- 标题含"model 层"/"service 层"/"handler 层"/"前端样式"
|
||||||
|
- 同一业务被拆为"后端接口"和"前端页面"两个独立需求
|
||||||
|
|
||||||
|
### 执行时机
|
||||||
|
|
||||||
|
1. **创建需求时**(`/req new` 或 `create_requirement`):检查标题,给出建议
|
||||||
|
2. **编辑需求时**:功能清单超 8 项时提醒"是否应拆分"
|
||||||
|
3. **独立评估**:用 `/req split <标题>` 预判粒度
|
||||||
|
|
||||||
|
### 三种输出
|
||||||
|
|
||||||
|
1. **粒度合适** → 正常创建
|
||||||
|
2. **建议拆分** → 列出子功能建议,用户确认后批量创建
|
||||||
|
3. **建议改 task/QUICK** → 提示"这个用任务更合适"
|
||||||
|
|
||||||
|
### 已有需求扩展功能的决策
|
||||||
|
|
||||||
|
> **"去掉这个新功能点,原需求还能独立交付吗?"**
|
||||||
|
> - 能 → 新建需求
|
||||||
|
> - 不能 → 修改原需求(`/req edit`)
|
||||||
|
|
||||||
|
| 场景 | 建议 |
|
||||||
|
|------|------|
|
||||||
|
| 新功能是原需求的自然延伸 | 修改原需求 |
|
||||||
|
| 新功能可独立上线 | 新建需求 |
|
||||||
|
| 原需求已完成/归档 | 必须新建 |
|
||||||
|
| 原需求开发中,新增会影响已有代码 | 新建(防范围蔓延) |
|
||||||
|
|
||||||
|
### 前后端拆分规则
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ 正确:
|
||||||
|
REQ-001 用户积分规则管理-后端(含 CRUD 全部接口)
|
||||||
|
REQ-002 用户积分规则管理-前端(含 CRUD 全部页面)
|
||||||
|
|
||||||
|
❌ 错误:
|
||||||
|
REQ-001 用户积分规则-新增接口
|
||||||
|
REQ-002 用户积分规则-查询接口
|
||||||
|
REQ-003 用户积分规则-修改接口
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 用户故事编写
|
## 用户故事编写
|
||||||
|
|
||||||
### 标准格式
|
### 标准格式
|
||||||
@@ -519,36 +438,9 @@ mcp__ai-proj__export_task_document_to_file
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 竞品分析模板
|
## 竞品分析
|
||||||
|
|
||||||
### 分析框架
|
> 竞品分析模板已移至 `req-compare` 插件。涉及竞品对比时自动激活。
|
||||||
|
|
||||||
```markdown
|
|
||||||
# [竞品名称] 分析
|
|
||||||
|
|
||||||
## 1. 产品概述
|
|
||||||
- 定位:
|
|
||||||
- 核心功能:
|
|
||||||
- 目标用户:
|
|
||||||
|
|
||||||
## 2. 功能对比
|
|
||||||
| 功能 | 我们 | 竞品A | 竞品B |
|
|
||||||
|------|------|-------|-------|
|
|
||||||
| 功能1 | ✅/❌/部分 | ... | ... |
|
|
||||||
|
|
||||||
## 3. 优劣势分析
|
|
||||||
### 优势
|
|
||||||
1. ...
|
|
||||||
|
|
||||||
### 劣势
|
|
||||||
1. ...
|
|
||||||
|
|
||||||
## 4. 可借鉴点
|
|
||||||
- ...
|
|
||||||
|
|
||||||
## 5. 差异化策略
|
|
||||||
- ...
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -591,7 +483,7 @@ mcp__ai-proj__export_task_document_to_file
|
|||||||
- [ ] 背景与目标明确
|
- [ ] 背景与目标明确
|
||||||
- [ ] 用户群体定义清晰
|
- [ ] 用户群体定义清晰
|
||||||
- [ ] 功能需求完整
|
- [ ] 功能需求完整
|
||||||
- [ ] 验收标准可测试
|
- [ ] 验收标准可测试(每条 AC 附带 VP-Data / VP-Steps / VP-Pass)
|
||||||
- [ ] 异常情况已考虑
|
- [ ] 异常情况已考虑
|
||||||
- [ ] 性能要求已定义
|
- [ ] 性能要求已定义
|
||||||
- [ ] 上线计划合理
|
- [ ] 上线计划合理
|
||||||
@@ -606,20 +498,22 @@ mcp__ai-proj__export_task_document_to_file
|
|||||||
- [ ] 操作可撤销
|
- [ ] 操作可撤销
|
||||||
- [ ] 符合用户习惯
|
- [ ] 符合用户习惯
|
||||||
|
|
||||||
### 技术方案检查
|
### 非功能需求检查
|
||||||
|
|
||||||
- [ ] 技术可行性验证
|
- [ ] 性能要求已量化(响应时间、并发量)
|
||||||
- [ ] 性能影响评估
|
- [ ] 安全需求已明确(权限、数据保护)
|
||||||
- [ ] 扩展性考虑
|
- [ ] 兼容性要求已定义(浏览器、设备)
|
||||||
- [ ] 安全性审查
|
- [ ] 可用性目标已设定
|
||||||
- [ ] 兼容性测试
|
|
||||||
|
> **技术方案可行性检查**在 design 阶段由 `req-design` 技能完成。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 常用工具
|
## 常用工具
|
||||||
|
|
||||||
### 原型设计
|
### 原型设计
|
||||||
- Figma
|
- **Stitch** (Google AI) — 集成在 `/req prototype`,自动从 PRD 生成原型
|
||||||
|
- Figma — 手动精细设计
|
||||||
- Sketch
|
- Sketch
|
||||||
- Axure
|
- Axure
|
||||||
|
|
||||||
@@ -660,111 +554,31 @@ mcp__ai-proj__export_task_document_to_file
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PRD 评审方法论
|
## Memory 隔离规则(强制,源自 devflow-claude 借鉴)
|
||||||
|
|
||||||
PRD 评审是需求流程的关键质量关卡。
|
**规则:本 skill 涉及模板/文档产出的命令禁止受 auto-memory 影响产出物。**
|
||||||
|
|
||||||
### 评审流程
|
### 禁止行为
|
||||||
|
1. 不得根据 memory 中的偏好跳过或合并 PRD 模板章节
|
||||||
|
2. 不得用 memory 里的历史需求/项目内容填充当前 PRD
|
||||||
|
3. 不得根据 memory 反馈调整 PRD 章节顺序、表格列数、标题层级
|
||||||
|
4. 不得读取 `~/.claude/projects/*/memory/` 辅助生成 PRD 正文
|
||||||
|
|
||||||
```
|
### 允许行为
|
||||||
PRD 提交 → 结构检查 → 清晰度评估 → 技术可行性 → 数据模型 → API 审查 → 结论
|
- memory 可影响**交互风格**(提问详略、确认节奏、语气)
|
||||||
```
|
- memory 可指导**命令选择**(如根据用户习惯推荐先走 req-compare 还是 req-prd)
|
||||||
|
- memory 可影响**非产出文本**(如对话中的说明)
|
||||||
|
|
||||||
### 结构完整性检查
|
### Why
|
||||||
|
auto-memory 设计用于跨会话建立用户画像。但 PRD/需求文档是正式产出物,必须由**模板结构 + 当前输入**决定,不能因 memory 中的偏好自作主张调整结构,否则会导致:
|
||||||
|
- 模板章节漂移(用户不知道为什么这次少了一章)
|
||||||
|
- 历史项目内容污染(张冠李戴)
|
||||||
|
- 产出不可复现
|
||||||
|
|
||||||
| 检查项 | 必须 | 说明 |
|
### How to apply
|
||||||
|--------|:----:|------|
|
执行 `/req prd` / PRD 编写 / 需求描述生成等命令时:
|
||||||
| 基本信息 | ✓ | 编号、标题、日期、作者 |
|
- 仅读取:模板文件、用户当前输入、引用的已有需求文档
|
||||||
| 需求背景 | ✓ | 为什么需要这个功能 |
|
- 不读取:memory 目录下的任何文件
|
||||||
| 目标用户 | ✓ | 面向的用户群体 |
|
- 产出前自检:章节数量和顺序与模板完全一致
|
||||||
| 功能描述 | ✓ | 详细功能需求 |
|
|
||||||
| 数据模型 | ✓ | 数据库表结构 |
|
|
||||||
| API 设计 | ✓ | RESTful 接口 |
|
|
||||||
| 验收标准 | ✓ | 验收条件 |
|
|
||||||
| 用户故事 | ○ | As a... I want... |
|
|
||||||
| 页面原型 | ○ | 界面布局 |
|
|
||||||
| 非功能需求 | ○ | 性能、安全 |
|
|
||||||
|
|
||||||
### 需求清晰度(SMART 原则)
|
**参考**:devflow-claude 的 `plugins/req/commands/_common.md` 同名规则。
|
||||||
|
|
||||||
| 原则 | 检查点 |
|
|
||||||
|------|--------|
|
|
||||||
| **S**pecific | 描述是否具体明确 |
|
|
||||||
| **M**easurable | 是否有量化指标 |
|
|
||||||
| **A**chievable | 技术上是否可行 |
|
|
||||||
| **R**elevant | 是否与业务目标相关 |
|
|
||||||
| **T**ime-bound | 是否有时间范围 |
|
|
||||||
|
|
||||||
**示例**:
|
|
||||||
- ❌ "用户可以搜索需求"
|
|
||||||
- ✅ "用户可按编号精确搜索、按标题模糊搜索、按状态筛选,结果分页显示"
|
|
||||||
|
|
||||||
### 技术可行性
|
|
||||||
|
|
||||||
| 维度 | 评估内容 |
|
|
||||||
|------|----------|
|
|
||||||
| 技术栈兼容 | 现有栈是否支持 |
|
|
||||||
| 架构影响 | 是否需修改架构 |
|
|
||||||
| 第三方依赖 | 是否引入新依赖 |
|
|
||||||
| 性能影响 | 潜在性能问题 |
|
|
||||||
| 安全考量 | 安全风险 |
|
|
||||||
|
|
||||||
### 数据模型验证
|
|
||||||
|
|
||||||
| 检查项 | 说明 |
|
|
||||||
|--------|------|
|
|
||||||
| 表结构 | 字段类型、约束、索引 |
|
|
||||||
| 关联关系 | 外键、多对多 |
|
|
||||||
| 命名规范 | 符合项目规范 |
|
|
||||||
| 扩展性 | 预留扩展字段 |
|
|
||||||
| 查询性能 | 常用查询有索引 |
|
|
||||||
|
|
||||||
### API 设计审查
|
|
||||||
|
|
||||||
| 检查项 | 标准 |
|
|
||||||
|--------|------|
|
|
||||||
| RESTful | URL 用名词,HTTP 方法语义正确 |
|
|
||||||
| 响应格式 | `{success, data, message}` |
|
|
||||||
| 错误处理 | 明确的错误码 |
|
|
||||||
| 分页 | `?page=1&page_size=20` |
|
|
||||||
| 版本控制 | `/api/v1/...` |
|
|
||||||
|
|
||||||
### 评审结论模板
|
|
||||||
|
|
||||||
**通过**:
|
|
||||||
```
|
|
||||||
✅ PRD 评审通过
|
|
||||||
|
|
||||||
【结论】文档完整,需求清晰,方案可行,建议批准。
|
|
||||||
【肯定】需求背景清晰、数据模型合理、API 规范
|
|
||||||
【建议】(非阻塞)补充性能测试用例
|
|
||||||
【评审人】xxx 【时间】2026-xx-xx
|
|
||||||
```
|
|
||||||
|
|
||||||
**驳回**:
|
|
||||||
```
|
|
||||||
❌ PRD 评审驳回
|
|
||||||
|
|
||||||
【原因】
|
|
||||||
1. 数据模型缺少索引
|
|
||||||
2. API 缺少错误处理
|
|
||||||
3. 搜索需求描述模糊
|
|
||||||
|
|
||||||
【修改要求】
|
|
||||||
□ 补充索引设计
|
|
||||||
□ 明确错误码
|
|
||||||
□ 细化搜索实现
|
|
||||||
|
|
||||||
【驳回人】xxx 【时间】2026-xx-xx
|
|
||||||
```
|
|
||||||
|
|
||||||
### 常见驳回原因
|
|
||||||
|
|
||||||
| 类别 | 问题 | 建议 |
|
|
||||||
|------|------|------|
|
|
||||||
| 结构缺失 | 缺数据模型或 API | 补充技术设计 |
|
|
||||||
| 需求模糊 | 描述不具体 | 按 SMART 重写 |
|
|
||||||
| 边界不清 | 缺异常处理 | 补充边界条件 |
|
|
||||||
| 设计缺陷 | 模型/API 不合理 | 重新设计 |
|
|
||||||
| 范围过大 | 难以实现 | 拆分为多需求 |
|
|
||||||
| 验收不明 | 缺验收标准 | 补充验收条件 |
|
|
||||||
|
|||||||
11
skills-req/req-prototype-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-prototype-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"name": "req-prototype-plugin",
|
||||||
|
"description": "原型生成与关联。支持 HTML 上传(/req prototype upload,iframe 嵌入详情页)和 Stitch AI 生成两种模式。",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"author": {
|
||||||
|
"name": "qiudl"
|
||||||
|
},
|
||||||
|
"install_name": "req-prototype",
|
||||||
|
"install_type": "skill",
|
||||||
|
"dir_category": "req"
|
||||||
|
}
|
||||||
328
skills-req/req-prototype-plugin/skills/SKILL.md
Normal file
328
skills-req/req-prototype-plugin/skills/SKILL.md
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
---
|
||||||
|
name: req-prototype
|
||||||
|
description: 原型生成与关联。支持两种模式:(1) Stitch AI 基于 PRD 自动生成 UI 原型截图;(2) AI 编写 HTML 原型并上传关联到需求详情页 iframe。当执行 /req prototype 或需要生成/上传界面原型时使用。
|
||||||
|
arguments: [REQ-ID] [upload|edit|variant] [--device desktop|mobile|tablet] [--model pro|flash] [--note "..."] [--prompt "..."]
|
||||||
|
---
|
||||||
|
|
||||||
|
# 原型设计 Skill (req-prototype)
|
||||||
|
|
||||||
|
## 概述
|
||||||
|
|
||||||
|
支持两种原型工作流:
|
||||||
|
|
||||||
|
| 模式 | 命令 | 适用场景 | 输出 |
|
||||||
|
|------|------|----------|------|
|
||||||
|
| **HTML 上传** | `/req prototype upload` | 快速展示、评审用静态原型 | iframe 嵌入需求详情页 |
|
||||||
|
| **Stitch AI** | `/req prototype` | 精细 UI 设计、多屏交互 | 截图回填 PRD 文档 |
|
||||||
|
|
||||||
|
## 前置条件
|
||||||
|
|
||||||
|
执行前必须检查:
|
||||||
|
|
||||||
|
| 检查项 | 方式 | 失败处理 |
|
||||||
|
|--------|------|----------|
|
||||||
|
| 需求存在 | `mcp__ai-proj__get_requirement` | 报错:需求不存在 |
|
||||||
|
| 后端运行中(upload 模式)| `curl http://localhost:8080/api/v1/health` | 报错:后端未启动 |
|
||||||
|
| PRD 文档存在(Stitch 模式)| 找 linkRole=prd 任务 + 检查文档 | 报错:请先执行 req-prd |
|
||||||
|
|
||||||
|
## 子命令
|
||||||
|
|
||||||
|
### 0. `/req prototype upload [REQ-ID] [--note "版本说明"]` — 上传 HTML 原型(**推荐**)
|
||||||
|
|
||||||
|
**适用场景**:快速为需求关联一个带样式的 HTML 原型,直接在需求详情页以 iframe 展示,供评审人预览交互流程。
|
||||||
|
|
||||||
|
**执行流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 获取需求信息(mcp__ai-proj__get_requirement)
|
||||||
|
2. 读取 PRD 或需求描述,提炼 UI 关键信息
|
||||||
|
3. AI 编写带完整样式的 HTML 原型文件(见设计规范)
|
||||||
|
4. 保存到 /tmp/proto_<req_id>_<timestamp>.html
|
||||||
|
5. 获取本地 JWT token(登录 API)
|
||||||
|
6. 上传到后端(multipart POST)
|
||||||
|
7. 确认上传成功,输出预览 URL
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5-6 执行方式**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 5. 获取 token(本地开发环境)
|
||||||
|
TOKEN=$(curl -s http://localhost:8080/api/v1/auth/login \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-d '{"username":"qiudl","password":"Admin@2026~"}' \
|
||||||
|
| python3 -c 'import sys,json; print(json.load(sys.stdin)["data"]["access_token"])')
|
||||||
|
|
||||||
|
# 6. 上传 HTML 原型并关联到需求
|
||||||
|
curl -s -X POST "http://localhost:8080/api/v1/requirements/<REQ_DB_ID>/prototype" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-F "file=@/tmp/proto_<req_id>_<timestamp>.html;type=text/html" \
|
||||||
|
-F "version_note=<--note 的值或空>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**成功响应**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"data": {
|
||||||
|
"url": "/api/v1/uploads/prototypes/proto_xxx.html",
|
||||||
|
"version_note": "...",
|
||||||
|
"uploaded_at": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**效果**:需求详情页(`/requirements/<id>` 或 `/platform/requirements/<id>`)自动出现「原型预览」卡片,iframe 加载上传的 HTML。
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `--note` | 空 | 版本说明,如"初稿 v1"、"评审修改版" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### HTML 原型设计规范
|
||||||
|
|
||||||
|
AI 生成的 HTML 原型必须满足以下要求:
|
||||||
|
|
||||||
|
**结构要求**:
|
||||||
|
- 完整独立的 HTML 文件(含 `<!DOCTYPE html>` + `<head>` + `<body>`)
|
||||||
|
- 所有样式内联在 `<style>` 标签中,不依赖外部 CDN
|
||||||
|
- 不使用 JavaScript 框架(纯 HTML+CSS,可用少量原生 JS)
|
||||||
|
- 适合在 600px 高度的 iframe 中展示
|
||||||
|
|
||||||
|
**视觉要求**:
|
||||||
|
- 与需求功能高度对应,体现核心交互流程
|
||||||
|
- 包含真实感数据(非"xxx"占位符)
|
||||||
|
- 顶部导航栏 / 侧边栏与项目风格一致(深色 header,现代 SaaS 风格)
|
||||||
|
- 底部加注释标注条(固定定位,说明版本和需求号)
|
||||||
|
|
||||||
|
**内容要求**:
|
||||||
|
- 覆盖需求描述中的核心功能点
|
||||||
|
- 展示关键数据状态(列表、表单、卡片等)
|
||||||
|
- 按钮/操作有视觉反馈样式(hover 色等)
|
||||||
|
|
||||||
|
**模板参考**(顶部 topbar + 侧边栏 + 主内容区):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<title>[需求标题] - 原型</title>
|
||||||
|
<style>
|
||||||
|
/* reset + layout */
|
||||||
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||||
|
background: #f0f2f5; color: #1a1a2e; min-height: 100vh; }
|
||||||
|
.topbar { background: #1e3a5f; color: #fff; padding: 0 24px; height: 52px;
|
||||||
|
display: flex; align-items: center; justify-content: space-between; }
|
||||||
|
.layout { display: flex; height: calc(100vh - 52px); }
|
||||||
|
.sidebar { width: 220px; background: #fff; border-right: 1px solid #e5e7eb;
|
||||||
|
padding: 16px 0; overflow-y: auto; }
|
||||||
|
.main { flex: 1; overflow-y: auto; padding: 24px; padding-bottom: 48px; }
|
||||||
|
/* 底部原型标注条 */
|
||||||
|
.annotation { position: fixed; bottom: 0; left: 0; right: 0;
|
||||||
|
background: rgba(30,58,95,.92); color: #fff;
|
||||||
|
padding: 6px 24px; font-size: 12px;
|
||||||
|
display: flex; justify-content: space-between; }
|
||||||
|
.annotation strong { color: #93c5fd; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="topbar"><!-- 导航 --></div>
|
||||||
|
<div class="layout">
|
||||||
|
<aside class="sidebar"><!-- 侧边栏 --></aside>
|
||||||
|
<main class="main"><!-- 主内容 --></main>
|
||||||
|
</div>
|
||||||
|
<div class="annotation">
|
||||||
|
<span>🎨 <strong>原型预览</strong> · [REQ-ID] — [需求标题]</span>
|
||||||
|
<span style="color:#93c5fd;">v1.0 · [日期] · 仅供评审参考</span>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. `/req prototype [REQ-ID]` — Stitch AI 生成原型
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 前置条件检查
|
||||||
|
2. 读取 PRD 文档(ai-proj task get-doc)
|
||||||
|
3. 提取 UI 相关内容(功能需求 + 交互设计章节)
|
||||||
|
4. PRD → Stitch Prompt 转换(中文 → 英文设计描述)
|
||||||
|
5. 创建 Stitch 项目(mcp__stitch__create_project)
|
||||||
|
6. 生成页面(mcp__stitch__generate_screen_from_text)
|
||||||
|
7. 获取截图(mcp__stitch__get_screen)
|
||||||
|
8. 回填 PRD「4.2 界面原型」章节
|
||||||
|
```
|
||||||
|
|
||||||
|
**参数**:
|
||||||
|
|
||||||
|
| 参数 | 默认值 | 说明 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `--device` | `desktop` | 设备类型:desktop / mobile / tablet |
|
||||||
|
| `--model` | `pro` | 模型:pro (GEMINI_3_1_PRO) / flash (GEMINI_3_FLASH) |
|
||||||
|
|
||||||
|
### 2. `/req prototype edit [REQ-ID] --prompt "..."` — 编辑原型
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 从 PRD「4.2 界面原型」元数据获取 stitch_project_id 和 screen_id
|
||||||
|
2. 调用 mcp__stitch__edit_screens 编辑指定页面
|
||||||
|
3. 获取更新后截图
|
||||||
|
4. 更新 PRD「4.2 界面原型」章节
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. `/req prototype variant [REQ-ID]` — 生成变体
|
||||||
|
|
||||||
|
**流程**:
|
||||||
|
|
||||||
|
```
|
||||||
|
1. 从 PRD「4.2 界面原型」元数据获取 stitch_project_id 和 screen_id
|
||||||
|
2. 调用 mcp__stitch__generate_variants 生成变体
|
||||||
|
3. 展示所有变体截图供用户选择
|
||||||
|
4. 用户选择后更新 PRD
|
||||||
|
```
|
||||||
|
|
||||||
|
**变体参数**:
|
||||||
|
|
||||||
|
| 参数 | 选项 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `creativeRange` | REFINE / EXPLORE / REIMAGINE | 创意程度 |
|
||||||
|
| `aspects` | LAYOUT / COLOR_SCHEME / IMAGES / TEXT_FONT / TEXT_CONTENT | 变化维度 |
|
||||||
|
|
||||||
|
## PRD → Stitch Prompt 转换策略
|
||||||
|
|
||||||
|
从 PRD 提取以下章节内容,转换为英文设计 prompt:
|
||||||
|
|
||||||
|
1. **功能需求**(第 3 章)→ 提取功能列表和交互描述
|
||||||
|
2. **交互设计**(第 4 章)→ 提取用户流程和界面描述
|
||||||
|
3. **目标用户**(第 2 章)→ 确定设计风格和复杂度
|
||||||
|
|
||||||
|
**转换模板**:
|
||||||
|
|
||||||
|
```
|
||||||
|
Design a [device_type] web application for [product_name].
|
||||||
|
|
||||||
|
Target users: [user_description]
|
||||||
|
|
||||||
|
Key features:
|
||||||
|
- [feature_1]: [description]
|
||||||
|
- [feature_2]: [description]
|
||||||
|
...
|
||||||
|
|
||||||
|
User flow:
|
||||||
|
1. [step_1]
|
||||||
|
2. [step_2]
|
||||||
|
...
|
||||||
|
|
||||||
|
UI requirements:
|
||||||
|
- [layout_requirement]
|
||||||
|
- [interaction_requirement]
|
||||||
|
...
|
||||||
|
|
||||||
|
Style: Clean, modern, professional SaaS interface with clear navigation.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Stitch API 参数映射
|
||||||
|
|
||||||
|
| 用户参数 | Stitch API 参数 | 值映射 |
|
||||||
|
|----------|-----------------|--------|
|
||||||
|
| `--device desktop` | `deviceType` | `DESKTOP` |
|
||||||
|
| `--device mobile` | `deviceType` | `MOBILE` |
|
||||||
|
| `--device tablet` | `deviceType` | `TABLET` |
|
||||||
|
| `--model pro` | `modelId` | `GEMINI_3_1_PRO` |
|
||||||
|
| `--model flash` | `modelId` | `GEMINI_3_FLASH` |
|
||||||
|
|
||||||
|
## PRD 回填逻辑
|
||||||
|
|
||||||
|
定位 PRD 中 `### 4.2 界面原型` 章节,替换内容为:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 4.2 界面原型
|
||||||
|
|
||||||
|
> Stitch AI 自动生成原型,基于本 PRD 功能需求和交互设计。
|
||||||
|
|
||||||
|
**原型预览**:
|
||||||
|
|
||||||
|
| 页面 | 截图 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| [screen_name_1] |  | [描述] |
|
||||||
|
| [screen_name_2] |  | [描述] |
|
||||||
|
|
||||||
|
**Stitch 项目信息**:
|
||||||
|
```yaml
|
||||||
|
stitch_project_id: "<project_id>"
|
||||||
|
screens:
|
||||||
|
- id: "<screen_id_1>"
|
||||||
|
name: "<screen_name_1>"
|
||||||
|
device: "<device_type>"
|
||||||
|
- id: "<screen_id_2>"
|
||||||
|
name: "<screen_name_2>"
|
||||||
|
device: "<device_type>"
|
||||||
|
model: "<model_id>"
|
||||||
|
generated_at: "<timestamp>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**编辑原型**:`/req prototype edit [REQ-ID] --prompt "修改说明"`
|
||||||
|
**生成变体**:`/req prototype variant [REQ-ID]`
|
||||||
|
```
|
||||||
|
|
||||||
|
## 异常处理
|
||||||
|
|
||||||
|
### Upload 模式
|
||||||
|
|
||||||
|
| 异常 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 后端 500 / `column prototype_urls does not exist` | 需执行数据库迁移:`psql -U <owner> -d <db> -c "ALTER TABLE requirements ADD COLUMN IF NOT EXISTS prototype_urls JSONB DEFAULT '[]';"` |
|
||||||
|
| token 获取失败(401)| 检查用户名密码,或改用生产环境 token |
|
||||||
|
| 需求 ID 不存在(404)| 确认使用数据库自增 ID,而非 display_id(REQ-xxx) |
|
||||||
|
| HTML 文件超过 5MB | 精简样式或拆分多版本上传 |
|
||||||
|
| iframe 不显示 | 检查 `prototype_urls` 字段是否非空:`mcp__ai-proj__get_requirement` 确认 |
|
||||||
|
|
||||||
|
### 原型展示规则
|
||||||
|
|
||||||
|
**原型必须用 iframe 展示**(不得用截图、图片嵌入或内联 HTML 方式替代)。
|
||||||
|
|
||||||
|
- PRD 文档的「4.2 界面原型」章节:使用 `<iframe>` 标签嵌入原型 URL,而非 `` 图片
|
||||||
|
- 需求详情页原型预览卡片:后端渲染 iframe,前端不得将 `prototype_urls` 内容渲染为 `<img>`
|
||||||
|
- 典型正确写法(PRD 文档内):
|
||||||
|
|
||||||
|
```html
|
||||||
|
<iframe src="/api/v1/uploads/prototypes/proto_xxx.html"
|
||||||
|
width="100%" height="600" frameborder="0"
|
||||||
|
style="border-radius:8px;border:1px solid #e5e7eb;">
|
||||||
|
</iframe>
|
||||||
|
```
|
||||||
|
|
||||||
|
> 背景:REQ-20260420-0031 反馈原型图用图片方式展示,无法交互预览,改为 iframe 后可正常使用。
|
||||||
|
|
||||||
|
|
||||||
|
### Stitch 模式
|
||||||
|
|
||||||
|
| 异常 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 需求无 PRD 文档 | 报错:`请先使用 req-prd 技能编写 PRD 文档` |
|
||||||
|
| PRD 无 UI 相关描述 | 警告并询问:`PRD 未包含明确的 UI 描述,是否基于功能需求自动推断?` |
|
||||||
|
| Stitch API 超时 | 提示:`Stitch 生成耗时约 1-2 分钟,请稍候...` 超过 3 分钟报错 |
|
||||||
|
| Stitch API 返回错误 | 展示错误信息,建议调整 prompt 或更换模型 |
|
||||||
|
| PRD 无「4.2 界面原型」章节 | 在「## 4. 交互设计」末尾自动追加该章节 |
|
||||||
|
| 已有原型元数据 | 询问:`已存在原型,是否覆盖?` |
|
||||||
|
|
||||||
|
## 版本管理
|
||||||
|
|
||||||
|
每次 `/req prototype upload` 都会追加一个新版本(自增 `version` 字段),需求详情页支持版本切换下拉框。多个版本并存时,最新版本默认展示。
|
||||||
|
|
||||||
|
可多次上传来迭代原型:
|
||||||
|
|
||||||
|
```
|
||||||
|
v1 → 初稿(评审前)
|
||||||
|
v2 → 评审修改版
|
||||||
|
v3 → 开发对齐版
|
||||||
|
```
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user