Compare commits
24 Commits
22107fa7b8
...
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 |
@@ -23,6 +23,30 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-core/publish-plugin",
|
||||
@@ -47,6 +71,80 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-dev/dev-arch-plugin",
|
||||
@@ -60,10 +158,127 @@
|
||||
],
|
||||
"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",
|
||||
"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",
|
||||
"category": "development",
|
||||
"keywords": [
|
||||
@@ -86,6 +301,43 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-dev/pull-request-plugin",
|
||||
@@ -99,10 +351,61 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-req/req-dev-plugin",
|
||||
"description": "Plugin for req-dev",
|
||||
"description": "[已废弃] 请使用 req-design-plugin。需求开发设计功能已迁移。",
|
||||
"version": "1.0.0",
|
||||
"category": "development",
|
||||
"keywords": [
|
||||
@@ -112,6 +415,19 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-req/req-plugin",
|
||||
@@ -128,7 +444,59 @@
|
||||
{
|
||||
"name": "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",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
@@ -141,7 +509,20 @@
|
||||
{
|
||||
"name": "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",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
@@ -328,6 +709,19 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-personal/ops-tools-plugin",
|
||||
@@ -353,6 +747,19 @@
|
||||
],
|
||||
"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",
|
||||
"source": "./skills-personal/req-deploy-plugin",
|
||||
@@ -365,6 +772,32 @@
|
||||
"operations"
|
||||
],
|
||||
"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",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-contract",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-ops",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-plan",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "finance",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "2.0.1",
|
||||
"author": {
|
||||
"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",
|
||||
"author": {
|
||||
"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",
|
||||
"author": {
|
||||
"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",
|
||||
"description": "Plugin for dev-coding",
|
||||
"version": "1.0.0",
|
||||
"description": "软件编码开发技能。Go 后端 + Vue/React 前端编码实现,集成 ai-proj 任务管理。",
|
||||
"version": "2.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-coding",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -1,43 +1,24 @@
|
||||
---
|
||||
name: dev-coding
|
||||
description: 软件编码开发技能。用于代码编写、功能实现、代码审查、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。支持 Go、Vue、React、iOS、Android、小程序等全栈开发。
|
||||
description: 软件编码开发技能。用于代码编写、功能实现、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。核心支持 Go 后端 + Vue/React 前端开发。
|
||||
---
|
||||
|
||||
# 软件编码开发 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)
|
||||
- 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 | - |
|
||||
| AI-Proj | 项目管理 | Go+Gin+PostgreSQL | React 18 | iOS+Android |
|
||||
| DICIAI | 进销存SaaS | Go+Gin+MySQL | Vue 3 | Android PDA |
|
||||
| 项目 | 类型 | 后端 | 前端 |
|
||||
|------|------|------|------|
|
||||
| TWMS | 仓储物流 | Go+Gin+MySQL | Vue 3 |
|
||||
| AI-Proj | 项目管理 | Go+Gin+PostgreSQL | React 18 |
|
||||
| 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 响应格式
|
||||
@@ -695,13 +373,6 @@ try {
|
||||
} catch (error) {
|
||||
message.error(error.message);
|
||||
}
|
||||
|
||||
// Swift
|
||||
do {
|
||||
let result = try await service.fetch()
|
||||
} catch {
|
||||
// 处理错误
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -808,3 +479,66 @@ fi
|
||||
4. **小步提交** - 频繁提交,每次做一件事
|
||||
5. **测试覆盖** - 核心逻辑必须有测试
|
||||
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",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-test",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
|
||||
| `android-testing.md` | Android 测试 (JUnit + Espresso + Compose) |
|
||||
| `e2e-testing.md` | E2E Playwright:API Mock 冒烟测试(无后端)+ 全链路集成测试 |
|
||||
| `templates/go-integration-test.md` | Go 集成测试模板(多步骤 API 流程、中间件验证、租户隔离) |
|
||||
| `templates/pdv-smoke-spec.md` | PDV 部署后验收 Playwright 模板(页面可达、菜单可见、API 连通) |
|
||||
|
||||
---
|
||||
|
||||
@@ -50,6 +51,7 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
|
||||
| E2E (Mock 冒烟) | `npm run test:e2e:smoke-mock` | `e2e-testing.md` §API Mock |
|
||||
| E2E (全链路) | `npm run test:e2e` | `e2e-testing.md` §全链路 |
|
||||
| E2E (Coolbuy PaaS) | `make e2e` | `e2e-testing.md` §Coolbuy |
|
||||
| E2E (部署后验收 PDV) | `E2E_BASE_URL=<url> npx playwright test e2e/pdv/` | §PDV |
|
||||
|
||||
---
|
||||
|
||||
@@ -145,6 +147,60 @@ ai-proj task append-doc --id <taskId> --content "# 测试报告
|
||||
|
||||
---
|
||||
|
||||
## 部署后 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 集成测试检测
|
||||
|
||||
### 模板映射
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
# E2E 测试 (Playwright)
|
||||
|
||||
## 两种 E2E 测试模式
|
||||
## 三种 E2E 测试模式
|
||||
|
||||
| 模式 | 后端依赖 | 速度 | 适用场景 | 门禁阶段 |
|
||||
|------|---------|------|---------|---------|
|
||||
| **API Mock 冒烟测试** | ❌ 无需后端 | 快(<30s) | UI 布局、路由、菜单、权限隔离 | E2E 冒烟门禁 |
|
||||
| **API Mock 冒烟测试** | ❌ 无需后端 | 快(<30s) | UI 布局、路由、菜单、权限隔离 | TG4 E2E 冒烟门禁 |
|
||||
| **全链路集成测试** | ✅ 需完整后端+DB | 慢(分钟级) | CRUD 业务流程、数据持久化 | 手动/CI |
|
||||
| **部署后验收 (PDV)** | ✅ 真实部署环境 | 中(<2min) | 功能入口可达、菜单可见、API 连通 | `/req deploy` 步骤 6 |
|
||||
|
||||
**⚠️ 关键原则: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` 技能中。
|
||||
|
||||
---
|
||||
|
||||
@@ -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",
|
||||
"author": {
|
||||
"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",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "data-excel",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -10,5 +10,8 @@
|
||||
"name": "doubao-voice",
|
||||
"path": "./skills/SKILL.md"
|
||||
}
|
||||
]
|
||||
],
|
||||
"install_name": "doubao-voice",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "feishu-bitable",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "feishu-docx",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.1.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "feishu",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "siyuan",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -5,128 +5,35 @@ description: 思源笔记 API 集成。通过自然语言创建、编辑、搜
|
||||
|
||||
# 思源笔记 API 集成 Skill
|
||||
|
||||
## 服务配置
|
||||
## 环境变量配置(必须)
|
||||
|
||||
### 阿里云生产环境 (推荐)
|
||||
本 skill 通过环境变量读取思源笔记连接信息,不同用户/组织配置不同的值。
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| 服务地址 | `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 (自动续期) |
|
||||
**在 `~/.claude/settings.json` 中配置:**
|
||||
|
||||
### Tailscale 内网环境 (备用)
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"SIYUAN_URL": "${SIYUAN_URL}",
|
||||
"SIYUAN_TOKEN": "your-api-token-here"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| 服务地址 | `http://47.93.23.182` (Tailscale) |
|
||||
| 局域网地址 | `http://192.168.1.50:6806` |
|
||||
| API Token | `nfnycjb1g8vbexb2` |
|
||||
| 版本 | 3.1.5 |
|
||||
| 部署位置 | 飞牛OS (Docker) |
|
||||
或在项目级 `.claude/settings.json` 中配置(优先级更高)。
|
||||
|
||||
**检查配置是否就绪:** 执行任何思源操作前,先确认环境变量已设置:
|
||||
|
||||
```bash
|
||||
echo "URL: ${SIYUAN_URL:-未配置}"
|
||||
echo "TOKEN: ${SIYUAN_TOKEN:+已配置}"
|
||||
```
|
||||
|
||||
如果未配置,提示用户在 `~/.claude/settings.json` 的 `env` 中添加 `SIYUAN_URL` 和 `SIYUAN_TOKEN`。
|
||||
|
||||
---
|
||||
|
||||
## 服务器管理 (阿里云)
|
||||
|
||||
### 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"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 保存门禁:敏感信息脱敏
|
||||
## 🔐 保存<E4BF9D><E5AD98><EFBFBD>禁:敏感<E6958F><E6849F>息脱敏
|
||||
|
||||
**保存任何内容到思源笔记前,必须先完成脱敏处理。**
|
||||
|
||||
@@ -201,8 +108,8 @@ api.upsert_doc(notebook_id, "/文档路径", content)
|
||||
所有 API 请求使用 POST 方法,需携带 Token:
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/xxx \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/xxx \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"param": "value"}'
|
||||
```
|
||||
@@ -239,8 +146,8 @@ curl -X POST https://siyuan.pipexerp.com/api/xxx \
|
||||
#### 列出所有笔记本
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/lsNotebooks \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/notebook/lsNotebooks \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
```
|
||||
@@ -248,8 +155,8 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/lsNotebooks \
|
||||
#### 创建笔记本
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/createNotebook \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/notebook/createNotebook \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name": "笔记本名称"}'
|
||||
```
|
||||
@@ -257,8 +164,8 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/createNotebook \
|
||||
#### 删除笔记本
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/notebook/removeNotebook \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/notebook/removeNotebook \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notebook": "笔记本ID"}'
|
||||
```
|
||||
@@ -276,22 +183,22 @@ curl -X POST https://siyuan.pipexerp.com/api/notebook/removeNotebook \
|
||||
```bash
|
||||
# 1. 先查询是否存在(按路径精确匹配)
|
||||
DOC_PATH="/网络管理/家庭Tailscale网络"
|
||||
EXISTING=$(curl -s -X POST https://siyuan.pipexerp.com/api/query/sql \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
EXISTING=$(curl -s -X POST ${SIYUAN_URL}/api/query/sql \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"stmt\": \"SELECT id FROM blocks WHERE type='d' AND hpath='${DOC_PATH}' LIMIT 1\"}" \
|
||||
| jq -r '.data[0].id // empty')
|
||||
|
||||
if [ -n "$EXISTING" ]; then
|
||||
# 2a. 存在则更新
|
||||
curl -s -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -s -X POST ${SIYUAN_URL}/api/block/updateBlock \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"id\": \"${EXISTING}\", \"dataType\": \"markdown\", \"data\": \"# 更新的内容\"}"
|
||||
else
|
||||
# 2b. 不存在则创建
|
||||
curl -s -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -s -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notebook": "笔记本ID", "path": "/网络管理/家庭Tailscale网络", "markdown": "# 新内容"}'
|
||||
fi
|
||||
@@ -300,8 +207,8 @@ fi
|
||||
#### 创建文档 (仅新建时使用)
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/filetree/createDocWithMd \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"notebook": "笔记本ID",
|
||||
@@ -313,8 +220,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/createDocWithMd \
|
||||
#### 获取文档内容
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/getDoc \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/filetree/getDoc \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "文档ID"}'
|
||||
```
|
||||
@@ -322,8 +229,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/getDoc \
|
||||
#### 删除文档
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/removeDoc \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/filetree/removeDoc \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notebook": "笔记本ID", "path": "/文档路径"}'
|
||||
```
|
||||
@@ -331,8 +238,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/removeDoc \
|
||||
#### 重命名文档
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/filetree/renameDoc \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/filetree/renameDoc \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"notebook": "笔记本ID", "path": "/旧路径", "title": "新标题"}'
|
||||
```
|
||||
@@ -344,8 +251,8 @@ curl -X POST https://siyuan.pipexerp.com/api/filetree/renameDoc \
|
||||
#### 插入块
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/block/insertBlock \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/block/insertBlock \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"dataType": "markdown",
|
||||
@@ -357,8 +264,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/insertBlock \
|
||||
#### 更新块
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/block/updateBlock \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"dataType": "markdown",
|
||||
@@ -370,8 +277,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/updateBlock \
|
||||
#### 删除块
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/block/deleteBlock \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/block/deleteBlock \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "块ID"}'
|
||||
```
|
||||
@@ -383,8 +290,8 @@ curl -X POST https://siyuan.pipexerp.com/api/block/deleteBlock \
|
||||
#### 全文搜索
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/search/fullTextSearchBlock \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/search/fullTextSearchBlock \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": "搜索关键词",
|
||||
@@ -395,8 +302,8 @@ curl -X POST https://siyuan.pipexerp.com/api/search/fullTextSearchBlock \
|
||||
#### SQL 查询
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/query/sql \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/query/sql \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"stmt": "SELECT * FROM blocks WHERE content LIKE '\''%关键词%'\'' LIMIT 10"
|
||||
@@ -410,8 +317,8 @@ curl -X POST https://siyuan.pipexerp.com/api/query/sql \
|
||||
#### 导出 Markdown
|
||||
|
||||
```bash
|
||||
curl -X POST https://siyuan.pipexerp.com/api/export/exportMdContent \
|
||||
-H "Authorization: Token nfnycjb1g8vbexb2" \
|
||||
curl -X POST ${SIYUAN_URL}/api/export/exportMdContent \
|
||||
-H "Authorization: Token ${SIYUAN_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"id": "文档ID"}'
|
||||
```
|
||||
@@ -421,13 +328,16 @@ curl -X POST https://siyuan.pipexerp.com/api/export/exportMdContent \
|
||||
## Python 封装
|
||||
|
||||
```python
|
||||
import os
|
||||
import requests
|
||||
from typing import Optional, Dict, Any
|
||||
|
||||
class SiYuanAPI:
|
||||
"""思源笔记 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.headers = {
|
||||
"Authorization": f"Token {token}",
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "siyuan-to-feishu",
|
||||
"install_type": "skill",
|
||||
"dir_category": "integration"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"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",
|
||||
"description": "Plugin for req-dev",
|
||||
"description": "[已废弃] 请使用 req-design-plugin。需求开发设计功能已迁移。",
|
||||
"version": "1.0.0",
|
||||
"deprecated": true,
|
||||
"author": {
|
||||
"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",
|
||||
"author": {
|
||||
"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)
|
||||
- **务实路线关闭也必须补全关联任务** — 每个进度条阶段需创建关联任务
|
||||
- **阶段内容门禁(防空转)** — `/req next` 时检查关键阶段任务是否有实质内容
|
||||
- **Harness 环境检测** — `/req dev` 启动开发前,快速检测项目基础设施(`.husky/` 存在?`scripts/check-*.sh` 存在?)。若两者都不存在(Level 0),输出一行提示:「项目无质量护栏,建议先运行 `/harness init`」。检测结果不阻塞开发,仅提示
|
||||
|
||||
## 禁止直接调用(必须走命令流程)
|
||||
|
||||
@@ -91,23 +92,40 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
||||
5. /req review pass → 评审通过 (approved, delivery_stage=analysis)
|
||||
6. /req next → 推进到 design/dev 阶段
|
||||
7. /req dev → 启动开发(创建【开发】任务, delivery_stage=dev)
|
||||
8. /req next → 推进到 review 阶段
|
||||
9. /req cr → 代码评审(创建【代码评审】任务)
|
||||
7.5 /req ci → CI 质量门禁检查与自动修复(PR 提交后执行)
|
||||
8. /req next → 推进到 review(需 quality_gate 证据)
|
||||
9. /req cr → 代码评审 + 约定检查 + bug约定建议(⭐ harness 自动)
|
||||
10. /req next → 推进到 testing 阶段
|
||||
11. /req test → 测试验收(5-Gate 流程)
|
||||
11. /req test → 测试验收(Gate 0B 约定检查 ⭐ + Gate 1-5)
|
||||
12. /req deploy → 批量部署(build-and-push.sh → Jenkins ai-proj)
|
||||
13. /req done → 归档 (archived) + git commit + push
|
||||
```
|
||||
|
||||
> ⭐ 标记的步骤是 Harness Engineering 自动嵌入的,无需手动调用。
|
||||
|
||||
**5 阶段文档**:
|
||||
|
||||
| 阶段 | 文档名 | 技能 |
|
||||
|------|--------|------|
|
||||
| PRD | 01-PRD.md | `req-prd` |
|
||||
| 测试 | 02-测试报告.md | 自动生成 |
|
||||
| 发布 | 03-发布记录.md | 自动生成 |
|
||||
| 发布 | 03-发布记录.md | 自动生成(含 PDV 验收章节) |
|
||||
| 归档 | 04-生命周期总结.md | 自动生成 |
|
||||
|
||||
**03-发布记录.md 中 PDV 章节模板**:
|
||||
|
||||
```markdown
|
||||
### 部署后验收 (PDV)
|
||||
| 检查项 | 结果 | 截图 |
|
||||
|--------|------|------|
|
||||
| 页面可达: /path/to/page | ✅ PASS | [截图] |
|
||||
| 菜单可见: {菜单名} | ✅ PASS | [截图] |
|
||||
| 基础渲染: 无白屏/JS 报错 | ✅ PASS | [截图] |
|
||||
| API: /api/v1/{路径} | ✅ PASS | — |
|
||||
|
||||
PDV 结论: ✅ 全部通过 / ❌ {N}项失败,阻断推进
|
||||
```
|
||||
|
||||
## 任务命名规范
|
||||
|
||||
| linkRole | 前缀 | 示例 |
|
||||
@@ -118,6 +136,7 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
||||
| code_review | 【代码评审】 | 【代码评审】CR: 代码审查 |
|
||||
| test | 【测试】 | 【测试】集成测试验证 |
|
||||
| deploy | 【部署】 | 【部署】部署到 staging |
|
||||
| verification | 【验收】 | 【验收】PDV: OKR 功能部署验收 |
|
||||
| documentation | 【文档】 | 【文档】API 文档更新 |
|
||||
|
||||
## 阶段内容门禁(防空转)
|
||||
@@ -132,6 +151,24 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
||||
|
||||
未通过 → 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 五视角扫描法
|
||||
|
||||
**核心原则**:实现阶段关注"怎么让它跑通",评审阶段关注"怎么让它出错"。AI 必须**切换到对抗性思维**,逐一用以下 5 个视角扫描代码。
|
||||
@@ -271,6 +308,10 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
||||
5. 自动创建目标阶段建议任务
|
||||
6. 展示:「✅ 已创建: #XXXX {任务名}。如需撤销请说明」
|
||||
|
||||
**dev → review 门禁**:
|
||||
- `pr_created`: PR 已创建
|
||||
- `quality_gate`: CI 质量门禁通过记录(质量门禁未通过时提示:「CI 质量门禁未通过,请先运行 `/req ci`」)
|
||||
|
||||
**阶段顺序**:`analysis → design → dev → review → testing → [待部署池] → released`
|
||||
|
||||
**`/req resume [REQ-ID]`** — 会话断点恢复:`ai-proj req get --id <id>` 获取 delivery_stage + 任务状态 + 建议操作
|
||||
@@ -287,26 +328,73 @@ Gate 4: 完整性检查 ── 三段式完整 + 验收标准 ≥ 2 条 + PRD
|
||||
2. `git diff` / `find` 确定变更范围(文件数、行数)
|
||||
3. 读取所有变更文件源码(非 test 文件)
|
||||
4. **五视角扫描**:逐一用攻击者/泄露者/并发者/边界者/依赖者视角审查
|
||||
5. `ai-proj task create` 创建【代码评审】任务并关联需求(linkRole=code_review)
|
||||
6. `ai-proj create-and-attach` 附加 CR 报告文档(必须含五视角扫描结果)
|
||||
7. 展示发现摘要,AskUserQuestion 确认是否创建 bug 修复任务
|
||||
8. 若有 High/Critical 发现 → `ai-proj task create` 创建关联修复任务
|
||||
5. **约定检查**:运行所有 `scripts/check-*.sh --ci`,将结果写入 CR 报告
|
||||
6. **⭐ Bug 约定建议**(bug 类需求自动触发:category=bug,或标题含 fix/修复/bug,或描述含根因/复现步骤):
|
||||
- 分析本次 bug 的根因模式
|
||||
- 判断可检测性:能写 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 deploy [--project <name>] [--env production]`** — 项目级批量部署(Staging 已自动化,push develop 即触发):
|
||||
1. 收集待部署需求(delivery_stage=testing + 全 test 任务 completed)
|
||||
2. AskUserQuestion 确认范围
|
||||
3. `ai-proj task create` 创建部署批次任务
|
||||
3. `ai-proj task create` 创建【部署】批次任务(linkRole=deploy)
|
||||
4. 部署前检查(变更检测、数据库迁移)
|
||||
5. 执行 `./scripts/build-and-push.sh prod --detect --deploy --wait --verify`
|
||||
6. 部署后验证(截图 + 定向验证)
|
||||
7. `ai-proj task append-doc` 记录部署文档 + 写入需求历史
|
||||
8. `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>`:
|
||||
- **推断类型**:有 implementation → code;无 implementation 有 prd/test → skill;仅 deploy → ops
|
||||
- **code 检查**:delivery_stage=released + deploy 任务完成 + 部署文档 + 所有任务完成
|
||||
- **code 检查**:delivery_stage=released + deploy 任务完成 + verification 任务完成(PDV 通过) + 部署文档 + 所有任务完成
|
||||
- **skill 检查**:delivery_stage≥testing + 所有任务完成
|
||||
- **ops 检查**:deploy 任务完成 + 所有任务完成
|
||||
|
||||
@@ -332,8 +420,10 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
||||
| design | 【评审】技术设计: {需求标题} | design |
|
||||
| dev | 【开发-后端】{需求标题}、【开发-前端】{需求标题} | implementation |
|
||||
| review | 【代码评审】{需求标题} | code_review |
|
||||
| review | 【约定】{约定名称}(bug 类需求,convention flow 自动创建) | documentation |
|
||||
| testing | 【测试】集成测试: {需求标题} | test |
|
||||
| deploy | 由 `/req deploy` 批量创建 | deploy |
|
||||
| released | 【验收】PDV: {需求标题}(`/req deploy` 自动创建) | verification |
|
||||
|
||||
## 测试环境流程
|
||||
|
||||
@@ -352,7 +442,8 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
||||
| implementation | 【开发】 | dev | 50% |
|
||||
| code_review | 【代码评审】 | review | 5% |
|
||||
| test | 【测试】 | testing | 15% |
|
||||
| deploy | 【部署】 | staging/released | 10% |
|
||||
| deploy | 【部署】 | staging/released | 5% |
|
||||
| verification | 【验收】 | released | 5% |
|
||||
| documentation | 【文档】 | any | 5% |
|
||||
|
||||
## 标准任务结构
|
||||
@@ -365,7 +456,9 @@ ai-proj task append-doc --id 5214 --content "# PRD: 用户认证功能\n..."
|
||||
├── 🔍 review → 【代码评审】CR: {需求标题} (linkRole: code_review)
|
||||
├── 🧪 testing → 【测试】集成测试: {需求标题} (linkRole: test)
|
||||
├── 🚀 staging → 【部署】部署到 staging (linkRole: deploy)
|
||||
└── 🏁 released → 【部署】部署到 prod (linkRole: deploy)
|
||||
└── 🏁 released
|
||||
├── 【部署】部署到 prod (linkRole: deploy)
|
||||
└── 【验收】PDV: {需求标题} (linkRole: verification)
|
||||
```
|
||||
|
||||
## ai-proj CLI 速查
|
||||
@@ -389,7 +482,7 @@ ai-proj req tasks --id <id> # 查看关联任务
|
||||
|
||||
**开发阶段**: `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`
|
||||
|
||||
## 相关技能
|
||||
|
||||
@@ -398,3 +491,4 @@ ai-proj req tasks --id <id> # 查看关联任务
|
||||
| `req-prd` | PRD 文档编写 + 评审方法论 |
|
||||
| `req-prototype` | Stitch 原型生成 + 迭代 |
|
||||
| `dev-test` | 测试 + 质量门禁 |
|
||||
| `req-test-gate` (harness) | 工程约束方法论。Gate 0-2 的约定检查由项目本地脚本定义,方法论见 `/harness` 命令 |
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "req-prd-plugin",
|
||||
"description": "Plugin for req-prd",
|
||||
"version": "1.0.0",
|
||||
"description": "产品需求设计技能。PRD 文档编写、需求分析、用户故事、对比式分析。纯产品视角,不含技术实现。",
|
||||
"version": "2.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "req-prd",
|
||||
"install_type": "skill",
|
||||
"dir_category": "req"
|
||||
}
|
||||
|
||||
@@ -9,248 +9,37 @@ description: 产品设计与需求管理。用于 PRD 文档编写、需求分
|
||||
|
||||
本技能用于辅助产品设计和需求管理工作,包括:
|
||||
- PRD 文档编写与管理
|
||||
- **参考对象对比式 PRD 编写**(核心能力)
|
||||
- 需求分析与优先级排序
|
||||
- 用户故事创建
|
||||
- 功能设计与规划
|
||||
- 与 ai-proj 任务系统集成
|
||||
|
||||
---
|
||||
**插件扩展**:
|
||||
- `req-compare` — 对比式 PRD 编写(系统平移/竞品借鉴时激活)
|
||||
- `req-prototype` — UI 原型生成
|
||||
|
||||
## 参考对象对比式 PRD 编写
|
||||
## 客户原话原则(REQ-20260416-0017 P1-8)
|
||||
|
||||
当进行**系统平移、功能迁移、竞品借鉴**时,应采用对比分析法编写 PRD,确保新系统功能完整且有所改进。
|
||||
**编写 PRD 时必须包含「客户原始诉求」章节(模板 1.4),保留客户/业务方原话,不做 AI 加工。**
|
||||
|
||||
### 适用场景
|
||||
**为什么**:
|
||||
- 产品经理转述会失真(借鉴自 devflow-claude 的"客户场景"设计)
|
||||
- 后续争议追溯时有据可查
|
||||
- 团队成员看到原话能建立同理心
|
||||
|
||||
| 场景 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| 系统平移 | 旧系统迁移到新技术栈 | 酷采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]
|
||||
- 技术栈:[技术栈描述]
|
||||
- 源码位置:[源码路径](如有)
|
||||
**填写规范**:
|
||||
- 原话用 Markdown `> 引用块` 包裹,区分于 AI 加工内容
|
||||
- 标注提出人、时间、出处(会议/邮件/聊天)
|
||||
- 特殊约束(时间/合规/预算)必须保留
|
||||
|
||||
---
|
||||
|
||||
## 2. 参考系统分析
|
||||
## 对比式 PRD 编写
|
||||
|
||||
### 2.1 功能截图
|
||||
[插入参考系统功能截图]
|
||||
> 系统平移、竞品借鉴、版本升级时,使用 `req-compare` 插件进行对比分析。
|
||||
> 该插件包含完整的对比工作流、对比式 PRD 模板和竞品分析模板。
|
||||
|
||||
### 2.2 数据模型(参考系统)
|
||||
```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 | 改用软删除时间戳 |
|
||||
**触发方式**:当需求涉及参考系统时,req-prd 自动推荐激活 req-compare 插件。
|
||||
|
||||
---
|
||||
|
||||
@@ -275,6 +64,20 @@ CREATE TABLE `prd_product_label` (
|
||||
|------|--------|--------|----------|
|
||||
| ... | ... | ... | ... |
|
||||
|
||||
### 1.4 客户原始诉求 ⭐ 强制保留
|
||||
|
||||
> **重要**:记录客户/业务方提出需求时的**原始描述**,**不做 AI 加工、不总结、不转述**。
|
||||
> 保留原话是为了后续溯源"我们当初为什么做这个"有据可查,避免产品经理转述失真。
|
||||
|
||||
- **场景1**(提出人:xxx / 时间:yyyy-mm-dd):
|
||||
> "原始描述引用..."
|
||||
- **场景2**:
|
||||
> "原始描述引用..."
|
||||
|
||||
**补充信息**(可选):
|
||||
- 会议/邮件/聊天记录链接
|
||||
- 客户特殊约束(如"必须在 Q2 前上线")
|
||||
|
||||
## 2. 用户分析
|
||||
### 2.1 目标用户
|
||||
[用户画像描述]
|
||||
@@ -333,12 +136,49 @@ CREATE TABLE `prd_product_label` (
|
||||
### 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. 附录
|
||||
- 相关文档链接
|
||||
- 参考资料
|
||||
```
|
||||
@@ -369,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 用户积分规则-修改接口
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 用户故事编写
|
||||
|
||||
### 标准格式
|
||||
@@ -523,36 +438,9 @@ mcp__ai-proj__export_task_document_to_file
|
||||
|
||||
---
|
||||
|
||||
## 竞品分析模板
|
||||
## 竞品分析
|
||||
|
||||
### 分析框架
|
||||
|
||||
```markdown
|
||||
# [竞品名称] 分析
|
||||
|
||||
## 1. 产品概述
|
||||
- 定位:
|
||||
- 核心功能:
|
||||
- 目标用户:
|
||||
|
||||
## 2. 功能对比
|
||||
| 功能 | 我们 | 竞品A | 竞品B |
|
||||
|------|------|-------|-------|
|
||||
| 功能1 | ✅/❌/部分 | ... | ... |
|
||||
|
||||
## 3. 优劣势分析
|
||||
### 优势
|
||||
1. ...
|
||||
|
||||
### 劣势
|
||||
1. ...
|
||||
|
||||
## 4. 可借鉴点
|
||||
- ...
|
||||
|
||||
## 5. 差异化策略
|
||||
- ...
|
||||
```
|
||||
> 竞品分析模板已移至 `req-compare` 插件。涉及竞品对比时自动激活。
|
||||
|
||||
---
|
||||
|
||||
@@ -595,7 +483,7 @@ mcp__ai-proj__export_task_document_to_file
|
||||
- [ ] 背景与目标明确
|
||||
- [ ] 用户群体定义清晰
|
||||
- [ ] 功能需求完整
|
||||
- [ ] 验收标准可测试
|
||||
- [ ] 验收标准可测试(每条 AC 附带 VP-Data / VP-Steps / VP-Pass)
|
||||
- [ ] 异常情况已考虑
|
||||
- [ ] 性能要求已定义
|
||||
- [ ] 上线计划合理
|
||||
@@ -610,13 +498,14 @@ mcp__ai-proj__export_task_document_to_file
|
||||
- [ ] 操作可撤销
|
||||
- [ ] 符合用户习惯
|
||||
|
||||
### 技术方案检查
|
||||
### 非功能需求检查
|
||||
|
||||
- [ ] 技术可行性验证
|
||||
- [ ] 性能影响评估
|
||||
- [ ] 扩展性考虑
|
||||
- [ ] 安全性审查
|
||||
- [ ] 兼容性测试
|
||||
- [ ] 性能要求已量化(响应时间、并发量)
|
||||
- [ ] 安全需求已明确(权限、数据保护)
|
||||
- [ ] 兼容性要求已定义(浏览器、设备)
|
||||
- [ ] 可用性目标已设定
|
||||
|
||||
> **技术方案可行性检查**在 design 阶段由 `req-design` 技能完成。
|
||||
|
||||
---
|
||||
|
||||
@@ -662,3 +551,34 @@ mcp__ai-proj__export_task_document_to_file
|
||||
- 数据安全分级
|
||||
- 敏感操作审计
|
||||
- 权限最小化原则
|
||||
|
||||
---
|
||||
|
||||
## Memory 隔离规则(强制,源自 devflow-claude 借鉴)
|
||||
|
||||
**规则:本 skill 涉及模板/文档产出的命令禁止受 auto-memory 影响产出物。**
|
||||
|
||||
### 禁止行为
|
||||
1. 不得根据 memory 中的偏好跳过或合并 PRD 模板章节
|
||||
2. 不得用 memory 里的历史需求/项目内容填充当前 PRD
|
||||
3. 不得根据 memory 反馈调整 PRD 章节顺序、表格列数、标题层级
|
||||
4. 不得读取 `~/.claude/projects/*/memory/` 辅助生成 PRD 正文
|
||||
|
||||
### 允许行为
|
||||
- memory 可影响**交互风格**(提问详略、确认节奏、语气)
|
||||
- memory 可指导**命令选择**(如根据用户习惯推荐先走 req-compare 还是 req-prd)
|
||||
- memory 可影响**非产出文本**(如对话中的说明)
|
||||
|
||||
### Why
|
||||
auto-memory 设计用于跨会话建立用户画像。但 PRD/需求文档是正式产出物,必须由**模板结构 + 当前输入**决定,不能因 memory 中的偏好自作主张调整结构,否则会导致:
|
||||
- 模板章节漂移(用户不知道为什么这次少了一章)
|
||||
- 历史项目内容污染(张冠李戴)
|
||||
- 产出不可复现
|
||||
|
||||
### How to apply
|
||||
执行 `/req prd` / PRD 编写 / 需求描述生成等命令时:
|
||||
- 仅读取:模板文件、用户当前输入、引用的已有需求文档
|
||||
- 不读取:memory 目录下的任何文件
|
||||
- 产出前自检:章节数量和顺序与模板完全一致
|
||||
|
||||
**参考**:devflow-claude 的 `plugins/req/commands/_common.md` 同名规则。
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "req-prototype-plugin",
|
||||
"description": "Stitch 原型生成与迭代。基于 PRD 文档自动生成 UI 原型。",
|
||||
"version": "1.0.0",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
---
|
||||
name: req-prototype
|
||||
description: Stitch 原型生成与迭代。基于 PRD 文档自动生成 UI 原型,支持编辑和变体生成。当执行 /req prototype 或需要生成界面原型时使用。
|
||||
arguments: [REQ-ID] [--device desktop|mobile|tablet] [--model pro|flash] [--prompt "..."]
|
||||
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 "..."]
|
||||
---
|
||||
|
||||
# Stitch 原型设计 Skill (req-prototype)
|
||||
# 原型设计 Skill (req-prototype)
|
||||
|
||||
## 概述
|
||||
|
||||
基于 PRD 文档自动生成 Stitch UI 原型,插入在 PRD 编写完成后、submit 评审前。
|
||||
支持两种原型工作流:
|
||||
|
||||
**核心流程**:读取 PRD → 提取 UI 描述 → 转英文 prompt → 调用 Stitch API → 截图回填 PRD
|
||||
| 模式 | 命令 | 适用场景 | 输出 |
|
||||
|------|------|----------|------|
|
||||
| **HTML 上传** | `/req prototype upload` | 快速展示、评审用静态原型 | iframe 嵌入需求详情页 |
|
||||
| **Stitch AI** | `/req prototype` | 精细 UI 设计、多屏交互 | 截图回填 PRD 文档 |
|
||||
|
||||
## 前置条件
|
||||
|
||||
@@ -18,13 +21,132 @@ arguments: [REQ-ID] [--device desktop|mobile|tablet] [--model pro|flash] [--prom
|
||||
|
||||
| 检查项 | 方式 | 失败处理 |
|
||||
|--------|------|----------|
|
||||
| 需求存在 | `ai-proj req get --id <id>` | 报错:需求不存在 |
|
||||
| PRD 文档存在 | `ai-proj req tasks --id <id>` 找 linkRole=prd 任务 + 检查文档 | 报错:请先执行 req-prd 编写 PRD |
|
||||
| PRD 包含 UI 描述 | 检查 PRD 中「功能需求」「交互设计」「界面原型」章节 | 警告:PRD 未包含 UI 相关描述,建议先补充 |
|
||||
| 需求存在 | `mcp__ai-proj__get_requirement` | 报错:需求不存在 |
|
||||
| 后端运行中(upload 模式)| `curl http://localhost:8080/api/v1/health` | 报错:后端未启动 |
|
||||
| PRD 文档存在(Stitch 模式)| 找 linkRole=prd 任务 + 检查文档 | 报错:请先执行 req-prd |
|
||||
|
||||
## 子命令
|
||||
|
||||
### 1. `/req prototype [REQ-ID]` — 生成原型
|
||||
### 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 生成原型
|
||||
|
||||
**流程**:
|
||||
|
||||
@@ -154,6 +276,36 @@ generated_at: "<timestamp>"
|
||||
|
||||
## 异常处理
|
||||
|
||||
### 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 文档` |
|
||||
@@ -162,3 +314,15 @@ generated_at: "<timestamp>"
|
||||
| Stitch API 返回错误 | 展示错误信息,建议调整 prompt 或更换模型 |
|
||||
| PRD 无「4.2 界面原型」章节 | 在「## 4. 交互设计」末尾自动追加该章节 |
|
||||
| 已有原型元数据 | 询问:`已存在原型,是否覆盖?` |
|
||||
|
||||
## 版本管理
|
||||
|
||||
每次 `/req prototype upload` 都会追加一个新版本(自增 `version` 字段),需求详情页支持版本切换下拉框。多个版本并存时,最新版本默认展示。
|
||||
|
||||
可多次上传来迭代原型:
|
||||
|
||||
```
|
||||
v1 → 初稿(评审前)
|
||||
v2 → 评审修改版
|
||||
v3 → 开发对齐版
|
||||
```
|
||||
|
||||
11
skills-req/req-research-plugin/.claude-plugin/plugin.json
Normal file
11
skills-req/req-research-plugin/.claude-plugin/plugin.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "req-research-plugin",
|
||||
"description": "需求调研插件。代码审计、数据库分析、现有功能调研。挂载在 analysis 阶段,需要深度调研时激活。",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "req-research",
|
||||
"install_type": "skill",
|
||||
"dir_category": "req"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user