Author SHA1 Message Date
qiudl dec25562a4 feat(req-prd): persist docs and prototypes remotely 2026-08-21 23:59:14 +09:30
qiudl d630b374a3 Merge PR #7: req-prd HTML prototype delivery loop
Publish req-prd and req-prototype 2.1.0.
2026-08-21 14:20:27 +00:00
qiudl 8fc1cd05b7 feat(req-prd): add HTML prototype delivery loop 2026-08-21 23:48:43 +09:30
admin b859a84455 Merge PR #6: req-prd design interview and defect convergence
Adds traceable one-question-per-round product design interviews and a defect-analysis convergence loop.
2026-08-21 13:05:55 +00:00
qiudl 9a1400758e feat(req-prd): add design interview and defect convergence workflow 2026-08-21 22:34:09 +09:30
qiudl a58dd1aff3 Merge PR #5: make Codex the default skill target
Validated Codex and explicit Claude install layouts, stable content digests, exclusions, and real user-scope installation.
2026-08-20 02:37:34 +00:00
qiudl bb5e6be73e feat(skills): make Codex the default install target 2026-08-20 12:07:10 +09:30
qiudl ef0e9ca1f0 Merge PR #4: refresh marketplace release workflows
Validated installer migration, marketplace integrity, skill frontmatter, plugin manifests, and sanitized release skills.
2026-08-20 01:45:40 +00:00
qiudl ad4e2b16a8 feat(skills): refresh marketplace release workflows 2026-08-20 10:34:16 +09:30
qiudlandClaude Sonnet 4.6 ba107a54a8 fix(req-prototype): fix arguments regex causing Skill loader error
arguments 字段中 [REQ-ID] 被 Skill loader 编译为 JS regex 时,
Q-I 字符范围(ASCII 81 > 73)触发 "range out of order" 错误。
改为 <REQ-ID> [subcommand] [options] 避免字符类范围问题。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 13:55:55 +09:30
qiudl 4ea6920a13 chore(marketplace): update marketplace.json — req-prototype upload 改用 MCP 直传 COS 2026-05-17 13:36:54 +09:30
qiudlandClaude Sonnet 4.6 039a749e3e feat(req-prototype): upload 模式改用 MCP upload_prototype 直传 COS
- 移除对本地后端(localhost:8080)的依赖
- 执行流程改为:生成 HTML → base64 编码 → mcp__ai-proj__upload_prototype
- 删除前置条件中「后端运行中」检查项
- 更新异常处理:移除 token/后端错误,新增 base64 兼容性说明

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 12:52:28 +09:30
qiudlandClaude Sonnet 4.6 daca19a262 chore(marketplace): req-plugin 归档前强制 AC 验收 Gate 0
- /req done 新增 Gate 0:归档前逐条确认验收标准
- 无 AC 或有未确认项均阻断,需提供证据或说明 N/A 原因
- AC 确认后自动追加验证记录到需求描述(带日期戳)
- 更新关键约束说明

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-17 08:20:38 +09:30
qiudl ddb5d5cb06 chore(marketplace): remove stale skills-personal entries
skills-personal/ directory no longer exists; marketplace.json had 9
plugin entries pointing to missing sources (gitea, openclaw,
ops-servers, ops-tools, qiudl-personal, reload-session, req-deploy,
save-session, search-sessions). Regenerated via generate-marketplace.py.
2026-05-07 02:40:47 +02:00
qiudlandClaude Sonnet 4.6 da57cccf96 chore(marketplace): update req-retro karpathy-score dimension, req-test-gate gate-0b docs
- req-retro: 新增 karpathy_score × 0.20 维度,QS 公式扩展为 5 维
- req-test-gate: Gate 0B 新增 check-surgical.sh Ratchet 文档

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 10:55:13 +09:30
qiudl 7eed2b8f10 chore(marketplace): add karpathy-guidelines-plugin, update dev-coding/dev-review/review-checklist
Karpathy 四原则融合到 req 技能工作流 (REQ-20260421-0003):
- dev-coding: 新增 Step 0「验证优先」(Goal-Driven Execution)
- dev-review: 五视角 → 六视角,新增 Scope 审计者 (Simplicity + Surgical)
- review-checklist/general: 新增 Karpathy 反模式速查表
- karpathy-guidelines-plugin: 新增独立插件,含四原则全文 + 与 req 工作流映射
2026-04-21 10:08:18 +09:30
qiudl 5a45916b2c feat: devflow-claude P0/P1 集成 + VP 三件套强制规则 2026-04-20 23:51:03 +00:00
qiudl bcea648e3c merge: resolve conflict in req-prototype SKILL.md, keep iframe display rule from main 2026-04-21 09:20:55 +09:30
qiudlandClaude Sonnet 4.6 e3513f137b feat(req-prd): PRD 模板新增验收标准章节强制含 VP 三件套(REQ-20260421-0002)
- 模板第7节新增「验收标准」章节,含 VP-Data/VP-Steps/VP-Pass 完整示例
- 检查清单「验收标准可测试」更新说明要求 VP 三件套
- 原第7节「风险评估」顺移为第8节

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:19:08 +09:30
qiudlandClaude Sonnet 4.6 011916ceb9 feat(req-review): 新增 VP 三件套强制检查(REQ-20260421-0002)
- 结构检查表新增「VP 三件套」必须项
- 新增「VP 三件套强制检查」章节,含四项检查指标和驳回条件
- 常见驳回原因更新:验收不明包含 VP 缺失场景

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:17:11 +09:30
dongliangandClaude Sonnet 4.6 48b792fb5a feat(req-prototype): 原型展示必须用 iframe,禁止图片方式 (REQ-20260420-0031)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 01:07:10 +09:30
qiudlandClaude Sonnet 4.6 2ab0a61eb9 fix(install): handle nested skills/ subdirectory (e.g. dev-test)
resolve_skills_src() detects when SKILL.md is one level deeper than skills/
and uses that subdirectory as rsync source instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 00:00:01 +09:30
qiudlandClaude Sonnet 4.6 de25f096e7 feat(sync): add install-skills.sh + install metadata to all 62 plugins
- Add install_name, install_type, dir_category fields to all 62 plugin.json files
  to resolve name-mapping and skill-vs-command routing issues
- Add install-skills.sh: idempotent cross-machine skill sync script
  - Routes skill→~/.claude/skills/<name>/, command→~/.claude/commands/<name>.md
  - rsync full skills/ directory (preserves multi-file skills like dev-test, req-deploy)
  - State file ~/.claude/.installed-skills.json tracks installed versions
  - Conflict detection: warns before overwriting locally modified files
  - --dry-run, --category, --force, --cleanup, --list flags
- Add 9 new plugins migrated from local ~/.claude (agent-swarm, ai-chat,
  defect-analysis, executing-plans, finishing-branch, frontend-design,
  req-audit, req-lookback, req-retro)
- Add update-plugin-meta.py helper used to bulk-update plugin.json
- Fix siyuan SKILL.md: remove hardcoded server credentials, use env vars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 23:55:26 +09:30
qiudlandClaude Sonnet 4.6 84d4e35a42 feat(req-prototype): 新增 HTML 上传模式,版本升至 v2.0.0
- 添加 /req prototype upload 子命令(推荐模式)
- 含完整 curl 上传流程、HTML 设计规范及模板
- 更新错误处理与版本管理说明
- marketplace 更新(49 个插件)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 18:06:32 +09:30
qiudl e5805cbb51 feat: P1-10/12/13/14 风险扫描 + 粒度判断 + Issue 集成 + PRD 校验 (REQ-20260416-0017)
P1-10: pm-risk skill — 三维度风险扫描
  需求层: 停滞/草稿滞留/开发无提交/测试超期/批准未开发
  代码层: 未合并分支/频繁修复文件/提交频率下降
  流程层: 跳过评审/PR 无 review/测试门禁跳过/直接推 main
  三级风险: 🔴 严重 / 🟡 警告 / 🔵 提示

P1-12: req-prd 需求粒度 AI 判断
  创建前启发式检查:标题过宽建议拆分、过窄建议合并或改 task
  粒度参考表 + 已有需求扩展决策表 + 前后端拆分规则

P1-13: dev-commit issue 集成规范
  分支名 -iN 后缀传递 issue 编号
  commit message 自动追加 closes #N

P1-14: hooks/validate-prd.sh — PRD 章节校验
  PostToolUse hook 自动检查 10 个必需章节
  缺失时给出明确提示

marketplace: 48 → 49 plugins (新增 pm-risk-plugin)
2026-04-19 13:33:26 +09:30
qiudl 79c4e55719 feat: P1-8 客户场景章节 + P1-9 pm-ask skill (REQ-20260416-0017)
P1-8: req-prd 模板增加 "1.4 客户原始诉求" 章节
  - 强制保留客户原话,不做 AI 加工转述
  - 标注提出人/时间/出处
  - 补充"客户原话原则"说明
  借鉴 devflow-claude requirement-template.md 的"1.3 客户场景"

P1-9: 新 skill pm-ask-plugin
  - 基于 ai-proj MCP + git log 真实数据的项目问答
  - 铁律:禁止编造,必须引用 REQ-XXX/commit-sha/文件路径
  - 数据不足时明确说明缺失
  - 区分事实和推测(⚠️ 推测:前缀)
  - 受众适配(高层/客户/内部/新人)
  借鉴 devflow-claude /pm:ask 命令

marketplace: 47 → 48 plugins
2026-04-16 23:04:54 +09:30
qiudl 23ea8fdca5 feat: 融合 devflow-claude P0 批机制 (REQ-20260416-0017)
P0-1: SessionStart Hook — hooks/session-context.sh
  从分支名解析 REQ-ID,调 MCP API 查询需求详情注入 system-reminder

P0-2: PreToolUse Hook — hooks/pre-tool-confirm.sh
  拦截生产推送、force push、docker prod 容器操作、git reset --hard 等

P0-3: Release Draft 闸门设计文档 — docs/design/release-draft-gate.md
  完整架构 + 渐进式落地路径(拆 7 个子任务延后)
  附最小可用脚本 hooks/release-draft.sh 创建 Gitea draft release

P0-4: Memory 隔离规则 — 写入 req-prd / req-design / req-workflow
  禁止 auto-memory 污染模板产出物(章节结构、字段定义、文档结构)

P0-5: CLAUDE.md 架构检查 + 架构片段库
  dev-coding skill 执行前检查架构关键词
  新增 templates/claude-md-snippets/ 含 Go+Gin / React+AntD / Vue+Element /
  MCP+TS / generic 五套骨架

P0-6: /commit 分支保护自动化 — 新 skill dev-commit-plugin
  保护分支自动建功能分支 + Conventional Commits + REQ-XXX 自动关联

安装:
  bash hooks/install.sh

后续:
  P0-3 完整实现拆 7 个子任务(P0-3.1 ~ P0-3.7)
  建议先部署 hooks 跑 1-2 周观察,再推进 Release 机制落地
