Compare commits
44
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
237631147a | ||
|
|
7d47762303 | ||
|
|
a7a753b833 | ||
|
|
aecd2ce521 | ||
|
|
41a1032ccf | ||
|
|
d5bdfcfb7c | ||
|
|
d43b414fc9 | ||
|
|
dde146995e | ||
|
|
d5550d62c5 | ||
|
|
c74fe71f0a | ||
|
|
2e0f7fe68c | ||
|
|
3ffee89840 | ||
|
|
998b22e905 | ||
|
|
826bba8485 | ||
|
|
f5c5bd3f40 | ||
|
|
4e6ae9036a | ||
|
|
3b38deb078 | ||
|
|
16e58047a0 | ||
|
|
dec25562a4 | ||
|
|
d630b374a3 | ||
|
|
8fc1cd05b7 | ||
|
|
b859a84455 | ||
|
|
9a1400758e | ||
|
|
a58dd1aff3 | ||
|
|
bb5e6be73e | ||
|
|
ef0e9ca1f0 | ||
|
|
ad4e2b16a8 | ||
|
|
ba107a54a8 | ||
|
|
4ea6920a13 | ||
|
|
039a749e3e | ||
|
|
daca19a262 | ||
|
|
ddb5d5cb06 | ||
|
|
da57cccf96 | ||
|
|
7eed2b8f10 | ||
|
|
5a45916b2c | ||
|
|
bcea648e3c | ||
|
|
e3513f137b | ||
|
|
011916ceb9 | ||
|
|
48b792fb5a | ||
|
|
2ab0a61eb9 | ||
|
|
de25f096e7 | ||
|
|
84d4e35a42 | ||
|
|
e5805cbb51 | ||
|
|
79c4e55719 |
+207
-69
@@ -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,56 @@
|
||||
],
|
||||
"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",
|
||||
@@ -59,6 +133,18 @@
|
||||
],
|
||||
"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",
|
||||
@@ -241,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",
|
||||
@@ -258,7 +393,7 @@
|
||||
"name": "review-checklist-plugin",
|
||||
"source": "./skills-dev/review-checklist-plugin",
|
||||
"description": "项目级代码评审检查清单。按项目积累的特定检查项,挂载在 dev-review 下自动加载。",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"category": "utility",
|
||||
"keywords": [
|
||||
"utility",
|
||||
@@ -266,6 +401,45 @@
|
||||
],
|
||||
"strict": false
|
||||
},
|
||||
{
|
||||
"name": "slark-cicd-plugin",
|
||||
"source": "./skills-dev/slark-cicd-plugin",
|
||||
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||
"version": "1.1.0",
|
||||
"category": "devops",
|
||||
"keywords": [
|
||||
"devops",
|
||||
"deployment",
|
||||
"operations"
|
||||
],
|
||||
"strict": false
|
||||
},
|
||||
{
|
||||
"name": "slark-dsh-cicd-plugin",
|
||||
"source": "./skills-dev/slark-dsh-cicd-plugin",
|
||||
"description": "DSH 插件包与 Slark 宿主补丁的发布实战技能:npm run pack 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、draft Release、发布后 SHA 核对、asar 完整性与 ai-proj 状态对齐。",
|
||||
"version": "1.0.0",
|
||||
"category": "devops",
|
||||
"keywords": [
|
||||
"devops",
|
||||
"deployment",
|
||||
"operations"
|
||||
],
|
||||
"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",
|
||||
@@ -305,11 +479,24 @@
|
||||
],
|
||||
"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",
|
||||
"description": "需求工作流管理入口。用于 /req 命令使用、需求生命周期管理、需求撰写(含草稿门禁)。",
|
||||
"version": "2.0.0",
|
||||
"version": "2.1.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"project-management",
|
||||
@@ -321,8 +508,8 @@
|
||||
{
|
||||
"name": "req-prd-plugin",
|
||||
"source": "./skills-req/req-prd-plugin",
|
||||
"description": "产品需求设计技能。PRD 文档编写、需求分析、用户故事、对比式分析。纯产品视角,不含技术实现。",
|
||||
"version": "2.0.0",
|
||||
"description": "产品需求设计技能。覆盖问答、PRD、缺陷与 OSS 原型闭环,文档双写本地和 ai-proj。",
|
||||
"version": "2.3.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"project-management",
|
||||
@@ -334,8 +521,8 @@
|
||||
{
|
||||
"name": "req-prototype-plugin",
|
||||
"source": "./skills-req/req-prototype-plugin",
|
||||
"description": "Stitch 原型生成与迭代。基于 PRD 文档自动生成 UI 原型。",
|
||||
"version": "1.0.0",
|
||||
"description": "原型生成与关联。支持 HTML 本地留源、OSS 正式交付、Requirement/iframe 验证及 Stitch AI。",
|
||||
"version": "2.2.0",
|
||||
"category": "productivity",
|
||||
"keywords": [
|
||||
"project-management",
|
||||
@@ -357,6 +544,19 @@
|
||||
],
|
||||
"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",
|
||||
@@ -412,7 +612,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",
|
||||
@@ -548,68 +748,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
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
# 已拒绝:为某一种 harness 单开分支
|
||||
|
||||
- 状态:拒绝(长期有效)
|
||||
- 日期:2026-10-31
|
||||
|
||||
## 想法
|
||||
|
||||
为 Codex / DSH / Claude 某一个客户端的特例,单独复制一份技能或加一个 harness 分支。
|
||||
|
||||
## 为什么拒绝
|
||||
|
||||
1. 复制出来的两份技能会立刻开始漂移,评测也要做两遍;
|
||||
2. 客户端差异应交给安装器(目录、调用模式、字段映射)处理,不该写进技能内容;
|
||||
3. 特例需求通常是个人的工作流调整,属于 `AGENTS.md` / `CLAUDE.md` 或 fork 的范围。
|
||||
|
||||
## 允许的替代路径
|
||||
|
||||
- 客户端差异 → 安装器加 target / 字段映射;
|
||||
- 个人流程 → 个人目录或 fork。
|
||||
@@ -0,0 +1,20 @@
|
||||
# 已拒绝:新增「随手想到」的技能
|
||||
|
||||
- 状态:拒绝(长期有效)
|
||||
- 日期:2026-10-31
|
||||
|
||||
## 想法
|
||||
|
||||
「我觉得应该有个技能做 X」——从想象出发新增技能,而不是从真实失败出发。
|
||||
|
||||
## 为什么拒绝
|
||||
|
||||
1. 技能目录是每次会话启动就要付的上下文预算,加一个就多一份;
|
||||
2. 没有真实失败做依据的技能,写出来是泛泛而谈,实际触发率极低;
|
||||
3. 技能之间会互相抢触发(description 相似),新增一个会稀释已有技能的准确率。
|
||||
|
||||
## 允许的替代路径
|
||||
|
||||
- 先用现有技能做一遍真实任务,记录卡点;
|
||||
- 确实组合不出来,再按 `SCOPE.md` 门槛提,附真实失败证据;
|
||||
- 只是个人偏好 → 放 `skills-personal/`,不进团队库。
|
||||
@@ -0,0 +1,19 @@
|
||||
# 已拒绝:把个人工作流调优塞进团队技能
|
||||
|
||||
- 状态:拒绝(长期有效)
|
||||
- 日期:2026-10-31
|
||||
|
||||
## 想法
|
||||
|
||||
把某个人的偏好(提交信息格式、分支命名、审查语气、工具习惯)直接写进团队技能。
|
||||
|
||||
## 为什么拒绝
|
||||
|
||||
1. 团队技能要能被所有人用,个人偏好会变成隐式规训;
|
||||
2. 一旦写进技能,改起来要走 PR,个人改不动;
|
||||
3. 这类偏好本来就该放个人 `AGENTS.md` / `CLAUDE.md`。
|
||||
|
||||
## 允许的替代路径
|
||||
|
||||
- 个人偏好 → 个人配置文件或 `skills-personal/`;
|
||||
- 如果是团队共识(不是个人偏好),走 `SCOPE.md` 门槛并附真实失败。
|
||||
@@ -0,0 +1,32 @@
|
||||
# Changelog
|
||||
|
||||
## Unreleased — 2026-10-31
|
||||
|
||||
### 新增
|
||||
|
||||
- 技能治理:`SCOPE.md` 收录门槛 + `.out-of-scope/` 拒绝档案。
|
||||
- 生命周期分桶(`promoted` / `beta` / `deprecated`)与调用模式(`model-invoked` / `user-invoked`)。
|
||||
- `scripts/validate-skills.py`:规范/大小/引用/调用模式/文档校验。
|
||||
- `scripts/generate-catalog.py`:生成可审阅的 `catalog.json`。
|
||||
- `scripts/scan-skill.sh` + `scripts/scan-allowlist.txt`:入库前安全扫描。
|
||||
- 新技能(beta):`skill-creator`、`ai-proj-helper-entry`、`verification-before-completion`。
|
||||
- `install-skills.sh`:`--agent dsh`、`--link`、`--audit`、`--include-beta`、`--no-validate`。
|
||||
|
||||
### 变更
|
||||
|
||||
- 18 个命令型技能转 `user-invoked`(biz/integration/dev-android|ios|pda/publish/发布链),目录预算减少 18 项。
|
||||
|
||||
- `dev-review`:增加「接收与响应评审意见」。
|
||||
- `req-design`:增加「任务依赖边(blocking edges)」。
|
||||
- `agent-swarm`:增加「并行编排流程」。
|
||||
- `dev-test`:增加「TDD 循环」。
|
||||
- `generate-marketplace.py`:只发布 `promoted`,beta/deprecated 不随 marketplace 分发。
|
||||
|
||||
- 按 SCOPE 降级两个无增量技能:`verification-before-completion`、`ai-proj-helper-entry`(规则已在用户级 AGENTS.md),入口路由内容转为 `docs/skill-routing.md`。
|
||||
|
||||
### 清理(2026-10-31)
|
||||
|
||||
- 11 个超过 20KB 的 `SKILL.md` 拆分:大章节移入 `references/details.md`,正文全部回到 20KB 预算内。
|
||||
- 7 处非标准 frontmatter 字段(`arguments`、`user_invocable`)移入 `metadata`。
|
||||
- 修复 `req-dev` 的 4 条相对链接;校验器改为跳过行内代码,消除 `` 误报。
|
||||
- `validate-skills.py`:23 个警告 → **0 个警告**。
|
||||
@@ -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,11 @@ skills:
|
||||
|
||||
- `skills-personal/` 不被 Git 跟踪,用于存放个人配置和工具
|
||||
- 其余 `skills-*` 目录均由 Git 版本控制
|
||||
- 所有目录都会被 `generate-marketplace.py` 自动扫描并加入 marketplace.json
|
||||
- 只有受 Git 跟踪的分类目录会被 `generate-marketplace.py` 扫描并加入公开 marketplace;`skills-personal/` 始终排除
|
||||
|
||||
## 技能治理规则
|
||||
|
||||
- 公开 marketplace 只发布 `status=promoted`;`beta`/`deprecated` 不进 marketplace(`generate-marketplace.py` 已按 status 过滤)。
|
||||
- 每个 `plugin.json` 建议带 `status`,每个 `SKILL.md` 在 `metadata.invocation` 标 `model-invoked` / `user-invoked`。
|
||||
- 任何技能增删改后,回改 `catalog.json` 与入口技能 `ai-proj-helper-entry`。
|
||||
- 提交前依次跑:`python3 scripts/validate-skills.py`、`./scripts/scan-skill.sh`、`python3 scripts/generate-catalog.py`。
|
||||
|
||||
@@ -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: 如何禁用不需要的技能?**
|
||||
|
||||
@@ -192,3 +195,18 @@ A: SSE 模式直连远程服务器,零依赖开箱即用(推荐)。stdio
|
||||
- **管理员**:Donglin Lai (qiudl)
|
||||
- **邮箱**:qiudl@zhiyuncai.com
|
||||
- **仓库**:https://gitea.pipexerp.com/pipexerp/ai-proj-helper
|
||||
|
||||
## 技能治理(2026-10-31 起)
|
||||
|
||||
- **真相源**:本仓库。`~/.agents/skills`、`~/.claude/skills`、`$DSH_HOME/skills` 都只是安装产物。
|
||||
- **收录门槛**见 [SCOPE.md](./SCOPE.md):只收「真实失败 + 组合不出来」,被拒的想法记在 `.out-of-scope/`。
|
||||
- **生命周期**:`promoted`(默认安装)/ `beta`(需 `--include-beta`)/ `deprecated`(不安装);新技能一律先进 beta。
|
||||
- **调用模式**:`model-invoked`(占模型目录预算)vs `user-invoked`(只由人触发,不占目录预算)。
|
||||
- **安装**:
|
||||
- Codex:`./install-skills.sh --agent codex`
|
||||
- DSH:`DSH_HOME=<path> ./install-skills.sh --agent dsh --link`
|
||||
- Claude(兼容):`./install-skills.sh --agent claude`
|
||||
- 看漂移:`./install-skills.sh --audit`
|
||||
- **校验 / 安全 / 清单**:`python3 scripts/validate-skills.py`、`./scripts/scan-skill.sh`、`python3 scripts/generate-catalog.py`
|
||||
- **建技能**:走 `skill-creator` 技能(收录门槛、分桶、调用模式、evals、四段式文档、路由回改),不要手写。
|
||||
- **路由表**:`docs/skill-routing.md`(什么任务用哪个技能,人读;由原 `ai-proj-helper-entry` 技能降级而来)。
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
# Scope — ai-proj-helper 技能的收录门槛
|
||||
|
||||
本仓库是团队每天在用的技能集,**不是技能收藏夹**。技能目录会在每次会话启动时占用模型的上下文预算,所以「多加一个」永远是有代价的。
|
||||
|
||||
## 门槛
|
||||
|
||||
新技能或改动**默认拒绝**,只有同时满足两条才进入评审:
|
||||
|
||||
1. **真实失败**:能说清在哪个真实任务/会话里出了问题——跑了什么、期望什么、实际什么。假设性的「这样会更好」不算。
|
||||
2. **组合不出来**:用现有技能组合做不到。能组合的,改现有技能,不新增。
|
||||
|
||||
规模不重要:一个词的修改和一次重写按同一标准审。
|
||||
|
||||
## 分桶
|
||||
|
||||
| 桶 | 含义 | 安装 | 进 README / plugin.json |
|
||||
| --- | --- | --- | --- |
|
||||
| `promoted` | 已验证、日常在用 | 默认 | 必须 |
|
||||
| `beta` | 公开试跑、收反馈 | `--include-beta` | 不进默认清单 |
|
||||
| `deprecated` | 不再使用 | 不安装 | 只留文档,标弃用 |
|
||||
|
||||
新技能一律先进 `beta`;promoted 需要真实使用记录 + evals 通过。
|
||||
|
||||
## 调用模式
|
||||
|
||||
- `model-invoked`(默认):进模型目录,agent 自己判断何时用。
|
||||
- `user-invoked`:只由人下命令触发,不占目录预算;必须写 `disable-model-invocation: true` + `metadata.invocation: user-invoked`。
|
||||
|
||||
判定:这件事是不是「人下命令才开始」?是 → user-invoked。
|
||||
|
||||
## 已经拒绝的想法
|
||||
|
||||
每个被拒概念一个文件,写在 [`.out-of-scope/`](./.out-of-scope/) 下,先读再提。当前已有:
|
||||
|
||||
- [`new-skills.md`](./.out-of-scope/new-skills.md)
|
||||
- [`harness-specific-branches.md`](./.out-of-scope/harness-specific-branches.md)
|
||||
- [`personal-workflow-tweaks.md`](./.out-of-scope/personal-workflow-tweaks.md)
|
||||
|
||||
## 提交流程
|
||||
|
||||
1. 先过 [`skill-creator`](./skills-core/skill-creator-plugin/skills/SKILL.md) 的收录门槛与质量门禁。
|
||||
2. `python3 scripts/validate-skills.py` 通过。
|
||||
3. `./scripts/scan-skill.sh` 无高危项。
|
||||
4. `python3 scripts/generate-catalog.py` 更新 `catalog.json`。
|
||||
5. 版本号 + CHANGELOG 更新,走 PR。
|
||||
@@ -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`
|
||||
|
||||
+71
-199
@@ -1,218 +1,90 @@
|
||||
# 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
|
||||
|
||||
## Sync Workflow
|
||||
|
||||
### 1. Edit Skills Locally
|
||||
|
||||
Work on your skills in `~/.claude/skills/`:
|
||||
```bash
|
||||
code ~/.claude/skills/my-skill/SKILL.md
|
||||
```
|
||||
|
||||
### 2. Run Sync Script
|
||||
Claude Code 需要显式选择目标:
|
||||
|
||||
```bash
|
||||
cd ~/path/to/claude-marketplace
|
||||
./sync-skills.sh
|
||||
./install-skills.sh --agent claude
|
||||
```
|
||||
|
||||
### 3. Review Changes
|
||||
安装器会复制完整技能目录,包括 `SKILL.md`、`references/`、`scripts/` 和 `assets/`。它用内容摘要区分仓库升级和本地修改:
|
||||
|
||||
- 目标未修改时,版本升级会自动安装。
|
||||
- 旧版只安装了 `SKILL.md` 时,会安全补齐仓库中的其他同源文件。
|
||||
- 目标存在本地修改时会跳过;确认覆盖后才使用 `--force`。
|
||||
- `--cleanup` 会删除状态文件记录中已从仓库移除的技能,使用前先运行 `--dry-run --cleanup`。
|
||||
|
||||
按分类安装或查看清单:
|
||||
|
||||
```bash
|
||||
git status
|
||||
git diff
|
||||
./install-skills.sh --list
|
||||
./install-skills.sh --category dev
|
||||
./install-skills.sh --exclude ai-proj-cicd-release
|
||||
```
|
||||
|
||||
### 4. Commit & Push
|
||||
## 将本机技能发布到仓库
|
||||
|
||||
不要批量复制整个 `~/.agents/skills/` 或其他 Agent 的安装目录。系统技能、第三方托管技能、包含机器路径或凭据的技能不应发布。
|
||||
|
||||
1. 选择确实属于本仓库、可供团队复用的技能。
|
||||
2. 在对应 `skills-*/<name>-plugin/` 下放置 `.claude-plugin/plugin.json` 和完整 `skills/` 目录。
|
||||
3. 清除用户名、绝对路径、内网地址、密钥标识和历史凭据;把环境差异改为从仓库配置解析。
|
||||
4. 更新插件版本并运行:
|
||||
|
||||
```bash
|
||||
python3 generate-marketplace.py
|
||||
claude plugin validate .
|
||||
git diff --check
|
||||
```
|
||||
|
||||
5. 审核变更后通过分支和 PR 发布。
|
||||
|
||||
## 本地个人技能
|
||||
|
||||
`skills-personal/` 受 `.gitignore` 保护,仅供当前机器使用。生成器明确排除此目录,避免
|
||||
`marketplace.json` 引用公开克隆中不存在的文件。若个人技能要转为团队技能,应先按上面的发布流程完成脱敏和审核。
|
||||
|
||||
## 常见问题
|
||||
|
||||
**本地修改被跳过怎么办?**
|
||||
|
||||
先比较仓库源和 `~/.agents/skills/<name>/`。保留本地修改时将其整理成插件变更;确认丢弃时再对该次安装使用 `--force`。Claude 目标改查 `~/.claude/skills/`。
|
||||
|
||||
**marketplace 没更新?**
|
||||
|
||||
运行 `python3 generate-marketplace.py`,然后检查 `.claude-plugin/marketplace.json` 是否只包含受 Git 跟踪且真实存在的 source。
|
||||
|
||||
**如何移除技能?**
|
||||
|
||||
删除插件目录、重新生成 marketplace、提交变更。使用者随后执行 `./install-skills.sh --dry-run --cleanup`,确认后再去掉 `--dry-run`。
|
||||
|
||||
## 安装矩阵(2026-10 更新)
|
||||
|
||||
| agent | 安装目录 | 默认模式 | 状态文件 |
|
||||
| --- | --- | --- | --- |
|
||||
| `codex`(默认) | `~/.agents/skills` | copy | `~/.agents/.ai-proj-helper-installed-skills.json` |
|
||||
| `dsh` | `$DSH_HOME/skills` | copy(开发机建议 `--link`) | `$DSH_HOME/.ai-proj-helper-installed-skills.json` |
|
||||
| `claude`(兼容) | `~/.claude/skills` | copy | `~/.claude/.installed-skills.json` |
|
||||
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "Update skill: description of changes"
|
||||
git push
|
||||
# 本机(DSH 开发机,改仓库即时生效)
|
||||
DSH_HOME="<your DSH_HOME>" ./install-skills.sh --agent dsh --link
|
||||
|
||||
# 远程/其他机器
|
||||
./install-skills.sh --agent codex
|
||||
|
||||
# 看谁落后了
|
||||
./install-skills.sh --agent codex --audit
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
|
||||
### Git Conflicts
|
||||
|
||||
```bash
|
||||
# Discard local changes
|
||||
git checkout .claude-plugin/marketplace.json
|
||||
|
||||
# Or merge manually
|
||||
git mergetool
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Descriptive Frontmatter
|
||||
|
||||
Always include in `SKILL.md`:
|
||||
```yaml
|
||||
---
|
||||
name: skill-name
|
||||
description: Clear, concise description of what this skill does
|
||||
---
|
||||
```
|
||||
|
||||
### 2. Version Bumping
|
||||
|
||||
When making significant changes:
|
||||
```bash
|
||||
# Update version in plugin.json
|
||||
{
|
||||
"version": "1.1.0" # was 1.0.0
|
||||
}
|
||||
```
|
||||
|
||||
### 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.
|
||||
`beta` 技能默认不装:需要时加 `--include-beta`。`promoted/beta/deprecated` 见 `SCOPE.md`。
|
||||
|
||||
+685
@@ -0,0 +1,685 @@
|
||||
{
|
||||
"repo": "ai-proj-helper",
|
||||
"generated_at": "2026-10-31T14:21:07Z",
|
||||
"total": 61,
|
||||
"counts": {
|
||||
"promoted/user-invoked": 18,
|
||||
"deprecated/user-invoked": 1,
|
||||
"promoted/model-invoked": 40,
|
||||
"beta/user-invoked": 1,
|
||||
"deprecated/model-invoked": 1
|
||||
},
|
||||
"skills": [
|
||||
{
|
||||
"name": "biz-contract",
|
||||
"category": "biz",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "商务合同撰写。支持多种合同类型:软件订阅合同、软件定制开发合同、物流合同、销售服务合同、贸易合同等。当用户提到合同、协议、签约、合作协议相关任务时自动激活。",
|
||||
"description_len": 78,
|
||||
"path": "skills-biz/biz-contract-plugin/skills/SKILL.md",
|
||||
"digest": "3c17cae1380d82a4"
|
||||
},
|
||||
{
|
||||
"name": "biz-ops",
|
||||
"category": "biz",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "商务运营技能。支持商业计划书(BP)撰写和商务合同起草。当用户提到商业计划书、BP、融资计划、商业模式、合同、协议、签约等相关任务时自动激活。",
|
||||
"description_len": 71,
|
||||
"path": "skills-biz/biz-ops-plugin/skills/SKILL.md",
|
||||
"digest": "aa5433074ba277d6"
|
||||
},
|
||||
{
|
||||
"name": "biz-plan",
|
||||
"category": "biz",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "商业计划书撰写。支持融资BP、战略规划、商业模式设计等。当用户提到商业计划书、BP、融资计划、商业模式、战略规划相关任务时自动激活。",
|
||||
"description_len": 66,
|
||||
"path": "skills-biz/biz-plan-plugin/skills/SKILL.md",
|
||||
"digest": "b6f43a914b2f7b45"
|
||||
},
|
||||
{
|
||||
"name": "finance",
|
||||
"category": "biz",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "财务对账技能。支持银行对账、应收对账、应付对账、内部往来对账等。通过自然语言实现账单导入、差异分析、对账报告生成。当用户提到对账、银行流水、应收应付、账单核对、财务核销相关任务时自动激活。",
|
||||
"description_len": 94,
|
||||
"path": "skills-biz/finance-plugin/skills/SKILL.md",
|
||||
"digest": "0a9209bc4bf4db3a"
|
||||
},
|
||||
{
|
||||
"name": "ai-proj-helper-entry",
|
||||
"category": "core",
|
||||
"version": "0.1.0",
|
||||
"status": "deprecated",
|
||||
"invocation": "user-invoked",
|
||||
"description": "ai-proj-helper 技能库路由:告诉你有哪些技能、什么任务用哪个、以及「REQ released + PDV 通过才算交付」的强制口径。人在开始需求/开发/评审/发布任务前主动调用。",
|
||||
"description_len": 96,
|
||||
"path": "skills-core/ai-proj-helper-entry-plugin/skills/SKILL.md",
|
||||
"digest": "41d39b76d4c71dbf"
|
||||
},
|
||||
{
|
||||
"name": "ai-proj",
|
||||
"category": "core",
|
||||
"version": "2.0.1",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "ai-proj 任务与需求管理。用于创建/查询/更新需求、任务、文档。当用户提到 ai-proj、任务管理、需求管理、REQ-XXX 相关操作时自动激活。",
|
||||
"description_len": 77,
|
||||
"path": "skills-core/ai-proj-plugin/skills/SKILL.md",
|
||||
"digest": "77ce4d2eb10e1d29"
|
||||
},
|
||||
{
|
||||
"name": "pm-ask",
|
||||
"category": "core",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "基于 ai-proj MCP + git log 真实数据的项目问答。禁止编造。当用户说\"/ask\"、\"问一下\"、\"统计一下\"、\"最近多少\"、\"谁做了多少\"、\"本月进度\"等项目相关问题时自动激活。",
|
||||
"description_len": 98,
|
||||
"path": "skills-core/pm-ask-plugin/skills/SKILL.md",
|
||||
"digest": "e5b69a0b56985472"
|
||||
},
|
||||
{
|
||||
"name": "pm-risk",
|
||||
"category": "core",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "三维度项目风险扫描(需求层/代码层/流程层)。当用户说\"/risk\"、\"风险扫描\"、\"有什么风险\"、\"哪些需求停滞了\"、\"分支健康\"时自动激活。",
|
||||
"description_len": 72,
|
||||
"path": "skills-core/pm-risk-plugin/skills/SKILL.md",
|
||||
"digest": "928cfec76018329f"
|
||||
},
|
||||
{
|
||||
"name": "publish",
|
||||
"category": "core",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "发布 ai-proj-helper 技能市场更新并通知飞书群。用户说 /publish 时激活。",
|
||||
"description_len": 48,
|
||||
"path": "skills-core/publish-plugin/skills/SKILL.md",
|
||||
"digest": "48bfbb4149baddcc"
|
||||
},
|
||||
{
|
||||
"name": "skill-creator",
|
||||
"category": "core",
|
||||
"version": "0.1.0",
|
||||
"status": "beta",
|
||||
"invocation": "user-invoked",
|
||||
"description": "团队技能的标准流程:收录门槛、分桶生命周期、调用模式、SKILL.md 规范、evals 全量、四段式文档、路由回改。当要新建技能、大改现有技能、或评审一个技能是否合格时使用。",
|
||||
"description_len": 88,
|
||||
"path": "skills-core/skill-creator-plugin/skills/SKILL.md",
|
||||
"digest": "18f18c43c3fa56a6"
|
||||
},
|
||||
{
|
||||
"name": "agent-browser",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "浏览器自动化技能。用于网页交互、E2E冒烟测试、需求验收验证、前端开发验证、截图对比。基于 Vercel agent-browser CLI。",
|
||||
"description_len": 71,
|
||||
"path": "skills-dev/agent-browser-plugin/skills/SKILL.md",
|
||||
"digest": "f382bffe3d1c84b2"
|
||||
},
|
||||
{
|
||||
"name": "agent-swarm",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Multi-agent orchestration using OpenAI Swarm patterns. Coordinate specialized agents for complex development workflows with handoffs and context sharing.",
|
||||
"description_len": 153,
|
||||
"path": "skills-dev/agent-swarm-plugin/skills/SKILL.md",
|
||||
"digest": "a0d8a329ab9468f5"
|
||||
},
|
||||
{
|
||||
"name": "ai-chat",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "AI Chat 测试与管理。发送消息测试 AI Chat 工具调用链路,管理工具开关和 Provider 配置,支持 local/staging 环境切换。",
|
||||
"description_len": 78,
|
||||
"path": "skills-dev/ai-chat-plugin/skills/SKILL.md",
|
||||
"digest": "81193de50b43d95c"
|
||||
},
|
||||
{
|
||||
"name": "ai-proj-cicd-release",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"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.",
|
||||
"description_len": 410,
|
||||
"path": "skills-dev/ai-proj-cicd-release-plugin/skills/SKILL.md",
|
||||
"digest": "49ef1b6865c2f1b3"
|
||||
},
|
||||
{
|
||||
"name": "ai-proj-macos-release",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"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.",
|
||||
"description_len": 337,
|
||||
"path": "skills-dev/ai-proj-macos-release-plugin/skills/SKILL.md",
|
||||
"digest": "6f2496b941706137"
|
||||
},
|
||||
{
|
||||
"name": "db-migration",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "数据库变更方案插件。Migration 脚本生成、数据迁移策略、回滚方案。挂载在 design 阶段,涉及数据库变更时由 req-design 推荐激活。",
|
||||
"description_len": 77,
|
||||
"path": "skills-dev/db-migration-plugin/skills/SKILL.md",
|
||||
"digest": "f8d6ebbddbbf7a71"
|
||||
},
|
||||
{
|
||||
"name": "defect-analysis",
|
||||
"category": "dev",
|
||||
"version": "1.1.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "系统性设计缺陷分析。对需求方案/代码架构进行多维度检查,发现隐藏的技术风险和设计漏洞。当用户提到缺陷检查、方案审查、设计审计时自动激活。",
|
||||
"description_len": 68,
|
||||
"path": "skills-dev/defect-analysis-plugin/skills/SKILL.md",
|
||||
"digest": "23dfafda92d0c622"
|
||||
},
|
||||
{
|
||||
"name": "deploy-rollback",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "回滚方案插件。部署后发现问题时的回滚策略和执行步骤。挂载在 deploy 阶段,部署出问题时激活。",
|
||||
"description_len": 49,
|
||||
"path": "skills-dev/deploy-rollback-plugin/skills/SKILL.md",
|
||||
"digest": "75ebf66a3f74195a"
|
||||
},
|
||||
{
|
||||
"name": "dev-android",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。当涉及 Android 开发任务时按需加载。",
|
||||
"description_len": 72,
|
||||
"path": "skills-dev/dev-android-plugin/skills/SKILL.md",
|
||||
"digest": "fb058d1059b56cdd"
|
||||
},
|
||||
{
|
||||
"name": "dev-arch",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "软件架构设计技能。用于系统设计、技术选型、架构评审、设计文档编写。当用户提到架构设计、系统设计、技术方案、API 设计相关任务时自动激活。",
|
||||
"description_len": 69,
|
||||
"path": "skills-dev/dev-arch-plugin/skills/SKILL.md",
|
||||
"digest": "c9a419c83a307a11"
|
||||
},
|
||||
{
|
||||
"name": "dev-cicd",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "CI/CD 流水线设计、优化与排查。适配 Gitea Actions + Go/Swift/Next.js/Docker 栈。当用户提到 CI、CD、流水线、pipeline、workflow、构建失败、runner 相关任务时自动激活。",
|
||||
"description_len": 119,
|
||||
"path": "skills-dev/dev-cicd-plugin/skills/SKILL.md",
|
||||
"digest": "a763f0f140516411"
|
||||
},
|
||||
{
|
||||
"name": "dev-coding",
|
||||
"category": "dev",
|
||||
"version": "2.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "软件编码开发技能。用于代码编写、功能实现、重构优化。集成 ai-proj CLI 进行任务管理和进度跟踪。核心支持 Go 后端 + Vue/React 前端开发。",
|
||||
"description_len": 81,
|
||||
"path": "skills-dev/dev-coding-plugin/skills/SKILL.md",
|
||||
"digest": "1b754e668965132f"
|
||||
},
|
||||
{
|
||||
"name": "dev-commit",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "智能 git commit — 分支保护 + 自动建功能分支 + Conventional Commits 生成。当用户说\"提交代码\"、\"commit\"、\"/commit\"、\"保存修改\"时自动激活。",
|
||||
"description_len": 99,
|
||||
"path": "skills-dev/dev-commit-plugin/skills/SKILL.md",
|
||||
"digest": "1560658c4a30df45"
|
||||
},
|
||||
{
|
||||
"name": "dev-deploy",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "应用部署技能。支持 iOS TestFlight、Docker 容器等多平台部署。当用户提到部署、发布、TestFlight、上架、build、archive 相关任务时自动激活。",
|
||||
"description_len": 90,
|
||||
"path": "skills-dev/dev-deploy-plugin/skills/SKILL.md",
|
||||
"digest": "fd15a9e9135bcc49"
|
||||
},
|
||||
{
|
||||
"name": "dev-integration",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "前后端联调技能。API 契约验证、接口对接、联调报告生成。对应 req 流程 integration 阶段。纯后端需求自动跳过。",
|
||||
"description_len": 64,
|
||||
"path": "skills-dev/dev-integration-plugin/skills/SKILL.md",
|
||||
"digest": "eff6be8eb57b7fef"
|
||||
},
|
||||
{
|
||||
"name": "dev-ios",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "iOS 开发插件。Swift/SwiftUI + MVVM 架构,TestFlight 部署。当涉及 iOS 开发任务时按需加载。",
|
||||
"description_len": 65,
|
||||
"path": "skills-dev/dev-ios-plugin/skills/SKILL.md",
|
||||
"digest": "44499e31f1abd5e2"
|
||||
},
|
||||
{
|
||||
"name": "dev-mcp",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "MCP Bridge 开发插件。TypeScript MCP 服务开发,Token 管理,HTTP 客户端模式。当涉及 mcp-task-bridge 开发时按需加载。",
|
||||
"description_len": 84,
|
||||
"path": "skills-dev/dev-mcp-plugin/skills/SKILL.md",
|
||||
"digest": "83d5912212fcfa4a"
|
||||
},
|
||||
{
|
||||
"name": "dev-pda",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先。当涉及 PDA/手持终端开发时按需加载。",
|
||||
"description_len": 57,
|
||||
"path": "skills-dev/dev-pda-plugin/skills/SKILL.md",
|
||||
"digest": "2524f2ecd4f2a834"
|
||||
},
|
||||
{
|
||||
"name": "dev-review",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "代码评审技能。六视角对抗性扫描法(含 Karpathy Scope 审计),用于 PR 代码审查、安全评审、质量检查。当执行 /req cr 或独立 PR review 时自动激活。",
|
||||
"description_len": 91,
|
||||
"path": "skills-dev/dev-review-plugin/skills/SKILL.md",
|
||||
"digest": "a8a1ec79b85e3227"
|
||||
},
|
||||
{
|
||||
"name": "dev-scaffold",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "模块脚手架插件。新建模块时自动生成分层代码骨架。挂载在 dev 阶段,新建模块时激活。",
|
||||
"description_len": 43,
|
||||
"path": "skills-dev/dev-scaffold-plugin/skills/SKILL.md",
|
||||
"digest": "4044787b1e468302"
|
||||
},
|
||||
{
|
||||
"name": "dev-test",
|
||||
"category": "dev",
|
||||
"version": "2.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "软件测试技能。用于单元测试、集成测试、E2E测试、测试用例设计。支持 Go、Vue、React、iOS、Android 等多平台测试。",
|
||||
"description_len": 67,
|
||||
"path": "skills-dev/dev-test-plugin/skills/dev-test/SKILL.md",
|
||||
"digest": "a03536a478eabe6c"
|
||||
},
|
||||
{
|
||||
"name": "executing-plans",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Use when you have a written implementation plan to execute in a separate session with review checkpoints",
|
||||
"description_len": 104,
|
||||
"path": "skills-dev/executing-plans-plugin/skills/SKILL.md",
|
||||
"digest": "8991a50091af83a4"
|
||||
},
|
||||
{
|
||||
"name": "finishing-a-development-branch",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Use when implementation is complete and all tests pass - verifies and creates PR",
|
||||
"description_len": 80,
|
||||
"path": "skills-dev/finishing-branch-plugin/skills/SKILL.md",
|
||||
"digest": "1102047ccc060e60"
|
||||
},
|
||||
{
|
||||
"name": "frontend-design",
|
||||
"category": "dev",
|
||||
"version": "1.0.1",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"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.",
|
||||
"description_len": 234,
|
||||
"path": "skills-dev/frontend-design-plugin/skills/SKILL.md",
|
||||
"digest": "84e9c104be7967cc"
|
||||
},
|
||||
{
|
||||
"name": "karpathy-guidelines",
|
||||
"category": "dev",
|
||||
"version": "1.0.1",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Karpathy 四原则编码行为守则。减少 LLM 常见编码错误:过度实现、静默假设、顺手重构、无验证标准。在任意编码场景激活。",
|
||||
"description_len": 64,
|
||||
"path": "skills-dev/karpathy-guidelines-plugin/skills/SKILL.md",
|
||||
"digest": "a68f347031110d6a"
|
||||
},
|
||||
{
|
||||
"name": "pull-request",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Use when starting new development tasks, creating pull requests, reviewing code, or managing PR feedback cycles on Gitea. Triggers on /pr commands or when user mentions PR, pull request, code review, or branch creation.",
|
||||
"description_len": 219,
|
||||
"path": "skills-dev/pull-request-plugin/skills/SKILL.md",
|
||||
"digest": "40dac0d290782bb5"
|
||||
},
|
||||
{
|
||||
"name": "review-checklist",
|
||||
"category": "dev",
|
||||
"version": "1.1.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "项目级代码评审检查清单。按项目积累特定检查项,挂载在 dev-review 下自动加载。每次 CR 时触发。",
|
||||
"description_len": 54,
|
||||
"path": "skills-dev/review-checklist-plugin/skills/SKILL.md",
|
||||
"digest": "5bae13b6a8d92650"
|
||||
},
|
||||
{
|
||||
"name": "slark-cicd",
|
||||
"category": "dev",
|
||||
"version": "1.1.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、生产前强制 staging 验收、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark(qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。",
|
||||
"description_len": 285,
|
||||
"path": "skills-dev/slark-cicd-plugin/skills/SKILL.md",
|
||||
"digest": "2919c19d8a38dd23"
|
||||
},
|
||||
{
|
||||
"name": "slark-dsh-cicd",
|
||||
"category": "dev",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "DSH 插件包与 Slark 宿主补丁的发布 runbook。覆盖 npm run pack 双 tgz 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、改指向后必须重新打包、draft→publish 节奏、GitHub HTTPS/SSH 与 fetch 卡死处置、发布后 SHA 核对、app.asar 完整性约束、ai-proj 发布状态对齐。当用户要发布或升级 DSH 插件(dsh-worktable 等)、发布/更新 Slark 宿主补丁、push 或下载 GitHub 失败、插件「检查更新」看不到新版本、需要 fork 发布、或排查 asar 完整性/签名问题时使用。",
|
||||
"description_len": 306,
|
||||
"path": "skills-dev/slark-dsh-cicd-plugin/skills/SKILL.md",
|
||||
"digest": "44a1322e15d42845"
|
||||
},
|
||||
{
|
||||
"name": "data-excel",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "Excel 数据处理与 BI 集成。通过自然语言操作 Excel 文件的读取、编辑、转换,并支持导入到 BI 系统(Metabase)进行可视化分析。当用户提到 Excel、表格处理、数据导入、BI 分析相关任务时自动激活。",
|
||||
"description_len": 112,
|
||||
"path": "skills-integration/data-excel-plugin/skills/SKILL.md",
|
||||
"digest": "8dc4be8f88cfd866"
|
||||
},
|
||||
{
|
||||
"name": "doubao-voice",
|
||||
"category": "integration",
|
||||
"version": "1.0.1",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "豆包语音API调用。支持语音合成(TTS)和唱歌。当用户提到语音合成、文字转语音、唱歌、豆包语音相关任务时自动激活。",
|
||||
"description_len": 58,
|
||||
"path": "skills-integration/doubao-voice-plugin/skills/SKILL.md",
|
||||
"digest": "7dc73d3b97e61fd2"
|
||||
},
|
||||
{
|
||||
"name": "feishu-bitable",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "飞书多维表格操作。用于记录增删改查、批量操作、筛选排序、数据同步。当需要操作飞书多维表格时使用。",
|
||||
"description_len": 48,
|
||||
"path": "skills-integration/feishu-bitable-plugin/skills/SKILL.md",
|
||||
"digest": "3bf37533007365a3"
|
||||
},
|
||||
{
|
||||
"name": "feishu-docx",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "飞书云文档操作。用于创建、编辑云文档,插入内容块,会议纪要生成。当需要操作飞书云文档时使用。",
|
||||
"description_len": 46,
|
||||
"path": "skills-integration/feishu-docx-plugin/skills/SKILL.md",
|
||||
"digest": "49d097bfa2c283ab"
|
||||
},
|
||||
{
|
||||
"name": "feishu",
|
||||
"category": "integration",
|
||||
"version": "1.1.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "飞书文档与多维表格操作入口。当用户提到飞书、云文档、多维表格、Bitable 相关任务时自动激活。",
|
||||
"description_len": 49,
|
||||
"path": "skills-integration/feishu-plugin/skills/SKILL.md",
|
||||
"digest": "973c6ac22104ee38"
|
||||
},
|
||||
{
|
||||
"name": "siyuan",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "思源笔记 API 集成。通过自然语言创建、编辑、搜索笔记,管理笔记本和文档树。当用户提到思源笔记、SiYuan、笔记管理、知识库相关任务时自动激活。",
|
||||
"description_len": 74,
|
||||
"path": "skills-integration/siyuan-plugin/skills/SKILL.md",
|
||||
"digest": "2736c2b708791763"
|
||||
},
|
||||
{
|
||||
"name": "siyuan-to-feishu",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "将思源笔记导出为 PDF 并发送到飞书群。当用户提到\"发送笔记\"、\"导出PDF发飞书\"、\"/siyuan send\"、\"分享到飞书\"相关任务时自动激活。",
|
||||
"description_len": 76,
|
||||
"path": "skills-integration/siyuan-to-feishu-plugin/skills/SKILL.md",
|
||||
"digest": "998dace910c1eb7a"
|
||||
},
|
||||
{
|
||||
"name": "wecom",
|
||||
"category": "integration",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "user-invoked",
|
||||
"description": "企业微信集成。通过自然语言发送消息、管理群机器人、操作审批流程、管理通讯录。当用户提到企业微信、微信工作、群机器人、企业号、wecom相关任务时自动激活。",
|
||||
"description_len": 77,
|
||||
"path": "skills-integration/wecom-plugin/skills/SKILL.md",
|
||||
"digest": "7084d88ccb87ba9b"
|
||||
},
|
||||
{
|
||||
"name": "req-audit",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "部署后审计。运行时日志检查 + 静态缺陷分析 + 设计偏移检测。可独立调用或由 /req done 自动触发。",
|
||||
"description_len": 55,
|
||||
"path": "skills-req/req-audit-plugin/skills/SKILL.md",
|
||||
"digest": "ff2e6197bbb059c6"
|
||||
},
|
||||
{
|
||||
"name": "req-compare",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "对比式需求分析插件。系统平移、竞品借鉴、版本升级时使用参考对象对比法编写 PRD。挂载在 analysis 阶段,需要对比分析时由 req-prd 推荐激活。",
|
||||
"description_len": 79,
|
||||
"path": "skills-req/req-compare-plugin/skills/SKILL.md",
|
||||
"digest": "3428e3f37ef9e9e8"
|
||||
},
|
||||
{
|
||||
"name": "req-design",
|
||||
"category": "req",
|
||||
"version": "2.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "需求开发设计技能。用于 PRD 到开发设计的转换:API 契约定义、数据模型变更方案、变更文件清单、开发任务拆分、技术风险评估。当用户执行 /req doc 或需要编写开发设计文档时自动激活。",
|
||||
"description_len": 96,
|
||||
"path": "skills-req/req-design-plugin/skills/SKILL.md",
|
||||
"digest": "74848800cab6e269"
|
||||
},
|
||||
{
|
||||
"name": "req-dev",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "需求开发计划编写技能。用于 PRD 到代码转换、代码库分析、开发任务拆分、技术方案设计。当用户执行 /req doc 或需要编写开发计划时自动激活。",
|
||||
"description_len": 74,
|
||||
"path": "skills-req/req-dev-plugin/skills/SKILL.md",
|
||||
"digest": "494a5be5e7a11c0b"
|
||||
},
|
||||
{
|
||||
"name": "req-lookback",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "回归测试。部署后自动验证变更涉及的功能是否正常。可独立调用或由 /req done 自动触发。",
|
||||
"description_len": 47,
|
||||
"path": "skills-req/req-lookback-plugin/skills/SKILL.md",
|
||||
"digest": "cc6bff614a8f7bf4"
|
||||
},
|
||||
{
|
||||
"name": "req",
|
||||
"category": "req",
|
||||
"version": "2.1.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "需求工作流管理。用于 /req 命令使用、需求全生命周期管理(创建→PRD→评审→开发→测试→部署→归档)。当用户提到需求管理、/req 命令、REQ-XXX 相关任务时自动激活。",
|
||||
"description_len": 90,
|
||||
"path": "skills-req/req-plugin/skills/SKILL.md",
|
||||
"digest": "193cf871b73faff1"
|
||||
},
|
||||
{
|
||||
"name": "req-prd",
|
||||
"category": "req",
|
||||
"version": "2.3.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "产品设计与需求管理。用于 PRD、需求分析、用户故事、功能设计和原型规划,并将正式需求文档双写到本地仓库与 ai-proj Task Document。",
|
||||
"description_len": 76,
|
||||
"path": "skills-req/req-prd-plugin/skills/SKILL.md",
|
||||
"digest": "7c4ad2baab6b2118"
|
||||
},
|
||||
{
|
||||
"name": "req-prototype",
|
||||
"category": "req",
|
||||
"version": "2.2.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "原型生成与关联。生成可交互 HTML、上传到 ai-proj OSS 并关联 Requirement/PRD iframe,也支持 Stitch AI 视觉探索。",
|
||||
"description_len": 81,
|
||||
"path": "skills-req/req-prototype-plugin/skills/SKILL.md",
|
||||
"digest": "7e362f4f895fb50a"
|
||||
},
|
||||
{
|
||||
"name": "req-research",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "需求调研插件。代码审计、数据库分析、现有功能调研、技术可行性评估。挂载在 analysis 阶段,需要深度调研时由 req-prd 推荐激活。",
|
||||
"description_len": 71,
|
||||
"path": "skills-req/req-research-plugin/skills/SKILL.md",
|
||||
"digest": "a94924f53bd4c80e"
|
||||
},
|
||||
{
|
||||
"name": "req-retro",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "复盘总结。自动采集数据、计算质量评分、跨需求模式识别、技能自动进化。可独立调用或由 /req done 自动触发。",
|
||||
"description_len": 57,
|
||||
"path": "skills-req/req-retro-plugin/skills/SKILL.md",
|
||||
"digest": "636b7099d85e9549"
|
||||
},
|
||||
{
|
||||
"name": "req-review",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "PRD 评审方法论。用于需求评审、PRD 文档审查、评审意见编写。当执行 /req review 或需要评审 PRD 文档时使用。",
|
||||
"description_len": 65,
|
||||
"path": "skills-req/req-review-plugin/skills/SKILL.md",
|
||||
"digest": "cd1a5181d72f35f4"
|
||||
},
|
||||
{
|
||||
"name": "req-test-gate",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "测试与质量门禁制度。覆盖需求级测试(Gates 1-5)、部署级验证(Deploy Gates)、持续回归(Regression)、Harness Engineering 工程约束方法论(Ratchet、约定建立、门禁层级)。当用户提到质量门禁、架构约束、约定检查、ratchet、harness 相关任务时自动激活。",
|
||||
"description_len": 159,
|
||||
"path": "skills-req/req-test-gate-plugin/skills/SKILL.md",
|
||||
"digest": "ceebcc2516ad40d3"
|
||||
},
|
||||
{
|
||||
"name": "req-workflow",
|
||||
"category": "req",
|
||||
"version": "1.0.0",
|
||||
"status": "promoted",
|
||||
"invocation": "model-invoked",
|
||||
"description": "需求完整工作流。用于从创建到归档的完整流程、Hook 自动同步、测试环境流程。当需要了解需求完整生命周期或同步策略时使用。",
|
||||
"description_len": 61,
|
||||
"path": "skills-req/req-workflow-plugin/skills/SKILL.md",
|
||||
"digest": "87499da3817d162d"
|
||||
},
|
||||
{
|
||||
"name": "verification-before-completion",
|
||||
"category": "req",
|
||||
"version": "0.1.0",
|
||||
"status": "deprecated",
|
||||
"invocation": "model-invoked",
|
||||
"description": "汇报「完成/修好/通过」或关闭任务、提交 PR、宣布 REQ 完成之前,必须先运行验证命令并附证据;没有证据不得声称完成。当准备说完成、关闭任务、或汇报交付前使用。",
|
||||
"description_len": 82,
|
||||
"path": "skills-req/verification-before-completion-plugin/skills/SKILL.md",
|
||||
"digest": "7c23649f4a0a8798"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# 技能路由表(人读)
|
||||
|
||||
> 由原 `ai-proj-helper-entry` 技能降级而来:内容属于「索引」而不是「规则」,做成文档即可,不占技能目录、不用维护技能生命周期。
|
||||
|
||||
## 交付终点(强制)
|
||||
|
||||
开发任务交付终点是:需求按 REQ 流程推进到具备发布条件并完成上线(`delivery_stage=released` + PDV 验收通过)。
|
||||
写代码、过单测、提 PR、合并都只是过程节点;任务 `completed` ≠ 需求完成。
|
||||
|
||||
## 主流程
|
||||
|
||||
讨论(`req`) → PRD(`req-prd`) → 评审(`req-review`) → 设计(`req-design`) → 开发(`dev-coding`/`karpathy-guidelines`) → 测试(`dev-test`+`req-test-gate`) → 代码评审(`dev-review`) → 发布(`slark-cicd`/`slark-dsh-cicd`/`publish`) → PDV 验收 → released
|
||||
|
||||
## 按任务选技能
|
||||
|
||||
| 任务 | 技能 |
|
||||
| --- | --- |
|
||||
| 讨论 / 调研 / 对比 | `req`、`req-research`、`req-compare` |
|
||||
| PRD / 原型 | `req-prd`、`req-prototype` |
|
||||
| PRD 评审 | `req-review` |
|
||||
| 设计 / 拆任务 | `req-design`、`dev-arch`、`db-migration` |
|
||||
| 编码 | `dev-coding`、`karpathy-guidelines` |
|
||||
| 测试 | `dev-test`、`req-test-gate` |
|
||||
| 代码评审 | `dev-review`、`review-checklist` |
|
||||
| 发布(Slark 仓库/Desktop) | `slark-cicd` |
|
||||
| 发布(DSH 插件/宿主补丁) | `slark-dsh-cicd` |
|
||||
| 发布(ai-proj 服务 / macOS 包) | `ai-proj-cicd-release`、`ai-proj-macos-release` |
|
||||
| 回滚 | `deploy-rollback` |
|
||||
| 部署后审计 / 回归 | `req-audit`、`req-lookback` |
|
||||
| 复盘 | `req-retro` |
|
||||
| 项目管理 | `project-management`、`pm-risk`、`pm-ask` |
|
||||
| 建/改技能 | `skill-creator` |
|
||||
|
||||
## 红线
|
||||
|
||||
- 不得自审批;不得绕过门禁(测试 Gate / 发布 Gate / 归档 Gate)
|
||||
- 不得把「某步骤完成」当结束;不得无证据声称完成
|
||||
- 不得把凭据/内网地址/绝对路径写进技能或文档
|
||||
+15
-14
@@ -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,17 +53,14 @@ 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
|
||||
def get_category_and_keywords(plugin_name):
|
||||
if any(x in plugin_name for x in ['dev-', 'coding', 'frontend']):
|
||||
return "development", ["development", "coding", "workflow"]
|
||||
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server']):
|
||||
elif any(x in plugin_name for x in ['ops-', 'deploy', 'server', 'slark-cicd', 'slark-dsh-cicd']):
|
||||
return "devops", ["devops", "deployment", "operations"]
|
||||
elif any(x in plugin_name for x in ['ai-proj', 'req']):
|
||||
return "productivity", ["project-management", "tasks", "requirements"]
|
||||
@@ -100,6 +96,14 @@ def scan_plugins(directory, source_prefix, disabled):
|
||||
with open(manifest_path) as f:
|
||||
manifest = json.load(f)
|
||||
|
||||
# Lifecycle buckets: only promoted plugins ship in the public
|
||||
# marketplace. beta/deprecated must be installed with --include-beta.
|
||||
import os
|
||||
status = manifest.get("status", "promoted")
|
||||
if status in ("beta", "deprecated") and os.environ.get("ALLOW_BETA") != "1":
|
||||
print(f" ⊘ {plugin_name} (status={status}, not shipped)")
|
||||
continue
|
||||
|
||||
category, keywords = get_category_and_keywords(plugin_name)
|
||||
|
||||
plugins.append({
|
||||
@@ -116,15 +120,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
|
||||
|
||||
Executable
+98
@@ -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
|
||||
@@ -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"
|
||||
|
||||
Executable
+548
@@ -0,0 +1,548 @@
|
||||
#!/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), dsh, or claude
|
||||
# --link Symlink skills from the repo instead of copying (dev machines)
|
||||
# --audit Show installed vs repo versions for the selected target
|
||||
# --include-beta Also install skills whose plugin.json status=beta
|
||||
# --no-validate Skip scripts/validate-skills.py before installing
|
||||
# --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
|
||||
LINK_MODE=false
|
||||
AUDIT_ONLY=false
|
||||
INCLUDE_BETA=false
|
||||
NO_VALIDATE=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, dsh or claude"; exit 1; }
|
||||
AGENT_TARGET="$2"; shift ;;
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--link) LINK_MODE=true ;;
|
||||
--audit) AUDIT_ONLY=true ;;
|
||||
--include-beta) INCLUDE_BETA=true ;;
|
||||
--no-validate) NO_VALIDATE=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}"
|
||||
;;
|
||||
dsh)
|
||||
if [[ -z "${DSH_HOME:-}" ]]; then
|
||||
error "--agent dsh requires DSH_HOME (or set AI_PROJ_HELPER_SKILLS_DIR explicitly)"
|
||||
exit 1
|
||||
fi
|
||||
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${DSH_HOME}/skills}"
|
||||
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${DSH_HOME}/.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, dsh 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
|
||||
|
||||
local status
|
||||
status="$(read_field "$json_path" status)"
|
||||
status="${status:-promoted}"
|
||||
|
||||
if [[ "$LIST_ONLY" == true ]]; then
|
||||
echo " [$dir_category] $status $effective_install_type:$install_name v$version"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$status" == "deprecated" ]]; then
|
||||
return
|
||||
fi
|
||||
if [[ "$status" == "beta" && "$INCLUDE_BETA" != true ]]; then
|
||||
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
|
||||
|
||||
# Dev machines may symlink instead of copying: repository edits apply at once.
|
||||
if [[ "$LINK_MODE" == true && "$effective_install_type" != "command" ]]; then
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
dry "$install_name → symlink $target_path → $src_dir"
|
||||
else
|
||||
mkdir -p "$SKILLS_DIR"
|
||||
rm -rf "$target_path"
|
||||
ln -sfn "$src_dir" "$target_path"
|
||||
state_set "$install_name" "$version" "symlink" "$(content_digest "$src_dir")"
|
||||
ok "$install_name → symlink (v$version)"
|
||||
fi
|
||||
INSTALL_ACTION=true
|
||||
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 --checksum --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)
|
||||
}
|
||||
|
||||
# ── Audit ──────────────────────────────────────────────────────────────────────
|
||||
audit_target() {
|
||||
python3 - "$REPO_DIR" "$STATE_FILE" "$AGENT_TARGET" "$SKILLS_DIR" <<'PYEOF'
|
||||
import json, sys, pathlib
|
||||
repo, state_file, agent, skills_dir = sys.argv[1:5]
|
||||
root = pathlib.Path(repo)
|
||||
state = {}
|
||||
if pathlib.Path(state_file).is_file():
|
||||
try: state = json.loads(pathlib.Path(state_file).read_text())
|
||||
except Exception: state = {}
|
||||
rows = []
|
||||
for pj in sorted(root.glob('skills-*/*-plugin/.claude-plugin/plugin.json')):
|
||||
try: meta = json.loads(pj.read_text())
|
||||
except Exception: continue
|
||||
name = meta.get('install_name')
|
||||
if not name: continue
|
||||
status = meta.get('status', 'promoted')
|
||||
if status == 'deprecated':
|
||||
continue
|
||||
want = meta.get('version', '')
|
||||
got = state.get(name, {}).get('version', '')
|
||||
itype = state.get(name, {}).get('install_type', '')
|
||||
drift = '' if got == want else ('missing' if not got else 'stale')
|
||||
rows.append((name, status, want, got or '-', itype or '-', drift))
|
||||
drift_rows = [r for r in rows if r[5]]
|
||||
print(f'audit agent={agent} skills_dir={skills_dir}')
|
||||
print(f'{"name":34} {"status":10} {"repo":8} {"installed":9} {"mode":8} drift')
|
||||
for r in rows:
|
||||
print(f'{r[0]:34} {r[1]:10} {r[2]:8} {r[3]:9} {r[4]:8} {r[5]}')
|
||||
print(f'\n{len(rows)} 个技能,{len(drift_rows)} 个漂移(未安装/版本落后)')
|
||||
raise SystemExit(1 if drift_rows else 0)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# ── 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
|
||||
|
||||
if [[ "$AUDIT_ONLY" == true ]]; then
|
||||
audit_target
|
||||
return
|
||||
fi
|
||||
|
||||
info "Installing skills for $AGENT_TARGET from: $REPO_DIR"
|
||||
if [[ "$NO_VALIDATE" != true && -f "$REPO_DIR/scripts/validate-skills.py" ]]; then
|
||||
if ! python3 "$REPO_DIR/scripts/validate-skills.py" >/tmp/ai-proj-helper-validate.log 2>&1; then
|
||||
error "validate-skills.py 未通过,修复后再装(或加 --no-validate 跳过)"
|
||||
tail -20 /tmp/ai-proj-helper-validate.log >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
[[ "$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 "$@"
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Generate catalog.json — the reviewable list of every skill in this repo.
|
||||
|
||||
Usage: python3 scripts/generate-catalog.py [--check]
|
||||
(default) write catalog.json
|
||||
--check fail if catalog.json is out of date (for CI)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
CATALOG = ROOT / "catalog.json"
|
||||
|
||||
|
||||
def frontmatter(text: str) -> dict:
|
||||
if not text.startswith("---"):
|
||||
return {}
|
||||
end = text.find("\n---", 3)
|
||||
if end == -1:
|
||||
return {}
|
||||
data: dict = {}
|
||||
metadata: dict = {}
|
||||
key = None
|
||||
for line in text[3:end].splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", line)
|
||||
if m:
|
||||
key = m.group(1)
|
||||
data[key] = m.group(2).strip().strip('"').strip("'")
|
||||
continue
|
||||
m = re.match(r"^\s+([A-Za-z0-9_-]+):\s*(.*)$", line)
|
||||
if m and key == "metadata":
|
||||
metadata[m.group(1)] = m.group(2).strip().strip('"').strip("'")
|
||||
if metadata:
|
||||
data["metadata"] = metadata
|
||||
return data
|
||||
|
||||
|
||||
def build() -> dict:
|
||||
skills = []
|
||||
for plugin_json in sorted(ROOT.glob("skills-*/*-plugin/.claude-plugin/plugin.json")):
|
||||
plugin_dir = plugin_json.parent.parent
|
||||
meta = json.loads(plugin_json.read_text(encoding="utf-8"))
|
||||
skill_files = sorted((plugin_dir / "skills").rglob("SKILL.md"))
|
||||
if not skill_files:
|
||||
continue
|
||||
skill_md = skill_files[0]
|
||||
fm = frontmatter(skill_md.read_text(encoding="utf-8", errors="replace"))
|
||||
desc = fm.get("description", "")
|
||||
body = skill_md.read_text(encoding="utf-8", errors="replace")
|
||||
skills.append(
|
||||
{
|
||||
"name": meta.get("install_name") or fm.get("name"),
|
||||
"category": meta.get("dir_category"),
|
||||
"version": meta.get("version"),
|
||||
"status": meta.get("status", "promoted"),
|
||||
"invocation": (fm.get("metadata") or {}).get("invocation", "model-invoked"),
|
||||
"description": desc,
|
||||
"description_len": len(desc),
|
||||
"path": str(skill_md.relative_to(ROOT)),
|
||||
"digest": hashlib.sha256(body.encode("utf-8")).hexdigest()[:16],
|
||||
}
|
||||
)
|
||||
counts: dict = {}
|
||||
for skill in skills:
|
||||
key = f"{skill['status']}/{skill['invocation']}"
|
||||
counts[key] = counts.get(key, 0) + 1
|
||||
return {
|
||||
"repo": "ai-proj-helper",
|
||||
"generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
|
||||
"total": len(skills),
|
||||
"counts": counts,
|
||||
"skills": skills,
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
catalog = build()
|
||||
text = json.dumps(catalog, ensure_ascii=False, indent=2) + "\n"
|
||||
if "--check" in sys.argv:
|
||||
if not CATALOG.is_file():
|
||||
print("catalog.json 不存在,请运行 python3 scripts/generate-catalog.py")
|
||||
return 1
|
||||
current = json.loads(CATALOG.read_text(encoding="utf-8"))
|
||||
current.pop("generated_at", None)
|
||||
fresh = dict(catalog)
|
||||
fresh.pop("generated_at", None)
|
||||
if current != fresh:
|
||||
print("catalog.json 已过期,请重新生成")
|
||||
return 1
|
||||
print("catalog.json 是最新的")
|
||||
return 0
|
||||
CATALOG.write_text(text, encoding="utf-8")
|
||||
print(f"写入 {CATALOG.relative_to(ROOT)}:{catalog['total']} 个技能 {catalog['counts']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,5 @@
|
||||
# 安全扫描允许清单(人工确认过的误报)
|
||||
# 格式:<路径子串>::<匹配文本中的特征串>
|
||||
# 例:某技能文档把危险命令当作“禁止清单”引用,而非真的要执行。
|
||||
skills-dev/slark-cicd-plugin::NEVER_AUTO
|
||||
skills-dev/slark-cicd-plugin::destructive
|
||||
Executable
+82
@@ -0,0 +1,82 @@
|
||||
#!/usr/bin/env bash
|
||||
# scan-skill.sh — security scan before a skill enters the team library.
|
||||
#
|
||||
# Usage: ./scripts/scan-skill.sh [plugin-dir ...]
|
||||
# With no arguments scans every skills-*/*-plugin directory.
|
||||
# Exit 0 = clean (warnings allowed), 1 = high severity finding.
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
HIGH=0
|
||||
WARN=0
|
||||
|
||||
# High severity: destructive, remote-code-execution, secret material.
|
||||
HIGH_PATTERNS=(
|
||||
'rm[[:space:]]+-[a-zA-Z]*r[a-zA-Z]*[[:space:]]+/($|[[:space:]])'
|
||||
'curl[^|]*\|[[:space:]]*(ba|z)?sh'
|
||||
'wget[^|]*\|[[:space:]]*(ba|z)?sh'
|
||||
'chmod[[:space:]]+777'
|
||||
'BEGIN (RSA|OPENSSH|EC|DSA) PRIVATE KEY'
|
||||
'sk-[A-Za-z0-9]{16,}'
|
||||
'AKIA[0-9A-Z]{16}'
|
||||
'ghp_[A-Za-z0-9]{20,}'
|
||||
)
|
||||
# Warnings: machine-specific paths, plain http endpoints.
|
||||
WARN_PATTERNS=(
|
||||
'/Users/[A-Za-z0-9._-]+/'
|
||||
'http://[A-Za-z0-9.-]+'
|
||||
'password[[:space:]]*=[[:space:]]*[^$]'
|
||||
)
|
||||
|
||||
targets=("$@")
|
||||
if [[ ${#targets[@]} -eq 0 ]]; then
|
||||
while IFS= read -r dir; do
|
||||
targets+=("$(dirname "$(dirname "$dir")")")
|
||||
done < <(find "$ROOT" -path "*/skills-*/*-plugin/.claude-plugin/plugin.json" | sort)
|
||||
fi
|
||||
|
||||
ALLOWLIST="$ROOT/scripts/scan-allowlist.txt"
|
||||
is_allowlisted() {
|
||||
# is_allowlisted <abs_path> <matched_line>
|
||||
local path="$1" line="$2" entry sub needle
|
||||
[[ -f "$ALLOWLIST" ]] || return 1
|
||||
while IFS= read -r entry; do
|
||||
[[ -z "$entry" || "$entry" == \#* ]] && continue
|
||||
sub="${entry%%::*}"
|
||||
needle="${entry#*::}"
|
||||
if [[ "$path" == *"$sub"* && "$line" == *"$needle"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
done < "$ALLOWLIST"
|
||||
return 1
|
||||
}
|
||||
|
||||
for dir in "${targets[@]}"; do
|
||||
rel="${dir#"$ROOT"/}"
|
||||
for pattern in "${HIGH_PATTERNS[@]}"; do
|
||||
while IFS= read -r hit; do
|
||||
abs="${hit%%:*}"
|
||||
detail="${hit#*:}"
|
||||
if is_allowlisted "$abs" "$hit"; then
|
||||
echo "[allow] $rel: $detail"
|
||||
continue
|
||||
fi
|
||||
echo "[HIGH] $rel: $detail"
|
||||
HIGH=$((HIGH + 1))
|
||||
done < <(grep -rnE --include='*.md' --include='*.sh' --include='*.py' --include='*.js' --include='*.ts' --include='*.json' -- "$pattern" "$dir" 2>/dev/null | cut -c1-200)
|
||||
done
|
||||
for pattern in "${WARN_PATTERNS[@]}"; do
|
||||
while IFS= read -r hit; do
|
||||
echo "[warn] $rel: ${hit%%:*}:${hit#*:}"
|
||||
WARN=$((WARN + 1))
|
||||
done < <(grep -rnE --include='*.md' --include='*.sh' --include='*.py' --include='*.js' --include='*.ts' -- "$pattern" "$dir" 2>/dev/null | cut -c1-200)
|
||||
done
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "扫描完成:${#targets[@]} 个插件,$HIGH 个高危,$WARN 个警告"
|
||||
if [[ "$HIGH" -gt 0 ]]; then
|
||||
echo "存在高危项,禁止入库,除非逐条确认并记录原因。"
|
||||
exit 1
|
||||
fi
|
||||
exit 0
|
||||
Executable
+162
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Validate plugins and SKILL.md files against the Agent Skills spec + team rules.
|
||||
|
||||
Usage: python3 scripts/validate-skills.py [--strict]
|
||||
Exit 0 = pass, 1 = errors found.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
ALLOWED_FRONTMATTER = {
|
||||
"name", "description", "license", "compatibility", "metadata",
|
||||
"allowed-tools", "disable-model-invocation", "version",
|
||||
}
|
||||
NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,63}$")
|
||||
BODY_MAX_BYTES = 20 * 1024
|
||||
DESC_MIN, DESC_MAX = 30, 1024
|
||||
STATUSES = {"promoted", "beta", "deprecated"}
|
||||
INVOCATIONS = {"model-invoked", "user-invoked"}
|
||||
REQUIRED_PLUGIN_FIELDS = ("install_name", "install_type", "dir_category", "version")
|
||||
|
||||
errors: list[str] = []
|
||||
warnings: list[str] = []
|
||||
|
||||
|
||||
def parse_frontmatter(text: str):
|
||||
"""Return (frontmatter dict, body text). Supports one level of nesting."""
|
||||
if not text.startswith("---"):
|
||||
return {}, text
|
||||
end = text.find("\n---", 3)
|
||||
if end == -1:
|
||||
return {}, text
|
||||
raw, body = text[3:end], text[end + 4:]
|
||||
data: dict = {}
|
||||
metadata: dict = {}
|
||||
key = None
|
||||
for line in raw.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
m = re.match(r"^([A-Za-z0-9_-]+):\s*(.*)$", line)
|
||||
if m:
|
||||
key = m.group(1)
|
||||
data[key] = m.group(2).strip().strip('"').strip("'")
|
||||
continue
|
||||
m = re.match(r"^\s+([A-Za-z0-9_-]+):\s*(.*)$", line)
|
||||
if m and key == "metadata":
|
||||
metadata[m.group(1)] = m.group(2).strip().strip('"').strip("'")
|
||||
elif key:
|
||||
data[key] = (data.get(key, "") + " " + line.strip()).strip()
|
||||
if metadata:
|
||||
data["metadata"] = metadata
|
||||
return data, body
|
||||
|
||||
|
||||
def check_plugin(plugin_json: Path):
|
||||
plugin_dir = plugin_json.parent.parent
|
||||
rel = plugin_dir.relative_to(ROOT)
|
||||
try:
|
||||
meta = json.loads(plugin_json.read_text(encoding="utf-8"))
|
||||
except Exception as exc: # noqa: BLE001
|
||||
errors.append(f"{rel}: plugin.json 解析失败: {exc}")
|
||||
return
|
||||
|
||||
for field in REQUIRED_PLUGIN_FIELDS:
|
||||
if not meta.get(field):
|
||||
errors.append(f"{rel}: plugin.json 缺少 {field}")
|
||||
status = meta.get("status", "")
|
||||
if status and status not in STATUSES:
|
||||
errors.append(f"{rel}: status 非法 {status!r}(应为 {sorted(STATUSES)})")
|
||||
install_name = meta.get("install_name", "")
|
||||
if install_name and not NAME_RE.match(install_name):
|
||||
errors.append(f"{rel}: install_name 不合规 {install_name!r}")
|
||||
|
||||
skills_dir = plugin_dir / "skills"
|
||||
skill_files = sorted(skills_dir.rglob("SKILL.md")) if skills_dir.is_dir() else []
|
||||
if not skill_files:
|
||||
errors.append(f"{rel}: 没有找到 skills/**/SKILL.md")
|
||||
return
|
||||
for skill_md in skill_files:
|
||||
check_skill(rel, skill_md, meta)
|
||||
|
||||
|
||||
def check_skill(rel: Path, skill_md: Path, meta: dict):
|
||||
text = skill_md.read_text(encoding="utf-8", errors="replace")
|
||||
fm, body = parse_frontmatter(text)
|
||||
where = f"{rel}/{skill_md.name}"
|
||||
|
||||
name = fm.get("name", "")
|
||||
if not name:
|
||||
errors.append(f"{where}: frontmatter 缺少 name")
|
||||
elif not NAME_RE.match(name):
|
||||
errors.append(f"{where}: name 不合规 {name!r}(小写字母/数字/连字符,≤64)")
|
||||
|
||||
desc = fm.get("description", "")
|
||||
if not desc:
|
||||
errors.append(f"{where}: frontmatter 缺少 description")
|
||||
elif not (DESC_MIN <= len(desc) <= DESC_MAX):
|
||||
warnings.append(f"{where}: description 长度 {len(desc)},建议 {DESC_MIN}-{DESC_MAX}(决定是否被触发)")
|
||||
|
||||
for key in fm:
|
||||
if key == "metadata":
|
||||
continue
|
||||
if key not in ALLOWED_FRONTMATTER:
|
||||
warnings.append(f"{where}: 非标准 frontmatter 字段 {key!r},建议移入 metadata")
|
||||
|
||||
body_bytes = len(body.encode("utf-8"))
|
||||
if body_bytes > BODY_MAX_BYTES:
|
||||
warnings.append(f"{where}: 正文 {body_bytes} 字节,超过 20KB 预算,建议拆 references/")
|
||||
|
||||
metadata = fm.get("metadata") or {}
|
||||
invocation = metadata.get("invocation", "")
|
||||
if invocation and invocation not in INVOCATIONS:
|
||||
errors.append(f"{where}: metadata.invocation 非法 {invocation!r}")
|
||||
disable_model = str(fm.get("disable-model-invocation", "")).lower() == "true"
|
||||
if invocation == "user-invoked" and not disable_model:
|
||||
errors.append(f"{where}: user-invoked 技能必须设置 disable-model-invocation: true")
|
||||
if invocation == "model-invoked" and disable_model:
|
||||
errors.append(f"{where}: model-invoked 技能不应设置 disable-model-invocation: true")
|
||||
|
||||
status = meta.get("status", "")
|
||||
if status == "promoted":
|
||||
docs = ROOT / "docs" / "skills" / f"{meta.get('install_name', name)}.md"
|
||||
if not docs.is_file():
|
||||
warnings.append(f"{where}: promoted 技能缺少四段式文档 docs/skills/{docs.name}")
|
||||
|
||||
# Relative links must resolve (inline code spans are examples, skip them).
|
||||
scan_body = re.sub(r"`[^`]*`", "", body)
|
||||
for target in re.findall(r"\]\((\.[^)\s]+|[A-Za-z0-9_-]+/[^)\s]+)\)", scan_body):
|
||||
if target.startswith(("http://", "https://", "#")):
|
||||
continue
|
||||
if not (skill_md.parent / target).exists():
|
||||
warnings.append(f"{where}: 引用的相对路径不存在: {target}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
strict = "--strict" in sys.argv
|
||||
plugins = sorted(ROOT.glob("skills-*/*-plugin/.claude-plugin/plugin.json"))
|
||||
if not plugins:
|
||||
print("未找到任何 plugin.json", file=sys.stderr)
|
||||
return 1
|
||||
for plugin_json in plugins:
|
||||
check_plugin(plugin_json)
|
||||
|
||||
for warning in warnings:
|
||||
print(f"[warn] {warning}")
|
||||
for error in errors:
|
||||
print(f"[error] {error}")
|
||||
print(f"\n检查 {len(plugins)} 个插件:{len(errors)} 个错误,{len(warnings)} 个警告")
|
||||
if errors:
|
||||
return 1
|
||||
if strict and warnings:
|
||||
print("--strict 模式下,警告视为失败")
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -4,5 +4,9 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-contract",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: biz-contract
|
||||
description: 商务合同撰写。支持多种合同类型:软件订阅合同、软件定制开发合同、物流合同、销售服务合同、贸易合同等。当用户提到合同、协议、签约、合作协议相关任务时自动激活。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 商务合同撰写技能 (biz-contract)
|
||||
|
||||
## 概述
|
||||
|
||||
本技能用于辅助商务合同的撰写与管理,支持多家公司主体、多种合同类型。
|
||||
@@ -631,112 +631,9 @@ api.publish_to_feishu(doc_id, "物流服务合同-妗晨与名风")
|
||||
|
||||
---
|
||||
|
||||
## 飞书文档集成
|
||||
|
||||
本技能已集成飞书文档,可将合同直接创建到飞书云文档。
|
||||
## 详细内容
|
||||
|
||||
### 权限说明
|
||||
以下章节已移到 [references/details.md](references/details.md):
|
||||
|
||||
**重要**:飞书文档默认对组织内权限是开放的,允许编辑。创建的合同文档在组织内成员均可查看和编辑。如需限制访问权限,请在飞书中手动调整文档权限设置。
|
||||
|
||||
### 可用工具
|
||||
|
||||
| 工具 | 功能 |
|
||||
|------|------|
|
||||
| `create_document` | 创建空白飞书文档 |
|
||||
| `write_document` | 向已有文档写入内容 |
|
||||
| `create_and_write_document` | 创建文档并写入内容(推荐) |
|
||||
| `get_document_info` | 获取文档信息 |
|
||||
|
||||
### 使用示例
|
||||
|
||||
```
|
||||
# 创建合同文档
|
||||
使用 feishu-doc MCP 的 create_and_write_document 工具:
|
||||
- title: "软件订阅服务合同-智慧云彩与妗晨"
|
||||
- content: [Markdown 格式的合同内容]
|
||||
- folder_token: [可选,指定文件夹]
|
||||
```
|
||||
|
||||
### 获取文件夹 Token
|
||||
|
||||
1. 在飞书中打开目标文件夹
|
||||
2. 复制 URL 中的 folder token(格式如:`fldcnXXXXXX`)
|
||||
3. 调用工具时传入 folder_token 参数
|
||||
|
||||
### 飞书输出格式规范
|
||||
|
||||
#### 开票信息表格
|
||||
|
||||
物流合同中的开票信息应使用**表格**格式展示,列宽配置:
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| 项目列宽 | 120px | 显示"名称"、"纳税人识别号"等标签 |
|
||||
| 内容列宽 | 480px | 显示具体信息 |
|
||||
|
||||
```python
|
||||
# 创建开票信息表格示例
|
||||
from feishu_docx import FeishuDocx
|
||||
|
||||
docx = FeishuDocx()
|
||||
doc = docx.create_document("物流服务合同")
|
||||
|
||||
# 创建表格(4行2列,指定列宽)
|
||||
table = docx.create_table(doc["document_id"], rows=4, cols=2, col_widths=[120, 480])
|
||||
|
||||
# 填充内容
|
||||
docx.fill_table(doc["document_id"], table, [
|
||||
["名称", "重庆妗晨工贸有限公司"],
|
||||
["纳税人识别号", "91500104MA7EJTPA6D"],
|
||||
["地址、电话", "重庆市大渡口区跳磴镇海康路106号1-1 / 15213397998"],
|
||||
["开户行及账号", "中国农业银行重庆大渡口天安支行 31230401040004386"],
|
||||
])
|
||||
```
|
||||
|
||||
#### 签章格式
|
||||
|
||||
推荐使用**独立区块式**签章格式:
|
||||
|
||||
```
|
||||
【甲 方】
|
||||
|
||||
单位名称:重庆妗晨工贸有限公司
|
||||
|
||||
法定代表人(或授权代表): (签字/盖章)
|
||||
|
||||
单位盖章:
|
||||
|
||||
|
||||
|
||||
签订日期: 年 月 日
|
||||
|
||||
─────────────────────────────────────────────────────
|
||||
|
||||
【乙 方】
|
||||
|
||||
单位名称:北京名风新能源科技有限公司
|
||||
|
||||
法定代表人(或授权代表): (签字/盖章)
|
||||
|
||||
单位盖章:
|
||||
|
||||
|
||||
|
||||
签订日期: 年 月 日
|
||||
```
|
||||
|
||||
**特点**:甲乙双方独立区块,留白充足,便于盖章签字。
|
||||
|
||||
#### 物流合同税率
|
||||
|
||||
| 服务类型 | 税率 | 发票类型 |
|
||||
|----------|------|----------|
|
||||
| 交通运输服务 | 9% | 增值税专用发票 |
|
||||
|
||||
#### 默认存储路径
|
||||
|
||||
| 平台 | 路径 |
|
||||
|------|------|
|
||||
| 飞书云文档 | ai-proj/01运营 (folder_token: `C80gfkRnzlonQ5d4AhOcOACDnNg`) |
|
||||
| 思源笔记 | 商务合同笔记本 /物流合同/{甲方简称}-{乙方简称}-{年份} |
|
||||
- 飞书文档集成
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# skills 详细内容
|
||||
|
||||
## 飞书文档集成
|
||||
|
||||
本技能已集成飞书文档,可将合同直接创建到飞书云文档。
|
||||
|
||||
### 权限说明
|
||||
|
||||
**重要**:飞书文档默认对组织内权限是开放的,允许编辑。创建的合同文档在组织内成员均可查看和编辑。如需限制访问权限,请在飞书中手动调整文档权限设置。
|
||||
|
||||
### 可用工具
|
||||
|
||||
| 工具 | 功能 |
|
||||
|------|------|
|
||||
| `create_document` | 创建空白飞书文档 |
|
||||
| `write_document` | 向已有文档写入内容 |
|
||||
| `create_and_write_document` | 创建文档并写入内容(推荐) |
|
||||
| `get_document_info` | 获取文档信息 |
|
||||
|
||||
### 使用示例
|
||||
|
||||
```
|
||||
# 创建合同文档
|
||||
使用 feishu-doc MCP 的 create_and_write_document 工具:
|
||||
- title: "软件订阅服务合同-智慧云彩与妗晨"
|
||||
- content: [Markdown 格式的合同内容]
|
||||
- folder_token: [可选,指定文件夹]
|
||||
```
|
||||
|
||||
### 获取文件夹 Token
|
||||
|
||||
1. 在飞书中打开目标文件夹
|
||||
2. 复制 URL 中的 folder token(格式如:`fldcnXXXXXX`)
|
||||
3. 调用工具时传入 folder_token 参数
|
||||
|
||||
### 飞书输出格式规范
|
||||
|
||||
#### 开票信息表格
|
||||
|
||||
物流合同中的开票信息应使用**表格**格式展示,列宽配置:
|
||||
|
||||
| 配置项 | 值 | 说明 |
|
||||
|--------|-----|------|
|
||||
| 项目列宽 | 120px | 显示"名称"、"纳税人识别号"等标签 |
|
||||
| 内容列宽 | 480px | 显示具体信息 |
|
||||
|
||||
```python
|
||||
# 创建开票信息表格示例
|
||||
from feishu_docx import FeishuDocx
|
||||
|
||||
docx = FeishuDocx()
|
||||
doc = docx.create_document("物流服务合同")
|
||||
|
||||
# 创建表格(4行2列,指定列宽)
|
||||
table = docx.create_table(doc["document_id"], rows=4, cols=2, col_widths=[120, 480])
|
||||
|
||||
# 填充内容
|
||||
docx.fill_table(doc["document_id"], table, [
|
||||
["名称", "重庆妗晨工贸有限公司"],
|
||||
["纳税人识别号", "91500104MA7EJTPA6D"],
|
||||
["地址、电话", "重庆市大渡口区跳磴镇海康路106号1-1 / 15213397998"],
|
||||
["开户行及账号", "中国农业银行重庆大渡口天安支行 31230401040004386"],
|
||||
])
|
||||
```
|
||||
|
||||
#### 签章格式
|
||||
|
||||
推荐使用**独立区块式**签章格式:
|
||||
|
||||
```
|
||||
【甲 方】
|
||||
|
||||
单位名称:重庆妗晨工贸有限公司
|
||||
|
||||
法定代表人(或授权代表): (签字/盖章)
|
||||
|
||||
单位盖章:
|
||||
|
||||
|
||||
|
||||
签订日期: 年 月 日
|
||||
|
||||
─────────────────────────────────────────────────────
|
||||
|
||||
【乙 方】
|
||||
|
||||
单位名称:北京名风新能源科技有限公司
|
||||
|
||||
法定代表人(或授权代表): (签字/盖章)
|
||||
|
||||
单位盖章:
|
||||
|
||||
|
||||
|
||||
签订日期: 年 月 日
|
||||
```
|
||||
|
||||
**特点**:甲乙双方独立区块,留白充足,便于盖章签字。
|
||||
|
||||
#### 物流合同税率
|
||||
|
||||
| 服务类型 | 税率 | 发票类型 |
|
||||
|----------|------|----------|
|
||||
| 交通运输服务 | 9% | 增值税专用发票 |
|
||||
|
||||
#### 默认存储路径
|
||||
|
||||
| 平台 | 路径 |
|
||||
|------|------|
|
||||
| 飞书云文档 | ai-proj/01运营 (folder_token: `C80gfkRnzlonQ5d4AhOcOACDnNg`) |
|
||||
| 思源笔记 | 商务合同笔记本 /物流合同/{甲方简称}-{乙方简称}-{年份} |
|
||||
@@ -4,5 +4,9 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-ops",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: biz-ops
|
||||
description: 商务运营技能。支持商业计划书(BP)撰写和商务合同起草。当用户提到商业计划书、BP、融资计划、商业模式、合同、协议、签约等相关任务时自动激活。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 商务运营 (biz-ops)
|
||||
|
||||
@@ -4,5 +4,9 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "biz-plan",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: biz-plan
|
||||
description: 商业计划书撰写。支持融资BP、战略规划、商业模式设计等。当用户提到商业计划书、BP、融资计划、商业模式、战略规划相关任务时自动激活。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 商业计划书撰写技能 (biz-plan)
|
||||
|
||||
## 概述
|
||||
|
||||
本技能用于辅助商业计划书的撰写与优化,特别适用于 B2B SaaS、供应链平台、产业互联网等领域。
|
||||
@@ -400,361 +400,9 @@ description: 商业计划书撰写。支持融资BP、战略规划、商业模
|
||||
|
||||
---
|
||||
|
||||
## PPT 设计能力
|
||||
|
||||
本技能支持生成商业计划书 PPT,输出为 HTML 格式的演示文稿,可直接在浏览器中全屏演示,也可转换为 PDF 或截图使用。
|
||||
## 详细内容
|
||||
|
||||
### PPT 输出方式
|
||||
以下章节已移到 [references/details.md](references/details.md):
|
||||
|
||||
当用户要求生成 PPT 时,使用以下方式:
|
||||
|
||||
1. **HTML 演示文稿**(推荐)
|
||||
- 生成单个 HTML 文件
|
||||
- 支持全屏演示(按 F 键)
|
||||
- 支持键盘翻页(← →)
|
||||
- 可直接打印为 PDF
|
||||
- 响应式设计,适配各种屏幕
|
||||
|
||||
2. **Markdown 演示脚本**
|
||||
- 输出每页内容的文字稿
|
||||
- 用户可自行复制到 PPT 软件
|
||||
|
||||
### HTML PPT 模板
|
||||
|
||||
生成 PPT 时使用以下 HTML 模板结构:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>商业计划书 - [公司名称]</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide {
|
||||
display: none;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 60px 80px;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slide.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 封面样式 */
|
||||
.slide.cover {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slide.cover h1 {
|
||||
font-size: 72px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.slide.cover .slogan {
|
||||
font-size: 28px;
|
||||
color: #a0aec0;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.slide.cover .info {
|
||||
font-size: 18px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
/* 内容页样式 */
|
||||
.slide h2 {
|
||||
font-size: 48px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 40px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.slide h3 {
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.slide p, .slide li {
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.slide ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.slide ul li {
|
||||
padding: 12px 0;
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slide ul li::before {
|
||||
content: "▸";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
/* 数据卡片 */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
flex: 1;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-card .value {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.metric-card .label {
|
||||
font-size: 18px;
|
||||
color: #a0aec0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 两栏布局 */
|
||||
.two-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* 流程图 */
|
||||
.flow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.flow-item {
|
||||
background: rgba(102, 126, 234, 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 24px 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
color: #667eea;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* 页码 */
|
||||
.page-number {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 40px;
|
||||
font-size: 16px;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
transition: width 0.3s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 导航提示 */
|
||||
.nav-hint {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 14px;
|
||||
color: #4a5568;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="progress" id="progress"></div>
|
||||
|
||||
<!-- Slide 1: 封面 -->
|
||||
<div class="slide cover active">
|
||||
<h1>[公司名称]</h1>
|
||||
<p class="slogan">[一句话 Slogan]</p>
|
||||
<p class="info">[日期] · 商业计划书</p>
|
||||
<span class="page-number">1 / N</span>
|
||||
</div>
|
||||
|
||||
<!-- Slide 2: 公司定位 -->
|
||||
<div class="slide">
|
||||
<h2>公司定位</h2>
|
||||
<h3>[一句话定位]</h3>
|
||||
<div class="metrics">
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]年</div>
|
||||
<div class="label">深耕行业</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]家</div>
|
||||
<div class="label">头部客户</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]轮</div>
|
||||
<div class="label">完成融资</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="page-number">2 / N</span>
|
||||
</div>
|
||||
|
||||
<!-- 更多 slides... -->
|
||||
|
||||
<p class="nav-hint">← → 翻页 · F 全屏 · ESC 退出</p>
|
||||
|
||||
<script>
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
const progress = document.getElementById('progress');
|
||||
let current = 0;
|
||||
|
||||
function showSlide(n) {
|
||||
slides[current].classList.remove('active');
|
||||
current = (n + slides.length) % slides.length;
|
||||
slides[current].classList.add('active');
|
||||
progress.style.width = ((current + 1) / slides.length * 100) + '%';
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowRight' || e.key === ' ') showSlide(current + 1);
|
||||
if (e.key === 'ArrowLeft') showSlide(current - 1);
|
||||
if (e.key === 'f' || e.key === 'F') {
|
||||
document.documentElement.requestFullscreen?.();
|
||||
}
|
||||
});
|
||||
|
||||
// 触摸滑动支持
|
||||
let touchStartX = 0;
|
||||
document.addEventListener('touchstart', e => touchStartX = e.touches[0].clientX);
|
||||
document.addEventListener('touchend', e => {
|
||||
const diff = e.changedTouches[0].clientX - touchStartX;
|
||||
if (Math.abs(diff) > 50) showSlide(current + (diff > 0 ? -1 : 1));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### BP PPT 标准页面结构
|
||||
|
||||
生成商业计划书 PPT 时,按以下结构组织:
|
||||
|
||||
| 页码 | 页面类型 | 内容要点 | 设计建议 |
|
||||
|------|----------|----------|----------|
|
||||
| 1 | 封面 | 公司名、Slogan、日期 | 居中大标题,渐变背景 |
|
||||
| 2 | 公司定位 | 一句话定位 + 核心数据 | 3-4 个数据卡片 |
|
||||
| 3 | 行业痛点 | 3-4 个核心痛点 | 图标 + 简短描述 |
|
||||
| 4 | 解决方案 | 产品价值主张 | 流程图或产品矩阵 |
|
||||
| 5 | 商业模式 | 收入来源、盈利方式 | 模式图 + 收入占比 |
|
||||
| 6 | 市场规模 | TAM/SAM/SOM | 同心圆图 + 数字 |
|
||||
| 7 | 竞争分析 | 竞争矩阵、差异化 | 2x2 矩阵图 |
|
||||
| 8 | 运营数据 | 核心指标、增长趋势 | 4 个大数字卡片 |
|
||||
| 9 | 客户案例 | 标杆客户、成果 | Logo + 数据对比 |
|
||||
| 10 | 团队介绍 | 核心成员背景 | 头像 + 简介 |
|
||||
| 11 | 里程碑 | 发展历程 | 时间线 |
|
||||
| 12 | 财务预测 | 3 年收入预测 | 柱状图示意 |
|
||||
| 13 | 战略规划 | 未来规划 | 阶段性目标 |
|
||||
| 14 | 融资计划 | 金额、用途、联系方式 | 饼图 + 联系信息 |
|
||||
|
||||
### PPT 设计原则
|
||||
|
||||
1. **一页一重点**:每页只传达一个核心信息
|
||||
2. **大字少字**:标题 48px+,正文 24px+,每页文字不超过 50 字
|
||||
3. **数据可视化**:用数字卡片、图表代替文字描述
|
||||
4. **留白充足**:内容不要铺满,保持呼吸感
|
||||
5. **配色统一**:使用 2-3 种主色,保持一致性
|
||||
6. **高对比度**:深色背景 + 浅色文字,或浅色背景 + 深色文字
|
||||
|
||||
### 配色方案
|
||||
|
||||
**深色科技风**(推荐):
|
||||
- 背景:#1a1a2e → #16213e(渐变)
|
||||
- 主色:#667eea(蓝紫色)
|
||||
- 辅助:#764ba2(紫色)
|
||||
- 文字:#e2e8f0(浅灰)
|
||||
- 次要文字:#a0aec0(中灰)
|
||||
|
||||
**浅色商务风**:
|
||||
- 背景:#ffffff
|
||||
- 主色:#2196F3(蓝色)
|
||||
- 辅助:#1976D2(深蓝)
|
||||
- 文字:#212121(深灰)
|
||||
- 次要文字:#616161(中灰)
|
||||
|
||||
### 使用示例
|
||||
|
||||
用户请求:
|
||||
> 帮我生成商业计划书 PPT
|
||||
|
||||
输出方式:
|
||||
1. 生成完整的 HTML 文件
|
||||
2. 保存到用户指定位置(如桌面、项目目录)
|
||||
3. 用户用浏览器打开即可演示
|
||||
|
||||
```bash
|
||||
# 打开 PPT
|
||||
open ~/Desktop/bp-presentation.html
|
||||
|
||||
# 或在浏览器中打开后按 F 进入全屏模式
|
||||
```
|
||||
|
||||
### 导出为 PDF
|
||||
|
||||
HTML PPT 可通过浏览器打印功能导出为 PDF:
|
||||
1. 在浏览器中打开 HTML 文件
|
||||
2. 按 Cmd+P (Mac) 或 Ctrl+P (Windows)
|
||||
3. 选择"另存为 PDF"
|
||||
4. 设置页面大小为横向 A4 或 16:9
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **内容优先**:先确定 BP 内容,再生成 PPT
|
||||
2. **数据占位**:敏感数据可用 [X] 占位
|
||||
3. **迭代优化**:可分页生成,逐步完善
|
||||
4. **本地演示**:HTML 文件完全离线可用,无需网络
|
||||
- PPT 设计能力
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
# skills 详细内容
|
||||
|
||||
## PPT 设计能力
|
||||
|
||||
本技能支持生成商业计划书 PPT,输出为 HTML 格式的演示文稿,可直接在浏览器中全屏演示,也可转换为 PDF 或截图使用。
|
||||
|
||||
### PPT 输出方式
|
||||
|
||||
当用户要求生成 PPT 时,使用以下方式:
|
||||
|
||||
1. **HTML 演示文稿**(推荐)
|
||||
- 生成单个 HTML 文件
|
||||
- 支持全屏演示(按 F 键)
|
||||
- 支持键盘翻页(← →)
|
||||
- 可直接打印为 PDF
|
||||
- 响应式设计,适配各种屏幕
|
||||
|
||||
2. **Markdown 演示脚本**
|
||||
- 输出每页内容的文字稿
|
||||
- 用户可自行复制到 PPT 软件
|
||||
|
||||
### HTML PPT 模板
|
||||
|
||||
生成 PPT 时使用以下 HTML 模板结构:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>商业计划书 - [公司名称]</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "PingFang SC", "Microsoft YaHei", sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.slide {
|
||||
display: none;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
padding: 60px 80px;
|
||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slide.active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 封面样式 */
|
||||
.slide.cover {
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.slide.cover h1 {
|
||||
font-size: 72px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 20px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.slide.cover .slogan {
|
||||
font-size: 28px;
|
||||
color: #a0aec0;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.slide.cover .info {
|
||||
font-size: 18px;
|
||||
color: #718096;
|
||||
}
|
||||
|
||||
/* 内容页样式 */
|
||||
.slide h2 {
|
||||
font-size: 48px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 40px;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.slide h3 {
|
||||
font-size: 32px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 24px;
|
||||
color: #a0aec0;
|
||||
}
|
||||
|
||||
.slide p, .slide li {
|
||||
font-size: 24px;
|
||||
line-height: 1.8;
|
||||
color: #e2e8f0;
|
||||
}
|
||||
|
||||
.slide ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.slide ul li {
|
||||
padding: 12px 0;
|
||||
padding-left: 40px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.slide ul li::before {
|
||||
content: "▸";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
/* 数据卡片 */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 40px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
flex: 1;
|
||||
background: rgba(102, 126, 234, 0.1);
|
||||
border: 1px solid rgba(102, 126, 234, 0.3);
|
||||
border-radius: 16px;
|
||||
padding: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.metric-card .value {
|
||||
font-size: 48px;
|
||||
font-weight: 700;
|
||||
color: #667eea;
|
||||
}
|
||||
|
||||
.metric-card .label {
|
||||
font-size: 18px;
|
||||
color: #a0aec0;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* 两栏布局 */
|
||||
.two-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 60px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
/* 流程图 */
|
||||
.flow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.flow-item {
|
||||
background: rgba(102, 126, 234, 0.15);
|
||||
border-radius: 12px;
|
||||
padding: 24px 32px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flow-arrow {
|
||||
color: #667eea;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
/* 页码 */
|
||||
.page-number {
|
||||
position: absolute;
|
||||
bottom: 30px;
|
||||
right: 40px;
|
||||
font-size: 16px;
|
||||
color: #4a5568;
|
||||
}
|
||||
|
||||
/* 进度条 */
|
||||
.progress {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 4px;
|
||||
background: linear-gradient(90deg, #667eea, #764ba2);
|
||||
transition: width 0.3s;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
/* 导航提示 */
|
||||
.nav-hint {
|
||||
position: fixed;
|
||||
bottom: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 14px;
|
||||
color: #4a5568;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="progress" id="progress"></div>
|
||||
|
||||
<!-- Slide 1: 封面 -->
|
||||
<div class="slide cover active">
|
||||
<h1>[公司名称]</h1>
|
||||
<p class="slogan">[一句话 Slogan]</p>
|
||||
<p class="info">[日期] · 商业计划书</p>
|
||||
<span class="page-number">1 / N</span>
|
||||
</div>
|
||||
|
||||
<!-- Slide 2: 公司定位 -->
|
||||
<div class="slide">
|
||||
<h2>公司定位</h2>
|
||||
<h3>[一句话定位]</h3>
|
||||
<div class="metrics">
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]年</div>
|
||||
<div class="label">深耕行业</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]家</div>
|
||||
<div class="label">头部客户</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="value">[X]轮</div>
|
||||
<div class="label">完成融资</div>
|
||||
</div>
|
||||
</div>
|
||||
<span class="page-number">2 / N</span>
|
||||
</div>
|
||||
|
||||
<!-- 更多 slides... -->
|
||||
|
||||
<p class="nav-hint">← → 翻页 · F 全屏 · ESC 退出</p>
|
||||
|
||||
<script>
|
||||
const slides = document.querySelectorAll('.slide');
|
||||
const progress = document.getElementById('progress');
|
||||
let current = 0;
|
||||
|
||||
function showSlide(n) {
|
||||
slides[current].classList.remove('active');
|
||||
current = (n + slides.length) % slides.length;
|
||||
slides[current].classList.add('active');
|
||||
progress.style.width = ((current + 1) / slides.length * 100) + '%';
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowRight' || e.key === ' ') showSlide(current + 1);
|
||||
if (e.key === 'ArrowLeft') showSlide(current - 1);
|
||||
if (e.key === 'f' || e.key === 'F') {
|
||||
document.documentElement.requestFullscreen?.();
|
||||
}
|
||||
});
|
||||
|
||||
// 触摸滑动支持
|
||||
let touchStartX = 0;
|
||||
document.addEventListener('touchstart', e => touchStartX = e.touches[0].clientX);
|
||||
document.addEventListener('touchend', e => {
|
||||
const diff = e.changedTouches[0].clientX - touchStartX;
|
||||
if (Math.abs(diff) > 50) showSlide(current + (diff > 0 ? -1 : 1));
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### BP PPT 标准页面结构
|
||||
|
||||
生成商业计划书 PPT 时,按以下结构组织:
|
||||
|
||||
| 页码 | 页面类型 | 内容要点 | 设计建议 |
|
||||
|------|----------|----------|----------|
|
||||
| 1 | 封面 | 公司名、Slogan、日期 | 居中大标题,渐变背景 |
|
||||
| 2 | 公司定位 | 一句话定位 + 核心数据 | 3-4 个数据卡片 |
|
||||
| 3 | 行业痛点 | 3-4 个核心痛点 | 图标 + 简短描述 |
|
||||
| 4 | 解决方案 | 产品价值主张 | 流程图或产品矩阵 |
|
||||
| 5 | 商业模式 | 收入来源、盈利方式 | 模式图 + 收入占比 |
|
||||
| 6 | 市场规模 | TAM/SAM/SOM | 同心圆图 + 数字 |
|
||||
| 7 | 竞争分析 | 竞争矩阵、差异化 | 2x2 矩阵图 |
|
||||
| 8 | 运营数据 | 核心指标、增长趋势 | 4 个大数字卡片 |
|
||||
| 9 | 客户案例 | 标杆客户、成果 | Logo + 数据对比 |
|
||||
| 10 | 团队介绍 | 核心成员背景 | 头像 + 简介 |
|
||||
| 11 | 里程碑 | 发展历程 | 时间线 |
|
||||
| 12 | 财务预测 | 3 年收入预测 | 柱状图示意 |
|
||||
| 13 | 战略规划 | 未来规划 | 阶段性目标 |
|
||||
| 14 | 融资计划 | 金额、用途、联系方式 | 饼图 + 联系信息 |
|
||||
|
||||
### PPT 设计原则
|
||||
|
||||
1. **一页一重点**:每页只传达一个核心信息
|
||||
2. **大字少字**:标题 48px+,正文 24px+,每页文字不超过 50 字
|
||||
3. **数据可视化**:用数字卡片、图表代替文字描述
|
||||
4. **留白充足**:内容不要铺满,保持呼吸感
|
||||
5. **配色统一**:使用 2-3 种主色,保持一致性
|
||||
6. **高对比度**:深色背景 + 浅色文字,或浅色背景 + 深色文字
|
||||
|
||||
### 配色方案
|
||||
|
||||
**深色科技风**(推荐):
|
||||
- 背景:#1a1a2e → #16213e(渐变)
|
||||
- 主色:#667eea(蓝紫色)
|
||||
- 辅助:#764ba2(紫色)
|
||||
- 文字:#e2e8f0(浅灰)
|
||||
- 次要文字:#a0aec0(中灰)
|
||||
|
||||
**浅色商务风**:
|
||||
- 背景:#ffffff
|
||||
- 主色:#2196F3(蓝色)
|
||||
- 辅助:#1976D2(深蓝)
|
||||
- 文字:#212121(深灰)
|
||||
- 次要文字:#616161(中灰)
|
||||
|
||||
### 使用示例
|
||||
|
||||
用户请求:
|
||||
> 帮我生成商业计划书 PPT
|
||||
|
||||
输出方式:
|
||||
1. 生成完整的 HTML 文件
|
||||
2. 保存到用户指定位置(如桌面、项目目录)
|
||||
3. 用户用浏览器打开即可演示
|
||||
|
||||
```bash
|
||||
# 打开 PPT
|
||||
open ~/Desktop/bp-presentation.html
|
||||
|
||||
# 或在浏览器中打开后按 F 进入全屏模式
|
||||
```
|
||||
|
||||
### 导出为 PDF
|
||||
|
||||
HTML PPT 可通过浏览器打印功能导出为 PDF:
|
||||
1. 在浏览器中打开 HTML 文件
|
||||
2. 按 Cmd+P (Mac) 或 Ctrl+P (Windows)
|
||||
3. 选择"另存为 PDF"
|
||||
4. 设置页面大小为横向 A4 或 16:9
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. **内容优先**:先确定 BP 内容,再生成 PPT
|
||||
2. **数据占位**:敏感数据可用 [X] 占位
|
||||
3. **迭代优化**:可分页生成,逐步完善
|
||||
4. **本地演示**:HTML 文件完全离线可用,无需网络
|
||||
@@ -4,5 +4,9 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "finance",
|
||||
"install_type": "skill",
|
||||
"dir_category": "biz",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: finance
|
||||
description: 财务对账技能。支持银行对账、应收对账、应付对账、内部往来对账等。通过自然语言实现账单导入、差异分析、对账报告生成。当用户提到对账、银行流水、应收应付、账单核对、财务核销相关任务时自动激活。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 财务对账技能
|
||||
|
||||
## 功能概述
|
||||
|
||||
- **银行对账**: 银行流水 vs 系统收付款记录匹配
|
||||
@@ -478,457 +478,6 @@ def export_reconciliation_to_excel(
|
||||
|
||||
---
|
||||
|
||||
## 常见对账场景
|
||||
|
||||
### 场景1: 月末银行对账
|
||||
|
||||
```python
|
||||
# 1. 加载数据
|
||||
bank_df = load_bank_statement('银行流水_202601.xlsx')
|
||||
system_df = load_system_records('收付款记录_202601.xlsx')
|
||||
|
||||
# 2. 执行对账
|
||||
result = reconcile(bank_df, system_df)
|
||||
|
||||
# 3. 查看汇总
|
||||
print(f"已匹配: {result['summary']['matched_count']} 笔")
|
||||
print(f"银行未达: {result['summary']['bank_only_count']} 笔, 金额 {result['summary']['bank_only_amount']:,.2f}")
|
||||
print(f"企业未达: {result['summary']['system_only_count']} 笔, 金额 {result['summary']['system_only_amount']:,.2f}")
|
||||
|
||||
# 4. 生成调节表
|
||||
report = generate_bank_reconciliation_report(
|
||||
bank_balance=150000.00, # 银行对账单余额
|
||||
book_balance=145000.00, # 企业账面余额
|
||||
bank_only=result['bank_only'],
|
||||
system_only=result['system_only'],
|
||||
as_of_date='2026-01-31'
|
||||
)
|
||||
print(report)
|
||||
|
||||
# 5. 导出 Excel
|
||||
export_reconciliation_to_excel(result, '银行对账_202601.xlsx', '2026-01-31')
|
||||
```
|
||||
|
||||
### 场景2: 客户对账
|
||||
|
||||
```python
|
||||
# 加载客户交易明细
|
||||
ar_transactions = pd.read_excel('应收明细.xlsx')
|
||||
|
||||
# 筛选特定客户
|
||||
customer_data = ar_transactions[ar_transactions['customer'] == '深圳XX公司']
|
||||
|
||||
# 生成对账单
|
||||
statement = generate_ar_statement(
|
||||
customer_name='深圳XX公司',
|
||||
transactions=customer_data,
|
||||
as_of_date='2026-01-31'
|
||||
)
|
||||
print(statement)
|
||||
```
|
||||
|
||||
### 场景3: 账龄分析
|
||||
|
||||
```python
|
||||
# 加载未收款数据
|
||||
receivables = pd.read_excel('应收账款.xlsx')
|
||||
|
||||
# 账龄分析
|
||||
aging = aging_analysis(
|
||||
receivables=receivables,
|
||||
as_of_date='2026-01-31',
|
||||
aging_buckets=[30, 60, 90, 180, 365]
|
||||
)
|
||||
print(aging)
|
||||
|
||||
# 导出
|
||||
aging.to_excel('账龄分析_202601.xlsx')
|
||||
```
|
||||
|
||||
### 场景4: 扫描单据OCR识别与对账匹配(诉讼证据核对)
|
||||
|
||||
适用于:承运商/供应商提供扫描件作为诉讼证据,需要与我司对账单进行匹配核对。
|
||||
|
||||
```python
|
||||
from pdf2image import convert_from_path
|
||||
import pytesseract
|
||||
import pandas as pd
|
||||
import re
|
||||
|
||||
def ocr_pdf_extract_orders(pdf_path: str, dpi: int = 150) -> pd.DataFrame:
|
||||
"""
|
||||
从 PDF 扫描件中 OCR 提取订单信息
|
||||
|
||||
Args:
|
||||
pdf_path: PDF 文件路径
|
||||
dpi: 图像分辨率,越高识别越准但速度越慢
|
||||
|
||||
Returns:
|
||||
DataFrame 包含 order_no, page, tc_no 等字段
|
||||
"""
|
||||
print(f"正在转换 PDF...")
|
||||
images = convert_from_path(pdf_path, dpi=dpi)
|
||||
print(f"共 {len(images)} 页,开始 OCR 识别...")
|
||||
|
||||
all_records = []
|
||||
for i, img in enumerate(images, 1):
|
||||
if i % 10 == 0:
|
||||
print(f" 处理进度: {i}/{len(images)}")
|
||||
|
||||
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
|
||||
|
||||
# 提取多种订单号格式(根据实际业务调整)
|
||||
hm_orders = re.findall(r'(HM\d{12,15})(?:-\d+)?', text) # 华住订单
|
||||
hpo_orders = re.findall(r'(HPO-\d{8}-\d+)', text) # 华住采购订单
|
||||
fy_orders = re.findall(r'(FY\d{9,})', text) # 凤悦订单
|
||||
phgco_orders = re.findall(r'(PHGCO\d{9,})', text) # 锦江订单
|
||||
tc_orders = re.findall(r'(TC\d{8,})', text) # 运单号
|
||||
|
||||
# 提取日期
|
||||
dates = re.findall(r'(\d{4}-\d{2}-\d{2})', text)
|
||||
|
||||
all_orders = hm_orders + hpo_orders + fy_orders + phgco_orders
|
||||
for order in all_orders:
|
||||
all_records.append({
|
||||
'order_no': order,
|
||||
'page': i,
|
||||
'tc_no': tc_orders[0] if tc_orders else None,
|
||||
'date': dates[0] if dates else None,
|
||||
'raw_text': text[:500] # 保留部分原文用于核查
|
||||
})
|
||||
|
||||
df = pd.DataFrame(all_records)
|
||||
df = df.drop_duplicates(subset=['order_no'])
|
||||
print(f"提取完成,共 {len(df)} 条唯一订单")
|
||||
return df
|
||||
|
||||
|
||||
def extract_base_order_no(order_no: str) -> str:
|
||||
"""
|
||||
提取基础订单号(移除后缀如 -1, -2)
|
||||
|
||||
例如: HM202505227320-1 -> HM202505227320
|
||||
"""
|
||||
if pd.isna(order_no):
|
||||
return None
|
||||
order_str = str(order_no)
|
||||
match = re.match(r'(HM\d{12,15}|HPO-\d{8}-\d+|FY\d{9,}|PHGCO\d{9,})', order_str)
|
||||
return match.group(1) if match else order_str
|
||||
|
||||
|
||||
def reconcile_scanned_with_excel(
|
||||
scanned_df: pd.DataFrame,
|
||||
excel_path: str,
|
||||
main_sheet: str,
|
||||
pending_sheet: str = None,
|
||||
order_column: str = '华住/锦江订单号'
|
||||
) -> dict:
|
||||
"""
|
||||
扫描单据与 Excel 对账单匹配
|
||||
|
||||
Args:
|
||||
scanned_df: OCR 提取的订单 DataFrame
|
||||
excel_path: Excel 对账单路径
|
||||
main_sheet: 主数据 Sheet 名称
|
||||
pending_sheet: 待结算/次月结 Sheet 名称(可选)
|
||||
order_column: 订单号列名
|
||||
|
||||
Returns:
|
||||
{
|
||||
'matched_main': 在主表中匹配的订单,
|
||||
'matched_pending': 在待结算表中匹配的订单,
|
||||
'not_found': 未找到的订单,
|
||||
'summary': 汇总统计
|
||||
}
|
||||
"""
|
||||
# 加载主表
|
||||
df_main = pd.read_excel(excel_path, sheet_name=main_sheet)
|
||||
# 处理表头(如果第一行是表头)
|
||||
if df_main.iloc[0].astype(str).str.contains(order_column).any():
|
||||
df_main.columns = df_main.iloc[0]
|
||||
df_main = df_main.iloc[1:].reset_index(drop=True)
|
||||
|
||||
# 提取基础订单号
|
||||
main_orders = set(df_main[order_column].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 加载待结算表(如有)
|
||||
pending_orders = set()
|
||||
df_pending = None
|
||||
if pending_sheet:
|
||||
try:
|
||||
df_pending = pd.read_excel(excel_path, sheet_name=pending_sheet)
|
||||
pending_orders = set(df_pending[order_column].apply(extract_base_order_no).dropna())
|
||||
except:
|
||||
pass
|
||||
|
||||
all_excel_orders = main_orders | pending_orders
|
||||
|
||||
# 扫描件订单号
|
||||
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 匹配分析
|
||||
matched_main = scan_orders & main_orders
|
||||
matched_pending = scan_orders & pending_orders
|
||||
not_found = scan_orders - all_excel_orders
|
||||
|
||||
# 汇总
|
||||
summary = {
|
||||
'scan_total': len(scan_orders),
|
||||
'matched_main_count': len(matched_main),
|
||||
'matched_pending_count': len(matched_pending),
|
||||
'not_found_count': len(not_found),
|
||||
'match_rate': (len(matched_main) + len(matched_pending)) / len(scan_orders) * 100 if scan_orders else 0
|
||||
}
|
||||
|
||||
return {
|
||||
'matched_main': matched_main,
|
||||
'matched_pending': matched_pending,
|
||||
'not_found': not_found,
|
||||
'summary': summary,
|
||||
'df_main': df_main,
|
||||
'df_pending': df_pending
|
||||
}
|
||||
|
||||
|
||||
def generate_litigation_reconciliation_report(
|
||||
result: dict,
|
||||
scanned_df: pd.DataFrame,
|
||||
output_path: str
|
||||
):
|
||||
"""
|
||||
生成诉讼对账分析报告
|
||||
"""
|
||||
summary = result['summary']
|
||||
|
||||
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
||||
# Sheet1: 汇总
|
||||
summary_df = pd.DataFrame({
|
||||
'项目': [
|
||||
'扫描件订单数',
|
||||
'在主对账单中找到',
|
||||
'在待结算表中找到',
|
||||
'完全未找到',
|
||||
'匹配率'
|
||||
],
|
||||
'数值': [
|
||||
summary['scan_total'],
|
||||
summary['matched_main_count'],
|
||||
summary['matched_pending_count'],
|
||||
summary['not_found_count'],
|
||||
f"{summary['match_rate']:.1f}%"
|
||||
]
|
||||
})
|
||||
summary_df.to_excel(writer, sheet_name='对账汇总', index=False)
|
||||
|
||||
# Sheet2: 未找到的订单
|
||||
if result['not_found']:
|
||||
not_found_df = scanned_df[
|
||||
scanned_df['order_no'].apply(extract_base_order_no).isin(result['not_found'])
|
||||
][['order_no', 'page', 'tc_no', 'date']]
|
||||
not_found_df.columns = ['订单号', 'PDF页码', '运单号', '日期']
|
||||
not_found_df.to_excel(writer, sheet_name='未找到订单', index=False)
|
||||
|
||||
# Sheet3: 待结算订单详情
|
||||
if result['matched_pending'] and result['df_pending'] is not None:
|
||||
pending_detail = result['df_pending'][
|
||||
result['df_pending']['华住/锦江订单号'].apply(extract_base_order_no).isin(result['matched_pending'])
|
||||
]
|
||||
pending_detail.to_excel(writer, sheet_name='待结算订单', index=False)
|
||||
|
||||
print(f"报告已保存: {output_path}")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```python
|
||||
# 1. 从 PDF 扫描件提取订单
|
||||
pdf_path = "承运商证据_6月业务.pdf"
|
||||
scanned_df = ocr_pdf_extract_orders(pdf_path, dpi=150)
|
||||
|
||||
# 2. 与 Excel 对账单匹配
|
||||
result = reconcile_scanned_with_excel(
|
||||
scanned_df=scanned_df,
|
||||
excel_path="对账单_2025年6月.xlsx",
|
||||
main_sheet="2025.6",
|
||||
pending_sheet="次月结",
|
||||
order_column="华住/锦江订单号"
|
||||
)
|
||||
|
||||
# 3. 查看匹配结果
|
||||
print(f"匹配率: {result['summary']['match_rate']:.1f}%")
|
||||
print(f"未找到: {result['summary']['not_found_count']} 条")
|
||||
|
||||
if result['not_found']:
|
||||
print("\n未找到的订单:")
|
||||
for order in result['not_found']:
|
||||
print(f" {order}")
|
||||
|
||||
# 4. 生成报告
|
||||
generate_litigation_reconciliation_report(
|
||||
result=result,
|
||||
scanned_df=scanned_df,
|
||||
output_path="诉讼对账分析报告.xlsx"
|
||||
)
|
||||
```
|
||||
|
||||
#### OCR 识别注意事项
|
||||
|
||||
1. **环境依赖**:
|
||||
```bash
|
||||
# macOS
|
||||
brew install tesseract tesseract-lang poppler
|
||||
pip install pdf2image pytesseract pandas openpyxl
|
||||
```
|
||||
|
||||
2. **识别精度**:
|
||||
- DPI 150 适合快速扫描,DPI 200-250 适合精确识别
|
||||
- 中文识别需要 `chi_sim` 语言包
|
||||
|
||||
3. **订单号格式差异**:
|
||||
- PDF 中可能是 `HM202505227320`
|
||||
- Excel 中可能是 `HM202505227320-1`、`HM202505227320-2`(带后缀)
|
||||
- 匹配时应使用基础订单号
|
||||
|
||||
4. **常见问题**:
|
||||
- 空白页(签收单背面)会被自动跳过
|
||||
- OCR 可能误识别字符(如 5→S, 0→O),建议人工抽查
|
||||
- 大 PDF 处理较慢,可先用低 DPI 预览
|
||||
|
||||
5. **"次月结"/"待结算" 订单**:
|
||||
- 这类订单通常标记为"没有报价"、"未建单"等
|
||||
- 虽然在系统中有记录,但可能未计入正式结算金额
|
||||
- 诉讼中需特别关注这类订单的状态
|
||||
|
||||
6. **多承运商订单归属校验(重要风险点)**:
|
||||
- 承运商可能将其他公司的订单混入证据,企图多收运费
|
||||
- 必须检查PDF订单是否出现在其他承运商的对账单中
|
||||
- 如果订单同时出现在多个承运商对账单中,则为异常订单
|
||||
|
||||
#### 多承运商订单归属校验
|
||||
|
||||
```python
|
||||
def check_order_ownership(
|
||||
scanned_df: pd.DataFrame,
|
||||
my_company_excel: str,
|
||||
other_carrier_excels: list[str],
|
||||
order_column: str = '华住/锦江订单号'
|
||||
) -> dict:
|
||||
"""
|
||||
检查订单归属,识别是否有其他承运商的订单被混入
|
||||
|
||||
Args:
|
||||
scanned_df: OCR 提取的订单 DataFrame
|
||||
my_company_excel: 我司对账单路径
|
||||
other_carrier_excels: 其他承运商对账单路径列表
|
||||
order_column: 订单号列名
|
||||
|
||||
Returns:
|
||||
{
|
||||
'my_orders': 属于我司的订单,
|
||||
'other_carrier_orders': 属于其他承运商的订单(异常),
|
||||
'disputed_orders': 同时出现在多处的争议订单,
|
||||
'summary': 汇总
|
||||
}
|
||||
"""
|
||||
# 提取PDF订单
|
||||
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 加载我司对账单订单
|
||||
my_orders = set()
|
||||
try:
|
||||
xl = pd.ExcelFile(my_company_excel)
|
||||
for sheet in xl.sheet_names:
|
||||
df = pd.read_excel(my_company_excel, sheet_name=sheet)
|
||||
for col in df.columns:
|
||||
if '订单' in str(col):
|
||||
orders = df[col].apply(extract_base_order_no).dropna()
|
||||
my_orders.update(orders)
|
||||
except Exception as e:
|
||||
print(f"加载我司对账单失败: {e}")
|
||||
|
||||
# 加载其他承运商对账单订单
|
||||
other_orders = {} # {carrier_name: set of orders}
|
||||
for excel_path in other_carrier_excels:
|
||||
carrier_name = excel_path.split('/')[-1]
|
||||
carrier_orders = set()
|
||||
try:
|
||||
xl = pd.ExcelFile(excel_path)
|
||||
for sheet in xl.sheet_names:
|
||||
df = pd.read_excel(excel_path, sheet_name=sheet)
|
||||
for col in df.columns:
|
||||
if '订单' in str(col):
|
||||
orders = df[col].apply(extract_base_order_no).dropna()
|
||||
carrier_orders.update(orders)
|
||||
other_orders[carrier_name] = carrier_orders
|
||||
except:
|
||||
pass
|
||||
|
||||
# 分析订单归属
|
||||
all_other_orders = set()
|
||||
for orders in other_orders.values():
|
||||
all_other_orders.update(orders)
|
||||
|
||||
# 分类
|
||||
only_mine = scan_orders & my_orders - all_other_orders # 仅在我司
|
||||
only_others = scan_orders & all_other_orders - my_orders # 仅在其他承运商(异常!)
|
||||
in_both = scan_orders & my_orders & all_other_orders # 同时出现(争议)
|
||||
nowhere = scan_orders - my_orders - all_other_orders # 哪里都没有
|
||||
|
||||
# 识别异常订单属于哪个承运商
|
||||
abnormal_details = []
|
||||
for order in only_others:
|
||||
for carrier, orders in other_orders.items():
|
||||
if order in orders:
|
||||
page = scanned_df[scanned_df['order_no'].apply(extract_base_order_no) == order]['page'].values
|
||||
abnormal_details.append({
|
||||
'order_no': order,
|
||||
'belongs_to': carrier,
|
||||
'pdf_page': page[0] if len(page) > 0 else None
|
||||
})
|
||||
|
||||
summary = {
|
||||
'scan_total': len(scan_orders),
|
||||
'my_orders_count': len(only_mine),
|
||||
'other_carrier_count': len(only_others), # 异常订单数
|
||||
'disputed_count': len(in_both),
|
||||
'not_found_count': len(nowhere)
|
||||
}
|
||||
|
||||
return {
|
||||
'my_orders': only_mine,
|
||||
'other_carrier_orders': only_others,
|
||||
'abnormal_details': abnormal_details,
|
||||
'disputed_orders': in_both,
|
||||
'not_found': nowhere,
|
||||
'summary': summary
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用示例(多承运商校验)
|
||||
|
||||
```python
|
||||
# 检查订单归属
|
||||
result = check_order_ownership(
|
||||
scanned_df=scanned_df,
|
||||
my_company_excel="智慧云彩对账单_2025年6月.xlsx",
|
||||
other_carrier_excels=[
|
||||
"友利速运对账单_2025年6月.xlsx",
|
||||
"其他承运商对账单.xlsx"
|
||||
]
|
||||
)
|
||||
|
||||
# 检查是否有异常订单
|
||||
if result['summary']['other_carrier_count'] > 0:
|
||||
print(f"⚠️ 发现 {result['summary']['other_carrier_count']} 个异常订单!")
|
||||
print("这些订单属于其他承运商,不应由我司支付:")
|
||||
for item in result['abnormal_details']:
|
||||
print(f" {item['order_no']} -> 属于 {item['belongs_to']} (PDF第{item['pdf_page']}页)")
|
||||
else:
|
||||
print("✓ 未发现异常订单,所有订单归属正确")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **数据格式统一**: 确保日期、金额格式一致后再对账
|
||||
@@ -946,3 +495,9 @@ else:
|
||||
- **data-excel**: 处理复杂的 Excel 数据转换
|
||||
- **feishu**: 将对账报告保存到飞书云文档
|
||||
- **siyuan**: 记录对账工作日志到思源笔记
|
||||
|
||||
## 详细内容
|
||||
|
||||
以下章节已移到 [references/details.md](references/details.md):
|
||||
|
||||
- 常见对账场景
|
||||
|
||||
@@ -0,0 +1,453 @@
|
||||
# skills 详细内容
|
||||
|
||||
## 常见对账场景
|
||||
|
||||
### 场景1: 月末银行对账
|
||||
|
||||
```python
|
||||
# 1. 加载数据
|
||||
bank_df = load_bank_statement('银行流水_202601.xlsx')
|
||||
system_df = load_system_records('收付款记录_202601.xlsx')
|
||||
|
||||
# 2. 执行对账
|
||||
result = reconcile(bank_df, system_df)
|
||||
|
||||
# 3. 查看汇总
|
||||
print(f"已匹配: {result['summary']['matched_count']} 笔")
|
||||
print(f"银行未达: {result['summary']['bank_only_count']} 笔, 金额 {result['summary']['bank_only_amount']:,.2f}")
|
||||
print(f"企业未达: {result['summary']['system_only_count']} 笔, 金额 {result['summary']['system_only_amount']:,.2f}")
|
||||
|
||||
# 4. 生成调节表
|
||||
report = generate_bank_reconciliation_report(
|
||||
bank_balance=150000.00, # 银行对账单余额
|
||||
book_balance=145000.00, # 企业账面余额
|
||||
bank_only=result['bank_only'],
|
||||
system_only=result['system_only'],
|
||||
as_of_date='2026-01-31'
|
||||
)
|
||||
print(report)
|
||||
|
||||
# 5. 导出 Excel
|
||||
export_reconciliation_to_excel(result, '银行对账_202601.xlsx', '2026-01-31')
|
||||
```
|
||||
|
||||
### 场景2: 客户对账
|
||||
|
||||
```python
|
||||
# 加载客户交易明细
|
||||
ar_transactions = pd.read_excel('应收明细.xlsx')
|
||||
|
||||
# 筛选特定客户
|
||||
customer_data = ar_transactions[ar_transactions['customer'] == '深圳XX公司']
|
||||
|
||||
# 生成对账单
|
||||
statement = generate_ar_statement(
|
||||
customer_name='深圳XX公司',
|
||||
transactions=customer_data,
|
||||
as_of_date='2026-01-31'
|
||||
)
|
||||
print(statement)
|
||||
```
|
||||
|
||||
### 场景3: 账龄分析
|
||||
|
||||
```python
|
||||
# 加载未收款数据
|
||||
receivables = pd.read_excel('应收账款.xlsx')
|
||||
|
||||
# 账龄分析
|
||||
aging = aging_analysis(
|
||||
receivables=receivables,
|
||||
as_of_date='2026-01-31',
|
||||
aging_buckets=[30, 60, 90, 180, 365]
|
||||
)
|
||||
print(aging)
|
||||
|
||||
# 导出
|
||||
aging.to_excel('账龄分析_202601.xlsx')
|
||||
```
|
||||
|
||||
### 场景4: 扫描单据OCR识别与对账匹配(诉讼证据核对)
|
||||
|
||||
适用于:承运商/供应商提供扫描件作为诉讼证据,需要与我司对账单进行匹配核对。
|
||||
|
||||
```python
|
||||
from pdf2image import convert_from_path
|
||||
import pytesseract
|
||||
import pandas as pd
|
||||
import re
|
||||
|
||||
def ocr_pdf_extract_orders(pdf_path: str, dpi: int = 150) -> pd.DataFrame:
|
||||
"""
|
||||
从 PDF 扫描件中 OCR 提取订单信息
|
||||
|
||||
Args:
|
||||
pdf_path: PDF 文件路径
|
||||
dpi: 图像分辨率,越高识别越准但速度越慢
|
||||
|
||||
Returns:
|
||||
DataFrame 包含 order_no, page, tc_no 等字段
|
||||
"""
|
||||
print(f"正在转换 PDF...")
|
||||
images = convert_from_path(pdf_path, dpi=dpi)
|
||||
print(f"共 {len(images)} 页,开始 OCR 识别...")
|
||||
|
||||
all_records = []
|
||||
for i, img in enumerate(images, 1):
|
||||
if i % 10 == 0:
|
||||
print(f" 处理进度: {i}/{len(images)}")
|
||||
|
||||
text = pytesseract.image_to_string(img, lang='chi_sim+eng')
|
||||
|
||||
# 提取多种订单号格式(根据实际业务调整)
|
||||
hm_orders = re.findall(r'(HM\d{12,15})(?:-\d+)?', text) # 华住订单
|
||||
hpo_orders = re.findall(r'(HPO-\d{8}-\d+)', text) # 华住采购订单
|
||||
fy_orders = re.findall(r'(FY\d{9,})', text) # 凤悦订单
|
||||
phgco_orders = re.findall(r'(PHGCO\d{9,})', text) # 锦江订单
|
||||
tc_orders = re.findall(r'(TC\d{8,})', text) # 运单号
|
||||
|
||||
# 提取日期
|
||||
dates = re.findall(r'(\d{4}-\d{2}-\d{2})', text)
|
||||
|
||||
all_orders = hm_orders + hpo_orders + fy_orders + phgco_orders
|
||||
for order in all_orders:
|
||||
all_records.append({
|
||||
'order_no': order,
|
||||
'page': i,
|
||||
'tc_no': tc_orders[0] if tc_orders else None,
|
||||
'date': dates[0] if dates else None,
|
||||
'raw_text': text[:500] # 保留部分原文用于核查
|
||||
})
|
||||
|
||||
df = pd.DataFrame(all_records)
|
||||
df = df.drop_duplicates(subset=['order_no'])
|
||||
print(f"提取完成,共 {len(df)} 条唯一订单")
|
||||
return df
|
||||
|
||||
|
||||
def extract_base_order_no(order_no: str) -> str:
|
||||
"""
|
||||
提取基础订单号(移除后缀如 -1, -2)
|
||||
|
||||
例如: HM202505227320-1 -> HM202505227320
|
||||
"""
|
||||
if pd.isna(order_no):
|
||||
return None
|
||||
order_str = str(order_no)
|
||||
match = re.match(r'(HM\d{12,15}|HPO-\d{8}-\d+|FY\d{9,}|PHGCO\d{9,})', order_str)
|
||||
return match.group(1) if match else order_str
|
||||
|
||||
|
||||
def reconcile_scanned_with_excel(
|
||||
scanned_df: pd.DataFrame,
|
||||
excel_path: str,
|
||||
main_sheet: str,
|
||||
pending_sheet: str = None,
|
||||
order_column: str = '华住/锦江订单号'
|
||||
) -> dict:
|
||||
"""
|
||||
扫描单据与 Excel 对账单匹配
|
||||
|
||||
Args:
|
||||
scanned_df: OCR 提取的订单 DataFrame
|
||||
excel_path: Excel 对账单路径
|
||||
main_sheet: 主数据 Sheet 名称
|
||||
pending_sheet: 待结算/次月结 Sheet 名称(可选)
|
||||
order_column: 订单号列名
|
||||
|
||||
Returns:
|
||||
{
|
||||
'matched_main': 在主表中匹配的订单,
|
||||
'matched_pending': 在待结算表中匹配的订单,
|
||||
'not_found': 未找到的订单,
|
||||
'summary': 汇总统计
|
||||
}
|
||||
"""
|
||||
# 加载主表
|
||||
df_main = pd.read_excel(excel_path, sheet_name=main_sheet)
|
||||
# 处理表头(如果第一行是表头)
|
||||
if df_main.iloc[0].astype(str).str.contains(order_column).any():
|
||||
df_main.columns = df_main.iloc[0]
|
||||
df_main = df_main.iloc[1:].reset_index(drop=True)
|
||||
|
||||
# 提取基础订单号
|
||||
main_orders = set(df_main[order_column].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 加载待结算表(如有)
|
||||
pending_orders = set()
|
||||
df_pending = None
|
||||
if pending_sheet:
|
||||
try:
|
||||
df_pending = pd.read_excel(excel_path, sheet_name=pending_sheet)
|
||||
pending_orders = set(df_pending[order_column].apply(extract_base_order_no).dropna())
|
||||
except:
|
||||
pass
|
||||
|
||||
all_excel_orders = main_orders | pending_orders
|
||||
|
||||
# 扫描件订单号
|
||||
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 匹配分析
|
||||
matched_main = scan_orders & main_orders
|
||||
matched_pending = scan_orders & pending_orders
|
||||
not_found = scan_orders - all_excel_orders
|
||||
|
||||
# 汇总
|
||||
summary = {
|
||||
'scan_total': len(scan_orders),
|
||||
'matched_main_count': len(matched_main),
|
||||
'matched_pending_count': len(matched_pending),
|
||||
'not_found_count': len(not_found),
|
||||
'match_rate': (len(matched_main) + len(matched_pending)) / len(scan_orders) * 100 if scan_orders else 0
|
||||
}
|
||||
|
||||
return {
|
||||
'matched_main': matched_main,
|
||||
'matched_pending': matched_pending,
|
||||
'not_found': not_found,
|
||||
'summary': summary,
|
||||
'df_main': df_main,
|
||||
'df_pending': df_pending
|
||||
}
|
||||
|
||||
|
||||
def generate_litigation_reconciliation_report(
|
||||
result: dict,
|
||||
scanned_df: pd.DataFrame,
|
||||
output_path: str
|
||||
):
|
||||
"""
|
||||
生成诉讼对账分析报告
|
||||
"""
|
||||
summary = result['summary']
|
||||
|
||||
with pd.ExcelWriter(output_path, engine='openpyxl') as writer:
|
||||
# Sheet1: 汇总
|
||||
summary_df = pd.DataFrame({
|
||||
'项目': [
|
||||
'扫描件订单数',
|
||||
'在主对账单中找到',
|
||||
'在待结算表中找到',
|
||||
'完全未找到',
|
||||
'匹配率'
|
||||
],
|
||||
'数值': [
|
||||
summary['scan_total'],
|
||||
summary['matched_main_count'],
|
||||
summary['matched_pending_count'],
|
||||
summary['not_found_count'],
|
||||
f"{summary['match_rate']:.1f}%"
|
||||
]
|
||||
})
|
||||
summary_df.to_excel(writer, sheet_name='对账汇总', index=False)
|
||||
|
||||
# Sheet2: 未找到的订单
|
||||
if result['not_found']:
|
||||
not_found_df = scanned_df[
|
||||
scanned_df['order_no'].apply(extract_base_order_no).isin(result['not_found'])
|
||||
][['order_no', 'page', 'tc_no', 'date']]
|
||||
not_found_df.columns = ['订单号', 'PDF页码', '运单号', '日期']
|
||||
not_found_df.to_excel(writer, sheet_name='未找到订单', index=False)
|
||||
|
||||
# Sheet3: 待结算订单详情
|
||||
if result['matched_pending'] and result['df_pending'] is not None:
|
||||
pending_detail = result['df_pending'][
|
||||
result['df_pending']['华住/锦江订单号'].apply(extract_base_order_no).isin(result['matched_pending'])
|
||||
]
|
||||
pending_detail.to_excel(writer, sheet_name='待结算订单', index=False)
|
||||
|
||||
print(f"报告已保存: {output_path}")
|
||||
```
|
||||
|
||||
#### 使用示例
|
||||
|
||||
```python
|
||||
# 1. 从 PDF 扫描件提取订单
|
||||
pdf_path = "承运商证据_6月业务.pdf"
|
||||
scanned_df = ocr_pdf_extract_orders(pdf_path, dpi=150)
|
||||
|
||||
# 2. 与 Excel 对账单匹配
|
||||
result = reconcile_scanned_with_excel(
|
||||
scanned_df=scanned_df,
|
||||
excel_path="对账单_2025年6月.xlsx",
|
||||
main_sheet="2025.6",
|
||||
pending_sheet="次月结",
|
||||
order_column="华住/锦江订单号"
|
||||
)
|
||||
|
||||
# 3. 查看匹配结果
|
||||
print(f"匹配率: {result['summary']['match_rate']:.1f}%")
|
||||
print(f"未找到: {result['summary']['not_found_count']} 条")
|
||||
|
||||
if result['not_found']:
|
||||
print("\n未找到的订单:")
|
||||
for order in result['not_found']:
|
||||
print(f" {order}")
|
||||
|
||||
# 4. 生成报告
|
||||
generate_litigation_reconciliation_report(
|
||||
result=result,
|
||||
scanned_df=scanned_df,
|
||||
output_path="诉讼对账分析报告.xlsx"
|
||||
)
|
||||
```
|
||||
|
||||
#### OCR 识别注意事项
|
||||
|
||||
1. **环境依赖**:
|
||||
```bash
|
||||
# macOS
|
||||
brew install tesseract tesseract-lang poppler
|
||||
pip install pdf2image pytesseract pandas openpyxl
|
||||
```
|
||||
|
||||
2. **识别精度**:
|
||||
- DPI 150 适合快速扫描,DPI 200-250 适合精确识别
|
||||
- 中文识别需要 `chi_sim` 语言包
|
||||
|
||||
3. **订单号格式差异**:
|
||||
- PDF 中可能是 `HM202505227320`
|
||||
- Excel 中可能是 `HM202505227320-1`、`HM202505227320-2`(带后缀)
|
||||
- 匹配时应使用基础订单号
|
||||
|
||||
4. **常见问题**:
|
||||
- 空白页(签收单背面)会被自动跳过
|
||||
- OCR 可能误识别字符(如 5→S, 0→O),建议人工抽查
|
||||
- 大 PDF 处理较慢,可先用低 DPI 预览
|
||||
|
||||
5. **"次月结"/"待结算" 订单**:
|
||||
- 这类订单通常标记为"没有报价"、"未建单"等
|
||||
- 虽然在系统中有记录,但可能未计入正式结算金额
|
||||
- 诉讼中需特别关注这类订单的状态
|
||||
|
||||
6. **多承运商订单归属校验(重要风险点)**:
|
||||
- 承运商可能将其他公司的订单混入证据,企图多收运费
|
||||
- 必须检查PDF订单是否出现在其他承运商的对账单中
|
||||
- 如果订单同时出现在多个承运商对账单中,则为异常订单
|
||||
|
||||
#### 多承运商订单归属校验
|
||||
|
||||
```python
|
||||
def check_order_ownership(
|
||||
scanned_df: pd.DataFrame,
|
||||
my_company_excel: str,
|
||||
other_carrier_excels: list[str],
|
||||
order_column: str = '华住/锦江订单号'
|
||||
) -> dict:
|
||||
"""
|
||||
检查订单归属,识别是否有其他承运商的订单被混入
|
||||
|
||||
Args:
|
||||
scanned_df: OCR 提取的订单 DataFrame
|
||||
my_company_excel: 我司对账单路径
|
||||
other_carrier_excels: 其他承运商对账单路径列表
|
||||
order_column: 订单号列名
|
||||
|
||||
Returns:
|
||||
{
|
||||
'my_orders': 属于我司的订单,
|
||||
'other_carrier_orders': 属于其他承运商的订单(异常),
|
||||
'disputed_orders': 同时出现在多处的争议订单,
|
||||
'summary': 汇总
|
||||
}
|
||||
"""
|
||||
# 提取PDF订单
|
||||
scan_orders = set(scanned_df['order_no'].apply(extract_base_order_no).dropna())
|
||||
|
||||
# 加载我司对账单订单
|
||||
my_orders = set()
|
||||
try:
|
||||
xl = pd.ExcelFile(my_company_excel)
|
||||
for sheet in xl.sheet_names:
|
||||
df = pd.read_excel(my_company_excel, sheet_name=sheet)
|
||||
for col in df.columns:
|
||||
if '订单' in str(col):
|
||||
orders = df[col].apply(extract_base_order_no).dropna()
|
||||
my_orders.update(orders)
|
||||
except Exception as e:
|
||||
print(f"加载我司对账单失败: {e}")
|
||||
|
||||
# 加载其他承运商对账单订单
|
||||
other_orders = {} # {carrier_name: set of orders}
|
||||
for excel_path in other_carrier_excels:
|
||||
carrier_name = excel_path.split('/')[-1]
|
||||
carrier_orders = set()
|
||||
try:
|
||||
xl = pd.ExcelFile(excel_path)
|
||||
for sheet in xl.sheet_names:
|
||||
df = pd.read_excel(excel_path, sheet_name=sheet)
|
||||
for col in df.columns:
|
||||
if '订单' in str(col):
|
||||
orders = df[col].apply(extract_base_order_no).dropna()
|
||||
carrier_orders.update(orders)
|
||||
other_orders[carrier_name] = carrier_orders
|
||||
except:
|
||||
pass
|
||||
|
||||
# 分析订单归属
|
||||
all_other_orders = set()
|
||||
for orders in other_orders.values():
|
||||
all_other_orders.update(orders)
|
||||
|
||||
# 分类
|
||||
only_mine = scan_orders & my_orders - all_other_orders # 仅在我司
|
||||
only_others = scan_orders & all_other_orders - my_orders # 仅在其他承运商(异常!)
|
||||
in_both = scan_orders & my_orders & all_other_orders # 同时出现(争议)
|
||||
nowhere = scan_orders - my_orders - all_other_orders # 哪里都没有
|
||||
|
||||
# 识别异常订单属于哪个承运商
|
||||
abnormal_details = []
|
||||
for order in only_others:
|
||||
for carrier, orders in other_orders.items():
|
||||
if order in orders:
|
||||
page = scanned_df[scanned_df['order_no'].apply(extract_base_order_no) == order]['page'].values
|
||||
abnormal_details.append({
|
||||
'order_no': order,
|
||||
'belongs_to': carrier,
|
||||
'pdf_page': page[0] if len(page) > 0 else None
|
||||
})
|
||||
|
||||
summary = {
|
||||
'scan_total': len(scan_orders),
|
||||
'my_orders_count': len(only_mine),
|
||||
'other_carrier_count': len(only_others), # 异常订单数
|
||||
'disputed_count': len(in_both),
|
||||
'not_found_count': len(nowhere)
|
||||
}
|
||||
|
||||
return {
|
||||
'my_orders': only_mine,
|
||||
'other_carrier_orders': only_others,
|
||||
'abnormal_details': abnormal_details,
|
||||
'disputed_orders': in_both,
|
||||
'not_found': nowhere,
|
||||
'summary': summary
|
||||
}
|
||||
```
|
||||
|
||||
#### 使用示例(多承运商校验)
|
||||
|
||||
```python
|
||||
# 检查订单归属
|
||||
result = check_order_ownership(
|
||||
scanned_df=scanned_df,
|
||||
my_company_excel="智慧云彩对账单_2025年6月.xlsx",
|
||||
other_carrier_excels=[
|
||||
"友利速运对账单_2025年6月.xlsx",
|
||||
"其他承运商对账单.xlsx"
|
||||
]
|
||||
)
|
||||
|
||||
# 检查是否有异常订单
|
||||
if result['summary']['other_carrier_count'] > 0:
|
||||
print(f"⚠️ 发现 {result['summary']['other_carrier_count']} 个异常订单!")
|
||||
print("这些订单属于其他承运商,不应由我司支付:")
|
||||
for item in result['abnormal_details']:
|
||||
print(f" {item['order_no']} -> 属于 {item['belongs_to']} (PDF第{item['pdf_page']}页)")
|
||||
else:
|
||||
print("✓ 未发现异常订单,所有订单归属正确")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "ai-proj-helper-entry-plugin",
|
||||
"description": "技能库路由:列出技能、什么任务用哪个、交付终点口径。由人主动调用,不占模型目录 token。",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "ai-proj-helper-entry",
|
||||
"install_type": "skill",
|
||||
"dir_category": "core",
|
||||
"status": "deprecated",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: ai-proj-helper-entry
|
||||
description: ai-proj-helper 技能库路由:告诉你有哪些技能、什么任务用哪个、以及「REQ released + PDV 通过才算交付」的强制口径。人在开始需求/开发/评审/发布任务前主动调用。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
owner: qiudl
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# ai-proj-helper 入口(路由器)
|
||||
|
||||
## 0. 这个技能是什么
|
||||
|
||||
一个**纯人触发**的路由器:人问「该用哪个技能 / 从哪开始」,就读这里。它不进模型目录,所以不占每次会话的目录 token;代价是必须由人主动喊。
|
||||
|
||||
> 路由规则:**任何技能增、删、改名、改行为后,必须回改本文件**。一个没提到新技能、或还指向已删技能的路由器,就是假的路由器(见 `skill-creator` §9)。
|
||||
|
||||
## 1. 交付终点(强制,优先于路由)
|
||||
|
||||
开发任务的交付终点是:需求按 REQ 流程推进到具备发布条件并完成上线(`delivery_stage=released` + PDV 验收通过)。
|
||||
|
||||
- 写代码、过单测、提 PR、过 internal gate、合并代码,都是过程节点,**不得当作交付终点**;
|
||||
- 任务 `completed` ≠ 需求完成;REQ 未 `released` 不得报完成;
|
||||
- 汇报格式:**当前 REQ 阶段 + 下一个要做的节点**;
|
||||
- 只有必须由人拍板/授权的阻塞(生产发布授权、密钥权限、需求变更、无法自动修复的失败)才停下来问,并写清卡点与下一步。
|
||||
|
||||
## 2. 主流程:需求 → 上线
|
||||
|
||||
```
|
||||
讨论(req) → PRD(req-prd) → 评审(req-review) → 设计(req-design)
|
||||
→ 开发(dev-coding / karpathy-guidelines) → 测试(dev-test + req-test-gate)
|
||||
→ 代码评审(dev-review) → 发布(publish / slark-cicd / slark-dsh-cicd) → PDV 验收 → released
|
||||
```
|
||||
|
||||
| 阶段 | 技能 |
|
||||
| --- | --- |
|
||||
| 讨论/调研 | `req`、`req-research`、`req-compare` |
|
||||
| PRD | `req-prd`、`req-prototype` |
|
||||
| 评审 | `req-review` |
|
||||
| 设计/拆任务 | `req-design`、`dev-arch`、`db-migration` |
|
||||
| 编码 | `dev-coding`、`karpathy-guidelines` |
|
||||
| 测试 | `dev-test`、`req-test-gate` |
|
||||
| 代码评审 | `dev-review`、`review-checklist` |
|
||||
| 发布 | `publish`、`slark-cicd`、`slark-dsh-cicd`、`deploy-rollback` |
|
||||
| 部署后 | `req-audit`、`req-lookback` |
|
||||
| 复盘 | `req-retro` |
|
||||
| 项目管理 | `project-management`、`pm-risk`、`pm-ask` |
|
||||
| 建/改技能 | `skill-creator` |
|
||||
| 汇报完成前 | `verification-before-completion` |
|
||||
|
||||
## 3. 按任务类型的旁路
|
||||
|
||||
- 「这个 bug 很难查」→ `diagnosing-bugs`(先建红循环再推理)
|
||||
- 「帮我审这个 PR」→ `dev-review`
|
||||
- 「发版」→ 对应 `*-cicd` runbook,不是通用 `dev-deploy`
|
||||
- 「不确定该用哪个」→ 读到这里就够了,别猜
|
||||
|
||||
## 4. 红线
|
||||
|
||||
- 不得自审批(提交人不能批自己的 REQ)
|
||||
- 不得绕过门禁(测试 Gate、发布 Gate、归档 Gate)
|
||||
- 不得把「某步骤完成」当结束
|
||||
- 不得无证据声称完成(见 `verification-before-completion`)
|
||||
- 不得把凭据/内网地址/绝对路径写进技能或文档
|
||||
|
||||
## 5. 技能从哪来
|
||||
|
||||
- 用户级:`~/.agents/skills/`(Codex)、`$DSH_HOME/skills/`(DSH)
|
||||
- 项目级:项目根 `.agents/skills/`
|
||||
- **真相源**:`ai-proj-helper` 仓库;本机目录是安装产物,不要直接改
|
||||
- 个人技能:`skills-personal/`(不入库、不发布)
|
||||
|
||||
## 6. 维护检查
|
||||
|
||||
- `./install-skills.sh --audit` 漂移数 > 0 → 先同步再干活
|
||||
- 技能有增删改 → 本文件 + `catalog.json` 必须同 PR 回改
|
||||
- 每季度人工过一遍本路由,删掉不再用的技能引用
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"skill_name": "ai-proj-helper-entry",
|
||||
"skill_version": "0.1.0",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"kind": "happy_path",
|
||||
"prompt": "我要给 qiu-slark 做个新功能,从哪开始",
|
||||
"expected_output": "给出 req → req-prd → req-review → req-design 的路径,并声明交付终点是 released + PDV,不是写完代码"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"kind": "edge_case",
|
||||
"prompt": "我已经把单测跑过了,这个需求可以算完成了吗",
|
||||
"expected_output": "否定:单测只是过程节点,REQ 未 released 不算完成;给出当前阶段与下一节点"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"kind": "failure_recovery",
|
||||
"prompt": "你说的那个技能我机器上没装,怎么办",
|
||||
"expected_output": "说明技能真相源在 ai-proj-helper 仓库、本机目录是安装产物;给出 install-skills.sh 安装/审计命令"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "2.0.1",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "ai-proj",
|
||||
"install_type": "skill",
|
||||
"dir_category": "core"
|
||||
}
|
||||
|
||||
@@ -2,11 +2,6 @@
|
||||
name: ai-proj
|
||||
description: ai-proj 任务与需求管理。用于创建/查询/更新需求、任务、文档。当用户提到 ai-proj、任务管理、需求管理、REQ-XXX 相关操作时自动激活。
|
||||
---
|
||||
|
||||
# ai-proj MCP 技能
|
||||
|
||||
通过 MCP (Model Context Protocol) 直接调用 ai-proj 服务,管理任务、需求和文档。
|
||||
|
||||
## 功能概述
|
||||
|
||||
| 模块 | 功能 |
|
||||
@@ -572,102 +567,6 @@ approved → [archive] → archived
|
||||
|
||||
---
|
||||
|
||||
## 六、每日聚焦
|
||||
|
||||
### 6.1 查看今日任务
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__get_daily_focus_tasks`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| date | string | ❌ | 日期 (YYYY-MM-DD) |
|
||||
| status | string | ❌ | 状态筛选: active/completed/removed |
|
||||
| priority | string | ❌ | 优先级筛选 |
|
||||
| include_suggestions | boolean | ❌ | 是否包含智能推荐 |
|
||||
|
||||
### 6.2 添加任务到今日聚焦
|
||||
|
||||
**单个添加**: `mcp__ai-proj__add_daily_focus_task`
|
||||
```
|
||||
参数:
|
||||
- task_id (number) - 任务ID
|
||||
- priority (string) - 优先级: critical/high/medium/low
|
||||
- estimated_duration_minutes (number) - 预估时长
|
||||
- notes (string) - 备注
|
||||
- focus_date (string) - 聚焦日期
|
||||
```
|
||||
|
||||
**批量添加**: `mcp__ai-proj__batch_add_daily_focus_tasks`
|
||||
```
|
||||
参数:
|
||||
- task_ids (array) - 任务ID列表
|
||||
- priority (string) - 统一优先级
|
||||
- notes (string) - 统一备注
|
||||
- focus_date (string)
|
||||
```
|
||||
|
||||
**快速添加当前任务**: `mcp__ai-proj__quick_add_current_task`
|
||||
```
|
||||
参数:
|
||||
- priority (string) - 优先级,默认high
|
||||
- notes (string)
|
||||
功能: 将当前正在进行的任务添加到今日聚焦
|
||||
```
|
||||
|
||||
### 6.3 管理今日任务
|
||||
|
||||
**更新**: `mcp__ai-proj__update_daily_focus_task`
|
||||
```
|
||||
参数:
|
||||
- id (number) - Daily Focus任务ID
|
||||
- priority, estimated_duration_minutes, notes
|
||||
```
|
||||
|
||||
**移除**: `mcp__ai-proj__remove_daily_focus_task`
|
||||
```
|
||||
参数: id (number)
|
||||
```
|
||||
|
||||
**完成**: `mcp__ai-proj__complete_daily_focus_task`
|
||||
```
|
||||
参数: id (number)
|
||||
```
|
||||
|
||||
**聚焦并计时**: `mcp__ai-proj__focus_task_with_timer`
|
||||
```
|
||||
参数:
|
||||
- daily_focus_task_id (number)
|
||||
- timer_description (string)
|
||||
```
|
||||
|
||||
**清理已完成**: `mcp__ai-proj__clear_completed_tasks`
|
||||
```
|
||||
参数:
|
||||
- date (string) - 日期
|
||||
- confirm (boolean) - 必须设为true
|
||||
```
|
||||
|
||||
### 6.4 统计与推荐
|
||||
|
||||
**获取统计**: `mcp__ai-proj__get_daily_focus_stats`
|
||||
```
|
||||
参数:
|
||||
- date (string)
|
||||
- period (string) - 统计周期: daily/weekly/monthly
|
||||
```
|
||||
|
||||
**获取任务推荐**: `mcp__ai-proj__get_task_recommendations`
|
||||
```
|
||||
参数:
|
||||
- date (string)
|
||||
- limit (number) - 推荐数量
|
||||
- exclude_existing (boolean) - 排除已存在任务
|
||||
返回: 基于优先级、截止日期等的智能推荐
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、项目管理
|
||||
|
||||
### 7.1 查看项目列表
|
||||
@@ -696,90 +595,6 @@ approved → [archive] → archived
|
||||
|
||||
---
|
||||
|
||||
## 八、项目手册
|
||||
|
||||
### 8.1 查看手册列表
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__list_manuals`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| status | string | ❌ | 状态筛选: draft/published/archived |
|
||||
| parentId | number | ❌ | 父级手册ID(获取子级列表) |
|
||||
| search | string | ❌ | 搜索关键词(标题或描述) |
|
||||
|
||||
**自然语言示例**:
|
||||
- "查看项目手册列表"
|
||||
- "列出已发布的手册"
|
||||
- "查看手册 5 的子章节"
|
||||
|
||||
### 8.2 创建手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__create_manual`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| title | string | ✅ | 手册标题 |
|
||||
| content | string | ❌ | 手册内容(Markdown格式) |
|
||||
| description | string | ❌ | 手册简介 |
|
||||
| parentId | number | ❌ | 父级手册ID(用于创建子章节) |
|
||||
| status | string | ❌ | 状态: draft/published,默认draft |
|
||||
|
||||
**自然语言示例**:
|
||||
- "创建项目手册: API 开发规范"
|
||||
- "在手册 5 下创建子章节: 接口设计规范"
|
||||
- "创建并发布手册: 前端开发规范"
|
||||
|
||||
### 8.3 获取手册详情
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__get_manual`
|
||||
|
||||
**参数**: `id` (number) - 手册ID
|
||||
|
||||
**返回**: 完整的手册内容(Markdown格式)
|
||||
|
||||
**自然语言示例**:
|
||||
- "查看手册 5 的内容"
|
||||
- "显示 API 开发规范手册"
|
||||
|
||||
### 8.4 更新手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__update_manual`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | number | ✅ | 手册ID |
|
||||
| title | string | ❌ | 新标题 |
|
||||
| content | string | ❌ | 新内容(Markdown格式) |
|
||||
| description | string | ❌ | 新简介 |
|
||||
| status | string | ❌ | 新状态: draft/published/archived |
|
||||
|
||||
**自然语言示例**:
|
||||
- "更新手册 5 的内容"
|
||||
- "发布手册 5"
|
||||
- "归档手册 5"
|
||||
|
||||
### 8.5 搜索手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__search_manuals`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| query | string | ✅ | 搜索关键词 |
|
||||
|
||||
**自然语言示例**:
|
||||
- "搜索项目手册中包含 API 的文档"
|
||||
- "查找数据库相关的手册"
|
||||
|
||||
---
|
||||
|
||||
## 九、报告与时间线
|
||||
|
||||
### 9.1 获取日报
|
||||
@@ -830,83 +645,6 @@ approved → [archive] → archived
|
||||
|
||||
---
|
||||
|
||||
## 十一、远程同步
|
||||
|
||||
### 11.1 任务同步
|
||||
|
||||
**同步任务**: `mcp__ai-proj__sync_task`
|
||||
```
|
||||
参数:
|
||||
- taskId (number)
|
||||
- direction (string) - to_remote/from_remote
|
||||
- includeDocument (boolean) - 是否同步文档
|
||||
- includeChildren (boolean) - 是否同步子任务
|
||||
```
|
||||
|
||||
**批量同步到远程**: `mcp__ai-proj__batch_sync_tasks_to_remote`
|
||||
```
|
||||
参数:
|
||||
- taskIds (array)
|
||||
- includeDocuments (boolean)
|
||||
- includeChildren (boolean)
|
||||
```
|
||||
|
||||
**获取同步状态**: `mcp__ai-proj__get_task_sync_status`
|
||||
```
|
||||
参数: taskId (number)
|
||||
```
|
||||
|
||||
**获取远程同步状态**: `mcp__ai-proj__get_remote_sync_status`
|
||||
```
|
||||
参数: taskId (number)
|
||||
返回: 比较本地和远程版本差异
|
||||
```
|
||||
|
||||
**通过UUID获取任务**: `mcp__ai-proj__get_task_by_uuid`
|
||||
```
|
||||
参数: uuid (string)
|
||||
用途: 跨库同步场景
|
||||
```
|
||||
|
||||
### 11.2 需求同步
|
||||
|
||||
**同步需求到远程**: `mcp__ai-proj__sync_requirement_to_remote`
|
||||
```
|
||||
参数:
|
||||
- requirementId (number)
|
||||
- includeHistory (boolean)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**从远程同步需求**: `mcp__ai-proj__sync_requirement_from_remote`
|
||||
```
|
||||
参数:
|
||||
- remoteRequirementId (number)
|
||||
- includeHistory (boolean)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**批量同步需求**: `mcp__ai-proj__batch_sync_requirements_to_remote`
|
||||
```
|
||||
参数:
|
||||
- requirementIds (array)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**同步需求-任务关联**: `mcp__ai-proj__sync_requirement_task_links`
|
||||
```
|
||||
参数:
|
||||
- linkUUIDs (array) - 关联关系的UUID列表
|
||||
- forceUpdate (boolean)
|
||||
```
|
||||
|
||||
**同步任务的需求关联**: `mcp__ai-proj__sync_task_links_to_remote`
|
||||
```
|
||||
参数: taskId (number)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 十二、开发工具
|
||||
|
||||
### 12.1 快速登录
|
||||
@@ -919,52 +657,6 @@ approved → [archive] → archived
|
||||
|
||||
---
|
||||
|
||||
## 自然语言操作示例
|
||||
|
||||
### 任务管理
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "创建一个任务叫做登录功能开发" | `create_task` |
|
||||
| "查看所有进行中的任务" | `list_tasks` |
|
||||
| "开始任务 5069" | `start_task` |
|
||||
| "完成任务 5069" | `complete_task` |
|
||||
| "暂停任务 5069" | `pause_task` |
|
||||
| "把任务 5069 移到项目2" | `move_task` |
|
||||
| "给任务 5069 创建子任务: 数据库设计" | `create_subtask` |
|
||||
|
||||
### 需求管理
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "创建需求: 用户权限管理" | `create_requirement` |
|
||||
| "查看待审核的需求" | `list_requirements` |
|
||||
| "提交需求 123 评审" | `requirement_action (submit)` |
|
||||
| "批准需求 123" | `requirement_action (approve)` |
|
||||
| "拒绝需求 123,原因是需求不完整" | `requirement_action (reject)` |
|
||||
| "将任务 5069 关联到需求 123" | `link_tasks_to_requirement` |
|
||||
|
||||
### 文档操作
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "为任务 5069 创建PRD文档" | `create-and-attach` |
|
||||
| "查看任务 5069 的文档" | `get_task_document` |
|
||||
| "更新任务 5069 的文档" | `update_task_document` |
|
||||
| "导出任务 5069 的文档到文件" | `export_task_document_to_file` |
|
||||
|
||||
### 计时与聚焦
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "开始计时任务 5069" | `start_task_with_timer` |
|
||||
| "停止计时" | `stop_timer` |
|
||||
| "查看今日任务" | `get_daily_focus_tasks` |
|
||||
| "把任务 5069 添加到今日聚焦" | `add_daily_focus_task` |
|
||||
| "生成今日工作报告" | `get_daily_work_report` |
|
||||
|
||||
---
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **项目ID默认值**: 大多数函数的 `projectId` 默认为 1
|
||||
@@ -1013,229 +705,6 @@ go run /tmp/genhash.go
|
||||
|
||||
---
|
||||
|
||||
## Google 日历集成配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
生产环境需要配置以下环境变量(`.env` 文件):
|
||||
|
||||
```bash
|
||||
GOOGLE_CLIENT_ID=<你的Client ID>.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=GOCSPX-<你的Client Secret>
|
||||
GOOGLE_PROJECT_ID=<你的项目ID>
|
||||
GOOGLE_REDIRECT_URL=https://ai.pipexerp.com/api/v1/auth/google/callback
|
||||
GOOGLE_CALENDAR_SCOPES=https://www.googleapis.com/auth/calendar
|
||||
```
|
||||
|
||||
### 获取 Google OAuth 凭据
|
||||
|
||||
#### 1. 创建 Google Cloud 项目
|
||||
|
||||
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. 创建新项目或选择现有项目
|
||||
3. 记录项目 ID
|
||||
|
||||
#### 2. 启用 API
|
||||
|
||||
1. 进入 **APIs & Services** → **Library**
|
||||
2. 搜索并启用 **Google Calendar API**
|
||||
|
||||
#### 3. 配置 OAuth 同意屏幕
|
||||
|
||||
1. 进入 **APIs & Services** → **OAuth consent screen**
|
||||
2. 选择 **External** → 创建
|
||||
3. 填写应用名称、支持邮箱
|
||||
4. 添加 Scopes:
|
||||
- `https://www.googleapis.com/auth/calendar`
|
||||
- `https://www.googleapis.com/auth/calendar.events`
|
||||
5. 添加测试用户(开发阶段)
|
||||
|
||||
#### 4. 创建 OAuth 凭据
|
||||
|
||||
1. 进入 **APIs & Services** → **Credentials**
|
||||
2. 点击 **+ CREATE CREDENTIALS** → **OAuth client ID**
|
||||
3. 选择 **Web application**
|
||||
4. 配置:
|
||||
- **Name**: `AI-Proj Web Client`
|
||||
- **Authorized JavaScript origins**: `https://ai.pipexerp.com`
|
||||
- **Authorized redirect URIs**: `https://ai.pipexerp.com/api/v1/auth/google/callback`
|
||||
5. 复制 Client ID 和 Client Secret
|
||||
|
||||
### 部署配置
|
||||
|
||||
```bash
|
||||
# 更新生产环境配置
|
||||
ssh tools_ai_proj "cd /home/ubuntu/apps/new-ai-proj && \
|
||||
sed -i 's|GOOGLE_CLIENT_ID=.*|GOOGLE_CLIENT_ID=<CLIENT_ID>|' .env && \
|
||||
sed -i 's|GOOGLE_CLIENT_SECRET=.*|GOOGLE_CLIENT_SECRET=<CLIENT_SECRET>|' .env && \
|
||||
sed -i 's|GOOGLE_PROJECT_ID=.*|GOOGLE_PROJECT_ID=<PROJECT_ID>|' .env && \
|
||||
docker restart ai_backend_prod"
|
||||
```
|
||||
|
||||
### 数据库迁移
|
||||
|
||||
Google 日历集成需要以下表:
|
||||
|
||||
```sql
|
||||
-- OAuth 状态表(CSRF 保护)
|
||||
CREATE TABLE oauth_states (
|
||||
id SERIAL PRIMARY KEY,
|
||||
state VARCHAR(255) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 项目日历配置表
|
||||
-- 见 migrations/20260210_01_project_calendar.sql
|
||||
|
||||
-- 日历 Webhook 表
|
||||
-- 见 migrations/20260211_01_calendar_webhooks.sql
|
||||
```
|
||||
|
||||
### 当前配置(2026-02)
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| Project ID | `project-bfa64769-1595-4c41-964` |
|
||||
| Client ID | `78721641513-...apps.googleusercontent.com` |
|
||||
| Redirect URL | `https://ai.pipexerp.com/api/v1/auth/google/callback` |
|
||||
|
||||
> **注意**: Client Secret 不在文档中记录,存储在生产环境 `.env` 文件中
|
||||
|
||||
---
|
||||
|
||||
## 十三、最佳实践与故障排查
|
||||
|
||||
### 13.1 创建需求的最佳实践
|
||||
|
||||
#### 问题:description 字段"请求数据格式错误"
|
||||
|
||||
**症状**:
|
||||
```json
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求数据格式错误"
|
||||
}
|
||||
```
|
||||
|
||||
**常见原因**:
|
||||
|
||||
| 原因 | 解决方案 |
|
||||
|------|----------|
|
||||
| **description 内容过长** | API 可能有字段长度限制,逐步测试找到上限 |
|
||||
| **Markdown 特殊字符** | 检查是否有未转义的特殊字符(如反引号、引号) |
|
||||
| **JSON 转义问题** | 确保 JSON 字符串正确转义(尤其是换行符、引号) |
|
||||
| **多层嵌套的代码块** | Markdown 代码块中的反引号可能导致解析失败 |
|
||||
|
||||
**正确的调试流程**:
|
||||
|
||||
```python
|
||||
# ❌ 错误做法:直接简化内容
|
||||
"创建失败 → 简化 description → 成功"
|
||||
问题:丢失了重要的需求细节
|
||||
|
||||
# ✅ 正确做法:找到根本原因
|
||||
步骤 1: 测试空描述
|
||||
create_requirement(description="")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 2: 测试纯文本
|
||||
create_requirement(description="简单文本")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 3: 测试简单 Markdown
|
||||
create_requirement(description="# 标题\n\n内容")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 4: 逐步增加复杂度
|
||||
create_requirement(description="包含代码块的Markdown")
|
||||
create_requirement(description="包含表格的Markdown")
|
||||
create_requirement(description="完整的技术方案")
|
||||
→ 找到临界点
|
||||
|
||||
步骤 5: 根据根因选择方案
|
||||
- 如果是长度限制 → 分段或使用附件
|
||||
- 如果是特殊字符 → 转义或替换
|
||||
- 如果是 Markdown → 简化格式或转纯文本
|
||||
```
|
||||
|
||||
**推荐方案**:
|
||||
|
||||
**方案 A:完整的需求 + 关联文档**
|
||||
```typescript
|
||||
// 1. 创建需求(简洁描述)
|
||||
const req = await create_requirement({
|
||||
title: "思源笔记增加 MCP 协议层支持",
|
||||
description: "核心方案:创建独立的 siyuan-mcp-bridge 服务...",
|
||||
projectId: 162
|
||||
});
|
||||
|
||||
// 2. 创建详细的技术方案文档
|
||||
const doc = await create_requirement_document({
|
||||
requirementId: req.id,
|
||||
title: "技术方案详细设计",
|
||||
content: `
|
||||
# 需求背景
|
||||
...(完整的技术方案,3000+ 字)
|
||||
|
||||
# 架构设计
|
||||
...
|
||||
|
||||
# 实施路线图
|
||||
...
|
||||
`
|
||||
});
|
||||
```
|
||||
|
||||
**方案 B:分段创建需求描述**
|
||||
```typescript
|
||||
// 1. 创建需求(基本信息)
|
||||
const req = await create_requirement({
|
||||
title: "思源笔记增加 MCP 协议层支持",
|
||||
description: "简短描述",
|
||||
projectId: 162
|
||||
});
|
||||
|
||||
// 2. 更新需求(逐步添加内容)
|
||||
await update_requirement({
|
||||
id: req.id,
|
||||
updates: {
|
||||
description: description_part1 // 添加背景
|
||||
}
|
||||
});
|
||||
|
||||
await update_requirement({
|
||||
id: req.id,
|
||||
updates: {
|
||||
description: description_part1 + description_part2 // 添加架构
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 经验总结
|
||||
|
||||
**原则 1:业务优先于技术**
|
||||
- ❌ 为了创建成功而牺牲需求完整性
|
||||
- ✅ 找到技术方案来满足业务需求
|
||||
|
||||
**原则 2:追根溯源**
|
||||
- ❌ 基于假设快速妥协("可能是太长了")
|
||||
- ✅ 通过调试找到真正的错误原因
|
||||
|
||||
**原则 3:给用户选择权**
|
||||
- ❌ 自作主张简化内容
|
||||
- ✅ 询问用户的偏好和优先级
|
||||
|
||||
**原则 4:保留完整信息**
|
||||
- ❌ 简化后的需求缺少关键信息
|
||||
- ✅ 通过关联文档保留详细内容
|
||||
|
||||
### 13.2 需求描述的格式建议
|
||||
|
||||
**推荐的需求描述结构**(简洁版):
|
||||
|
||||
```markdown
|
||||
## 核心方案
|
||||
1. 关键点 1(一句话)
|
||||
2. 关键点 2(一句话)
|
||||
@@ -1246,139 +715,15 @@ await update_requirement({
|
||||
- 第2层:组件名(职责)
|
||||
- 第3层:组件名(职责)
|
||||
|
||||
## 实施周期
|
||||
X周(阶段1 + 阶段2 + 阶段3)
|
||||
```
|
||||
|
||||
**详细内容放在关联文档**:
|
||||
- 需求背景与业务价值
|
||||
- 详细的技术方案
|
||||
- 实施路线图与时间规划
|
||||
- 风险评估与应对措施
|
||||
- 验收标准
|
||||
## 详细内容
|
||||
|
||||
**Markdown 格式注意事项**:
|
||||
以下章节已移到 [references/details.md](references/details.md):
|
||||
|
||||
```markdown
|
||||
✅ 安全的格式:
|
||||
- 简单的标题(# ## ###)
|
||||
- 无序列表(-)
|
||||
- 有序列表(1. 2. 3.)
|
||||
- 简单的表格
|
||||
- 加粗(**text**)
|
||||
|
||||
⚠️ 可能有问题的格式:
|
||||
- 三重反引号代码块(```)
|
||||
- HTML 标签
|
||||
- 复杂的嵌套列表
|
||||
- 大量的特殊字符
|
||||
- 超长的单行文本
|
||||
```
|
||||
|
||||
### 13.3 更新需求的最佳实践
|
||||
|
||||
**使用 update_requirement 补充详细内容**:
|
||||
|
||||
```typescript
|
||||
// 创建需求后,逐步更新描述
|
||||
await update_requirement({
|
||||
id: 857, // REQ-20260217-0001
|
||||
updates: {
|
||||
description: `
|
||||
## 需求背景
|
||||
|
||||
### 当前问题
|
||||
思源笔记仅提供 REST API 接口,AI 操作复杂...
|
||||
|
||||
### 业务价值
|
||||
为思源笔记添加 MCP 协议层支持,使其能够...
|
||||
|
||||
## 技术方案
|
||||
|
||||
### 架构设计
|
||||
...(详细内容)
|
||||
`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**测试长度限制**:
|
||||
|
||||
```bash
|
||||
# 逐步增加内容,找到上限
|
||||
第1次: 200字 → 成功
|
||||
第2次: 500字 → 成功
|
||||
第3次: 1000字 → 成功
|
||||
第4次: 2000字 → 成功
|
||||
第5次: 5000字 → 失败 ← 找到上限
|
||||
|
||||
结论: description 字段最大约 3000-4000 字
|
||||
```
|
||||
|
||||
### 13.4 故障排查清单
|
||||
|
||||
遇到 API 错误时,按以下步骤排查:
|
||||
|
||||
**步骤 1:分析错误信息**
|
||||
```json
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求数据格式错误"
|
||||
}
|
||||
```
|
||||
- [ ] 错误码是什么?(BAD_REQUEST / VALIDATION_ERROR / AUTH_FAILED)
|
||||
- [ ] 错误信息是否有具体提示?
|
||||
- [ ] 是哪个字段导致的错误?
|
||||
|
||||
**步骤 2:检查参数格式**
|
||||
- [ ] 必填参数是否都提供了?
|
||||
- [ ] 参数类型是否正确?(number vs string)
|
||||
- [ ] 枚举值是否在允许范围内?
|
||||
- [ ] JSON 格式是否正确?
|
||||
|
||||
**步骤 3:简化测试**
|
||||
- [ ] 尝试最小化的参数集
|
||||
- [ ] 逐个添加可选参数
|
||||
- [ ] 找到导致错误的具体参数
|
||||
|
||||
**步骤 4:查看后端日志**(如有权限)
|
||||
```bash
|
||||
# 生产环境日志
|
||||
ssh tools_ai_proj "docker logs ai_backend_prod --tail 100"
|
||||
|
||||
# 开发环境日志
|
||||
cd ~/coding/new-ai-proj/backend
|
||||
tail -f logs/app.log
|
||||
```
|
||||
|
||||
**步骤 5:参考类似的成功案例**
|
||||
- [ ] 查看 SKILL.md 中的示例
|
||||
- [ ] 查看之前成功的调用记录
|
||||
- [ ] 对比参数差异
|
||||
|
||||
### 13.5 常见错误与解决方案
|
||||
|
||||
| 错误 | 可能原因 | 解决方案 |
|
||||
|------|----------|----------|
|
||||
| `BAD_REQUEST` | 参数格式错误 | 检查参数类型和 JSON 格式 |
|
||||
| `VALIDATION_ERROR` | 参数验证失败 | 检查必填字段和枚举值 |
|
||||
| `AUTH_FAILED` | 认证失败 | 检查 Token 是否过期 |
|
||||
| `PERMISSION_DENIED` | 权限不足 | 检查用户角色和权限 |
|
||||
| `NOT_FOUND` | 资源不存在 | 检查 ID 是否正确 |
|
||||
| `INTERNAL_ERROR` | 服务器错误 | 查看后端日志排查 |
|
||||
|
||||
### 13.6 需求 description 长度测试记录
|
||||
|
||||
**测试日期**: 2026-02-17
|
||||
|
||||
| 长度 | 格式 | 结果 | 备注 |
|
||||
|------|------|------|------|
|
||||
| ~200字 | 简单文本 | ✅ 成功 | 核心方案3点 |
|
||||
| ~3000字 | 完整 Markdown | ❌ 失败 | "请求数据格式错误" |
|
||||
| 待测试 | 1000字 Markdown | ? | 需要进一步测试 |
|
||||
| 待测试 | 2000字 Markdown | ? | 需要进一步测试 |
|
||||
|
||||
**建议**:
|
||||
- 需求描述控制在 1000 字以内
|
||||
- 详细内容使用关联文档
|
||||
- 复杂格式改为纯文本
|
||||
- 六、每日聚焦
|
||||
- 八、项目手册
|
||||
- 十一、远程同步
|
||||
- 自然语言操作示例
|
||||
- Google 日历集成配置
|
||||
- 十三、最佳实践与故障排查
|
||||
- 实施周期
|
||||
|
||||
@@ -0,0 +1,664 @@
|
||||
# skills 详细内容
|
||||
|
||||
## 六、每日聚焦
|
||||
|
||||
### 6.1 查看今日任务
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__get_daily_focus_tasks`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| date | string | ❌ | 日期 (YYYY-MM-DD) |
|
||||
| status | string | ❌ | 状态筛选: active/completed/removed |
|
||||
| priority | string | ❌ | 优先级筛选 |
|
||||
| include_suggestions | boolean | ❌ | 是否包含智能推荐 |
|
||||
|
||||
### 6.2 添加任务到今日聚焦
|
||||
|
||||
**单个添加**: `mcp__ai-proj__add_daily_focus_task`
|
||||
```
|
||||
参数:
|
||||
- task_id (number) - 任务ID
|
||||
- priority (string) - 优先级: critical/high/medium/low
|
||||
- estimated_duration_minutes (number) - 预估时长
|
||||
- notes (string) - 备注
|
||||
- focus_date (string) - 聚焦日期
|
||||
```
|
||||
|
||||
**批量添加**: `mcp__ai-proj__batch_add_daily_focus_tasks`
|
||||
```
|
||||
参数:
|
||||
- task_ids (array) - 任务ID列表
|
||||
- priority (string) - 统一优先级
|
||||
- notes (string) - 统一备注
|
||||
- focus_date (string)
|
||||
```
|
||||
|
||||
**快速添加当前任务**: `mcp__ai-proj__quick_add_current_task`
|
||||
```
|
||||
参数:
|
||||
- priority (string) - 优先级,默认high
|
||||
- notes (string)
|
||||
功能: 将当前正在进行的任务添加到今日聚焦
|
||||
```
|
||||
|
||||
### 6.3 管理今日任务
|
||||
|
||||
**更新**: `mcp__ai-proj__update_daily_focus_task`
|
||||
```
|
||||
参数:
|
||||
- id (number) - Daily Focus任务ID
|
||||
- priority, estimated_duration_minutes, notes
|
||||
```
|
||||
|
||||
**移除**: `mcp__ai-proj__remove_daily_focus_task`
|
||||
```
|
||||
参数: id (number)
|
||||
```
|
||||
|
||||
**完成**: `mcp__ai-proj__complete_daily_focus_task`
|
||||
```
|
||||
参数: id (number)
|
||||
```
|
||||
|
||||
**聚焦并计时**: `mcp__ai-proj__focus_task_with_timer`
|
||||
```
|
||||
参数:
|
||||
- daily_focus_task_id (number)
|
||||
- timer_description (string)
|
||||
```
|
||||
|
||||
**清理已完成**: `mcp__ai-proj__clear_completed_tasks`
|
||||
```
|
||||
参数:
|
||||
- date (string) - 日期
|
||||
- confirm (boolean) - 必须设为true
|
||||
```
|
||||
|
||||
### 6.4 统计与推荐
|
||||
|
||||
**获取统计**: `mcp__ai-proj__get_daily_focus_stats`
|
||||
```
|
||||
参数:
|
||||
- date (string)
|
||||
- period (string) - 统计周期: daily/weekly/monthly
|
||||
```
|
||||
|
||||
**获取任务推荐**: `mcp__ai-proj__get_task_recommendations`
|
||||
```
|
||||
参数:
|
||||
- date (string)
|
||||
- limit (number) - 推荐数量
|
||||
- exclude_existing (boolean) - 排除已存在任务
|
||||
返回: 基于优先级、截止日期等的智能推荐
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、项目手册
|
||||
|
||||
### 8.1 查看手册列表
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__list_manuals`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| status | string | ❌ | 状态筛选: draft/published/archived |
|
||||
| parentId | number | ❌ | 父级手册ID(获取子级列表) |
|
||||
| search | string | ❌ | 搜索关键词(标题或描述) |
|
||||
|
||||
**自然语言示例**:
|
||||
- "查看项目手册列表"
|
||||
- "列出已发布的手册"
|
||||
- "查看手册 5 的子章节"
|
||||
|
||||
### 8.2 创建手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__create_manual`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| title | string | ✅ | 手册标题 |
|
||||
| content | string | ❌ | 手册内容(Markdown格式) |
|
||||
| description | string | ❌ | 手册简介 |
|
||||
| parentId | number | ❌ | 父级手册ID(用于创建子章节) |
|
||||
| status | string | ❌ | 状态: draft/published,默认draft |
|
||||
|
||||
**自然语言示例**:
|
||||
- "创建项目手册: API 开发规范"
|
||||
- "在手册 5 下创建子章节: 接口设计规范"
|
||||
- "创建并发布手册: 前端开发规范"
|
||||
|
||||
### 8.3 获取手册详情
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__get_manual`
|
||||
|
||||
**参数**: `id` (number) - 手册ID
|
||||
|
||||
**返回**: 完整的手册内容(Markdown格式)
|
||||
|
||||
**自然语言示例**:
|
||||
- "查看手册 5 的内容"
|
||||
- "显示 API 开发规范手册"
|
||||
|
||||
### 8.4 更新手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__update_manual`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | number | ✅ | 手册ID |
|
||||
| title | string | ❌ | 新标题 |
|
||||
| content | string | ❌ | 新内容(Markdown格式) |
|
||||
| description | string | ❌ | 新简介 |
|
||||
| status | string | ❌ | 新状态: draft/published/archived |
|
||||
|
||||
**自然语言示例**:
|
||||
- "更新手册 5 的内容"
|
||||
- "发布手册 5"
|
||||
- "归档手册 5"
|
||||
|
||||
### 8.5 搜索手册
|
||||
|
||||
**MCP 函数**: `mcp__ai-proj__search_manuals`
|
||||
|
||||
**参数**:
|
||||
| 参数 | 类型 | 必填 | 说明 |
|
||||
|------|------|------|------|
|
||||
| projectId | number | ✅ | 项目ID |
|
||||
| query | string | ✅ | 搜索关键词 |
|
||||
|
||||
**自然语言示例**:
|
||||
- "搜索项目手册中包含 API 的文档"
|
||||
- "查找数据库相关的手册"
|
||||
|
||||
---
|
||||
|
||||
## 十一、远程同步
|
||||
|
||||
### 11.1 任务同步
|
||||
|
||||
**同步任务**: `mcp__ai-proj__sync_task`
|
||||
```
|
||||
参数:
|
||||
- taskId (number)
|
||||
- direction (string) - to_remote/from_remote
|
||||
- includeDocument (boolean) - 是否同步文档
|
||||
- includeChildren (boolean) - 是否同步子任务
|
||||
```
|
||||
|
||||
**批量同步到远程**: `mcp__ai-proj__batch_sync_tasks_to_remote`
|
||||
```
|
||||
参数:
|
||||
- taskIds (array)
|
||||
- includeDocuments (boolean)
|
||||
- includeChildren (boolean)
|
||||
```
|
||||
|
||||
**获取同步状态**: `mcp__ai-proj__get_task_sync_status`
|
||||
```
|
||||
参数: taskId (number)
|
||||
```
|
||||
|
||||
**获取远程同步状态**: `mcp__ai-proj__get_remote_sync_status`
|
||||
```
|
||||
参数: taskId (number)
|
||||
返回: 比较本地和远程版本差异
|
||||
```
|
||||
|
||||
**通过UUID获取任务**: `mcp__ai-proj__get_task_by_uuid`
|
||||
```
|
||||
参数: uuid (string)
|
||||
用途: 跨库同步场景
|
||||
```
|
||||
|
||||
### 11.2 需求同步
|
||||
|
||||
**同步需求到远程**: `mcp__ai-proj__sync_requirement_to_remote`
|
||||
```
|
||||
参数:
|
||||
- requirementId (number)
|
||||
- includeHistory (boolean)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**从远程同步需求**: `mcp__ai-proj__sync_requirement_from_remote`
|
||||
```
|
||||
参数:
|
||||
- remoteRequirementId (number)
|
||||
- includeHistory (boolean)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**批量同步需求**: `mcp__ai-proj__batch_sync_requirements_to_remote`
|
||||
```
|
||||
参数:
|
||||
- requirementIds (array)
|
||||
- includeComments (boolean)
|
||||
```
|
||||
|
||||
**同步需求-任务关联**: `mcp__ai-proj__sync_requirement_task_links`
|
||||
```
|
||||
参数:
|
||||
- linkUUIDs (array) - 关联关系的UUID列表
|
||||
- forceUpdate (boolean)
|
||||
```
|
||||
|
||||
**同步任务的需求关联**: `mcp__ai-proj__sync_task_links_to_remote`
|
||||
```
|
||||
参数: taskId (number)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 自然语言操作示例
|
||||
|
||||
### 任务管理
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "创建一个任务叫做登录功能开发" | `create_task` |
|
||||
| "查看所有进行中的任务" | `list_tasks` |
|
||||
| "开始任务 5069" | `start_task` |
|
||||
| "完成任务 5069" | `complete_task` |
|
||||
| "暂停任务 5069" | `pause_task` |
|
||||
| "把任务 5069 移到项目2" | `move_task` |
|
||||
| "给任务 5069 创建子任务: 数据库设计" | `create_subtask` |
|
||||
|
||||
### 需求管理
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "创建需求: 用户权限管理" | `create_requirement` |
|
||||
| "查看待审核的需求" | `list_requirements` |
|
||||
| "提交需求 123 评审" | `requirement_action (submit)` |
|
||||
| "批准需求 123" | `requirement_action (approve)` |
|
||||
| "拒绝需求 123,原因是需求不完整" | `requirement_action (reject)` |
|
||||
| "将任务 5069 关联到需求 123" | `link_tasks_to_requirement` |
|
||||
|
||||
### 文档操作
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "为任务 5069 创建PRD文档" | `create-and-attach` |
|
||||
| "查看任务 5069 的文档" | `get_task_document` |
|
||||
| "更新任务 5069 的文档" | `update_task_document` |
|
||||
| "导出任务 5069 的文档到文件" | `export_task_document_to_file` |
|
||||
|
||||
### 计时与聚焦
|
||||
|
||||
| 用户说 | 调用函数 |
|
||||
|--------|----------|
|
||||
| "开始计时任务 5069" | `start_task_with_timer` |
|
||||
| "停止计时" | `stop_timer` |
|
||||
| "查看今日任务" | `get_daily_focus_tasks` |
|
||||
| "把任务 5069 添加到今日聚焦" | `add_daily_focus_task` |
|
||||
| "生成今日工作报告" | `get_daily_work_report` |
|
||||
|
||||
---
|
||||
|
||||
## Google 日历集成配置
|
||||
|
||||
### 环境变量
|
||||
|
||||
生产环境需要配置以下环境变量(`.env` 文件):
|
||||
|
||||
```bash
|
||||
GOOGLE_CLIENT_ID=<你的Client ID>.apps.googleusercontent.com
|
||||
GOOGLE_CLIENT_SECRET=GOCSPX-<你的Client Secret>
|
||||
GOOGLE_PROJECT_ID=<你的项目ID>
|
||||
GOOGLE_REDIRECT_URL=https://ai.pipexerp.com/api/v1/auth/google/callback
|
||||
GOOGLE_CALENDAR_SCOPES=https://www.googleapis.com/auth/calendar
|
||||
```
|
||||
|
||||
### 获取 Google OAuth 凭据
|
||||
|
||||
#### 1. 创建 Google Cloud 项目
|
||||
|
||||
1. 访问 [Google Cloud Console](https://console.cloud.google.com/)
|
||||
2. 创建新项目或选择现有项目
|
||||
3. 记录项目 ID
|
||||
|
||||
#### 2. 启用 API
|
||||
|
||||
1. 进入 **APIs & Services** → **Library**
|
||||
2. 搜索并启用 **Google Calendar API**
|
||||
|
||||
#### 3. 配置 OAuth 同意屏幕
|
||||
|
||||
1. 进入 **APIs & Services** → **OAuth consent screen**
|
||||
2. 选择 **External** → 创建
|
||||
3. 填写应用名称、支持邮箱
|
||||
4. 添加 Scopes:
|
||||
- `https://www.googleapis.com/auth/calendar`
|
||||
- `https://www.googleapis.com/auth/calendar.events`
|
||||
5. 添加测试用户(开发阶段)
|
||||
|
||||
#### 4. 创建 OAuth 凭据
|
||||
|
||||
1. 进入 **APIs & Services** → **Credentials**
|
||||
2. 点击 **+ CREATE CREDENTIALS** → **OAuth client ID**
|
||||
3. 选择 **Web application**
|
||||
4. 配置:
|
||||
- **Name**: `AI-Proj Web Client`
|
||||
- **Authorized JavaScript origins**: `https://ai.pipexerp.com`
|
||||
- **Authorized redirect URIs**: `https://ai.pipexerp.com/api/v1/auth/google/callback`
|
||||
5. 复制 Client ID 和 Client Secret
|
||||
|
||||
### 部署配置
|
||||
|
||||
```bash
|
||||
# 更新生产环境配置
|
||||
ssh tools_ai_proj "cd /home/ubuntu/apps/new-ai-proj && \
|
||||
sed -i 's|GOOGLE_CLIENT_ID=.*|GOOGLE_CLIENT_ID=<CLIENT_ID>|' .env && \
|
||||
sed -i 's|GOOGLE_CLIENT_SECRET=.*|GOOGLE_CLIENT_SECRET=<CLIENT_SECRET>|' .env && \
|
||||
sed -i 's|GOOGLE_PROJECT_ID=.*|GOOGLE_PROJECT_ID=<PROJECT_ID>|' .env && \
|
||||
docker restart ai_backend_prod"
|
||||
```
|
||||
|
||||
### 数据库迁移
|
||||
|
||||
Google 日历集成需要以下表:
|
||||
|
||||
```sql
|
||||
-- OAuth 状态表(CSRF 保护)
|
||||
CREATE TABLE oauth_states (
|
||||
id SERIAL PRIMARY KEY,
|
||||
state VARCHAR(255) NOT NULL UNIQUE,
|
||||
user_id INTEGER NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 项目日历配置表
|
||||
-- 见 migrations/20260210_01_project_calendar.sql
|
||||
|
||||
-- 日历 Webhook 表
|
||||
-- 见 migrations/20260211_01_calendar_webhooks.sql
|
||||
```
|
||||
|
||||
### 当前配置(2026-02)
|
||||
|
||||
| 配置项 | 值 |
|
||||
|--------|-----|
|
||||
| Project ID | `project-bfa64769-1595-4c41-964` |
|
||||
| Client ID | `78721641513-...apps.googleusercontent.com` |
|
||||
| Redirect URL | `https://ai.pipexerp.com/api/v1/auth/google/callback` |
|
||||
|
||||
> **注意**: Client Secret 不在文档中记录,存储在生产环境 `.env` 文件中
|
||||
|
||||
---
|
||||
|
||||
## 十三、最佳实践与故障排查
|
||||
|
||||
### 13.1 创建需求的最佳实践
|
||||
|
||||
#### 问题:description 字段"请求数据格式错误"
|
||||
|
||||
**症状**:
|
||||
```json
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求数据格式错误"
|
||||
}
|
||||
```
|
||||
|
||||
**常见原因**:
|
||||
|
||||
| 原因 | 解决方案 |
|
||||
|------|----------|
|
||||
| **description 内容过长** | API 可能有字段长度限制,逐步测试找到上限 |
|
||||
| **Markdown 特殊字符** | 检查是否有未转义的特殊字符(如反引号、引号) |
|
||||
| **JSON 转义问题** | 确保 JSON 字符串正确转义(尤其是换行符、引号) |
|
||||
| **多层嵌套的代码块** | Markdown 代码块中的反引号可能导致解析失败 |
|
||||
|
||||
**正确的调试流程**:
|
||||
|
||||
```python
|
||||
# ❌ 错误做法:直接简化内容
|
||||
"创建失败 → 简化 description → 成功"
|
||||
问题:丢失了重要的需求细节
|
||||
|
||||
# ✅ 正确做法:找到根本原因
|
||||
步骤 1: 测试空描述
|
||||
create_requirement(description="")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 2: 测试纯文本
|
||||
create_requirement(description="简单文本")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 3: 测试简单 Markdown
|
||||
create_requirement(description="# 标题\n\n内容")
|
||||
→ 是否成功?
|
||||
|
||||
步骤 4: 逐步增加复杂度
|
||||
create_requirement(description="包含代码块的Markdown")
|
||||
create_requirement(description="包含表格的Markdown")
|
||||
create_requirement(description="完整的技术方案")
|
||||
→ 找到临界点
|
||||
|
||||
步骤 5: 根据根因选择方案
|
||||
- 如果是长度限制 → 分段或使用附件
|
||||
- 如果是特殊字符 → 转义或替换
|
||||
- 如果是 Markdown → 简化格式或转纯文本
|
||||
```
|
||||
|
||||
**推荐方案**:
|
||||
|
||||
**方案 A:完整的需求 + 关联文档**
|
||||
```typescript
|
||||
// 1. 创建需求(简洁描述)
|
||||
const req = await create_requirement({
|
||||
title: "思源笔记增加 MCP 协议层支持",
|
||||
description: "核心方案:创建独立的 siyuan-mcp-bridge 服务...",
|
||||
projectId: 162
|
||||
});
|
||||
|
||||
// 2. 创建详细的技术方案文档
|
||||
const doc = await create_requirement_document({
|
||||
requirementId: req.id,
|
||||
title: "技术方案详细设计",
|
||||
content: `
|
||||
# 需求背景
|
||||
...(完整的技术方案,3000+ 字)
|
||||
|
||||
# 架构设计
|
||||
...
|
||||
|
||||
# 实施路线图
|
||||
...
|
||||
`
|
||||
});
|
||||
```
|
||||
|
||||
**方案 B:分段创建需求描述**
|
||||
```typescript
|
||||
// 1. 创建需求(基本信息)
|
||||
const req = await create_requirement({
|
||||
title: "思源笔记增加 MCP 协议层支持",
|
||||
description: "简短描述",
|
||||
projectId: 162
|
||||
});
|
||||
|
||||
// 2. 更新需求(逐步添加内容)
|
||||
await update_requirement({
|
||||
id: req.id,
|
||||
updates: {
|
||||
description: description_part1 // 添加背景
|
||||
}
|
||||
});
|
||||
|
||||
await update_requirement({
|
||||
id: req.id,
|
||||
updates: {
|
||||
description: description_part1 + description_part2 // 添加架构
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### 经验总结
|
||||
|
||||
**原则 1:业务优先于技术**
|
||||
- ❌ 为了创建成功而牺牲需求完整性
|
||||
- ✅ 找到技术方案来满足业务需求
|
||||
|
||||
**原则 2:追根溯源**
|
||||
- ❌ 基于假设快速妥协("可能是太长了")
|
||||
- ✅ 通过调试找到真正的错误原因
|
||||
|
||||
**原则 3:给用户选择权**
|
||||
- ❌ 自作主张简化内容
|
||||
- ✅ 询问用户的偏好和优先级
|
||||
|
||||
**原则 4:保留完整信息**
|
||||
- ❌ 简化后的需求缺少关键信息
|
||||
- ✅ 通过关联文档保留详细内容
|
||||
|
||||
### 13.2 需求描述的格式建议
|
||||
|
||||
**推荐的需求描述结构**(简洁版):
|
||||
|
||||
```markdown
|
||||
## 实施周期
|
||||
X周(阶段1 + 阶段2 + 阶段3)
|
||||
```
|
||||
|
||||
**详细内容放在关联文档**:
|
||||
- 需求背景与业务价值
|
||||
- 详细的技术方案
|
||||
- 实施路线图与时间规划
|
||||
- 风险评估与应对措施
|
||||
- 验收标准
|
||||
|
||||
**Markdown 格式注意事项**:
|
||||
|
||||
```markdown
|
||||
✅ 安全的格式:
|
||||
- 简单的标题(# ## ###)
|
||||
- 无序列表(-)
|
||||
- 有序列表(1. 2. 3.)
|
||||
- 简单的表格
|
||||
- 加粗(**text**)
|
||||
|
||||
⚠️ 可能有问题的格式:
|
||||
- 三重反引号代码块(```)
|
||||
- HTML 标签
|
||||
- 复杂的嵌套列表
|
||||
- 大量的特殊字符
|
||||
- 超长的单行文本
|
||||
```
|
||||
|
||||
### 13.3 更新需求的最佳实践
|
||||
|
||||
**使用 update_requirement 补充详细内容**:
|
||||
|
||||
```typescript
|
||||
// 创建需求后,逐步更新描述
|
||||
await update_requirement({
|
||||
id: 857, // REQ-20260217-0001
|
||||
updates: {
|
||||
description: `
|
||||
## 需求背景
|
||||
|
||||
### 当前问题
|
||||
思源笔记仅提供 REST API 接口,AI 操作复杂...
|
||||
|
||||
### 业务价值
|
||||
为思源笔记添加 MCP 协议层支持,使其能够...
|
||||
|
||||
## 技术方案
|
||||
|
||||
### 架构设计
|
||||
...(详细内容)
|
||||
`
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
**测试长度限制**:
|
||||
|
||||
```bash
|
||||
# 逐步增加内容,找到上限
|
||||
第1次: 200字 → 成功
|
||||
第2次: 500字 → 成功
|
||||
第3次: 1000字 → 成功
|
||||
第4次: 2000字 → 成功
|
||||
第5次: 5000字 → 失败 ← 找到上限
|
||||
|
||||
结论: description 字段最大约 3000-4000 字
|
||||
```
|
||||
|
||||
### 13.4 故障排查清单
|
||||
|
||||
遇到 API 错误时,按以下步骤排查:
|
||||
|
||||
**步骤 1:分析错误信息**
|
||||
```json
|
||||
{
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "请求数据格式错误"
|
||||
}
|
||||
```
|
||||
- [ ] 错误码是什么?(BAD_REQUEST / VALIDATION_ERROR / AUTH_FAILED)
|
||||
- [ ] 错误信息是否有具体提示?
|
||||
- [ ] 是哪个字段导致的错误?
|
||||
|
||||
**步骤 2:检查参数格式**
|
||||
- [ ] 必填参数是否都提供了?
|
||||
- [ ] 参数类型是否正确?(number vs string)
|
||||
- [ ] 枚举值是否在允许范围内?
|
||||
- [ ] JSON 格式是否正确?
|
||||
|
||||
**步骤 3:简化测试**
|
||||
- [ ] 尝试最小化的参数集
|
||||
- [ ] 逐个添加可选参数
|
||||
- [ ] 找到导致错误的具体参数
|
||||
|
||||
**步骤 4:查看后端日志**(如有权限)
|
||||
```bash
|
||||
# 生产环境日志
|
||||
ssh tools_ai_proj "docker logs ai_backend_prod --tail 100"
|
||||
|
||||
# 开发环境日志
|
||||
cd ~/coding/new-ai-proj/backend
|
||||
tail -f logs/app.log
|
||||
```
|
||||
|
||||
**步骤 5:参考类似的成功案例**
|
||||
- [ ] 查看 SKILL.md 中的示例
|
||||
- [ ] 查看之前成功的调用记录
|
||||
- [ ] 对比参数差异
|
||||
|
||||
### 13.5 常见错误与解决方案
|
||||
|
||||
| 错误 | 可能原因 | 解决方案 |
|
||||
|------|----------|----------|
|
||||
| `BAD_REQUEST` | 参数格式错误 | 检查参数类型和 JSON 格式 |
|
||||
| `VALIDATION_ERROR` | 参数验证失败 | 检查必填字段和枚举值 |
|
||||
| `AUTH_FAILED` | 认证失败 | 检查 Token 是否过期 |
|
||||
| `PERMISSION_DENIED` | 权限不足 | 检查用户角色和权限 |
|
||||
| `NOT_FOUND` | 资源不存在 | 检查 ID 是否正确 |
|
||||
| `INTERNAL_ERROR` | 服务器错误 | 查看后端日志排查 |
|
||||
|
||||
### 13.6 需求 description 长度测试记录
|
||||
|
||||
**测试日期**: 2026-02-17
|
||||
|
||||
| 长度 | 格式 | 结果 | 备注 |
|
||||
|------|------|------|------|
|
||||
| ~200字 | 简单文本 | ✅ 成功 | 核心方案3点 |
|
||||
| ~3000字 | 完整 Markdown | ❌ 失败 | "请求数据格式错误" |
|
||||
| 待测试 | 1000字 Markdown | ? | 需要进一步测试 |
|
||||
| 待测试 | 2000字 Markdown | ? | 需要进一步测试 |
|
||||
|
||||
**建议**:
|
||||
- 需求描述控制在 1000 字以内
|
||||
- 详细内容使用关联文档
|
||||
- 复杂格式改为纯文本
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,9 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "publish",
|
||||
"install_type": "skill",
|
||||
"dir_category": "core",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
---
|
||||
name: publish
|
||||
description: 发布 ai-proj-helper 技能市场更新并通知飞书群。用户说 /publish 时激活。
|
||||
user_invocable: true
|
||||
metadata:
|
||||
user_invocable: true
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# publish - 技能市场发布与通知
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "skill-creator-plugin",
|
||||
"description": "团队技能创建与维护的标准流程:收录门槛、分桶生命周期、调用模式、evals、四段式文档、路由回改。",
|
||||
"version": "0.1.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "skill-creator",
|
||||
"install_type": "skill",
|
||||
"dir_category": "core",
|
||||
"status": "beta",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
---
|
||||
name: skill-creator
|
||||
description: 团队技能的标准流程:收录门槛、分桶生命周期、调用模式、SKILL.md 规范、evals 全量、四段式文档、路由回改。当要新建技能、大改现有技能、或评审一个技能是否合格时使用。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
owner: qiudl
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 技能创建(团队标准流程)
|
||||
|
||||
## 0. 一句话
|
||||
|
||||
先有真实失败,再写技能;没有 evals 的技能不算完成;**技能目录是预算,不是收藏夹**。
|
||||
|
||||
## 1. 何时用
|
||||
|
||||
- 新建技能
|
||||
- 大改现有技能(改 description、改核心流程、改门禁)
|
||||
- 评审别人提上来的技能
|
||||
- 判断某个想法「该不该做成技能」
|
||||
|
||||
何时不用:只改错别字/格式 → 直接改;一次性个人偏好 → 放个人技能,不进团队。
|
||||
|
||||
## 2. 收录门槛(先过这一关,再谈怎么写)
|
||||
|
||||
新技能**默认拒绝**,只有同时满足两条才立项:
|
||||
|
||||
1. **真实失败**:能说出在哪个真实任务/会话里出了问题——跑了什么、期望什么、实际什么。假想改进(「这样会更好」)不算。
|
||||
2. **组合不出来**:用现有技能组合做不到;能组合的,就改现有技能,不新增。
|
||||
|
||||
被拒的想法记进仓库 `.out-of-scope/<概念>.md`,写清理由,避免下次重复讨论。
|
||||
|
||||
## 3. 分桶与生命周期
|
||||
|
||||
| 桶 | 含义 | 是否安装 | 是否进 README / plugin.json |
|
||||
| --- | --- | --- | --- |
|
||||
| `promoted` | 已验证、日常在用 | 默认安装 | 必须出现 |
|
||||
| `beta` | 公开试跑,收反馈 | 需 `--include-beta` | 不进默认清单 |
|
||||
| `deprecated` | 不再用 | 不安装 | 留文档,标弃用 |
|
||||
|
||||
- 新技能一律先进 `beta`,跑够真实使用 + evals 通过才 `promoted`。
|
||||
- 只有 `promoted` 能进模型目录;beta 不占目录预算。
|
||||
|
||||
## 4. 调用模式(省目录 token 的关键)
|
||||
|
||||
每个技能二选一,写进 frontmatter:
|
||||
|
||||
- **`model-invoked`**(默认):进模型目录,agent 自己判断何时用。
|
||||
- **`user-invoked`**:只由人下命令触发,**不进模型目录**;必须同时写 `disable-model-invocation: true` 和 `metadata.invocation: user-invoked`。
|
||||
|
||||
判定口径:这件事是不是「人下命令才开始」?是 → user-invoked。典型:publish、retro、triage、发布 runbook、建技能。
|
||||
|
||||
## 5. SKILL.md 硬约束(Agent Skills 规范)
|
||||
|
||||
- 目录:`SKILL.md` + 可选 `scripts/`、`references/`、`assets/`
|
||||
- frontmatter 必填:`name`(小写字母/数字/连字符,≤64)、`description`(≤1024,写清「做什么 + 何时用」)
|
||||
- 可选:`license`、`compatibility`、`metadata`、`allowed-tools`
|
||||
- **非标准字段**一律进 `metadata`
|
||||
- 正文 ≤20KB(约 5000 token),细节拆 `references/`
|
||||
- 只写 agent 不知道的东西:项目约定、边界、坑;不写通用常识
|
||||
|
||||
## 6. 正文结构模板
|
||||
|
||||
```
|
||||
# 名称
|
||||
## 0. 一句话
|
||||
## 1. 何时用 / 何时不用
|
||||
## 2. 步骤(可执行、有顺序)
|
||||
## 3. 硬约束与红线
|
||||
## 4. 常见坑(gotchas)
|
||||
## 5. 验证清单
|
||||
## 6. 参考(references/ 链接)
|
||||
```
|
||||
|
||||
## 7. evals(强制、全量)
|
||||
|
||||
- 路径:`evals/evals.json`;至少 3 条:正常路径 / 边界输入 / 失败恢复
|
||||
- 跑「有技能 / 无技能」对比,结果存 `iteration-<N>/`,按 rubric 打分,PR 附结果
|
||||
|
||||
## 8. 四段式文档(每个 promoted 技能一篇)
|
||||
|
||||
`docs/skills/<name>.md`,四节固定顺序:
|
||||
|
||||
1. **What it does**
|
||||
2. **When to reach for it**
|
||||
3. **Common questions**
|
||||
4. **It's working if**(必须给可验证证据,不许写「感觉对了」)
|
||||
|
||||
beta 不写文档;弃用保留文档并标 archived。
|
||||
|
||||
## 9. 路由回改(硬规则)
|
||||
|
||||
所有技能增、删、改名、改行为后,**必须同步回改入口技能 `ai-proj-helper-entry` 和 `catalog.json`**。一个没提到新技能、或还在路由到已删技能的路由器,就是假的路由器。
|
||||
|
||||
## 10. 质量门禁(提 PR 前逐条打勾)
|
||||
|
||||
- [ ] 过了收录门槛(真实失败 + 组合不出来)
|
||||
- [ ] 桶与调用模式已标,`beta` 不得出现在默认清单
|
||||
- [ ] `name`/`description` 合规,description 含「何时用」
|
||||
- [ ] 正文 ≤20KB,细节已拆 references
|
||||
- [ ] evals ≥3 条,有「有/无技能」对比
|
||||
- [ ] 四段式文档已写(promoted 才需要)
|
||||
- [ ] 入口路由与 `catalog.json` 已回改
|
||||
- [ ] `plugin.json` 已登记 `install_name`/`install_type`/`dir_category`/`status`/版本号
|
||||
- [ ] `python3 scripts/validate-skills.py` 通过
|
||||
- [ ] `./scripts/scan-skill.sh` 无高危项
|
||||
- [ ] 版本号与 CHANGELOG 已更新
|
||||
- [ ] 无绝对路径、无凭据、无内网地址
|
||||
|
||||
## 11. 反模式
|
||||
|
||||
- 一次写 800 行「大全」,什么都想覆盖
|
||||
- description 写成「各种任务都能用」
|
||||
- 没有 evals 就提 PR;没有四段式文档就 promoted
|
||||
- 直接复制外部技能,不改写成我们的 REQ/ai-proj 语境
|
||||
- 把临时排障步骤固化成团队技能(这类放 personal)
|
||||
- 技能改名后忘了回改路由
|
||||
|
||||
## 12. 评审别人技能时看什么
|
||||
|
||||
1. 收录门槛过没过(有没有真实失败)
|
||||
2. description 能不能让模型判断「何时用」
|
||||
3. 桶与调用模式是否正确(该 user-invoked 的别占目录)
|
||||
4. 和现有技能是否重复(先查 `catalog.json`)
|
||||
5. evals 是否覆盖失败路径、文档是否有「It's working if」
|
||||
6. 是否触碰红线(自审批、绕过门禁、绝对路径、凭据)
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"skill_name": "skill-creator",
|
||||
"skill_version": "0.1.0",
|
||||
"evals": [
|
||||
{
|
||||
"id": 1,
|
||||
"kind": "happy_path",
|
||||
"prompt": "我想加一个新技能,用来在每次部署后自动跑一遍冒烟",
|
||||
"expected_output": "先问真实失败场景(哪次部署出过问题、跑了什么、期望什么),再判断现有 req-lookback/req-audit 能否组合;给出 bucket/调用模式/evals 计划"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"kind": "edge_case",
|
||||
"prompt": "直接帮我把这个想法写成 SKILL.md:一个能自动优化代码风格的技能",
|
||||
"expected_output": "拒绝直接写,指出没有真实失败证据,要求先给案例;若无证据则建议放个人技能"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"kind": "failure_recovery",
|
||||
"prompt": "技能有 evals 但 validate-skills.py 报了 name 不合规,怎么处理",
|
||||
"expected_output": "说明 name 规则(小写/数字/连字符、≤64),给出改名步骤,并提示要同步 install_name、catalog、入口路由"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "agent-browser",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -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,417 @@
|
||||
---
|
||||
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)
|
||||
|
||||
## 并行编排流程
|
||||
|
||||
把大任务拆给多个子 agent 时按这个顺序:
|
||||
|
||||
1. **先划写入边界**:每个子 agent 拥有互不重叠的目录/文件;共享文件(锁文件、入口文件)由编排者最后统一改。
|
||||
2. **子 agent 必须新上下文**:不要 fork 父上下文,否则它继承了本该甩掉的包袱。
|
||||
3. **每个子任务给验收标准 + 回报格式**:三句话(结果 / 证据路径 / 下一步)。
|
||||
4. **子 agent 不做合并与发布**:只交分支或补丁,合并由编排者做。
|
||||
5. **汇总时先跑集成验证**,再回报;冲突由编排者解,不让子 agent 互改。
|
||||
6. 两个子 agent 不得写同一文件;不确定时先串行。
|
||||
@@ -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"
|
||||
}
|
||||
@@ -0,0 +1,538 @@
|
||||
---
|
||||
name: ai-chat
|
||||
description: AI Chat 测试与管理。发送消息测试 AI Chat 工具调用链路,管理工具开关和 Provider 配置,支持 local/staging 环境切换。
|
||||
metadata:
|
||||
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,12 @@
|
||||
{
|
||||
"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",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
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.
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 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,12 @@
|
||||
{
|
||||
"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",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
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.
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# 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.
|
||||
@@ -2,5 +2,10 @@
|
||||
"name": "db-migration-plugin",
|
||||
"description": "数据库变更方案插件。Migration 脚本生成、数据迁移策略、回滚方案。挂载在 design 阶段,涉及数据库变更时激活。",
|
||||
"version": "1.0.0",
|
||||
"author": { "name": "qiudl" }
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "db-migration",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -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 + 事件委托 |
|
||||
@@ -2,5 +2,10 @@
|
||||
"name": "deploy-rollback-plugin",
|
||||
"description": "回滚方案插件。部署后发现问题时的回滚策略、数据修复、灰度回退。挂载在 deploy 阶段。",
|
||||
"version": "1.0.0",
|
||||
"author": { "name": "qiudl" }
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "deploy-rollback",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
{"name":"dev-android-plugin","description":"Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。按需加载。","version":"1.0.0","author":{"name":"qiudl"}}
|
||||
{
|
||||
"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",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: dev-android
|
||||
description: Android 开发插件。Kotlin + Jetpack Compose + Hilt 依赖注入。当涉及 Android 开发任务时按需加载。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# Android 开发插件 (dev-android)
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-arch",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-cicd",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "2.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-coding",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -73,6 +73,72 @@ ai-proj task append-doc --id <taskId> --content "实现说明"
|
||||
|
||||
---
|
||||
|
||||
## 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 | 验证脚本每条命令的期望输出值 |
|
||||
|
||||
---
|
||||
|
||||
## Go 后端开发
|
||||
|
||||
### 分层架构
|
||||
@@ -542,3 +608,55 @@ fi
|
||||
### 非阻断原则
|
||||
|
||||
架构信息缺失时**仅警告不阻止**。用户仍可继续,但会被告知"生成的建议可能不够准确"。
|
||||
|
||||
---
|
||||
|
||||
## Step 0.5:Shell 环境自检(node/npm command not found 先看这里)
|
||||
|
||||
**在任何 `npm` / `pnpm` / `node` 命令之前,先确认解释器可解析。** 报 `command not found` 时
|
||||
**不要**下结论"当前环境没有 shell",也**不要**去找"给 agent 配 shell"的开关(DSH 没有这个开关)。
|
||||
|
||||
### 根因(不要重复侦察;已在 macOS + Slark/DSH 上定位到代码)
|
||||
|
||||
| # | 事实 |
|
||||
|---|------|
|
||||
| 1 | DSH 的 bash 工具(`@deepseek-ai/dsh-bash-local`)一律以 **`bash -c`(非登录、非交互)** 执行 → **不读** `~/.zprofile` / `~/.zshrc` / `~/.profile` |
|
||||
| 2 | 子进程环境 = **宿主进程环境** − 凭据类变量 − `DSH_*`(`@deepseek-ai/dsh-subprocess`)→ **PATH 不被清洗**,它等于启动宿主那个进程的 PATH |
|
||||
| 3 | 宿主由 GUI 应用(Slark.app)拉起 → 继承 macOS GUI 的最小 PATH(`/usr/local/bin:/bin:/usr/bin:...`)→ node 不在里面 |
|
||||
| 4 | `HOME` **未设置**(不是空值);`USER` / `TMPDIR` / `LANG` 同样未设置 |
|
||||
| 5 | `@deepseek-ai/dsh-shell-env` 只注册/注入 **`DSH_*`** 变量,`PATH` 是保留键 → 改 profile/`cordis.patch.yml` 加 PATH **无效** |
|
||||
|
||||
### 正确做法(两行 export,别多一步)
|
||||
|
||||
```bash
|
||||
export HOME="/Users/johnq" # 必须补:git 等直接读 $HOME
|
||||
export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:$PATH" # node/npm/pnpm
|
||||
cd <项目> && npm install && npm run build
|
||||
```
|
||||
|
||||
**为什么 `HOME` 必须补**(实测):
|
||||
|
||||
- `~` 在 bash 里靠 passwd 查表展开,所以 `ls ~`、`cd ~/x` 仍能用 → **容易误判环境正常**;
|
||||
- 直接读 `$HOME` 的会崩:`git` → `fatal: $HOME not set`(`status` / `commit` / `config --global` 全废);
|
||||
带 `set -u` 的脚本 → `HOME: unbound variable`(例:ai-proj-helper 的 `install-skills.sh`);
|
||||
- node 侧多数无事(`os.homedir()` 走 passwd 兜底,只有 `process.env.HOME` 是 `undefined`)
|
||||
→ **「node 能跑」≠ 环境完整**,别据此跳过 `HOME`。
|
||||
|
||||
- **本机 node 必须用这一支**(定义在 `~/.zprofile`,对齐项目 `engines.node`):
|
||||
`/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin/node` = v22.22.3,npm 10.9.8。
|
||||
- `/opt/homebrew/bin/node` 是**另一支 v26**,不要混用;`/usr/local/bin/node` 不存在。
|
||||
- 备选①:`/bin/zsh -lic '<命令>'`(登录 zsh 会读 `~/.zprofile`)。
|
||||
- 备选②:绝对路径直调 `/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin/node build.mjs`。
|
||||
- 路径变了先 `ls -d /Users/johnq/.local/node-v*-darwin-arm64/bin` 重新取**真实版本号**,不要猜。
|
||||
|
||||
### 交付纪律(配合「Step 0:验证优先」)
|
||||
|
||||
- **禁止**把"没有 shell / 不能构建"写成交付结论:bash 工具是好的,缺的只是 `HOME` + `PATH`。
|
||||
正确表述是"我未执行构建(缺 node PATH,已给用户补 PATH 后的构建命令)"。
|
||||
- 构建/测试没跑就说没跑(`未经构建与实机验证`),**不要**用"构建配置已就绪"之类中间信号冒充验收。
|
||||
- 校验环境的最快方式(输出直接贴进交付):
|
||||
|
||||
```bash
|
||||
export HOME="/Users/johnq"; export PATH="/Users/johnq/.local/node-v22.22.3-darwin-arm64/bin:$PATH"
|
||||
node -v && npm -v && git status --short | head -3
|
||||
```
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-commit",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -114,6 +114,48 @@ feat/REQ-xxx-i42 + commit → commit message 自动加 "closes #42"
|
||||
确认提交?(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 进行中的
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-deploy",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
name: dev-deploy
|
||||
description: 应用部署技能。支持 iOS TestFlight、Docker 容器等多平台部署。当用户提到部署、发布、TestFlight、上架、build、archive 相关任务时自动激活。
|
||||
---
|
||||
|
||||
# 应用部署 Skill (dev-deploy)
|
||||
|
||||
## 概述
|
||||
|
||||
管理应用从构建到发布的完整部署流程,支持多平台:
|
||||
@@ -558,186 +555,6 @@ preflight_docker() {
|
||||
|
||||
---
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### 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
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 与需求工作流集成
|
||||
|
||||
部署完成后更新需求状态:
|
||||
@@ -774,3 +591,10 @@ ai-proj task append-doc --id <task_id> --content "部署记录..."
|
||||
| 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)` |
|
||||
|
||||
## 详细内容
|
||||
|
||||
以下章节已移到 [references/details.md](references/details.md):
|
||||
|
||||
- 回滚策略
|
||||
- 部署监控
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
# skills 详细内容
|
||||
|
||||
## 回滚策略
|
||||
|
||||
### 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
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-integration",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
{"name":"dev-ios-plugin","description":"iOS 开发插件。Swift/SwiftUI + MVVM 架构、TestFlight 部署、Xcode 构建。按需加载。","version":"1.0.0","author":{"name":"qiudl"}}
|
||||
{
|
||||
"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",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: dev-ios
|
||||
description: iOS 开发插件。Swift/SwiftUI + MVVM 架构,TestFlight 部署。当涉及 iOS 开发任务时按需加载。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# iOS 开发插件 (dev-ios)
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
{"name":"dev-mcp-plugin","description":"MCP Bridge 开发插件。TypeScript MCP 服务开发、Token 管理、HTTP 客户端模式。按需加载。","version":"1.0.0","author":{"name":"qiudl"}}
|
||||
{
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -1 +1,12 @@
|
||||
{"name":"dev-pda-plugin","description":"PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先模式。按需加载。","version":"1.0.0","author":{"name":"qiudl"}}
|
||||
{
|
||||
"name": "dev-pda-plugin",
|
||||
"description": "PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先模式。按需加载。",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "dev-pda",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev",
|
||||
"invocation": "user-invoked"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: dev-pda
|
||||
description: PDA 应用开发插件。Android 原生 + 扫码枪集成 + 离线优先。当涉及 PDA/手持终端开发时按需加载。
|
||||
metadata:
|
||||
invocation: user-invoked
|
||||
disable-model-invocation: true
|
||||
---
|
||||
|
||||
# PDA 应用开发插件 (dev-pda)
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-review",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
name: dev-review
|
||||
description: 代码评审技能。五视角对抗性扫描法,用于 PR 代码审查、安全评审、质量检查。当执行 /req cr 或独立 PR review 时自动激活。
|
||||
description: 代码评审技能。六视角对抗性扫描法(含 Karpathy Scope 审计),用于 PR 代码审查、安全评审、质量检查。当执行 /req cr 或独立 PR review 时自动激活。
|
||||
---
|
||||
|
||||
# 代码评审 Skill (dev-review)
|
||||
|
||||
## 概述
|
||||
|
||||
独立的代码评审技能,核心方法论是**五视角对抗性扫描法**。
|
||||
独立的代码评审技能,核心方法论是**六视角对抗性扫描法**(五个传统安全/健壮性视角 + Karpathy Scope 审计视角)。
|
||||
|
||||
**适用场景**:
|
||||
- `/req cr [REQ-ID]` — 需求流程中的代码评审阶段
|
||||
@@ -22,7 +22,7 @@ description: 代码评审技能。五视角对抗性扫描法,用于 PR 代码
|
||||
|
||||
| 上游 | 本技能输入 | 本技能输出 | 下游 |
|
||||
|------|-----------|-----------|------|
|
||||
| dev-coding | PR diff + 开发设计文档 | CR 报告(五视角扫描 + 发现汇总 + 结论) | dev-test |
|
||||
| dev-coding | PR diff + 开发设计文档 | CR 报告(六视角扫描 + 发现汇总 + 结论) | dev-test |
|
||||
|
||||
---
|
||||
|
||||
@@ -67,7 +67,7 @@ description: 代码评审技能。五视角对抗性扫描法,用于 PR 代码
|
||||
|
||||
---
|
||||
|
||||
## 五视角对抗性扫描法
|
||||
## 六视角对抗性扫描法
|
||||
|
||||
### 总览
|
||||
|
||||
@@ -146,6 +146,28 @@ file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
|
||||
- [ ] Redis 不可用时是否有降级方案?(缓存穿透到数据库)
|
||||
- [ ] token 过期/刷新逻辑是否正确?(access vs refresh 不同策略)
|
||||
|
||||
### 视角6:Scope 审计者(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 报告模板
|
||||
@@ -160,7 +182,7 @@ file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
|
||||
### 变更概要
|
||||
{1-3 句描述本次变更的目的和范围}
|
||||
|
||||
### 五视角扫描结果
|
||||
### 六视角扫描结果
|
||||
|
||||
#### 1. 攻击者视角
|
||||
{扫描发现,或 "未发现问题"}
|
||||
@@ -177,6 +199,9 @@ file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
|
||||
#### 5. 依赖者视角
|
||||
{扫描发现,或 "未发现问题"}
|
||||
|
||||
#### 6. Scope 审计者视角(Karpathy)
|
||||
{扫描发现,或 "所有变更文件均在设计清单范围内,无过度实现"}
|
||||
|
||||
### 审查发现汇总
|
||||
|
||||
| # | 严重度 | 文件:行号 | ���角 | 描述 | 建议 |
|
||||
@@ -221,7 +246,7 @@ file:line — Store.GetByID(id) 缺少 tenant_id 过滤,
|
||||
| 文档存在 | CR 任务有附加文档 |
|
||||
| 字数 | ≥ 500 字 |
|
||||
| 代码引用 | 含 `file:line` 格式的引用 |
|
||||
| 五视角扫描 | 含全部 5 个视角章节 |
|
||||
| 六视角扫描 | 含全部 6 个视角章节(含 Scope 审计者) |
|
||||
| 结论章节 | 含明确的通过/不通过结论 |
|
||||
|
||||
---
|
||||
@@ -278,3 +303,13 @@ mcp__ai-proj__link_tasks_to_requirement({
|
||||
4. **严重度准确** — 不要所有问题都标 High,按实际影响分级
|
||||
5. **建议可操作** — 每个发现必须附带具体修复建议
|
||||
6. **关注变更** — 评审范围是 diff,不要对未变更的代码提意见(除非变更引入了对旧代码的新风险)
|
||||
|
||||
## 接收与响应评审意见
|
||||
|
||||
评审不是单向输出,收到意见后按这套来:
|
||||
|
||||
1. **先分类**:阻断项(必须修)/ 建议项(可讨论)/ 澄清项(问题理解不一致)。
|
||||
2. **先复述再回应**:用自己的话重述意见,确认理解一致,再给证据或修改。
|
||||
3. **不辩解**:用证据说话(代码、测试、日志),不用语气词反驳。
|
||||
4. **逐条回评**:每条写「已修(commit)/ 不修(理由)/ 需澄清(具体问题)」,不得默默跳过。
|
||||
5. **阻断项没修完不得合并**;建议项不修要记录原因,进 PR 描述。
|
||||
|
||||
@@ -2,5 +2,10 @@
|
||||
"name": "dev-scaffold-plugin",
|
||||
"description": "模块脚手架插件。新建模块时自动生成分层代码骨架(Model/Repository/Service/Handler/Route)。挂载在 dev 阶段。",
|
||||
"version": "1.0.0",
|
||||
"author": { "name": "qiudl" }
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "dev-scaffold",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
"version": "2.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "dev-test",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -215,3 +215,11 @@ AI 根据需求变更范围动态生成 Playwright spec,模板见 `templates/p
|
||||
若 git diff 显示同一功能的 `handlers/`、`middleware/`、`routes/` 文件均有变更(通过命名模式识别,如 `impersonation_handler.go` + `impersonation_middleware.go` + `impersonation_routes.go`),则除单元测试外**额外生成** `backend/tests/` 下的集成测试。
|
||||
|
||||
识别方式:提取文件名中的功能前缀(如 `impersonation`),若在三个目录中均出现,则触发集成测试生成。
|
||||
|
||||
## TDD 循环(红 → 绿 → 重构)
|
||||
|
||||
1. **一次只做一个行为**:写一个小测试,先确认它**失败**,并确认失败原因就是你要修的那件事(不是编译错误/环境问题)。
|
||||
2. **最小实现让它变绿**,不做顺手重构。
|
||||
3. **绿了再重构**,重构期间测试保持绿。
|
||||
4. 禁止先写实现再补测试;补的测试要能证明它真的会红(临时改坏一次验证)。
|
||||
5. 交付时附:测试命令、通过/失败数、以及「曾经红过」的证据(日志或截图记录)。
|
||||
|
||||
@@ -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,696 @@
|
||||
---
|
||||
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.
|
||||
metadata:
|
||||
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+Enum,50行 | 一个函数,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"
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{
|
||||
"name": "review-checklist-plugin",
|
||||
"description": "项目级代码评审检查清单。按项目积累的特定检查项,挂载在 dev-review 下自动加载。",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
}
|
||||
},
|
||||
"install_name": "review-checklist",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# 通用代码评审检查清单
|
||||
|
||||
适用于所有项目,补充五视角扫描法。
|
||||
|
||||
## API 设计
|
||||
- [ ] RESTful 命名是否规范?(复数名词、无动词)
|
||||
- [ ] 分页参数是否有默认值和上限?
|
||||
- [ ] 响应格式是否统一?(code/message/data)
|
||||
|
||||
## 错误处理
|
||||
- [ ] 错误是否被正确传播?(不要吞掉错误)
|
||||
- [ ] 用户可见的错误消息是否友好?(不暴露技术细节)
|
||||
- [ ] 是否有 panic recover 兜底?
|
||||
|
||||
## 性能
|
||||
- [ ] 列表查询是否有分页?(不允许无限制查询)
|
||||
- [ ] N+1 查询问题?(循环内查数据库)
|
||||
- [ ] 是否有不必要的全表扫描?(缺少索引)
|
||||
|
||||
## 可维护性
|
||||
- [ ] 魔法数字是否提取为常量?
|
||||
- [ ] 复杂业务逻辑是否有注释说明?
|
||||
- [ ] 函数是否过长?(超过 100 行考虑拆分)
|
||||
@@ -13,16 +13,17 @@ description: 项目级代码评审检查清单。按项目积累特定检查项
|
||||
|
||||
## 使用方式
|
||||
|
||||
1. `dev-review` 执行五视角扫描时,自动加载当前项目的检查清单
|
||||
2. 扫描完成后,逐条检查清单项
|
||||
3. 检查结果附加到 CR 报告的「项目检查清单」章节
|
||||
1. `dev-review` 执行五视角扫描时,先读取 `references/general.md`
|
||||
2. 如果仓库是 AI-Proj,读取 `references/ai-proj.md`;如果是 Coolbuy PaaS,读取 `references/coolbuy-paas.md`
|
||||
3. 扫描完成后,逐条检查适用的清单项
|
||||
4. 检查结果附加到 CR 报告的「项目检查清单」章节
|
||||
|
||||
## 检查清单文件
|
||||
|
||||
```
|
||||
review-checklist-plugin/
|
||||
├── skills/SKILL.md # 本文件
|
||||
└── checklists/
|
||||
review-checklist/
|
||||
├── SKILL.md # 本文件
|
||||
└── references/
|
||||
├── ai-proj.md # AI-Proj 项目清单
|
||||
├── coolbuy-paas.md # 酷采3.0 项目清单
|
||||
└── general.md # 通用清单(所有项目适用)
|
||||
@@ -32,7 +33,7 @@ review-checklist-plugin/
|
||||
|
||||
当 CR 中发现了一个**项目特有**的问题模式,且未来可能复发时:
|
||||
|
||||
1. 打开对应项目的检查清单文件
|
||||
1. 打开 `references/` 中对应项目的检查清单文件
|
||||
2. 添加条目,格式:`- [ ] {检查项} — 教训:{来源}`
|
||||
3. 标注严重度和适用范围
|
||||
|
||||
|
||||
+9
-1
@@ -18,6 +18,14 @@
|
||||
- [ ] 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 发 camel,gin 静默忽略不匹配参数(REQ-20260711-0003)
|
||||
|
||||
### Redis
|
||||
- [ ] Redis key 是否有 TTL?— 缺少 TTL 导致内存泄露
|
||||
- [ ] Redis 不可用时是否降级到数据库?
|
||||
@@ -37,6 +45,6 @@
|
||||
|
||||
## 通用
|
||||
|
||||
- [ ] `.env` ��凭据文件是否被意外加入 git?
|
||||
- [ ] `.env` 等凭据文件是否被意外加入 git?
|
||||
- [ ] 是否有硬编码的 URL/IP/端口?— 应使用配置
|
||||
- [ ] 错误日志是否包含足够的上下文信息?(user_id, tenant_id, request_id)
|
||||
+1
-1
@@ -8,7 +8,7 @@
|
||||
|
||||
### 数据迁移
|
||||
- [ ] 从酷采2.0迁移的字段映射是否正确?(varchar ID → bigint ID)
|
||||
- [ ] 迁移脚本是否处理了酷采2.0���软删除标记(is_delete → deleted_at)?
|
||||
- [ ] 迁移脚本是否处理了酷采2.0 的软删除标记(is_delete → deleted_at)?
|
||||
|
||||
## 前端(Vue 3 + Ant Design Vue)
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# 通用代码评审检查清单
|
||||
|
||||
每次代码审查都应加载本清单。
|
||||
|
||||
适用于所有项目,补充六视角扫描法(五传统视角 + Karpathy Scope 视角)。
|
||||
|
||||
## Karpathy 反模式速查(Scope 审计者视角辅助)
|
||||
|
||||
基于 [andrej-karpathy-skills](https://github.com/forrestchang/andrej-karpathy-skills) EXAMPLES.md 提炼。
|
||||
|
||||
### ❌ 反模式 → ✅ 正确做法
|
||||
|
||||
| 场景 | 反模式(LLM 常犯) | 正确做法 |
|
||||
|------|-----------------|---------|
|
||||
| "做个导出功能" | 静默假设文件格式/字段/分页,直接实现 | 列出3种解读(API/文件/任务队列),问用户选哪种 |
|
||||
| "让搜索更快" | 同时加缓存+索引+async,200行 | 列出3种"更快"含义+估算,等确认再做 |
|
||||
| "加个折扣计算" | Strategy+Abstract+Enum+DataClass,50行 | `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 行考虑拆分)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "slark-cicd-plugin",
|
||||
"description": "Slark 仓库 staging、生产与 Desktop 安装包的端到端 CI/CD 发布技能。",
|
||||
"version": "1.1.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "slark-cicd",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
---
|
||||
name: slark-cicd
|
||||
description: Slark 仓库 staging、生产和 Desktop 安装包的端到端 CI/CD 可执行 runbook。覆盖本地预推快检、PR 与内网 ci/internal-gate 门禁、合并 origin/main、生产前强制 staging 验收、审批式生产发布,以及在 m5max 构建签名、公证并上传 macOS/Windows Desktop 包到 OSS。当用户要在 slark(qiudl/qiu-slark)里发布/上线、部署到生产或 staging、发布 Desktop 安装包、盯 CI/合并 PR、复现或验证运行时行为、或排查发布链/推送路由问题时使用。
|
||||
---
|
||||
|
||||
# Slark CI/CD runbook
|
||||
|
||||
驱动 Slark 从改代码到上线的端到端流程。权威流程与完整细节见仓库文档
|
||||
`docs/cicd-staging-production.md`(本技能命令与之一致);本文件是可直接照做的分阶段清单。
|
||||
|
||||
仓库:`qiudl/qiu-slark`(本机克隆通常在 `/Users/donglinlai/coding/slark`)。所有命令在**仓库根**执行。
|
||||
`ai-proj` 相关命令加 `GODEBUG=netdns=go` 前缀。
|
||||
|
||||
## 铁律(先记死)
|
||||
|
||||
- GitHub `origin/main` 是唯一代码权威、合并入口与生产发布来源;`internal` 只用于 CI/镜像,永不发布。
|
||||
- 生产发布只接受**最新 `origin/main` 的精确 40 位 SHA**;`HEAD != origin/main` 会被 `deploy.sh` 拒绝。
|
||||
- 生产发布必须在**普通 clone** 中执行,且 `.git` 必须是目录;`release-prod.sh` / `deploy.sh` 会拒绝
|
||||
`.git` 为指针文件的 git worktree。不要污染现有 checkout:主目录脏或被占用时,新建干净的临时 clone。
|
||||
- **生产发布前必须先发布并验收 staging,不得跳过**:同一最新 `origin/main` 精确 SHA 必须先完成迁移演练、
|
||||
staging 正式部署、隔离检查和需求功能验收。把证据报告给用户后,必须再次取得明确的生产确认;开始 staging
|
||||
前获得的生产授权不能替代这次确认。未通过、未完成或用户未确认时停止,不得执行生产发布命令。
|
||||
- 审批式部署,永不自动:亲手敲的 `sudo`/`rm -rf`/`dd`/`curl|sh`/包安装是 `destructive`(NEVER_AUTO),
|
||||
**每次都要人审**;命中审批闸就**等人批**,绝不绕过、绝不把这些 key 加进 auto-approve 白名单。
|
||||
- staging 与生产硬隔离:staging 脚本带生产 IP 拒运行护栏、不 push origin、不反向 rsync。
|
||||
- 当前 Slark staging 权威目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`
|
||||
(Name=`slark-staging`,2026-08-18 核验公网 IP `15.135.112.181`)。公网 IP 自动分配,发布前必须按
|
||||
实例 ID 重查并确认 Name/状态;不得凭历史 IP 判断目标。
|
||||
- `82.157.141.202` 是 ai-proj 预生产宿主机。其 `/opt/slark-staging` cohost 是隔离的 Slark 兼容验收栈,
|
||||
但不是 Slark 独立预生产;只有任务明确指定 cohost 时才能使用,不得作为普通 Slark staging 的默认目标。
|
||||
- staging 发布若影响 `packages/daemon`、`packages/runtime`、Server/daemon 公共协议或 Desktop 公共运行链,
|
||||
必须把 `server-staging` 与 `daemon-staging` 更新到同一精确 SHA;两者版本不一致不得宣告 staging 成功。
|
||||
- 只修 PR 范围内的 CI 失败;不为过检去改 CI/workflow 或做无关改动。
|
||||
|
||||
## 选择链路
|
||||
|
||||
- 「发布/上线/部署生产」→ 生产发布链:阶段 1→2→3(同 SHA staging 验收)→ 人工确认 →4→5。
|
||||
- 「Desktop/桌面版打包、发布、上传 OSS」→ 阶段 1→2 后走 `docs/desktop-oss-release.md` 的 m5max 双平台链路。
|
||||
- 「盯 CI/合并 PR」→ 阶段 1→2(用 babysit 心态循环到 green + mergeable + 评论收口)。
|
||||
- 「复现/验证运行时行为、别碰生产」→ staging 预验链(阶段 3,可独立随时跑)。
|
||||
|
||||
## 阶段 1:PR 与内网 gate(`ci/internal-gate`)
|
||||
|
||||
真实门禁是内网 CI(GitHub Actions 已停用)。检查与自查:
|
||||
|
||||
```bash
|
||||
gh pr checks <PR> # 应见 ci/internal-gate
|
||||
gh pr view <PR> --json state,mergeable,mergeStateStatus,isDraft,reviewDecision
|
||||
```
|
||||
|
||||
- 基础门(全 PR):release-contract / member-gate / authz-matrix / controlled-content-exits /
|
||||
onconflict-predicate / migration-idempotency / wsl-path-translation / typecheck / test(全量)。
|
||||
- 条件外部门(按改动路径):改 server DB/迁移/schema → `postgresql-security`;改 web → `web-e2e`;
|
||||
android / daemon+desktop 各有门。
|
||||
- 本地复跑基础门可跑部分:`pnpm -r typecheck && pnpm test`。
|
||||
|
||||
红了:只修本 PR 范围内的失败。疑似无关的合并阻塞→先 merge 最新 `main`(可能别的 PR 已修)。
|
||||
`BLOCKED: <label> requires <VAR>` = runner 环境未配外部门命令,属运维问题,不是 PR 代码问题。
|
||||
|
||||
## 阶段 2:合并到 `origin/main`
|
||||
|
||||
合并前确认:`mergeable=MERGEABLE`、`mergeStateStatus=CLEAN`、非 draft、`ci/internal-gate` 绿、评论收口。
|
||||
|
||||
```bash
|
||||
gh pr merge <PR> --merge # 仓库风格:merge commit
|
||||
```
|
||||
|
||||
若本机 `main` 被其他 worktree 占用导致 `--delete-branch` 本地 checkout 失败(属本地副作用):
|
||||
|
||||
```bash
|
||||
gh pr view <PR> --json state,mergedAt,mergeCommit # 确认 GitHub 侧 MERGED
|
||||
git push origin --delete <feature-branch> # 删远端
|
||||
git branch -D <feature-branch> # 删本地(先切走)
|
||||
```
|
||||
|
||||
## 阶段 3:staging 真机预验(生产前强制,也可独立运行)
|
||||
|
||||
生产候选必须使用最新 `origin/main` 的同一精确 SHA。先执行迁移演练,再正式部署;随后完成隔离检查和本次需求的
|
||||
功能验收。验收不能只看通用 health:凡需求涉及真实集成(如飞书 OAuth),staging 缺少对应 provider/凭据时必须
|
||||
明确标为未完成并停止生产链,除非用户在看到该限制后明确接受。完成后报告目标实例、SHA、Server/Daemon revision、
|
||||
迁移演练、健康、隔离和功能结果,并等待用户再次确认是否进入生产。
|
||||
|
||||
### 3.1 当前权威路径:Slark 独立 staging
|
||||
|
||||
稳定目标是 AWS `ap-southeast-2` 实例 `i-013e5ca3fdfddc13e`(Name=`slark-staging`)。它使用自动分配
|
||||
公网 IP,因此每次部署前先查询当前 IP;2026-08-18 核验值为 `15.135.112.181`:
|
||||
|
||||
```bash
|
||||
aws ec2 describe-instances --region ap-southeast-2 \
|
||||
--instance-ids i-013e5ca3fdfddc13e \
|
||||
--query 'Reservations[0].Instances[0].{Name:Tags[?Key==`Name`]|[0].Value,State:State.Name,PublicIp:PublicIpAddress}' \
|
||||
--output table
|
||||
|
||||
export STAGING_SSH_KEY=~/.ssh/cloud-server-syd.pem
|
||||
export STAGING_REMOTE=ubuntu@15.135.112.181
|
||||
export STAGING_REMOTE_IP=15.135.112.181
|
||||
export STAGING_BASE=http://15.135.112.181
|
||||
|
||||
scripts/deploy-staging.sh --migrate-dry-run
|
||||
scripts/deploy-staging.sh
|
||||
STAGING_TOKEN=$(cat /tmp/.staging_token) scripts/staging-seed.sh
|
||||
scripts/staging-verify-isolation.sh
|
||||
```
|
||||
|
||||
不得把查询到的 Name 非 `slark-staging` 的机器当作目标。四件套细节与环境隔离约束见
|
||||
`docs/req-20260811-0059-staging-environment-design.md`。
|
||||
|
||||
### 3.2 非权威路径:ai-proj cohost(仅明确指定时)
|
||||
|
||||
ai-proj cohost 位于 `ubuntu@82.157.141.202:/opt/slark-staging`。全程使用隔离数据、数据库、密钥和状态目录,
|
||||
但宿主机身份仍是 ai-proj staging;普通“Slark 预生产”不得走这里。仅在任务明确要求 cohost 兼容验收时,
|
||||
先在**干净的独立 worktree**检出待验精确 SHA,再执行:
|
||||
|
||||
```bash
|
||||
STAGING_SSH_KEY=~/.ssh/ai_proj_stg.pem \
|
||||
STAGING_KNOWN_HOSTS_FILE=~/.ssh/known_hosts \
|
||||
STAGING_REMOTE=ubuntu@82.157.141.202 \
|
||||
STAGING_REMOTE_IP=82.157.141.202 \
|
||||
scripts/deploy-staging-cohost.sh
|
||||
```
|
||||
|
||||
如果改动影响 daemon/runtime/公共协议,还需用 Compose `daemon` profile 更新 `daemon-staging`,并核对
|
||||
Server/daemon revision 同为待验 SHA。
|
||||
|
||||
### 3.3 环境选择检查
|
||||
|
||||
普通 Slark staging 默认走 3.1;只有用户或需求明确说“ai-proj cohost”才走 3.2。任何验收都必须记录:
|
||||
环境稳定身份、当次 IP、待验 40 位 SHA、Server/daemon revision/status、Server health 和目标功能结果。
|
||||
|
||||
routed-remote 要 `network=enabled` 必须同时满足:① agent `workspace_access=write` +
|
||||
`repository_access=write`;② per-agent 工作区是可写 git worktree。细节见
|
||||
`docs/req-20260811-0059-staging-environment-design.md`。staging 刻意不碰 `deploy.sh` 的生产闸门。
|
||||
|
||||
## 阶段 4:生产发布(审批式)
|
||||
|
||||
**前置门禁**:阶段 3 已对同一 SHA 完成 staging 部署和验收,证据已报告,且用户在报告之后明确确认生产发布。
|
||||
缺任一项即停止;不得把 PR 合并授权、较早的生产授权或单纯的“继续”视为验收后确认。
|
||||
|
||||
**唯一姿势**:从干净的普通 clone 发布最新 `origin/main`(`HEAD == origin/main` 精确 SHA)。staging 可用
|
||||
独立 worktree,但生产脚本为保证同步与发布边界,会拒绝 worktree;不要在用户已有脏 checkout 中清理或发布。
|
||||
|
||||
```bash
|
||||
# 若当前仓库不是普通 clone,先在安全父目录创建一次性干净 clone:
|
||||
git clone https://github.com/qiudl/qiu-slark.git <clean-production-clone>
|
||||
cd <clean-production-clone>
|
||||
|
||||
git fetch origin main
|
||||
test -d .git # 必须通过;worktree 的 .git 是文件
|
||||
git status --porcelain # 必须干净
|
||||
git checkout --detach "$(git rev-parse origin/main)" # 在产品分支上时先 detach 到该 SHA
|
||||
|
||||
pnpm release:prod # = release-prod.sh → deploy.sh --backend --no-install --no-mirror
|
||||
# 备选:scripts/deploy.sh --pull --backend
|
||||
```
|
||||
|
||||
- `deploy.sh` 用 `check-production-release-sha.sh` 锁 `HEAD == origin/main` SHA,不符即拒。
|
||||
- `--backend` 才跑迁移:owner 连接(DDL 权)与运行时 `slark_app`(DML-only)分离;幂等、失败即非零。
|
||||
- 命中审批闸(发布/sudo)→ **等人批**;不要绕。脚本内部 SSH 载荷子进程的 sudo 不触发闸。
|
||||
- 普通工作 clone 发布完切回原分支:`git checkout <branch>`;一次性发布 clone 可保留作审计,清理时用可恢复方式。
|
||||
|
||||
### HANDOFF_BLOCKED 恢复入口
|
||||
|
||||
发布准备遇到 `HANDOFF_BLOCKED`、handoff manifest=`failed_closed` 或命令状态不确定时,停止发布切换;禁止靠重跑
|
||||
发布脚本、直接改表或默认 replay 来“解卡”。先把 manifest、命令、execution、lease/outbox 和审计证据做只读盘点,
|
||||
再让用户明确授权每条记录的 reconciliation action/outcome 及是否允许继续发布。恢复细节见
|
||||
[references/production-handoff-recovery.md](references/production-handoff-recovery.md);仅遇到该类事故时读取。
|
||||
|
||||
`COMMAND_AUTHORITY_CHANGED` 是有效并发护栏,不是可忽略错误:重新读取当前 authority/transport/execution generation,
|
||||
确认期间没有新活动,再在原授权范围内幂等续办。若发现新工作、活跃 lease/outbox 或无法解释的副作用,立即停止并
|
||||
重新请求决策。
|
||||
|
||||
## 阶段 5:发布后核查
|
||||
|
||||
```bash
|
||||
scripts/verify-deploy.sh # 健康(前端/api/health=200)、bundle、server/daemon SHA、迁移集、federation、凭据
|
||||
```
|
||||
|
||||
预期 `✓ deploy verification passed: server=<sha> daemon=<sha>` 且 `<sha> == origin/main`,舰队 `online ≥ 发布前`。
|
||||
完成前还要独立复核 governed release 账本为 `succeeded/complete`、四组件实际 SHA 均为目标 SHA、租约已释放、
|
||||
新 handoff manifest=`verified`;若本次处理过旧 manifest,还要核对其全部 case 已达授权终态且 replay 数为 0。
|
||||
|
||||
默认生产包装器使用 `--no-mirror`:构建 daemon candidate binary 用于运行态验证不等于上传镜像资产。镜像落后告警
|
||||
在该模式下是预期的非阻断结果;不得未经单独授权补传镜像。交付说明要明确国内安装器可能回退 upstream,避免把
|
||||
本地构建、生产 daemon 更新和镜像上传混为一谈。
|
||||
|
||||
## Desktop m5max 双平台发布
|
||||
|
||||
Desktop 安装包不走 Server 生产部署脚本。权威流程见仓库 `docs/desktop-oss-release.md`,已验证基线为
|
||||
REQ-20260819-0005 / Desktop 0.3.1:
|
||||
|
||||
- 版本 PR 通过 `ci/internal-gate` 并合并后,m5max 从最新 `origin/main` 精确 SHA 的干净普通 clone 构建。
|
||||
- m5max 原生构建、Developer ID 签名并公证/staple macOS DMG/ZIP;交叉构建 Windows x64 NSIS EXE。
|
||||
- Darwin 不能执行 Windows 原生 `smoke:windows`。只有发布负责人明确接手 Windows 原生验收时才能继续交叉打包,交付证据必须标注该边界。
|
||||
- 资产不回传开发机;由 m5max 直接上传 `xiaoqu-public-file/slark/desktop/releases/v<version>/`,manifest 最后更新。
|
||||
- `xiaoqu-public-file` 的 canonical 凭证源是发布控制机上的 `/Users/donglinlai/coding/param.rxt`(必须为 `0600`);AK/SK 分别位于 XML 标签 `ossAccessKeyId` / `ossAccessKeySecret`。不得改猜 Bitwarden、Keychain、通用 `credentials.env` 或 m5max 本地配置;只有该文件缺失、权限不符或标签解析为空时才停止并报告。
|
||||
- m5max 不持久化 OSS 凭证。发布控制机从 `param.rxt` 提取 AK/SK 后,只用 `printf '%s\n%s\n' ... | ssh m5max ...` 经 SSH stdin 传两行;远端用 `read -r` 接收并仅为当次 shell 导出 `OSS_ACCESS_KEY_ID` / `OSS_ACCESS_KEY_SECRET`,随后运行上传脚本。禁止 `scp` 凭证文件、写远端临时文件或把值打印到日志。
|
||||
- m5max 非交互 SSH 必须显式补 `PATH=/opt/homebrew/bin:/usr/local/bin:$HOME/.local/bin:$PATH`;已验证 `ossutil` 位于 `/Users/johnq/.local/bin/ossutil`。执行前检查 Node、三个安装包、精确 main SHA 和干净工作树,避免因交互 shell 环境差异重新猜路径。
|
||||
- m5max 默认出口若走 `utun`,只为当次 `ossutil` 使用 `--bind-address "$(ipconfig getifaddr en0)"`,不得修改整机 Tailscale/默认路由。
|
||||
- 完成前核对三份资产 SHA256/大小、macOS 签名与公证、公开 URL/manifest,并回写 ai-proj 证据。
|
||||
|
||||
## 推送路由自查(换机器/新克隆必做)
|
||||
|
||||
```bash
|
||||
git remote -v # origin 收发都应是 https://github.com/qiudl/qiu-slark.git
|
||||
git config --get-regexp '^url\.' # 有输出 = pushInsteadOf 改写,push 可能被静默重定向到内网
|
||||
scripts/git-release-governance.sh check-local
|
||||
scripts/git-release-governance.sh status
|
||||
```
|
||||
|
||||
## 故障分流
|
||||
|
||||
| 现象 | 处置 |
|
||||
| --- | --- |
|
||||
| `release-prod` 拒绝 HEAD≠origin/main | `git fetch origin main` 后 detach 到 origin/main SHA,工作树须干净 |
|
||||
| `release-prod` 拒绝 git worktree / `.git` 不是目录 | 改用干净的普通 clone,锁定最新 `origin/main`;不要在现有脏 checkout 中强行切换 |
|
||||
| `ci/internal-gate` red | 看 gate 分组日志找 FAIL 项;`pnpm -r typecheck && pnpm test` 本地复现修复 |
|
||||
| `BLOCKED: <label> requires <VAR>` | runner 未配外部门命令,报运维配 `SLARK_CI_*_CMD`,非 PR 问题 |
|
||||
| 合并阻塞但与 PR 无关 | merge 最新 `main` 再看 |
|
||||
| push 成功但 GitHub 没有 | 查 `git config --get-regexp '^url\.'`,移除 pushInsteadOf 或显式指定 GitHub URL |
|
||||
| staging routed-remote 恒断网 | 查两道放网杠杆(workspace_access=write + 可写 git worktree) |
|
||||
| cohost Server 已更新但 daemon 仍是旧 SHA | 用 `--profile daemon up -d daemon-staging` 更新 daemon;核对两容器 revision 后才算完成 |
|
||||
| cohost daemon 启动即退出/fatal | 看 `docker logs slark_daemon_staging`;修复凭据/环境或版本契约,禁止用旧 daemon 冒充验收通过 |
|
||||
| verify 失败 SHA 不符 | 部署到了旧码,重发 |
|
||||
| `HANDOFF_BLOCKED` / manifest=`failed_closed` | 停止切换并读取 handoff recovery 参考;只读盘点后取得逐项 action/outcome 授权,禁止默认 replay |
|
||||
| reconciliation 返回 `COMMAND_AUTHORITY_CHANGED` | 查当前 generation 与新活动;无新活动才在原授权内幂等续办,有变化则停止重新决策 |
|
||||
| `--no-mirror` 后提示镜像落后 | 预期非阻断;报告 upstream fallback,未经单独授权不上传镜像 |
|
||||
| 生产授权已给但 staging 尚未验收 | 先完成同 SHA staging 全量验收,报告证据并重新等用户确认;不得沿用较早授权 |
|
||||
| staging 缺少需求依赖的真实集成配置 | 将对应验收标为未完成并停止生产链;补齐配置后重验,或让用户基于明确限制作决定 |
|
||||
|
||||
## ai-proj 流程(强制)
|
||||
|
||||
任何改产品/代码行为的工作,动手前先在 ai-proj 立项 + 建 Task + 关联,实现/测试/交付回写;
|
||||
commit/PR 引用 `REQ-...`。纯文档/工具/只读排查除外。详见仓库 `AGENTS.md` / `CLAUDE.md`。
|
||||
@@ -0,0 +1,70 @@
|
||||
# Production handoff recovery
|
||||
|
||||
仅在生产发布被 coding-session handoff 阻塞、manifest 进入 `failed_closed`,或存在 `needs_review` / 不确定命令时读取。
|
||||
目标是保留 fail-closed 语义,在明确授权下终结歧义状态,并证明没有误重放命令。
|
||||
|
||||
## 先冻结发布,不先解锁
|
||||
|
||||
- 停止发布切换和自动重试;记录 release ID、候选 SHA、manifest ID、阻塞发生阶段与原始错误。
|
||||
- 不把“继续”“再试一次”解释为 replay、force-end 或生产发布授权。向用户展示证据后,确认语句应绑定 manifest、
|
||||
条目数量或 command ID、目标 action/outcome、是否禁止 replay,以及要继续发布的精确 SHA。
|
||||
- 不直接更新 handoff、command、execution 表来改变状态。优先走已部署版本的内部治理 API;只有 API 无法恢复且
|
||||
已获得对应操作授权时,才可调用该版本现有 domain repository,并保留 lifecycle event 与 audit log。
|
||||
|
||||
## 只读证据盘点
|
||||
|
||||
对 manifest 中每条 item 核对并保存摘要:
|
||||
|
||||
- command ID、session/project/computer ID、command status、execution state;
|
||||
- manifest 记录的 authority term、transport generation、execution generation,以及当前实际值;
|
||||
- command/execution lease 是否过期,outbox 是否仍有 active/pending 记录;
|
||||
- session 中是否还有其他活跃命令或新事件;
|
||||
- daemon acknowledgement、result/receipt、审计事件是否能证明执行或未执行。
|
||||
|
||||
若 lease/outbox 仍活跃、存在新命令、证据相互矛盾,或无法排除外部副作用,停止恢复并请求新的人工判断。
|
||||
|
||||
## Action 与 outcome 必须匹配证据
|
||||
|
||||
- `no_action / not_executed`:只有权威证据能证明命令从未执行,且治理接口允许该组合时使用。
|
||||
- `terminate / terminated`:用于显式受控终结仍处于 uncertain/active 语义的命令;先通过 domain 操作取消 command、
|
||||
force-end execution、完成 session 并写审计,再完成 reconciliation,不产生 replay。
|
||||
- `mark_failed / failed`:证据确认失败、且不能安全恢复为成功或未执行时使用。
|
||||
- `replay / replayed_success`:仅在用户明确授权 replay 且 exactly-once/幂等边界得到证明时使用。发布解卡绝不能默认 replay。
|
||||
|
||||
不要为了迎合预先选择的 outcome 忽略现场证据;如果授权与可证明事实不兼容,停止并把差异报告给用户。
|
||||
|
||||
## 受治理恢复顺序
|
||||
|
||||
1. 使用精确 manifest/item 建立或读取 reconciliation case。
|
||||
2. 取得具备 action、evidence digest、operator 和有效期约束的审批。
|
||||
3. 记录 decision;执行 domain 终结或治理 apply;随后用权威运行态证据 confirm terminal outcome。
|
||||
4. 若 apply 返回 `COMMAND_AUTHORITY_CHANGED`,不要覆盖或绕过。重新读取当前三类 generation 和新活动:
|
||||
- generation 只因刚完成的受控终结而变化,且无新命令、lease/outbox 时,可在同一授权 action 下幂等续办;
|
||||
- generation 对应新工作或原因不明时,停止并重新审批。
|
||||
5. 每个 item 都 terminal 后,确认 manifest 自动或受治理地进入 `reconciled`,再启动新的发布尝试。
|
||||
|
||||
凭据失败发生在事务前时应视为零变更并记录。不要反复猜测 owner 凭据;不得打印连接串或密钥。若必须使用运行时
|
||||
`slark_app` 连接,只能通过已部署 domain repository、正确的 per-transaction project/user RLS context 和审计路径操作。
|
||||
|
||||
## 零重放与发布恢复验收
|
||||
|
||||
恢复完成至少证明:
|
||||
|
||||
- `terminalCount == totalCount`,每条 decision action 与 terminal outcome 等于用户授权;
|
||||
- 禁止 replay 的场景下,decision action=`replay` 数和 execution replay attempt 增量均为 0;
|
||||
- `terminate/terminated` 场景下,command=`cancelled`、execution=`force_ended`,并存在对应 lifecycle/audit 证据;
|
||||
- 旧 manifest=`reconciled`,没有 active handoff reservation 残留。
|
||||
|
||||
继续生产发布时使用新 governed release / handoff manifest,不复用 failed-closed manifest。重新检查候选仍是最新
|
||||
`origin/main`、同 SHA staging eligibility receipt 仍有效、工作树干净;任何一项变化都回到 staging 与人工确认门禁。
|
||||
|
||||
发布成功后再独立执行一次 `scripts/verify-deploy.sh`,并只读核对:release=`succeeded/complete`、target/actual Web、
|
||||
Server、daemon、artifact SHA 全相等、lease 已释放、finished 已记录、新 manifest=`verified`、舰队在线数不低于基线。
|
||||
将旧 manifest reconciliation 与新发布证据一起回写 ai-proj。
|
||||
|
||||
## `--no-mirror` 的解释边界
|
||||
|
||||
- `pnpm release:prod` 默认走 `deploy.sh --backend --no-install --no-mirror`;不上传 daemon 镜像资产。
|
||||
- 发布过程仍可能在隔离 candidate 目录构建 daemon binary,用于同 SHA 验证或部署运行态 daemon;这不是镜像上传。
|
||||
- mirror freshness 告警表示镜像源仍旧,国内安装器可能回退到较慢 upstream。它不阻断当前生产运行态发布。
|
||||
- 补传 mirror 是单独的外部写操作,需要单独授权和凭据;不能为了消除告警在发布收尾时顺手执行。
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "slark-dsh-cicd-plugin",
|
||||
"description": "DSH 插件包与 Slark 宿主补丁的发布实战技能:npm run pack 双资产、无上游写权限时的 fork 发布、干净单提交与树一致性 gate、draft Release、发布后 SHA 核对、asar 完整性与 ai-proj 状态对齐。",
|
||||
"version": "1.0.0",
|
||||
"author": {
|
||||
"name": "qiudl"
|
||||
},
|
||||
"install_name": "slark-dsh-cicd",
|
||||
"install_type": "skill",
|
||||
"dir_category": "dev"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user