2026-04-16 21:02:29 +09:30
qiudlandClaude Opus 4.6 bfe3815626 fix(skill): 修复质量检查发现的引用问题
- req-workflow: req-dev → req-design (2处)
- req-design: changelog 标注"原名 req-dev"
- marketplace.json 重新生成 (44→46 plugins)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:47:01 +09:30
qiudlandClaude Opus 4.6 3706d7f32d feat(skill): REQ-20260406-0004 技能三层分离重构(7主线+16插件)
批次1: req-prd 瘦身 + req-design 重定位 + dev-coding 聚焦
批次2: dev-review 新建 + review-checklist 插件
批次3: dev-integration 新建 + req-compare 拆出
批次4: 插件完善 (req-research/db-migration/dev-scaffold/deploy-rollback)
批次5: 平台拆分 (dev-ios/dev-android/dev-mcp/dev-pda) + dev 分组更新
批次6: marketplace.json 32→44 plugins

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 17:44:08 +09:30
dongliang 31c2d5a474 feat(dev-deploy): SSH timeout + xcodegen cwd lessons (#11 #12) 2026-04-06 17:41:02 +09:30
dongliang ccbdfd7eb3 feat(dev-cicd): docker exec health check + nginx mount lesson (#93 first green) 2026-04-06 13:49:15 +09:30
dongliang f0e5735ffa feat(dev-cicd): CD deployment checklist + health check port mismatch lesson 2026-04-06 12:42:48 +09:30
dongliang 52b8c85b94 feat(dev-cicd): docker compose up --no-deps lesson 2026-04-06 11:53:38 +09:30
dongliang 0e24828a6d feat(dev-cicd): add docker compose pull scope + Docker Hub timeout lesson 2026-04-06 11:49:18 +09:30
dongliangandClaude Opus 4.6 d564e6dbf9 feat(dev-cicd): add Docker context analysis + .dockerignore check
从实际 CI 失败中学到的经验:
- 缺 .dockerignore 导致 768MB context 传入(node_modules)
- ACR push denied 因镜像路径缺 namespace
- /cicd analyze 增加 .dockerignore + 凭据硬编码 + 镜像命名扫描

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:43:21 +09:30
dongliangandClaude Opus 4.6 a58dc39795 feat: add dev-cicd skill + enhance dev-deploy
新增 dev-cicd(CI/CD 流水线设计/优化/排查):
- Gitea Actions 模板(Go/iOS/Web/Docker)
- Pipeline 优化(浅克隆/缓存/并发取消)
- 故障排查决策树(20+ 常见错误)
- 安全检查清单 + Runner 管理

增强 dev-deploy(部署执行):
- Docker Staging/Production 部署模板
- 部署前健康检查(证书/Docker/磁盘)
- 回滚策略(TestFlight/Docker/数据库)
- 部署监控(Feishu通知/ASC API)

技能总数: 28 (dev 分类: 7)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 11:10:13 +09:30
dongliangandClaude Opus 4.6 b5f44ac6aa feat: add dev-deploy skill — iOS TestFlight deployment
新增部署技能,含 iOS TestFlight 完整部署流程:
- SSH 远程构建 + 无签名 Archive + Export 签名上传
- ASC API 补全合规/测试说明/版本关联
- 10 个坑的经验教训总结
- 一键部署脚本模板 + 检查清单

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 09:46:23 +09:30
qiudlandClaude Opus 4.6 b6bd4bbfed feat(req): 新增 /req ci 命令 + dev→review quality_gate 门禁说明
- req 技能新增 /req ci 命令规范(CI 检查与自动修复循环)
- 补充 dev→review 阶段 quality_gate 门禁说明
- req-workflow 流程概览增加 7.5 /req ci 步骤

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 13:19:55 +10:30
qiudlandClaude Opus 4.6 187f5621c9 feat(req): 部署门禁制度 — PDV 验收任务 + Deploy Gate 1-3
在 /req deploy 流程中增加部署后 E2E 验收(Post-Deploy Verification)门禁:
- 新增 verification linkRole 和【验收】任务命名规范
- Deploy Gate 1 健康检查 / Gate 2 PDV 任务完成 / Gate 3 证据完整
- PDV Playwright spec 模板(页面可达、菜单可见、API 连通)
- 同步更新 req-workflow、dev-test、e2e-testing 相关文档

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 09:34:08 +10:30
qiudlandClaude Opus 4.6 b9c808cce0 feat(req-test-gate): 集成 Harness Engineering 工程约束方法论
将项目级的 Ratchet/约定检测方法论融入 req-test-gate 技能,
通过 /req 流程三个节点自动触发(dev 环境检测、cr 约定建议、test Gate 0B),
无需手动记忆执行。

新增文档:harness-engineering.md、ratchet-pattern.md、convention-flow.md、
project-bootstrap.md 及 4 个模板(ratchet/convention 脚本、GATES.md、pre-commit)。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-26 11:34:42 +10:30
qiudlandClaude Opus 4.6 e3924e6b2b feat(skills): 新增 agent-browser 浏览器自动化技能
基于 Vercel agent-browser CLI,支持网页交互、E2E 冒烟测试、需求验收验证、前端开发验证和截图对比。

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 16:07:04 +10:30
qiudlandClaude Opus 4.6 22107fa7b8 feat(req): 集成 Stitch 原型设计到需求工作流
新建 req-prototype 技能,支持基于 PRD 自动生成 Stitch UI 原型。
同步 req-review、req-workflow 技能到仓库,并更新 req、req-prd 中的
原型相关引用。

REQ-20260320-0005

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 23:42:24 +10:30
qiudlandClaude Opus 4.6 0724357ff4 feat(dev-test): 添加集成测试模板 + TG2 检测规则
- 新增 templates/go-integration-test.md 集成测试代码骨架模板
- SKILL.md 增加 TG2 集成测试检测:跨 handlers/middleware/routes 变更自动触发

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 14:28:44 +10:30
133 changed files with 12784 additions and 1443 deletions
+424 -67
View File
@@ -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,106 @@
],
"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": "ai-proj-cicd-release-plugin",
"source": "./skills-dev/ai-proj-cicd-release-plugin",
"description": "执行和审计 AI-Proj 服务从 Gitea 门禁、不可变镜像、预发验证到生产发布和回滚的 CI/CD 流程。",
"version": "1.0.0",
"category": "productivity",
"keywords": [
"project-management",
"tasks",
"requirements"
],
"strict": false
},
{
"name": "ai-proj-macos-release-plugin",
"source": "./skills-dev/ai-proj-macos-release-plugin",
"description": "构建、签名、公证、发布并验证 AI-Proj macOS Apple Silicon 安装包。",
"version": "1.0.0",
"category": "productivity",
"keywords": [
"project-management",
"tasks",
"requirements"
],
"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.1.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 +184,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 +327,55 @@
],
"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.1",
"category": "development",
"keywords": [
"development",
"coding",
"workflow"
],
"strict": false
},
{
"name": "karpathy-guidelines-plugin",
"source": "./skills-dev/karpathy-guidelines-plugin",
"description": "Karpathy 四原则编码行为守则(Think Before Coding / Simplicity First / Surgical Changes / Goal-Driven Execution)。已深度融合到 req 技能工作流各阶段,可独立激活用于任意编码场景。",
"version": "1.0.1",
"category": "utility",
"keywords": [
"utility",
"tools"
],
"strict": false
},
{
"name": "pull-request-plugin",
"source": "./skills-dev/pull-request-plugin",
@@ -99,10 +389,61 @@
],
"strict": false
},
{
"name": "review-checklist-plugin",
"source": "./skills-dev/review-checklist-plugin",
"description": "项目级代码评审检查清单。按项目积累的特定检查项,挂载在 dev-review 下自动加载。",
"version": "1.1.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 +453,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 +482,59 @@
{
"name": "req-prd-plugin",
"source": "./skills-req/req-prd-plugin",
"description": "Plugin for req-prd",
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
"version": "2.2.0",
"category": "productivity",
"keywords": [
"project-management",
"tasks",
"requirements"
],
"strict": false
},
{
"name": "req-prototype-plugin",
"source": "./skills-req/req-prototype-plugin",
"description": "原型生成与关联。支持 HTML 本地留源、OSS 正式交付、Requirement/iframe 验证及 Stitch AI。",
"version": "2.2.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 +547,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": [
@@ -167,7 +586,7 @@
"name": "doubao-voice-plugin",
"source": "./skills-integration/doubao-voice-plugin",
"description": "Doubao (豆包) Voice API integration for TTS and ASR",
"version": "1.0.0",
"version": "1.0.1",
"category": "utility",
"keywords": [
"utility",
@@ -303,68 +722,6 @@
"tools"
],
"strict": false
},
{
"name": "gitea-plugin",
"source": "./skills-personal/gitea-plugin",
"description": "Gitea 代码托管与 CI/CD 管理。用于 Gitea Actions workflow 管理、Runner 管理、PR 操作、仓库配置。",
"version": "1.0.0",
"category": "utility",
"keywords": [
"utility",
"tools"
],
"strict": false
},
{
"name": "openclaw-plugin",
"source": "./skills-personal/openclaw-plugin",
"description": "OpenClaw (龙虾) 远程 AI 计算调度系统 - 概念设计与运维管理",
"version": "1.0.0",
"category": "utility",
"keywords": [
"utility",
"tools"
],
"strict": false
},
{
"name": "ops-tools-plugin",
"source": "./skills-personal/ops-tools-plugin",
"description": "Plugin for ops-tools",
"version": "1.0.0",
"category": "devops",
"keywords": [
"devops",
"deployment",
"operations"
],
"strict": false
},
{
"name": "qiudl-personal-plugin",
"source": "./skills-personal/qiudl-personal-plugin",
"description": "Plugin for qiudl-personal",
"version": "1.0.0",
"category": "utility",
"keywords": [
"utility",
"tools"
],
"strict": false
},
{
"name": "req-deploy-plugin",
"source": "./skills-personal/req-deploy-plugin",
"description": "Plugin for req-deploy",
"version": "1.0.0",
"category": "devops",
"keywords": [
"devops",
"deployment",
"operations"
],
"strict": false
}
]
}
+9 -9
View File
@@ -1,14 +1,14 @@
# ai-proj-helper
Claude Code 技能市场 + MCP 配置管理工具。
Codex 优先、兼容 Claude Code 的 Agent Skills 市场与 MCP 配置管理工具。
## 快速开始
```bash
./init.sh
./install-skills.sh
```
交互式配置 MCP 连接(默认 SSE 模式)+ 自动注册技能市场到 `~/.claude/plugins/known_marketplaces.json`。支持命令行参数
默认安装到 Codex 标准目录 `~/.agents/skills`。Claude Code 的 MCP 与 marketplace 初始化使用
```bash
./init.sh --mode sse --token aiproj_pk_xxx
@@ -18,11 +18,11 @@ Claude Code 技能市场 + MCP 配置管理工具。
```
ai-proj-helper/
├── skills-core/ # 基础设施 (1): ai-proj
├── skills-dev/ # 开发 (4): dev-arch, dev-coding, dev-test, pull-request
├── skills-req/ # 需求 (4): req, req-prd, req-dev, req-test-gate
├── skills-integration/ # 集成 (8): feishu, feishu-bitable, feishu-docx, wecom, siyuan, siyuan-to-feishu, data-excel, doubao-voice
├── skills-biz/ # 商务 (4): biz-contract, biz-ops, biz-plan, finance
├── skills-core/ # 基础设施技能
├── skills-dev/ # 开发与发布技能
├── skills-req/ # 需求管理技能
├── skills-integration/ # 第三方集成技能
├── skills-biz/ # 商务技能
├── skills-personal/ # 个人(.gitignore 排除)
├── claude-config.yaml # 技能启用/禁用 + MCP 配置
├── init.sh # MCP 初始化
@@ -51,4 +51,4 @@ skills:
- `skills-personal/` 不被 Git 跟踪,用于存放个人配置和工具
- 其余 `skills-*` 目录均由 Git 版本控制
- 所有目录会被 `generate-marketplace.py` 自动扫描并加入 marketplace.json
- 只有受 Git 跟踪的分类目录会被 `generate-marketplace.py` 扫描并加入公开 marketplace`skills-personal/` 始终排除
+14 -11
View File
@@ -1,6 +1,6 @@
# ai-proj-helper — 让 Claude Code 变成你的项目经理
# ai-proj-helper — 让 Codex / Claude Code 变成你的项目经理
> 一套开箱即用的 Claude Code 技能包 + MCP 服务,帮你用自然语言管理需求、写代码、做评审、同步飞书,把 AI 助手变成真正的项目经理
> 一套遵循 Agent Skills 标准的技能包 + MCP 服务,支持 Codex,并兼容 Claude Code
## 它能帮你做什么
@@ -66,7 +66,7 @@ PRD 文档存储在思源笔记中,可以导出发送到飞书群,通过飞
### 前置条件
- **Claude Code** 已安装([安装指南](https://docs.anthropic.com/en/docs/claude-code/overview)
- **Codex**[Skills 文档](https://developers.openai.com/codex/skills))或 **Claude Code** 已安装
- **ai-proj 账号 + MCP API Key**:联系管理员获取(Key 格式: `aiproj_pk_xxx`
### 一键部署(2 步搞定)
@@ -76,17 +76,16 @@ PRD 文档存储在思源笔记中,可以导出发送到飞书群,通过飞
git clone https://gitea.pipexerp.com/pipexerp/ai-proj-helper.git
cd ai-proj-helper
# 2. 运行初始化(按提示输入 API Key 即可)
./init.sh
# 2. 默认安装到 Codex 的用户级标准目录 ~/.agents/skills
./install-skills.sh
```
脚本会自动完成:
- 配置 MCP 服务器连接(`~/.claude/.mcp.json`
- 注册技能市场到 Claude Code`~/.claude/plugins/known_marketplaces.json`
安装器会复制完整技能目录,包括 `SKILL.md`、references、scripts 和 assets。Codex 会自动发现 `~/.agents/skills` 中的技能;若没有出现,重启 Codex。
也支持命令行参数跳过交互
Claude Code 用户显式选择 Claude 目标;需要同时配置 MCP 和 marketplace 时运行 `init.sh`
```bash
./install-skills.sh --agent claude
./init.sh --mode sse --token aiproj_pk_xxx
```
@@ -163,7 +162,7 @@ skills:
- **mode**: MCP 连接模式。`sse` 直连远程服务器(推荐),`stdio` 在本地启动 Node.js 进程
- **disabled**: 不需要的技能可以加到这里,重新运行 `./init.sh` 生效
- **personal_dir**: 个人技能目录,默认不被 Git 跟踪
- **personal_dir**: 本机个人技能目录,默认不被 Git 跟踪,也不会写入公开 marketplace
## 常见问题
@@ -171,13 +170,17 @@ skills:
A: 需要先联系管理员获取 MCP API Key(格式 `aiproj_pk_xxx`),然后在提示处输入。
**Q: 安装后 Codex 没有识别到技能?**
A: 确认技能位于 `~/.agents/skills/<name>/SKILL.md`,然后重启 Codex。Codex CLI 也可用 `/skills` 查看。
**Q: 安装后 Claude Code 没有识别到技能?**
A: 重启 Claude Code 后生效。如果仍不生效,检查 `~/.claude/plugins/known_marketplaces.json` 中是否包含 `ai-proj-helper` 条目。
**Q: 如何更新到最新版本?**
A: 进入项目目录执行 `git pull`,然后重新运行 `./init.sh`
A: 进入项目目录执行 `git pull`,然后运行 `./install-skills.sh`Claude Code 用户增加 `--agent claude`
**Q: 如何禁用不需要的技能?**
+25 -32
View File
@@ -1,28 +1,23 @@
# Setup Guide
# Claude Code Marketplace Setup
## 1. Create Repository on Gitea
本页只描述 Claude Code marketplace。Codex 用户直接运行 `./install-skills.sh`,技能默认安装到 `~/.agents/skills`
Go to https://gitea.pipexerp.com and create a new repository:
- Name: `claude-marketplace`
- Visibility: Private or Public (your choice)
- **Do NOT** initialize with README (we already have one)
## 2. Push to Gitea
## 1. Clone the Gitea Repository
```bash
cd /Users/junhuang/coolbuy/claude-marketplace
git push -u origin main
git clone https://gitea.pipexerp.com/pipexerp/ai-proj-helper.git
cd ai-proj-helper
```
## 3. Test Installation
## 2. Test Installation
### Add the marketplace
```bash
# SSH (recommended)
/plugin marketplace add git@gitea.pipexerp.com:huangjun/claude-marketplace.git
# SSH
/plugin marketplace add ssh://git@gitea.pipexerp.com:10022/pipexerp/ai-proj-helper.git
# OR HTTPS (requires credential configuration)
/plugin marketplace add https://gitea.pipexerp.com/huangjun/claude-marketplace.git
/plugin marketplace add https://gitea.pipexerp.com/pipexerp/ai-proj-helper.git
```
### List available plugins
@@ -42,12 +37,12 @@ git push -u origin main
# Check for your installed plugins
```
## 4. Update Plugins Later
## 3. Update Plugins Later
When you make changes and push updates:
```bash
cd /Users/junhuang/coolbuy/claude-marketplace
cd /path/to/ai-proj-helper
# Make changes to plugins
# ...
@@ -67,7 +62,7 @@ Users update with:
/plugin update ai-proj-plugin@coolbuy-claude-plugins
```
## 5. Private Repository Setup
## 4. Repository Authentication
If your Gitea repo is private, users need authentication:
@@ -87,30 +82,28 @@ To create a Gitea token:
3. Give it "Read repository" permissions
4. Copy the token and add to your environment
## 6. Structure Overview
## 5. Structure Overview
```
claude-marketplace/
ai-proj-helper/
├── .claude-plugin/
│ └── marketplace.json # Catalog of all plugins
├── plugins/
│ ├── ai-proj-plugin/
├── .claude-plugin/
│ │ │ └── plugin.json # Plugin metadata
│ │ └── skills/
│ │ └── SKILL.md # Skill definition
│ └── [33 more plugins...]
├── skills-core/ # Core plugins
├── skills-dev/ # Development and release plugins
├── skills-req/ # Requirement plugins
├── skills-integration/ # Integration plugins
├── skills-biz/ # Business plugins
├── README.md # User documentation
├── SETUP.md # This file
── convert-skills.sh # Conversion script (reference)
── generate-marketplace.py # Marketplace generator
└── install-skills.sh # Versioned local installer
```
## Next Steps
1.Push to Gitea: `git push -u origin main`
2.Test locally: `/plugin marketplace add <url>`
3.Install plugins: `/plugin install <name>@coolbuy-claude-plugins`
4. ✅ Share with team: Send them the repository URL
1.Test locally: `/plugin marketplace add <url>`
2.Install plugins: `/plugin install <name>@coolbuy-claude-plugins`
3.Share with team: Send them the repository URL
## Troubleshooting
@@ -123,7 +116,7 @@ claude-marketplace/
- Check plugin name is correct
- Ensure marketplace.json is valid: `cat .claude-plugin/marketplace.json | jq`
**"Skills not working"**
**"Skills not working in Claude Code"**
- Skills are Agent Skills (auto-invoked by Claude when relevant)
- They don't create slash commands
- Check plugin installation: `/plugin list`
+40 -189
View File
@@ -1,218 +1,69 @@
# Skill Sync Guide
## Overview
仓库中的插件是团队技能的发布源。默认安装目标是 Codex 的用户级标准目录 `~/.agents/skills/`。个人技能保留在
`skills-personal/` 或其他本机目录,不会进入公开 marketplace。
This guide explains how to keep your local skills (`~/.claude/skills/`) synchronized with the marketplace plugins.
## Quick Sync
## 从仓库更新本机
```bash
cd /path/to/claude-marketplace
./sync-skills.sh
git pull
./install-skills.sh --dry-run
./install-skills.sh
```
This will:
1. ✅ Compare local skills with marketplace plugins
2. Add new skills as plugins
3. 📝 Update changed skills
4. ✓ Skip unchanged plugins
Claude Code 需要显式选择目标:
## Sync Workflow
### 1. Edit Skills Locally
Work on your skills in `~/.claude/skills/`:
```bash
code ~/.claude/skills/my-skill/SKILL.md
./install-skills.sh --agent claude
```
### 2. Run Sync Script
安装器会复制完整技能目录,包括 `SKILL.md``references/``scripts/``assets/`。它用内容摘要区分仓库升级和本地修改:
- 目标未修改时,版本升级会自动安装。
- 旧版只安装了 `SKILL.md` 时,会安全补齐仓库中的其他同源文件。
- 目标存在本地修改时会跳过;确认覆盖后才使用 `--force`
- `--cleanup` 会删除状态文件记录中已从仓库移除的技能,使用前先运行 `--dry-run --cleanup`
按分类安装或查看清单:
```bash
cd ~/path/to/claude-marketplace
./sync-skills.sh
./install-skills.sh --list
./install-skills.sh --category dev
./install-skills.sh --exclude ai-proj-cicd-release
```
### 3. Review Changes
## 将本机技能发布到仓库
不要批量复制整个 `~/.agents/skills/` 或其他 Agent 的安装目录。系统技能、第三方托管技能、包含机器路径或凭据的技能不应发布。
1. 选择确实属于本仓库、可供团队复用的技能。
2. 在对应 `skills-*/<name>-plugin/` 下放置 `.claude-plugin/plugin.json` 和完整 `skills/` 目录。
3. 清除用户名、绝对路径、内网地址、密钥标识和历史凭据;把环境差异改为从仓库配置解析。
4. 更新插件版本并运行:
```bash
git status
git diff
```
### 4. Commit & Push
```bash
git add .
git commit -m "Update skill: description of changes"
git push
```
### 5. Team Updates
Team members update with:
```bash
/plugin marketplace update coolbuy-claude-plugins
/plugin update <plugin-name>@coolbuy-claude-plugins
```
## Automated Sync (Optional)
### Git Hook (Pre-commit)
Auto-sync when committing changes to skills:
```bash
# In your dotfiles/skills repo
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
# Auto-sync skills to marketplace
~/path/to/claude-marketplace/sync-skills.sh
EOF
chmod +x .git/hooks/pre-commit
```
### Cron Job (Scheduled)
Sync daily at 9 AM:
```bash
crontab -e
# Add this line:
0 9 * * * cd ~/path/to/claude-marketplace && ./sync-skills.sh && git add . && git commit -m "Daily sync" && git push
```
## Skill Splitting Guidelines
From `~/.claude/CLAUDE.md`:
- **Token Limit**: Single skill ≤ 10,000 tokens
- **Check Size**: `wc -w ~/.claude/skills/<skill>/SKILL.md`
- **When to Split**: If > 7,500 words (≈10,000 tokens)
### Split Strategy
When a skill grows too large:
1. **Entry Skill** - Overview + command routing (<100 lines)
- Example: `req/SKILL.md`
2. **Command Reference** - Detailed commands (<200 lines)
- Example: `req-commands/SKILL.md`
3. **Workflow Guide** - Complete processes (<200 lines)
- Example: `req-workflow/SKILL.md`
4. **Methodology** - Complex concepts (<150 lines)
- Example: `req-review/SKILL.md`
## Troubleshooting
### Sync Script Fails
```bash
# Check permissions
ls -la sync-skills.sh
# Make executable
chmod +x sync-skills.sh
# Check paths
echo $HOME/.claude/skills
```
### marketplace.json Not Updated
```bash
# Manually regenerate
python3 generate-marketplace.py
# Or edit directly
code .claude-plugin/marketplace.json
claude plugin validate .
git diff --check
```
### Git Conflicts
5. 审核变更后通过分支和 PR 发布。
```bash
# Discard local changes
git checkout .claude-plugin/marketplace.json
## 本地个人技能
# Or merge manually
git mergetool
```
`skills-personal/` 受 `.gitignore` 保护,仅供当前机器使用。生成器明确排除此目录,避免
`marketplace.json` 引用公开克隆中不存在的文件。若个人技能要转为团队技能,应先按上面的发布流程完成脱敏和审核。
## Best Practices
## 常见问题
### 1. Descriptive Frontmatter
**本地修改被跳过怎么办?**
Always include in `SKILL.md`:
```yaml
---
name: skill-name
description: Clear, concise description of what this skill does
---
```
先比较仓库源和 `~/.agents/skills/<name>/`。保留本地修改时将其整理成插件变更;确认丢弃时再对该次安装使用 `--force`。Claude 目标改查 `~/.claude/skills/`。
### 2. Version Bumping
**marketplace 没更新?**
When making significant changes:
```bash
# Update version in plugin.json
{
"version": "1.1.0" # was 1.0.0
}
```
运行 `python3 generate-marketplace.py`,然后检查 `.claude-plugin/marketplace.json` 是否只包含受 Git 跟踪且真实存在的 source。
### 3. Testing Before Sync
**如何移除技能?**
```bash
# Test skill locally first
/skill-name
# Then sync to marketplace
./sync-skills.sh
```
### 4. Commit Messages
Use clear, descriptive messages:
```bash
git commit -m "Add feishu-bitable plugin for table operations"
git commit -m "Update req-workflow with new approval process"
git commit -m "Fix: Correct PRD template in req-prd"
```
## Monitoring
### Check Sync Status
```bash
# Compare local vs marketplace
diff -qr ~/.claude/skills /tmp/claude-marketplace/plugins
```
### List Differences
```bash
# Find skills not in marketplace
comm -23 <(ls ~/.claude/skills | sort) <(ls plugins | sed 's/-plugin$//' | sort)
# Find plugins not in local
comm -13 <(ls ~/.claude/skills | sort) <(ls plugins | sed 's/-plugin$//' | sort)
```
## FAQ
**Q: Can I sync in reverse (marketplace → local)?**
A: Not recommended. Treat local skills as the source of truth.
**Q: What about binary files (images, scripts)?**
A: Copy them manually to the plugin directory, then commit.
**Q: How do I remove a plugin?**
A: Delete the plugin directory, regenerate marketplace.json, commit, and push.
**Q: Can I sync specific skills only?**
A: Modify `sync-skills.sh` to accept a skill name parameter.
删除插件目录、重新生成 marketplace、提交变更。使用者随后执行 `./install-skills.sh --dry-run --cleanup`,确认后再去掉 `--dry-run`。
+253
View 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), -- 生成的 tagpublish 后才有
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 APIdraft + 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 只生成简单反向 DDLCREATE→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(数据模型)开始实施
+6 -13
View File
@@ -14,28 +14,27 @@ script_dir = Path(__file__).parent.resolve()
config_file = script_dir / "claude-config.yaml"
marketplace_file = script_dir / ".claude-plugin" / "marketplace.json"
# Skill directories (label, directory name)
# Public marketplace skill directories. skills-personal is deliberately
# excluded: it is gitignored and must never produce sources that disappear from
# a public clone of this repository.
SKILL_DIRS = [
("core", "skills-core"),
("dev", "skills-dev"),
("req", "skills-req"),
("integration", "skills-integration"),
("biz", "skills-biz"),
("personal", "skills-personal"),
]
def load_config():
"""Load claude-config.yaml and return disabled list + personal_dir."""
"""Load claude-config.yaml and return the disabled plugin list."""
disabled = []
personal = "skills-personal"
if config_file.exists() and HAS_YAML:
with open(config_file) as f:
cfg = yaml.safe_load(f) or {}
skills_cfg = cfg.get("skills", {})
disabled = skills_cfg.get("disabled", []) or []
personal = skills_cfg.get("personal_dir", personal)
elif config_file.exists():
# Fallback: parse disabled list without PyYAML
in_disabled = False
@@ -54,10 +53,7 @@ def load_config():
disabled.append(val)
elif stripped and not stripped.startswith("#"):
break
if stripped.startswith("personal_dir:"):
personal = stripped.split(":", 1)[1].strip().strip('"').strip("'")
return disabled, personal
return disabled
# Category mapping
@@ -116,15 +112,12 @@ def scan_plugins(directory, source_prefix, disabled):
# Load config
disabled_skills, personal_dir_name = load_config()
disabled_skills = load_config()
# Collect plugins from all skill directories
plugins = []
counts = {}
for label, dir_name in SKILL_DIRS:
# personal_dir may be overridden by config
if label == "personal":
dir_name = personal_dir_name
skill_path = script_dir / dir_name
if not skill_path.is_dir():
continue
+100
View 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
View 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 "⚠️ 未安装 jqPreToolUse 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
View 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
View 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
View 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
View 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
+8 -27
View File
@@ -1,6 +1,6 @@
#!/bin/bash
# ai-proj-helper 初始化脚本
# 配置 MCP 连接 + 安装技能到 ~/.claude/skills/
# 配置 Claude MCP 连接 + 安装 Claude 技能
set -e
@@ -183,32 +183,13 @@ EOF
fi
fi
# ── Install skills to ~/.claude/skills/ ──────────────────────────────
# ── Install complete skill packages ──────────────────────────────────
# Use the versioned installer as the single installation path so references,
# scripts and assets stay beside SKILL.md and local edits are not overwritten.
echo "📦 安装技能到 ~/.claude/skills/ ..."
SKILLS_DIR="$HOME/.claude/skills"
mkdir -p "$SKILLS_DIR"
SKILL_COUNT=0
for plugin_dir in "$SCRIPT_DIR"/skills-*/; do
for skill_path in "$plugin_dir"*-plugin/; do
[ -d "$skill_path" ] || continue
skill_md="$skill_path/skills/SKILL.md"
[ -f "$skill_md" ] || continue
# Extract skill name: ai-proj-plugin -> ai-proj
dir_name=$(basename "$skill_path")
skill_name="${dir_name%-plugin}"
target_dir="$SKILLS_DIR/$skill_name"
mkdir -p "$target_dir"
# Copy SKILL.md (overwrite if exists)
cp "$skill_md" "$target_dir/SKILL.md"
SKILL_COUNT=$((SKILL_COUNT + 1))
done
done
echo " 已安装 $SKILL_COUNT 个技能"
echo "✅ 技能安装完成 → $SKILLS_DIR"
"$SCRIPT_DIR/install-skills.sh" --agent claude
SKILL_COUNT=$(python3 -c 'import json, os; p=os.path.expanduser("~/.claude/.installed-skills.json"); print(len(json.load(open(p))) if os.path.exists(p) else 0)' 2>/dev/null || echo 0)
echo "✅ 技能安装完成"
# ── Verify MCP connection ────────────────────────────────────────────
echo ""
@@ -270,7 +251,7 @@ if $HAS_CLAUDE; then
else
echo " ✅ MCP 服务器 → $MCP_CONFIG"
fi
echo " ✅ 技能 ($SKILL_COUNT 个) → $SKILLS_DIR"
echo " ✅ 技能 ($SKILL_COUNT 个) → ~/.claude/skills"
echo ""
echo "重启 Claude Code 即可使用。"
echo "如需更改配置,编辑 claude-config.yaml 后重新运行 ./init.sh"
+456
View File
@@ -0,0 +1,456 @@
#!/usr/bin/env bash
# install-skills.sh — Cross-agent skill sync from ai-proj-helper
#
# Usage:
# ./install-skills.sh [options]
#
# Options:
# --agent <agent> Install target: codex (default) or claude
# --dry-run Preview changes without writing anything
# --category <cat> Only install plugins in dir_category=<cat>
# Valid values: biz, core, dev, integration, personal, req
# --exclude <name> Skip one install_name (repeatable)
# --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=""
COMMANDS_DIR=""
STATE_FILE=""
AGENT_TARGET="codex"
DRY_RUN=false
CATEGORY_FILTER=""
EXCLUDED_NAMES=()
FORCE=false
CLEANUP=false
LIST_ONLY=false
INSTALL_ACTION=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
--agent)
[[ $# -ge 2 ]] || { error "--agent requires codex or claude"; exit 1; }
AGENT_TARGET="$2"; shift ;;
--dry-run) DRY_RUN=true ;;
--force) FORCE=true ;;
--cleanup) CLEANUP=true ;;
--list) LIST_ONLY=true ;;
--category)
[[ $# -ge 2 ]] || { error "--category requires a value"; exit 1; }
CATEGORY_FILTER="$2"; shift ;;
--exclude)
[[ $# -ge 2 ]] || { error "--exclude requires an install_name"; exit 1; }
EXCLUDED_NAMES+=("$2"); shift ;;
--help|-h)
grep '^#' "$0" | grep -v '!/usr' | sed 's/^# \?//'
exit 0 ;;
*)
error "Unknown argument: $1"
exit 1 ;;
esac
shift
done
case "$AGENT_TARGET" in
codex)
# ~/.agents/skills is the current user-level Codex discovery location and
# is intentionally agent-neutral. Commands are installed as normal skills.
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.agents/skills}"
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.agents/.ai-proj-helper-installed-skills.json}"
;;
claude)
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.claude/skills}"
COMMANDS_DIR="${AI_PROJ_HELPER_COMMANDS_DIR:-${HOME}/.claude/commands}"
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.claude/.installed-skills.json}"
;;
*)
error "Unsupported agent: $AGENT_TARGET (expected codex or claude)"
exit 1
;;
esac
# ── 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_digest() {
# state_digest <install_name> -> prints installed content digest or empty string
local name="$1"
if [[ -f "$STATE_FILE" ]]; then
python3 -c "
import json
try:
d=json.load(open('$STATE_FILE'))
print(d.get('$name',{}).get('content_digest',''))
except: pass
" 2>/dev/null || true
fi
}
state_set() {
# state_set <install_name> <version> <install_type> <content_digest>
local name="$1" ver="$2" itype="$3" digest="$4"
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','content_digest':'$digest','agent':'$AGENT_TARGET'}
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
}
content_digest() {
# Stable digest for one command file or a complete skill directory.
python3 - "$1" <<'PY'
import hashlib
import os
import pathlib
import sys
target = pathlib.Path(sys.argv[1])
if not target.exists():
print("")
raise SystemExit
digest = hashlib.sha256()
files = [target] if target.is_file() else sorted(
path for path in target.rglob("*") if path.is_file() or path.is_symlink()
)
for path in files:
# A single-file command is renamed when installed for Claude. Hash its
# content under a stable logical name so source and target compare equally.
relative = "." if target.is_file() else path.relative_to(target).as_posix()
digest.update(relative.encode("utf-8"))
digest.update(b"\0")
if path.is_symlink():
digest.update(b"link\0")
digest.update(os.readlink(path).encode("utf-8"))
else:
digest.update(path.read_bytes())
digest.update(b"\0")
print(digest.hexdigest())
PY
}
is_compatible_subset() {
# True when every file in an existing legacy target also exists unchanged in
# the repository source. This safely upgrades old SKILL.md-only installs.
python3 - "$1" "$2" <<'PY'
import pathlib
import sys
source = pathlib.Path(sys.argv[1])
target = pathlib.Path(sys.argv[2])
if not source.is_dir() or not target.is_dir():
raise SystemExit(1)
for target_path in target.rglob("*"):
if target_path.is_dir():
continue
source_path = source / target_path.relative_to(target)
if not source_path.is_file() or target_path.is_symlink() != source_path.is_symlink():
raise SystemExit(1)
if target_path.is_symlink():
if target_path.readlink() != source_path.readlink():
raise SystemExit(1)
elif target_path.read_bytes() != source_path.read_bytes():
raise SystemExit(1)
raise SystemExit(0)
PY
}
# 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"
}
# ── Install a single plugin ────────────────────────────────────────────────────
install_plugin() {
INSTALL_ACTION=false
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)"
local excluded
for excluded in ${EXCLUDED_NAMES[@]+"${EXCLUDED_NAMES[@]}"}; do
[[ "$install_name" == "$excluded" ]] && return
done
# 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
local effective_install_type="$install_type"
if [[ "$AGENT_TARGET" == "codex" ]]; then
effective_install_type="skill"
fi
if [[ "$LIST_ONLY" == true ]]; then
echo " [$dir_category] $effective_install_type:$install_name v$version"
return
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")"
local source_path target_path
if [[ "$effective_install_type" == "command" ]]; then
source_path="$src_dir/SKILL.md"
target_path="$COMMANDS_DIR/${install_name}.md"
else
source_path="$src_dir"
target_path="$SKILLS_DIR/$install_name"
fi
if [[ ! -e "$source_path" ]]; then
warn "$install_name: install source not found, skipping"
return
fi
# A recorded content digest distinguishes repository updates from user edits.
# Legacy state is adopted automatically only when the target is missing or
# already identical to the repository source.
local current_version recorded_digest source_digest target_digest
current_version="$(state_get "$install_name")"
recorded_digest="$(state_digest "$install_name")"
source_digest="$(content_digest "$source_path")"
target_digest="$(content_digest "$target_path")"
if [[ "$FORCE" == false && -n "$target_digest" && "$target_digest" == "$source_digest" ]]; then
if [[ "$DRY_RUN" == false && ( "$current_version" != "$version" || "$recorded_digest" != "$source_digest" ) ]]; then
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
fi
return
fi
local legacy_subset=false
if [[ "$effective_install_type" == "skill" && -z "$recorded_digest" && -n "$target_digest" ]]; then
if is_compatible_subset "$source_path" "$target_path"; then
legacy_subset=true
fi
fi
if [[ "$FORCE" == false && -n "$target_digest" && "$legacy_subset" == false ]]; then
if [[ -z "$recorded_digest" || "$target_digest" != "$recorded_digest" ]]; then
warn "$install_name: local files were modified or have legacy unverified state — skipping (use --force once to adopt repository content)"
return
fi
fi
# Perform install
if [[ "$effective_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"
INSTALL_ACTION=true
else
mkdir -p "$COMMANDS_DIR"
cp "$src_md" "$COMMANDS_DIR/${install_name}.md"
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
ok "$install_name → command (v$version)"
INSTALL_ACTION=true
fi
else
# Standard skill directory → the selected agent's discovery root.
local dst_dir="$SKILLS_DIR/$install_name"
if [[ "$DRY_RUN" == true ]]; then
dry "$install_name$dst_dir/"
INSTALL_ACTION=true
else
mkdir -p "$dst_dir"
# rsync resolved source (handles nested skills/ structures)
rsync -a --delete "$src_dir/" "$dst_dir/"
state_set "$install_name" "$version" "$effective_install_type" "$source_digest"
ok "$install_name → skill (v$version)"
INSTALL_ACTION=true
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 skills for $AGENT_TARGET 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
while IFS= read -r json_path; do
install_plugin "$json_path"
if [[ "$INSTALL_ACTION" == 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"
}
@@ -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
View 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
@@ -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
View 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"
}
@@ -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"
}
@@ -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-testE2E 冒烟测试)
在 dev-test 的 Gate 4E2E 冒烟测试)中使用:
```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` 获取机器可读输出
@@ -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"
}
@@ -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)
@@ -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
View 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 解析失败。
@@ -0,0 +1,11 @@
{
"name": "ai-proj-cicd-release-plugin",
"description": "执行和审计 AI-Proj 服务从 Gitea 门禁、不可变镜像、预发验证到生产发布和回滚的 CI/CD 流程。",
"version": "1.0.0",
"author": {
"name": "qiudl"
},
"install_name": "ai-proj-cicd-release",
"install_type": "skill",
"dir_category": "dev"
}
@@ -0,0 +1,73 @@
---
name: ai-proj-cicd-release
description: Execute and audit the AI-Proj service CI/CD flow across Gitea gates, immutable image builds, staging verification, approved production release, rollback, and evidence capture. Use for AI-Proj CI status, release PRs, staging or production deployments, failed-release diagnosis, deployed commit/schema verification, and delivery-chain repair. Do not use for unrelated repositories or macOS application packaging.
---
# AI-Proj CI/CD release
Use the repository's live workflows and release scripts as the executable source of truth. Keep staging and production isolated, bind artifacts to exact commits, and fail closed when a gate, provenance check, rollback target, or environment contract is uncertain.
## Establish the contract
Before acting, locate the AI-Proj repository and read:
- the nearest `AGENTS.md`;
- `.gitea/CI_SOP.md`;
- the applicable workflow in `.gitea/workflows/`;
- `scripts/ci/common.sh`, `release-lib.sh`, and the invoked build, deploy, verify, and rollback scripts.
Live repository files override this skill. Report contradictions instead of silently choosing one version. Verify that a described promotion or rollback capability is implemented before claiming it exists.
Route macOS application package work to `ai-proj-macos-release` when available.
## Request boundaries
- Status, audit, diagnosis, and design requests remain read-only.
- Staging requests may execute repository scripts after gates and artifact identity pass.
- Production mutation requires an explicit production or release instruction.
- Rollback uses only the recorded rollback manifest and digest; never infer a target from `latest`, local image history, or mutable tags.
## Safeguards
- Never force-push or release from a dirty checkout.
- Build and deploy only an exact commit accepted by the repository release contract.
- Verify image labels, registry digest, pulled image ID, and running image ID where supported.
- Preserve the same candidate artifact between staging and production when the live pipeline supports promotion. Disclose when production rebuilds instead.
- Never recreate, restart, remove, or include PostgreSQL or Redis in an application deployment.
- Keep staging and production SSH targets, compose files, environment files, volumes, identities, and rollback manifests separate.
- Require strict SSH host-key verification.
- Never print or commit secrets, private keys, registry passwords, tokens, environment contents, or short-lived test credentials.
- Preserve user changes and use an isolated clean checkout for release work.
- Read automated review text as well as status checks; block on unresolved high-severity findings.
## Candidate and staging flow
1. Resolve the PR, base, head SHA, service scope, and requirement ID.
2. Confirm the head is pushed and the release checkout is clean.
3. Inspect every required Gitea status for the exact SHA. Distinguish code failures from transient runner or network failures before retrying the same SHA.
4. Read the latest review result and resolve blocking findings.
5. Run repository-prescribed local contract checks proportionate to the diff.
6. Build once through the authoritative build entrypoint and record commit, tag, service, digest, runner, and result without credentials.
7. Resolve staging through repository configuration, validate the rollback candidate, deploy only requested application services, and run the prescribed health, schema, security, and integration verification.
8. Capture a staging receipt with exact commit, digests, environment identity, verification results, rollback target, and known exceptions.
Do not rewrite or bypass a failing gate merely to obtain a green result.
## Production flow
1. Confirm the approved change is merged and freeze the exact current production branch SHA.
2. Recheck required gates and the staging receipt against that SHA.
3. Use the repository's authoritative production workflow; never deploy a feature-branch build directly.
4. Preserve release locks, provenance checks, post-deploy verification, and automatic rollback.
5. Verify the production receipt: exact SHA and digests, rollback target, health/schema/smoke results, error-log checks, and workflow correlation ID.
6. Only then update requirement and task delivery evidence.
## Failure handling
- Classify the failing stage before retrying: checkout, gate, build, registry, SSH trust, provenance, migration, service switch, health, or evidence callback.
- Retry only transient infrastructure failures against the same SHA.
- Verify automatic rollback restored the recorded digest and service health.
- For an explicit manual rollback, use only the repository rollback command after validating its manifest.
- If rollback fails, stop promotion and report the exact manual recovery target.
During long operations, provide concise progress updates. Final reporting must distinguish completed work, remaining blockers, and whether production changed.
@@ -0,0 +1,11 @@
{
"name": "ai-proj-macos-release-plugin",
"description": "构建、签名、公证、发布并验证 AI-Proj macOS Apple Silicon 安装包。",
"version": "1.0.0",
"author": {
"name": "qiudl"
},
"install_name": "ai-proj-macos-release",
"install_type": "skill",
"dir_category": "dev"
}
@@ -0,0 +1,43 @@
---
name: ai-proj-macos-release
description: Build, sign, notarize, publish, and verify the AI-Proj macOS Apple Silicon package through the repository's release chain. Use when asked to release, republish, update, or repair the downloadable macOS application, including Gatekeeper failures and download-manifest updates. Do not use for service deployments or unrelated applications.
---
# AI-Proj macOS release
Use this workflow only for an explicitly requested AI-Proj macOS package release. Repository scripts and current project instructions are authoritative; stop and report any contradiction.
## Resolve the release contract
Before building, read the nearest `AGENTS.md`, the desktop package configuration, and the repository's macOS build, publish, and verification scripts. Resolve from those files:
- application version and architecture;
- production API configuration;
- signing identity and notarization mechanism;
- object-storage bucket, endpoint, release prefix, and public manifest;
- required website or download-manifest fallback version.
Do not copy machine-specific credential paths or identifiers into source control. Use the operator's configured secure credential provider without printing secret values.
## Release flow
1. Confirm the requested release version is unused. Keep package metadata, native application metadata, artifact filename, public manifest, and website fallback aligned.
2. Use a clean checkout of the exact approved commit. Run the repository's production desktop build script with the production API mode.
3. Require a valid Developer ID signature. If the repository's default notarization profile is unavailable, use another already-authorized App Store Connect credential source only after confirming its key, key ID, and issuer belong together.
4. Submit the final package to Apple notarization, wait for acceptance, staple the ticket, and validate it.
5. Mount the package read-only and verify the nested application with `codesign`, `spctl`, and the repository's smoke checks. Require Gatekeeper to report a notarized Developer ID.
6. Publish through the repository script. Use its configured object-storage credentials and upload mode; never handcraft a mutable public path when the script provides immutable versioned objects.
7. Require remote read-back verification of size and SHA-256. Upload artifacts first and update the public manifest last.
8. Download the public artifact independently and repeat signature, notarization, Gatekeeper, size, and checksum verification.
9. Confirm the manifest's latest version and asset URL, then update the website entry if the request includes it.
## Failure boundaries
- Missing signing or storage credentials: stop and report the missing configured provider; do not search broadly through personal files.
- Notarization authentication failure: stop and correct the credential tuple; never publish an unnotarized package.
- Gatekeeper reports an unnotarized or invalid application: do not publish.
- Upload stalls or fails: use only an alternative mode supported by the repository script, then repeat remote checksum verification.
- A public version already exists: do not overwrite it unless the user explicitly authorizes replacement and the repository permits it.
- Never expose signing keys, API keys, keychain passwords, storage credentials, or token values in logs, commits, manifests, or bundles.
Record the exact commit, version, artifact checksum and size, Apple result, public URL, manifest result, and verification outcome. Do not mark the release complete until the independently downloaded artifact passes all checks.
@@ -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"
}
@@ -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)
@@ -0,0 +1,11 @@
{
"name": "defect-analysis-plugin",
"description": "系统性设计缺陷分析。对需求方案/代码架构进行多维度检查,发现隐藏的技术风险和设计漏洞。当用户提到缺陷检查、方案审查、设计审计时自动激活。",
"version": "1.1.0",
"author": {
"name": "qiudl"
},
"install_name": "defect-analysis",
"install_type": "command",
"dir_category": "dev"
}
@@ -0,0 +1,161 @@
---
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 轮后仍有新发现 → 继续;20 轮作为阶段复盘点,不得误报为已收敛
- 达到 20 轮仍有新发现时,汇总剩余风险面并请求用户确认是否继续;用户已明确要求持续审计时可继续下一阶段
- 只有出现一轮 0 个新缺陷时才标记收敛;达到授权范围、时间或预算边界时应报告“尚未收敛”,不得伪装完成
- 每个缺陷标注严重度和轮次
## 输出格式
对每个缺陷:
```
### 缺陷 #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 + 事件委托 |
@@ -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"
}
@@ -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/内存)
@@ -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"
}
@@ -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"
}
@@ -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
View 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 单 JobGo+Web+Docker),macOS runner 单 JobiOS)。
---
## 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 自动缓存到 DerivedDataself-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 → ~10MBDocker 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" 很慢 → 缺少 .dockerignorenode_modules 被传入)
│ ├── build 成功但 push denied → 镜像路径缺 namespaceregistry/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:80deploy 必须包含 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"
}
+144 -344
View File
@@ -1,43 +1,24 @@
---
name: dev-coding
description: 软件编码开发技能。用于代码编写、功能实现、代码审查、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。支持 GoVueReact、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,77 @@ 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 |
---
## Step 0:验证优先(Karpathy: Goal-Driven Execution
**编写任何代码前,必须先写验证脚本。** 规则来源:Karpathy "Goal-Driven Execution" 原则。
> "Define success criteria. Loop until verified."
> "Fix the bug" → "Write a test that reproduces it, then make it pass"
### 执行流程
```
① 写验证脚本(按类型选择)
② 运行一遍,确认全部 FAIL(证明功能确实不存在 / bug 确实存在)
③ 编码实现
④ 再次运行验证脚本,全部 PASS → 完成
```
### 后端验证脚本模板
实现 API 前,先写好所有 curl 命令并标注期望结果:
```bash
# 验证脚本:REQ-XXXX [功能名]
BASE="http://localhost:8080"
TOKEN="<JWT>"
echo "=== T1: 正常创建 ==="
curl -s -X POST "$BASE/api/v1/xxx" \
-H "Authorization: Bearer $TOKEN" \
-d '{"name":"test"}' | jq '.code'
# 期望: 0
echo "=== T2: 缺少必填字段 ==="
curl -s -X POST "$BASE/api/v1/xxx" \
-H "Authorization: Bearer $TOKEN" \
-d '{}' | jq '.code'
# 期望: 非 0(参数错误)
echo "=== T3: 跨租户访问 ==="
curl -s -X GET "$BASE/api/v1/xxx/999" \
-H "Authorization: Bearer $TOKEN_OTHER_TENANT" | jq '.code'
# 期望: 403
```
**先运行 → 全部 FAIL → 编码 → 再次运行 → 全部 PASS**
### 前端验证脚本模板
实现页面前,先列出所有 `data-testid` 和期望的 DOM 状态:
```
验证清单(编码前先确认这些状态不存在 / 行为不正确):
- data-testid="xxx-btn-submit" 点击 → 列表刷新,行数增加 1
- data-testid="xxx-table" 行数 === API 返回 total
- data-testid="xxx-input-name" 空值提交 → 显示「请输入名称」提示
```
### 与 VP 三件套的关系
| VP 协议 | 验证优先对应 |
|---------|------------|
| VP-Data | 先在环境建好测试数据(curl 确认成功) |
| VP-Steps | **即为本节验证脚本** — 编码前写好,编码后执行 |
| VP-Pass | 验证脚本每条命令的期望输出值 |
---
@@ -349,309 +396,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 +439,6 @@ try {
} catch (error) {
message.error(error.message);
}
// Swift
do {
let result = try await service.fetch()
} catch {
// 处理错误
}
```
---
@@ -808,3 +545,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 Designai-proj frontend 风格) |
| `vue-element.md` | Vue 3 + Element Pluscoolbuy-paas 风格) |
| `mcp-typescript.md` | MCP bridge TypeScriptmcp-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 Providersauth, 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
- E2EPlaywright
- 测试文件:`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
- E2EPlaywright / Cypress
@@ -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"
}
@@ -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
@@ -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"
}
@@ -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)` |
@@ -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"
}
@@ -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,确认自动转换正确
@@ -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
View 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 中移除
@@ -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
View 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 # 集成测试
```
@@ -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
View 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 → 下次重试
```
@@ -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"
}
@@ -0,0 +1,305 @@
---
name: dev-review
description: 代码评审技能。六视角对抗性扫描法(含 Karpathy Scope 审计),用于 PR 代码审查、安全评审、质量检查。当执行 /req cr 或独立 PR review 时自动激活。
---
# 代码评审 Skill (dev-review)
## 概述
独立的代码评审技能,核心方法论是**六视角对抗性扫描法**(五个传统安全/健壮性视角 + Karpathy Scope 审计视角)。
**适用场景**
- `/req cr [REQ-ID]` — 需求流程中的代码评审阶段
- 独立 PR review — 不绑定需求的代码审查
- 安全评审 — 专项安全扫描
**核心原则**:实现阶段关注"怎么让它跑通",评审阶段关注**"怎么让它出错"**。AI 必须切换到对抗性思维。
---
## 技能间契约
| 上游 | 本技能输入 | 本技能输出 | 下游 |
|------|-----------|-----------|------|
| dev-coding | PR diff + 开发设计文档 | CR 报告(六视角扫描 + 发现汇总 + 结论) | dev-test |
---
## 工作流程
```
1. 确定评审范围
├── git diff 获取变更文件列表
├── 统计文件数、行数
└── 排除 test 文件(单独审查)
2. 读取变更代码
├── 逐个读取变更件源码
├── 理解业务上下文
└── 参考开发设计文档(如有)
3. 五视角扫描(核心)
├── 攻击视角
├── 泄露者视角
├── 并发者视角
├── 边界者视角
└── 依赖者视角
4. 加载项目检查清单(如有)
└── review-checklist 插件
5. 生成 CR 报告
├── 变更概要
├── 五视角扫描结果
├── 发现汇总表
└── 结论(通过/有条件通过/需修改)
6. 创建评审任务(req 流程内)
├── ai-proj task create【代码评审】
├── 关联需求(linkRole=code_review
└── 附加 CR 报告文档
7. 处理发现
├── Critical/High → 创建修复任务
└── Medium/Low → 记录建议
```
---
## 六视角对抗性扫描法
### 总览
| 视角 | 思维模式 | 核心问题 |
|------|---------|---------|
| **1. 攻击者** | "我怎么绕过/用它?" | 跨租户泄露、越权访问、参数注入、重放攻击 |
| **2. 泄露者** | "它暴露了什么不该暴露的?" | 错误信息泄露、日志敏感数据、响应内部细节 |
| **3. 并发者** | "两个请求同时来会怎样?" | 竞态条件、双重扣款、幂等性缺失、锁粒度 |
| **4. 边界者** | "极端输入会怎样?" | 空值/零值/负值/超长字符串、类型溢出、分页越界 |
| **5. 依赖者** | "外部服务挂了会怎样?" | 超时处理、重试策略、降级方案、连接泄露 |
### 视角1:攻击者(多租户安全)
**思维模式**:我是恶意用户,如何绕过权限获取他人数据。
扫描清单:
- [ ] 所有 Store/Repository 层查询是否带 `tenant_id` 过滤?
- [ ] 通过 ID 直接查询的方法是否校验归属?
- [ ] 用户只能操作自己的数据?(consumer_id 校验)
- [ ] URL/请求参数是否有注入风险?(SQL、URL、命令注入)
- [ ] 外部输入是否直接拼接到查询/URL?(应使用参数化查询或编码)
- [ ] 批量操作是否逐条校验权限?(不能只校验第一条)
- [ ] 文件上传是否有类型/大小限制?
**典型发现示例**
```
file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
攻击者可通过遍 ID 获取其他租户数据。
建议:添加 WHERE tenant_id = ? 条件。
```
### 视角2:泄露者(信息安全)
**思维模式**:我是安全审计员,检查每个出口是否泄露了不该泄露的信息。
扫描清单:
- [ ] 错误消息是否泄露业务状态?(如"手机号未注册"暴露用户存在性)
- [ ] 日志是否打印了密码、token、密钥、身份证号?
- [ ] 响应是否包含不必要的内部字段?(如内部 ID、数据库字段名、堆栈跟踪)
- [ ] panic recover 后是否返回了内部错误详情?
- [ ] 导出/下载功能是否过滤了敏感字段?
### 视角3:并发者(数据一致性)
**思维模式**:两个用户同时操作同一条数据,会发生什么。
扫描清单:
- [ ] 涉及金额/库存变更是否使用事务 + 悲观锁/乐观锁?
- [ ] 关键操作是否有幂等保护?(bizNo 唯一索引、幂等键)
- [ ] 全局状态(如进程内计数器、缓存)重启后是否安全?
- [ ] 是否有 TOCTOU(检查-使用)竞态?(先查状态再操作,中间被修改)
- [ ] 并发创建是否会产生重复数据?(唯一约束)
### 视角4:边界者(健壮性)
**思维模**:用最极端的输入来测试系统的承受能力。
扫描清单:
- [ ] 必填参数是否有 `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 不同策略)
### 视角6Scope 审计者(Karpathy: Simplicity + Surgical
**思维模式**:每一行变更,需求有没有要求它?
> "Touch only what you must. Clean up only your own mess."
> "Every changed line should trace directly to the user's request."
扫描清单:
- [ ] diff 中变更的**每个文件**,是否都在 req-design 变更文件清单中?(超出清单 = 疑似顺手重构)
- [ ] 新增的函数/方法/结构体,每个都有对应 AC 需要它?
- [ ] 是否引入了"未来可能用到"的参数、配置项、可选字段、接口抽象?
- [ ] 是否修改了本次 AC 无关的注释、格式、变量名、import 顺序?
- [ ] 代码量是否合理?实现简单 AC 超过 200 行须说明必要性
"If you write 200 lines and it could be 50, rewrite it"
- [ ] 错误处理是否只覆盖真实会发生的场景?不为不可能的情况写防御代码
**典型发现示例**
```
backend/services/user_service.go:45 — 新增了 WithRetry 参数,但 AC 中无重试需求。
建议:移除该参数,AC 有需要时再添加。严重度:Low
```
---
## CR 报告模板
```markdown
## 代评审报告 — {需求标题/PR 标题}
**日期**: YYYY-MM-DD
**评审范围**: {N} 个文件, {M} 行变更
**评审人**: AI (dev-review)
### 变更概要
{1-3 句描述本次变更的目的和范围}
### 六视角扫描结果
#### 1. 攻击者视角
{扫描发现,或 "未发现问题"}
#### 2. 泄露者视角
{扫描发现,或 "未发现问题"}
#### 3. 并发者视角
{扫描发现,或 "未发现问题"}
#### 4. 边界者视角
{扫描发现,或 "未发现问题"}
#### 5. 依赖者视角
{扫描发现,或 "未发现问题"}
#### 6. Scope 审计者视角(Karpathy
{扫描发现,或 "所有变更文件均在设计清单范围内,无过度实现"}
### 审查发现汇总
| # | 严重度 | 文件:行号 | 角 | 描述 | 建议 |
|---|--------|----------|------|------|------|
| 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` 格式的引用 |
| 六视角扫描 | 含全部 6 个视角章节(含 Scope 审计者) |
| 结论章节 | 含明确的通过/不通过结论 |
---
## 插件支持
| 插件 | 触发条件 | 说明 |
|------|---------|------|
| `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 告 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. **先理解再审查** — 读完所有变更件后再开始扫描,避免断章取义
2. **对抗性思维** — 切换到"怎么让它出错"的心态,不是"怎么让它跑通"
3. **证据驱动** — 每个发现必须引用具体的 `file:line`
4. **严重度准确** — 不要所有问题都标 High,按实际影响分级
5. **建议可操作** — 每个发现必须附带具体修复建议
6. **关注变更** — 评审范围是 diff,不要对未变更的代码提意见(除非变更引入了对旧代码的新风险)
@@ -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"
}
@@ -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"
}
@@ -14,6 +14,8 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
| `ios-testing.md` | iOS 测试 (XCTest + Swift Concurrency) |
| `android-testing.md` | Android 测试 (JUnit + Espresso + Compose) |
| `e2e-testing.md` | E2E PlaywrightAPI Mock 冒烟测试(无后端)+ 全链路集成测试 |
| `templates/go-integration-test.md` | Go 集成测试模板(多步骤 API 流程、中间件验证、租户隔离) |
| `templates/pdv-smoke-spec.md` | PDV 部署后验收 Playwright 模板(页面可达、菜单可见、API 连通) |
---
@@ -49,6 +51,7 @@ description: 软件测试技能。用于单元测试、集成测试、E2E测试
| E2E (Mock 冒烟) | `npm run test:e2e:smoke-mock` | `e2e-testing.md` §API Mock |
| E2E (全链路) | `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 |
---
@@ -141,3 +144,74 @@ ai-proj task append-doc --id <taskId> --content "# 测试报告
7. **Mock 仅限 Handler 层** - handler 层可以 mock biz 接口 + httptest
8. **E2E 冒烟测试必须用 API Mock** - E2E 门禁不能依赖后端,否则形同虚设。用 `page.route()` 拦截 API,见 `e2e-testing.md`。质量门禁流程(Gates 1-5)定义在 `req-test-gate` 技能中
9. **李宁测试用例** - Excel 导出见 `coolbuy-legacy` 技能的 `test-cases-excel.md`
---
## 部署后 E2E 验收 (PDV — Post-Deploy Verification)
部署后验收是独立于 TG4 的 E2E 冒烟模式,在 `/req deploy` 健康检查通过后执行。
### PDV vs TG4 区别
| 维度 | TG4 (开发阶段 E2E 冒烟) | PDV (部署后验收) |
|------|------------------------|-----------------|
| **触发时机** | `/req test` Gate 4 | `/req deploy` 步骤 6 |
| **环境** | 本地开发环境,API Mock | 真实部署环境 (staging/prod) |
| **目的** | 验证前端逻辑、UI 渲染 | 验证功能入口可达、基本可用 |
| **API** | `page.route()` 拦截 | 真实后端 API |
| **范围** | 回归冒烟 | 仅新功能可达性 |
### PDV 检查项
| 检查项 | 说明 | 示例 |
|--------|------|------|
| **页面可达** | 需求涉及的前端页面返回 200 | `/okr/my`, `/okr/team` |
| **菜单可见** | 新增菜单项在侧栏中出现 | OKR 菜单对目标用户角色可见 |
| **基础渲染** | 页面无白屏/JS 报错 | 页面有预期的标题/组件 |
| **API 连通** | 关键 API 带 token 调用返回正常 | `GET /api/v1/okr/objectives` 返回 200 |
### PDV 不做什么
- 不做完整回归测试(那是 TG4 的事)
- 不测试复杂业务流程(如多步骤表单提交)
- 不替代手动验收
- 只做「功能入口可达 + 基本可用」的冒烟验证
### PDV 执行方式
```bash
E2E_BASE_URL=<部署环境URL> npx playwright test e2e/pdv/ --project=chromium
```
### PDV Spec 生成规则
AI 根据需求变更范围动态生成 Playwright spec,模板见 `templates/pdv-smoke-spec.md`。生成流程:
1. 从需求关联任务提取前端变更范围(页面路由、菜单项、API 端点)
2. 登录测试账号(使用 storageState 或手动登录)
3. 验证新增菜单项可见(检查 `.ant-menu` 包含目标文本)
4. 导航到新页面,验证非白屏(title 不含 error/500/404
5. 调用关键 API,验证返回状态码 < 500
6. 每步截图保存为证据
### PDV 结果判定
- **全部 PASS** → 继续推进到 released
- **任一 FAIL** → 阻断推进,在部署文档记录失败项,通知修复
---
## TG2 集成测试检测
### 模板映射
| 变更范围 | 测试输出位置 | 模板 |
|----------|-------------|------|
| 单个 handler 或 service | `*_test.go` (同目录) | `go-testing.md` |
| handlers/ + middleware/ + routes/ (同一功能) | `tests/{feature}_integration_test.go` | `templates/go-integration-test.md` |
### 检测规则
若 git diff 显示同一功能的 `handlers/``middleware/``routes/` 文件均有变更(通过命名模式识别,如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go`),则除单元测试外**额外生成** `backend/tests/` 下的集成测试。
识别方式:提取文件名中的功能前缀(如 `impersonation`),若在三个目录中均出现,则触发集成测试生成。
@@ -1,14 +1,17 @@
# E2E 测试 (Playwright)
## 种 E2E 测试模式
## 种 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,127 @@
# Go 集成测试模板
## 适用场景
当 git diff 显示同一功能的 **handlers/ + middleware/ + routes/** 文件均有变更时,除单元测试外应额外生成集成测试。
适用于:
- 多步骤 API 流程(登录→操作→验证→退出)
- 中间件拦截验证(权限、限流、模拟状态限制)
- 租户隔离 / 数据隔离验证
- 跨模块交互(handler ↔ middleware ↔ repository
## 检测规则
通过命名模式识别同一功能的跨文件变更:
```
handlers/{feature}_handler.go
middleware/{feature}_middleware.go
routes/{feature}_routes.go
```
例如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go` 同时变更 → 生成 `tests/impersonation_integration_test.go`
## 生成规则
1. 测试文件放在 `backend/tests/` 目录(与 `test_helpers.go` 同包)
2. 使用 `SetupTestApp` + `defer TeardownTestApp` 初始化真实路由和数据库
3. 每个测试完全自包含:独立创建/清理数据
4. 异步持久化操作(goroutine 写 DB)需 `time.Sleep(200-500ms)` 后再验证
## 代码骨架
```go
package tests
import (
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestFeature_Scenario(t *testing.T) {
testApp := SetupTestApp(t)
defer TeardownTestApp(t, testApp)
// === Setup: 创建测试数据 ===
// 使用 test_helpers.go 中的 helper 函数
sysAdmin := CreateTestSystemUser(t, testApp, "admin_scenario")
// tenantID := CreateTestTenant(t, testApp, "scenario_tenant")
// enterprise := CreateTestEnterpriseWithTenant(t, testApp, "Corp", "CODE", tenantID)
// tenantAdmin := CreateTestTenantAdmin(t, testApp, "tadmin", enterprise.ID, tenantID)
// === Cleanup: 按外键约束顺序清理 ===
defer CleanupImpersonationTestData(t, testApp,
[]int{sysAdmin.ID},
[]int{}, // enterpriseIDs
[]int64{}, // tenantIDs
)
// === 状态变量:跨步骤传递 ===
var token string
t.Run("Step1_InitialAction", func(t *testing.T) {
w := MakeAuthenticatedRequest(t, testApp, http.MethodPost,
"/api/v1/...",
map[string]string{"key": "value"},
sysAdmin,
)
require.Equal(t, http.StatusOK, w.Code, "Step1 failed: %s", w.Body.String())
var resp map[string]interface{}
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
// 提取后续步骤需要的数据
token = resp["data"].(map[string]interface{})["token"].(string)
})
t.Run("Step2_VerifyState", func(t *testing.T) {
if token == "" {
t.Skip("Step1 failed")
}
// 使用 Step1 产出的 token 继续验证
})
t.Run("StepN_AsyncVerification", func(t *testing.T) {
// 异步写入的数据需要等待
time.Sleep(300 * time.Millisecond)
// 然后验证 DB 数据
})
}
```
## 4 种必测场景
| 场景类型 | 说明 | 示例 |
|----------|------|------|
| **Happy Path** | 完整正常流程 | 开始模拟 → 访问 API → 查状态 → 退出 → 确认恢复 |
| **权限拒绝** | 无权限用户尝试操作 | 普通用户尝试模拟 → 403 |
| **隔离验证** | 跨租户/跨企业数据隔离 | tenant2 admin 访问 tenant1 企业 → 403 |
| **边界条件** | 输入校验、状态冲突 | 原因太短 → 400;已在模拟中再次模拟 → 403 |
## Helper 函数参考
```go
// 来自 test_helpers.go
SetupTestApp(t) // 初始化完整应用(路由 + DB
CreateTestSystemUser(t, app, username) // 系统管理员 + JWT
CreateTestTenant(t, app, name) // 创建租户,返回 int64 ID
CreateTestEnterpriseWithTenant(t, app, name, code, tenantID) // 带租户的企业
CreateTestTenantAdmin(t, app, username, enterpriseID, tenantID...) // 租户管理员
CreateTestEnterpriseUser(t, app, username, enterpriseID) // 普通企业用户
ExtractImpersonationToken(t, response) // 从模拟响应提取 token
MakeAuthenticatedRequest(t, app, method, path, body, user) // 发送认证请求
CleanupImpersonationTestData(t, app, userIDs, enterpriseIDs, tenantIDs) // 按FK顺序清理
```
## 注意事项
- **Token 链式传递**:模拟 API 返回新 token,后续请求必须用新 token
- **异步持久化**handler 用 goroutine 写 session/audit,测试需 Sleep 后再查
- **数据隔离**:每个测试用唯一的 username/code,避免测试间干扰
- **清理顺序**:外键约束要求先删子表再删父表
@@ -0,0 +1,147 @@
# PDV Smoke Spec 模板
部署后验收 (Post-Deploy Verification) Playwright 测试模板。AI 根据需求变更范围,基于此模板动态生成验收 spec。
## 使用方式
```bash
# 指定部署环境 URL 执行
E2E_BASE_URL=https://staging.example.com npx playwright test e2e/pdv/ --project=chromium
```
## Spec 模板结构
```typescript
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:3000';
test.describe('PDV: {需求标题}', () => {
test.beforeEach(async ({ page }) => {
// 方式 1: 使用 storageState(推荐,需预先保存登录状态)
// test.use({ storageState: 'e2e/.auth/user.json' });
// 方式 2: 手动登录
await page.goto(`${BASE_URL}/login`);
await page.fill('input[name="username"]', '{测试账号}');
await page.fill('input[name="password"]', '{测试密码}');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard/**');
});
test('菜单可见性: {菜单名}', async ({ page }) => {
await page.goto(`${BASE_URL}/`);
await page.waitForSelector('.ant-menu');
// 检查侧栏包含新菜单项
const menu = page.locator('.ant-menu');
await expect(menu).toContainText('{菜单名}');
// 截图证据
await page.screenshot({ path: 'e2e-results/pdv-menu-{菜单名}.png', fullPage: false });
});
test('页面可达: {页面路由}', async ({ page }) => {
const response = await page.goto(`${BASE_URL}{页面路由}`);
// 验证 HTTP 状态
expect(response?.status()).toBeLessThan(400);
// 验证非白屏 — title 不含错误关键词
await expect(page).not.toHaveTitle(/error|500|404|not found/i);
// 验证页面有核心内容(非空白)
await expect(page.locator('{核心选择器}')).toBeVisible({ timeout: 10000 });
// 检查无 JS 报错(通过 console error 监听)
const errors: string[] = [];
page.on('console', msg => {
if (msg.type() === 'error') errors.push(msg.text());
});
await page.waitForTimeout(2000);
expect(errors.filter(e => !e.includes('favicon'))).toHaveLength(0);
// 截图证据
await page.screenshot({ path: 'e2e-results/pdv-page-{页面名}.png', fullPage: true });
});
test('API 连通: {接口描述}', async ({ request }) => {
// 需要带认证 token 调用
const resp = await request.get(`${BASE_URL}/api/v1/{路径}`, {
headers: {
'Authorization': 'Bearer {token}',
},
});
// 验证非 5xx 错误
expect(resp.status()).toBeLessThan(500);
// 可选:验证响应结构
// const body = await resp.json();
// expect(body).toHaveProperty('data');
});
});
```
## 占位符说明
| 占位符 | 含义 | 来源 |
|--------|------|------|
| `{需求标题}` | 需求名称 | `ai-proj req get --id <id>` |
| `{菜单名}` | 新增的菜单文本 | 从需求关联的前端任务中提取 |
| `{页面路由}` | 新增/变更的前端路由 | 从前端路由配置或 PRD 提取 |
| `{核心选择器}` | 页面核心内容的 CSS 选择器 | 如 `h1`, `.page-title`, `[data-testid="xxx"]` |
| `{测试账号}` / `{测试密码}` | 测试环境登录凭据 | 环境配置 |
| `{token}` | API 认证 token | 登录后获取 |
| `{接口描述}` / `{路径}` | 关键 API 端点 | 从后端路由或 PRD 提取 |
| `{页面名}` | 截图文件名标识 | 自定义 |
## 生成示例(OKR 功能)
```typescript
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.E2E_BASE_URL || 'http://localhost:3000';
test.describe('PDV: OKR 团队/对齐/设置/评分功能', () => {
test.beforeEach(async ({ page }) => {
await page.goto(`${BASE_URL}/login`);
await page.fill('input[name="username"]', 'testuser');
await page.fill('input[name="password"]', 'TestPass123');
await page.click('button[type="submit"]');
await page.waitForURL('**/dashboard/**');
});
test('菜单可见性: OKR', async ({ page }) => {
await page.goto(`${BASE_URL}/`);
await page.waitForSelector('.ant-menu');
await expect(page.locator('.ant-menu')).toContainText('OKR');
await page.screenshot({ path: 'e2e-results/pdv-menu-okr.png' });
});
test('页面可达: /okr/my', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/okr/my`);
expect(response?.status()).toBeLessThan(400);
await expect(page).not.toHaveTitle(/error|500|404/i);
await expect(page.locator('h1, .page-title')).toBeVisible({ timeout: 10000 });
await page.screenshot({ path: 'e2e-results/pdv-page-okr-my.png', fullPage: true });
});
test('页面可达: /okr/team', async ({ page }) => {
const response = await page.goto(`${BASE_URL}/okr/team`);
expect(response?.status()).toBeLessThan(400);
await expect(page).not.toHaveTitle(/error|500|404/i);
await expect(page.locator('h1, .page-title')).toBeVisible({ timeout: 10000 });
await page.screenshot({ path: 'e2e-results/pdv-page-okr-team.png', fullPage: true });
});
test('API 连通: OKR objectives', async ({ request }) => {
const resp = await request.get(`${BASE_URL}/api/v1/okr/objectives`);
expect(resp.status()).toBeLessThan(500);
});
});
```
@@ -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"
}
@@ -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"
}
@@ -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
@@ -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.1",
"author": {
"name": "qiudl"
},
"install_name": "frontend-design",
"install_type": "skill",
"dir_category": "dev"
}
@@ -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 配置完整
- [ ] 包含组件文档描述
- [ ] 交互状态可测试
- [ ] 响应式展示
### 视觉质量检查
- [ ] 字体选择有特色
- [ ] 配色方案协调
- [ ] 动画流畅自然
- [ ] 间距一致
- [ ] 暗色主题支持
@@ -0,0 +1,11 @@
{
"name": "karpathy-guidelines",
"description": "Karpathy 四原则编码行为守则(Think Before Coding / Simplicity First / Surgical Changes / Goal-Driven Execution)。已深度融合到 req 技能工作流各阶段,可独立激活用于任意编码场景。",
"version": "1.0.1",
"author": {
"name": "qiudl"
},
"install_name": "karpathy-guidelines",
"install_type": "skill",
"dir_category": "dev"
}
@@ -0,0 +1,97 @@
---
name: karpathy-guidelines
description: Karpathy 四原则编码行为守则。减少 LLM 常见编码错误:过度实现、静默假设、顺手重构、无验证标准。在任意编码场景激活。
---
# Karpathy Guidelines Skill
> 来源:[andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills)
> 在本项目中已深度融合到 req 技能工作流各阶段。
## 四原则
### 1. Think Before Coding(编前推理)
> "Don't assume. Don't hide confusion. Surface tradeoffs."
**在写第一行代码前:**
- 显式列出本次实现的假设(数据格式、边界条件、依赖接口)
- 如存在多种解读,列出所有方案(附估算),不要静默选择
- 如有更简单的实现方式,说出来
- 遇到不清晰的地方,停下来,指出混乱点,提问
**在 req 工作流中:** → 已嵌入 `req-prd` 的「Phase 0 假设倾倒协议」
---
### 2. Simplicity First(简单优先)
> "Minimum code that solves the problem. Nothing speculative."
**禁止:**
- 添加未被需求要求的功能
- 为单次使用的代码添加抽象
- 添加未被请求的"灵活性"或"可配置性"
- 为不可能发生的场景写错误处理
- 写了 200 行但 50 行就够的代码 → 重写
**自检:** "一个高级工程师看这段代码会觉得过度设计吗?" 如果是 → 简化
**在 req 工作流中:** → 已嵌入 `req-design` 过度设计检查 + `dev-review` 第六视角
---
### 3. Surgical Changes(手术式修改)
> "Touch only what you must. Clean up only your own mess."
**修改现有代码时:**
- 不要"顺手改进"相邻代码、注释或格式
- 不要重构没有损坏的代码
- 匹配现有代码风格,即使你会做不同的选择
- 发现不相关的死代码 → 提及但不删除
**你的变更造成的孤儿:**
- 删除你的变更导致的无用 import/变量/函数
- 不要删除已存在的死代码(除非被要求)
**铁律:** diff 中每一行修改都应该可以追溯到用户的需求
**在 req 工作流中:** → 已嵌入 `dev-review` 第六视角 + `check-surgical.sh` Harness 脚本
---
### 4. Goal-Driven Execution(目标驱动执行)
> "Define success criteria. Loop until verified."
**将请求转化为可验证目标:**
- "加验证" → "为无效输入写测试,然后让它通过"
- "修 bug" → "写一个复现 bug 的测试,然后让它通过"
- "重构 X" → "确保测试在重构前后都通过"
**多步任务需要说明计划:**
```
1. [步骤] → 验证: [检查项]
2. [步骤] → 验证: [检查项]
3. [步骤] → 验证: [检查项]
```
**在 req 工作流中:** → 已嵌入 `dev-coding` 的「Step 0 验证优先」+ VP 三件套协议
---
## 与 req 工作流的映射
| 原则 | 生效阶段 | 落地机制 |
|------|---------|---------|
| Think Before Coding | req-prd 启动前 | Phase 0 假设倾倒协议 |
| Simplicity First | req-design + dev-review | 过度设计检查 + 第六视角 |
| Surgical Changes | dev-review + CI | 第六视角 + check-surgical.sh |
| Goal-Driven Execution | dev-coding | Step 0 验证优先 + VP 三件套 |
## 反模式速查
| 场景 | ❌ LLM 常犯 | ✅ 正确做法 |
|------|-----------|-----------|
| "做个导出功能" | 静默假设格式/字段,直接实现 | 列出3种解读,等用户确认 |
| "让搜索更快" | 同时加缓存+索引+async | 列出3种"更快"含义,确认再做 |
| "加折扣计算" | Strategy+Abstract+Enum50行 | 一个函数,3行 |
| "修空邮件bug" | 顺手加用户名校验+类型注解 | 只改空邮件的那2行 |
| "修认证bug" | 直接修改,无验证标准 | 先写复现测试,修复后验证通过 |
@@ -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.1.0",
"author": {
"name": "qiudl"
},
"install_name": "review-checklist",
"install_type": "skill",
"dir_category": "dev"
}
@@ -0,0 +1,50 @@
---
name: review-checklist
description: 项目级代码评审检查清单。按项目积累特定检查项,挂载在 dev-review 下自动加载。每次 CR 时触发。
---
# 代码评审检查清单插件 (review-checklist)
## 概述
本插件为 `dev-review` 提供**项目特定的检查清单**,补充五视角扫描法之外的项目级经验。
检查清单按项目独立维护,每个项目文件记录该项目踩过的坑和必查项。
## 使用方式
1. `dev-review` 执行五视角扫描时,先读取 `references/general.md`
2. 如果仓库是 AI-Proj,读取 `references/ai-proj.md`;如果是 Coolbuy PaaS,读取 `references/coolbuy-paas.md`
3. 扫描完成后,逐条检查适用的清单项
4. 检查结果附加到 CR 报告的「项目检查清单」章节
## 检查清单文件
```
review-checklist/
├── SKILL.md # 本文件
└── references/
├── ai-proj.md # AI-Proj 项目清单
├── coolbuy-paas.md # 酷采3.0 项目清单
└── general.md # 通用清单(所有项目适用)
```
## 如何添加检查项
当 CR 中发现了一个**项目特有**的问题模式,且未来可能复发时:
1. 打开 `references/` 中对应项目的检查清单文件
2. 添加条目,格式:`- [ ] {检查项} — 教训:{来源}`
3. 标注严重度和适用范围
**不要添加**五视角扫描已覆盖的通用安全/并发/边界问题。
## CR 报告附加章节
```markdown
### 项目检查清单({项目名}
| # | 检查项 | 结果 | 说明 |
|---|--------|------|------|
| 1 | {检查项} | ✅/❌/N/A | {说明} |
```
@@ -0,0 +1,50 @@
# 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 导致登录失败
### 租户隔离(多企业安全,源自 REQ-20260711-0004
- [ ] 隔离/权限类修复是否枚举了威胁模型的**所有读取面**?— list 枚举 + 单条直读 + 按 ID/pattern 查 + count + 关联子查询。教训:P1 只修 list 面漏了 find_task/get-by-id 直读面,直读即绕过枚举防护,audit 才逮到高危残留
- [ ] 同一威胁在**镜像面**是否一并处理?— 一个对象类型(task)漏,同类(project/document/manual/history)大概率同漏
- [ ] MCP 裸 SQL(不走仓储层)是否应用 `ResolveTenantScope` / scope 片段?— SSE 面 list_tasks/list_projects 曾裸 SQL 无企业过滤
- [ ] context 注入的是**类型化 key**`EnterpriseIDContextKey{}`)而非字符串 key?— 字符串 key 与 `ResolveTenantScope` 读的类型化 key 不通,静默失效
- [ ] scope 解析不出 / 依赖为 nil 时是否 **fail-closed**(空哨兵拒绝)而非跳过(退化全量)?
- [ ] MCP endpoint 参数是否 snake_case + camelCase 双绑?— CLI 发 snake、bridge 发 camelgin 静默忽略不匹配参数(REQ-20260711-0003
### 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` 等凭据文件是否被意外加入 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 的软删除标记(is_delete → deleted_at)?
## 前端(Vue 3 + Ant Design Vue
### i18n
- [ ] 新增文案是否使用 `$t()` 国际化?— 不允许硬编码中文
- [ ] i18n key 是否在 zh-CN 和 en-US 都有定义?
### 权限
- [ ] 按钮/菜单是否有权限控制(v-permission 指令)?
@@ -0,0 +1,46 @@
# 通用代码评审检查清单
每次代码审查都应加载本清单。
适用于所有项目,补充六视角扫描法(五传统视角 + Karpathy Scope 视角)。
## Karpathy 反模式速查(Scope 审计者视角辅助)
基于 [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills) EXAMPLES.md 提炼。
### ❌ 反模式 → ✅ 正确做法
| 场景 | 反模式(LLM 常犯) | 正确做法 |
|------|-----------------|---------|
| "做个导出功能" | 静默假设文件格式/字段/分页,直接实现 | 列出3种解读(API/文件/任务队列),问用户选哪种 |
| "让搜索更快" | 同时加缓存+索引+async200行 | 列出3种"更快"含义+估算,等确认再做 |
| "加个折扣计算" | Strategy+Abstract+Enum+DataClass50行 | `def calc_discount(amount, pct): return amount * pct / 100` |
| "修保存偏好的bug" | 顺手加 merge/validate/notify/cache | 只改最小范围,加注释"其他特性按需再加" |
| "修空邮件校验bug" | 顺手加用户名校验+类型注解+docstring | 只改空邮件的那2行 |
| "加日志到上传函数" | 改引号风格+加类型注解+重构return逻辑 | 只加日志,保持原有代码风格 |
| "修认证bug" | "我会检查代码并做改进"(无标准) | 先写测试复现bug,再实现修复,再跑测试 |
| "加限流" | 一次提交Redis+多策略+配置系统+监控 | 分4步,每步独立可验证可部署 |
### 触发关键词(出现时加强 Scope 审计)
`export/导出` `faster/更快` `manage/管理` `notify/通知` `fix/修复` `improve/改进` `refactor/重构` `add/添加`
## API 设计
- [ ] RESTful 命名是否规范?(复数名词、无动词)
- [ ] 分页参数是否有默认值和上限?
- [ ] 响应格式是否统一?(code/message/data
## 错误处理
- [ ] 错误是否被正确传播?(不要吞掉错误)
- [ ] 用户可见的错误消息是否友好?(不暴露技术细节)
- [ ] 是否有 panic recover 兜底?
## 性能
- [ ] 列表查询是否有分页?(不允许无限制查询)
- [ ] N+1 查询问题?(循环内查数据库)
- [ ] 是否有不必要的全表扫描?(缺少索引)
## 可维护性
- [ ] 魔法数字是否提取为常量?
- [ ] 复杂业务逻辑是否有注释说明?
- [ ] 函数是否过长?(超过 100 行考虑拆分)
@@ -4,5 +4,8 @@
"version": "1.0.0",
"author": {
"name": "qiudl"
}
},
"install_name": "data-excel",
"install_type": "skill",
"dir_category": "integration"
}
@@ -1,14 +1,11 @@
{
"name": "doubao-voice-plugin",
"description": "Doubao (豆包) Voice API integration for TTS and ASR",
"version": "1.0.0",
"version": "1.0.1",
"author": {
"name": "qiudl"
},
"skills": [
{
"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"
}
+60 -150
View File
@@ -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"
```
---
## 🔐 保存门禁:敏感信息脱敏
## 🔐 保存禁:敏感息脱敏
**保存任何内容到思源笔记前,必须先完成脱敏处理。**
@@ -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"
}
@@ -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
View 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'"
```
**降级 1CI 日志**
```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'
```
**降级 2N/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`
- 任务标题: `【审计】部署后审计: {需求标题}`
- 报告附加到任务文档
@@ -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"
}

Some files were not shown because too many files have changed in this diff Show More