diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
index 0826315..dc65ad8 100644
--- a/.agents/plugins/marketplace.json
+++ b/.agents/plugins/marketplace.json
@@ -51,6 +51,18 @@
"authentication": "ON_INSTALL"
},
"category": "Productivity"
+ },
+ {
+ "name": "zentao",
+ "source": {
+ "source": "local",
+ "path": "./plugins/zentao"
+ },
+ "policy": {
+ "installation": "AVAILABLE",
+ "authentication": "ON_INSTALL"
+ },
+ "category": "Productivity"
}
]
}
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index b0eec24..f3ad116 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -24,6 +24,11 @@
"name": "obsidian",
"source": "./plugins/obsidian",
"description": "Obsidian 知识库 AI 协作插件族:检测到 .obsidian/ 目录自动激活,含 vault 管理、搜索图谱、frontmatter、任务、每日笔记、Bases 数据库、Canvas 视觉层、版本历史、插件配置、PKM 编排工作流共 10 个技能"
+ },
+ {
+ "name": "zentao",
+ "source": "./plugins/zentao",
+ "description": "禅道项目管理系统:项目集/产品/项目/执行、需求(story/epic/requirement)、Bug、任务、测试、计划与发布、反馈工单八个工作流技能,自动配置 MCP 连接(禅道「个人中心 → 获取凭证」自助生成 14 天 Token)"
}
]
}
diff --git a/CLAUDE.md b/CLAUDE.md
index 8f9242c..c32b3dc 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -92,8 +92,9 @@ description: 一句话说明该技能的用途(Claude 用此判断何时触发
| `huanxi-admin` | `/huanxi-admin-shared` `/huanxi-admin-report` `/huanxi-admin-module` `/huanxi-admin-ops` | 寰汐企业管理系统 · 管理端(4 技能),全量视角,需后台管理员发放 hxa_ Token,普通员工无需安装 |
| `memcore` | `/memory-sync` `/memory-update` `/memory-lint` `/memcore-shared`(内部 include) | 项目记忆体系核心引擎 |
| `obsidian` | `/obsidian` `/obsidian-bases` `/obsidian-canvas` `/obsidian-daily` `/obsidian-history` `/obsidian-meta` `/obsidian-plugins` `/obsidian-search` `/obsidian-tasks` `/obsidian-workflow-pkm` | Obsidian 知识库完整工作流(10 个技能;对标 kepano/obsidian-skills 31.8k★ 与 AgriciDaniel/claude-obsidian) |
+| `zentao` | `/zentao-shared` `/zentao-project` `/zentao-story` `/zentao-bug` `/zentao-task` `/zentao-test` `/zentao-plan` `/zentao-misc` | 禅道项目管理系统(8 个技能),含 MCP Server 自动配置(禅道「个人中心 → 获取凭证」14 天 Token);MCP Server 是基于开源 [merzzzl/openapi-mcp-server](https://github.com/merzzzl/openapi-mcp-server) 二次开发的 zentao-mcp 网桥,部署在 `pm.ops.yixiong-tech.com/mcp`,请求体字段统一包在 `payload` 里 |
-四个插件均有 Codex/ChatGPT 桌面应用版本(见上方「Codex plugin.json」一节)。huanxi/huanxi-admin/obsidian 的 Codex 版共用本表里的同一份 `skills/`;`memcore` 的 Codex 版是独立目录 `plugins/memcore-codex/`(内容与下方 Claude 版 memcore 不同,改动时两边分别维护,不要假设同步)。已调研并确认不做 Google Antigravity(agy)兼容——其官方文档目前没有 marketplace 概念。
+五个插件均有 Codex/ChatGPT 桌面应用版本(见上方「Codex plugin.json」一节)。huanxi/huanxi-admin/obsidian/zentao 的 Codex 版共用本表里的同一份 `skills/`;`memcore` 的 Codex 版是独立目录 `plugins/memcore-codex/`(内容与下方 Claude 版 memcore 不同,改动时两边分别维护,不要假设同步)。已调研并确认不做 Google Antigravity(agy)兼容——其官方文档目前没有 marketplace 概念。
### memcore 技能调用关系
diff --git a/README.md b/README.md
index 05d1c27..0807d33 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,7 @@
/plugin install huanxi-admin@yixiong-claude-hub
/plugin install memcore@yixiong-claude-hub
/plugin install obsidian@yixiong-claude-hub
+/plugin install zentao@yixiong-claude-hub
```
已安装的插件会在会话启动时自动检测更新——本仓库不走语义化版本号,每次推送 `main` 分支即视为新版本。
@@ -47,17 +48,18 @@ codex plugin marketplace add https://gitea.rk-health.com/yixiong/yixiong-claude-
codex plugin add huanxi-admin@yixiong-codex-hub
codex plugin add memcore@yixiong-codex-hub
codex plugin add obsidian@yixiong-codex-hub
+ codex plugin add zentao@yixiong-codex-hub
```
-- **ChatGPT 桌面应用(图形界面)**:市场源注册生效后,打开 **设置(Settings)→ 插件(Plugins)**,或侧边栏的 **Plugins** 入口,就能看到我们的市场和这四个插件,点击安装即可,不用碰命令行。
+- **ChatGPT 桌面应用(图形界面)**:市场源注册生效后,打开 **设置(Settings)→ 插件(Plugins)**,或侧边栏的 **Plugins** 入口,就能看到我们的市场和这五个插件,点击安装即可,不用碰命令行。
安装完成后开一个新会话,Codex 才会加载新装的技能和 MCP 工具。
-> ⚠️ **桌面应用读不到 shell 里 `export` 的环境变量**:`huanxi`/`huanxi-admin` 的 Token 走环境变量注入(见下一节),但 macOS/Windows 上从 Dock/开始菜单启动的图形应用不会继承 `~/.zshrc` 里 `export` 的变量——这是终端应用和图形应用两种不同的启动路径决定的,不是我们插件的问题。桌面应用场景要设置成**系统级/用户级持久环境变量**才行,具体见下一节。
+> ⚠️ **桌面应用读不到 shell 里 `export` 的环境变量**:`huanxi`/`huanxi-admin`/`zentao` 的 Token 走环境变量注入(见下一节),但 macOS/Windows 上从 Dock/开始菜单启动的图形应用不会继承 `~/.zshrc` 里 `export` 的变量——这是终端应用和图形应用两种不同的启动路径决定的,不是我们插件的问题。桌面应用场景要设置成**系统级/用户级持久环境变量**才行,具体见下一节。
>
> ⚠️ **插件级 Token 配置目前是 Codex 官方还没定案的能力**:截至本文写作时,插件打包的 MCP server 没有类似 Claude 侧「安装时弹窗填 Token」的正式支持(见 [openai/codex#24401](https://github.com/openai/codex/issues/24401)),环境变量是目前唯一现实可用的路径。装完插件连不上 MCP,先检查 Token 环境变量是不是在 Codex/ChatGPT 启动**之前**就已经生效。
-> Codex 侧的插件清单独立维护在 [`.agents/plugins/marketplace.json`](./.agents/plugins/marketplace.json)。除 `memcore` 外,其余三个插件与 Claude Code 版共用同一份 `skills/`;`memcore` 因为架构差异(会话入口、记忆目录约定、有无远程同步都不同)走的是独立目录 [`plugins/memcore-codex/`](./plugins/memcore-codex),两边分开维护。
+> Codex 侧的插件清单独立维护在 [`.agents/plugins/marketplace.json`](./.agents/plugins/marketplace.json)。除 `memcore` 外,其余四个插件与 Claude Code 版共用同一份 `skills/`;`memcore` 因为架构差异(会话入口、记忆目录约定、有无远程同步都不同)走的是独立目录 [`plugins/memcore-codex/`](./plugins/memcore-codex),两边分开维护。
## 插件一览
@@ -67,6 +69,7 @@ codex plugin marketplace add https://gitea.rk-health.com/yixiong/yixiong-claude-
| [`huanxi-admin`](./plugins/huanxi-admin) | 4 | 寰汐管理端——汇报盘点、模块与成员配置、运维简报 | 需要后台管理员发放 `hxa_` Token 的管理岗 | ✅ | ✅ |
| [`memcore`](./plugins/memcore) / [`memcore-codex`](./plugins/memcore-codex) | 4 | 项目记忆体系核心引擎——跨会话记忆的同步/增量更新/健康校验 | 用 Claude Code 或 Codex 做长期项目的开发者 | ✅ | ✅ |
| [`obsidian`](./plugins/obsidian) | 10 | Obsidian 知识库全套协作工作流——vault 管理、搜索图谱、Bases、Canvas、每日笔记等 | 用 Obsidian 做知识管理的人 | ✅ | ✅ |
+| [`zentao`](./plugins/zentao) | 8 | 禅道项目管理——项目集/产品/项目/执行、需求(story/epic/requirement)、Bug、任务、测试、计划与发布、反馈工单 | 用禅道管项目/需求/Bug/任务的人 | ✅ | ✅ |
### huanxi / huanxi-admin
@@ -99,6 +102,30 @@ Claude Code 版([`plugins/memcore`](./plugins/memcore))以 `.claude/memory/`
检测到项目里有 `.obsidian/` 目录会自动激活,覆盖 vault 管理、全文与图谱搜索、frontmatter、任务、每日笔记、Bases 数据库、Canvas 视觉层、版本历史、插件配置、PKM 编排十个技能,对标社区里比较成熟的 Obsidian 技能实现后做了扩展。**无需任何配置**,Claude Code 和 Codex CLI 共用同一份技能内容。
+### zentao
+
+禅道是蚁熊内部的项目/需求/Bug/任务管理系统。插件覆盖项目集(program)/产品/项目/执行的层级管理、需求条线(story 用户故事 / epic 业务需求 / requirement 用户需求三种类型及其状态机)、Bug 全流程、任务状态机、测试用例与测试单、产品计划/版本/发布、反馈与工单、附件改名,共 8 个工作流技能。
+
+**配置步骤:**
+
+1. 登录禅道,头像下拉菜单点「获取凭证」:
+
+
+
+2. 点击「生成凭证」——注意提示:生成新凭证会让旧凭证立即失效,且明文只在生成后展示一次,务必当场保存;有效期 14 天,到期需重新获取:
+
+
+
+3. 弹窗会给出「API 地址」和「Token」两项,Claude Code / Codex 安装时都只需要 Token(API 地址已经写死在插件的 MCP 配置里,不用手动填):
+
+
+
+4. **Claude Code**:执行 `/plugin install` 时会提示输入 Token,直接粘贴即可——存放在系统钥匙链,不会明文写入配置文件。
+5. **Codex CLI / ChatGPT 桌面应用**:Token 走环境变量注入,设置方式同 `huanxi`:
+
+ - **命令行**:`export ZENTAO_TOKEN=你的token`,建议写进 `~/.zshrc` / `~/.bashrc`
+ - **ChatGPT 桌面应用**:macOS 用 `launchctl setenv ZENTAO_TOKEN 你的token`,Windows 走「系统属性 → 环境变量」新增用户变量 `ZENTAO_TOKEN`,设置后需重新打开应用
+
## 开发
给这个市场新增插件、技能实现规范、`marketplace.json`/`plugin.json` 格式说明,见 [CLAUDE.md](./CLAUDE.md)。
diff --git a/docs/images/zentao-token/01-menu.png b/docs/images/zentao-token/01-menu.png
new file mode 100644
index 0000000..f34ffa5
Binary files /dev/null and b/docs/images/zentao-token/01-menu.png differ
diff --git a/docs/images/zentao-token/02-generate.png b/docs/images/zentao-token/02-generate.png
new file mode 100644
index 0000000..41c64df
Binary files /dev/null and b/docs/images/zentao-token/02-generate.png differ
diff --git a/docs/images/zentao-token/03-token.png b/docs/images/zentao-token/03-token.png
new file mode 100644
index 0000000..1803195
Binary files /dev/null and b/docs/images/zentao-token/03-token.png differ
diff --git a/plugins/zentao/.claude-plugin/plugin.json b/plugins/zentao/.claude-plugin/plugin.json
new file mode 100644
index 0000000..9494124
--- /dev/null
+++ b/plugins/zentao/.claude-plugin/plugin.json
@@ -0,0 +1,24 @@
+{
+ "name": "zentao",
+ "description": "禅道项目管理系统:项目集/产品/项目/执行、需求(story/epic/requirement)、Bug、任务、测试、计划与发布、反馈工单等工作流技能,自动配置 MCP 连接。",
+ "author": {
+ "name": "蚁熊团队"
+ },
+ "userConfig": {
+ "token": {
+ "type": "string",
+ "title": "禅道 API Token",
+ "description": "在禅道「个人中心 → 获取凭证」自助生成,14 天有效期,到期需重新生成",
+ "sensitive": true
+ }
+ },
+ "mcpServers": {
+ "zentao": {
+ "type": "http",
+ "url": "https://pm.ops.yixiong-tech.com/mcp",
+ "headers": {
+ "token": "${user_config.token}"
+ }
+ }
+ }
+}
diff --git a/plugins/zentao/.codex-plugin/plugin.json b/plugins/zentao/.codex-plugin/plugin.json
new file mode 100644
index 0000000..1159932
--- /dev/null
+++ b/plugins/zentao/.codex-plugin/plugin.json
@@ -0,0 +1,19 @@
+{
+ "name": "zentao",
+ "version": "1.0.0",
+ "description": "禅道项目管理系统插件,含项目集/产品/项目/执行、需求、Bug、任务、测试、计划与发布、反馈工单八个工作流技能,自动配置 MCP 连接。",
+ "author": {
+ "name": "蚁熊团队"
+ },
+ "skills": "./skills",
+ "mcpServers": "./.mcp.json",
+ "interface": {
+ "displayName": "禅道",
+ "shortDescription": "禅道项目/需求/Bug/任务/测试工作流",
+ "longDescription": "禅道项目管理系统插件:覆盖项目集、产品、项目、执行、需求(story/epic/requirement)、Bug、任务、测试用例与测试单、产品计划、版本、发布、反馈、工单等工作流技能。安装后需在禅道「个人中心 → 获取凭证」自助生成 14 天有效期的 Token,并配置为环境变量 ZENTAO_TOKEN。",
+ "developerName": "蚁熊团队",
+ "category": "Productivity",
+ "capabilities": ["Interactive", "Write"],
+ "defaultPrompt": "帮我看看我名下有哪些未解决的 Bug 和任务"
+ }
+}
diff --git a/plugins/zentao/.mcp.json b/plugins/zentao/.mcp.json
new file mode 100644
index 0000000..8f06f15
--- /dev/null
+++ b/plugins/zentao/.mcp.json
@@ -0,0 +1,9 @@
+{
+ "mcpServers": {
+ "zentao": {
+ "type": "http",
+ "url": "https://pm.ops.yixiong-tech.com/mcp",
+ "bearer_token_env_var": "ZENTAO_TOKEN"
+ }
+ }
+}
diff --git a/plugins/zentao/skills/zentao-bug/SKILL.md b/plugins/zentao/skills/zentao-bug/SKILL.md
new file mode 100644
index 0000000..e4b8fd3
--- /dev/null
+++ b/plugins/zentao/skills/zentao-bug/SKILL.md
@@ -0,0 +1,96 @@
+---
+name: zentao-bug
+description: "禅道 Bug 管理:创建、查询、解决、关闭、激活 Bug。当用户说「提个 bug」「这个 bug 修好了」「bug 关掉」「查一下未解决的 bug」时使用。"
+---
+
+# 禅道 Bug 管理
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节)。**
+
+---
+
+## 状态机
+
+```
+激活(open) ──resolve 解决──▶ 已解决(resolved) ──close 关闭──▶ 已关闭(closed)
+ ▲ │
+ └──────────────────── activate 激活(重新打开)────────────────┘
+```
+
+`close` 也可以直接对一个未解决的 bug 使用(跳过 resolve 直接关闭,比如确认不是真实
+问题),但更常见的路径是先 resolve 再 close。
+
+---
+
+## 查
+
+```
+get_products_productID_bugs(productID, ...)
+get_projects_projectID_bugs(projectID, ...)
+get_executions_executionID_bugs(executionID, ...)
+get_bugs_bugID(bugID) 详情
+```
+
+用户问"未解决的 bug"时先确认要看哪个维度(产品/项目/执行),三个列表接口过滤范围不同。
+
+---
+
+## 建
+
+```
+post_bugs({ payload: { productID, title, openedBuild, project?, execution?, severity?,
+ pri?, type?, steps?, story? } })
+```
+
+`productID`/`title`/`openedBuild` 三个必填——**`openedBuild`(影响版本)容易漏**,
+不是可选项,**是字符串数组**,元素是版本 ID,主干传 `["trunk"]`(不是单个字符串
+`"trunk"`),可以同时关联多个版本。
+
+`type`(Bug 类型)受限枚举:`codeerror` 代码错误 | `config` 配置相关 | `install` 安装部署
+| `security` 安全相关 | `performance` 性能问题 | `standard` 标准规范 | `automation` 测试脚本
+| `designdefect` 设计缺陷 | `others` 其他。
+
+`severity`(严重程度)/`pri`(优先级)不传默认都是 3。
+
+`story` 字段可以关联一个相关需求(story ID)。
+
+---
+
+## 解决(resolve)
+
+```
+put_bugs_bugID_resolve({ bugID, payload: { resolution, resolvedDate?, resolvedBuild?,
+ assignedTo?, comment? } })
+```
+
+`resolution` 必填,受限枚举:`fixed` 已解决 | `notrepro` 无法重现 | `bydesign` 设计如此 |
+`duplicate` 重复Bug | `external` 外部原因 | `postponed` 延期处理 | `willnotfix` 不予解决 |
+`tostory` 转为需求。
+
+**先跟用户确认具体是哪种解决方式再调用**(`zentao-shared` 全局约定:状态流转类操作先
+确认)——`fixed` 和 `willnotfix`/`notrepro` 对提交者的观感完全不同,不要因为用户说
+"这个处理一下"就默认填 `fixed`。`tostory` 这个选项比较特殊,选它意味着这个 bug 会被
+转成一条需求,用之前跟用户确认清楚是不是真的要转。
+
+---
+
+## 关闭(close)/ 激活(activate)
+
+```
+put_bugs_bugID_close({ bugID, payload: { comment? } })
+put_bugs_bugID_activate({ bugID, payload: { openedBuild?, assignedTo?, comment? } })
+```
+
+都没有必填字段。关闭前确认这个 bug 确实该关了(比如已经 resolve 过,或者提交者认可
+不是真实问题);激活是把已关闭/已解决的 bug 重新打开,一般用于验证不通过要打回。
+
+---
+
+## 改 / 删
+
+```
+put_bugs_bugID({ bugID, payload: {...} })
+delete_bugs_bugID({ bugID })
+```
+
+删除不可逆,执行前必须确认。
diff --git a/plugins/zentao/skills/zentao-misc/SKILL.md b/plugins/zentao/skills/zentao-misc/SKILL.md
new file mode 100644
index 0000000..99cbb5b
--- /dev/null
+++ b/plugins/zentao/skills/zentao-misc/SKILL.md
@@ -0,0 +1,101 @@
+---
+name: zentao-misc
+description: "禅道反馈、工单、应用管理、附件改名。当用户说「提个反馈」「开个工单」「工单关掉」「建个应用」「改一下附件名字」时使用。"
+---
+
+# 禅道反馈 / 工单 / 应用 / 附件
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节)。**
+
+---
+
+## 反馈(feedback)
+
+```
+get_products_productID_feedbacks(productID, ...)
+get_feedbacks_feedbackID(feedbackID)
+
+post_feedbacks({ payload: { product, title, module?, type?, desc?, feedbackBy?, source? } })
+put_feedbacks_feedbackID({ feedbackID, payload: {...} })
+delete_feedbacks_feedbackID({ feedbackID })
+
+put_feedbacks_feedbackID_close({ feedbackID, payload: { closedReason, comment? } })
+put_feedbacks_feedbackID_activate({ feedbackID, payload: {...} })
+```
+
+**创建字段名注意:所属产品叫 `product`,不是 `productID`**(跟大多数其他实体不一致,
+`zentao-plan` 的 build/release 也是这个坑)。
+
+**"没有反馈/工单"和"参数传错"在这两个接口上长得一模一样**:实测发现某个产品下没有
+反馈/工单记录时,后端返回的是 `HTTP 200` + **完全空的响应体**,跟 `zentao-shared` 里说的
+"路径参数传漏/传错 → 静默返回空结果"是同一种表现。`zentao-shared` 那条"先怀疑参数
+传漏了,再怀疑数据本身"的排查顺序在这两个接口上不管用——换任何一个真实存在的
+`productID` 都可能一样是空的。真要鉴别,换个已知有数据的接口(比如 `get_products`)
+确认这个 productID 本身没写错,或者直接问用户这个产品下是否本来就没有反馈/工单。
+
+`type`(反馈类型)受限枚举:`story` 需求 | `task` 任务 | `bug` Bug | `todo` 待办 |
+`advice` 建议 | `issue` 问题 | `risk` 风险 | `opportunity` 机会——这个类型决定了反馈
+可能被后续转化成对应的实体,跟用户确认清楚类型再提交。
+
+`closedReason` 关闭必填,受限枚举:`commented` 已处理 | `repeat` 重复 | `refuse` 不予采纳。
+关闭前按 `zentao-shared` 全局约定跟用户确认原因。
+
+---
+
+## 工单(ticket)
+
+```
+get_products_productID_tickets(productID, ...)
+get_tickets_ticketID(ticketID)
+
+post_tickets({ payload: { product, title, module?, type?, desc?, assignedTo?, deadline?,
+ openedBuild? } })
+put_tickets_ticketID({ ticketID, payload: {...} })
+delete_tickets_ticketID({ ticketID })
+
+put_tickets_ticketID_close({ ticketID, payload: { closedReason, comment } })
+put_tickets_ticketID_activate({ ticketID, payload: {...} })
+```
+
+同样是 `product` 不是 `productID`。`type`(工单类型)受限枚举:`code` 程序报错 |
+`data` 数据错误 | `stuck` 流程卡断 | `security` 安全问题 | `affair` 事务。
+
+`openedBuild`(影响版本)**是字符串数组**,可以关联多个版本,不是单个 ID,跟 `zentao-bug`
+里 `openedBuild` 的数组结构是同一个模式。
+
+**关闭工单 `closedReason` 和 `comment` 都必填**(反馈关闭只要求 `closedReason`,工单
+两个都要)——`closedReason` 枚举:`commented` 已处理 | `repeat` 重复 | `refuse` 不予处理。
+
+---
+
+## 应用(system)
+
+```
+get_products_productID_systems(productID, ...)
+
+post_systems({ payload: { productID, integrated, children, name, desc? } })
+put_systems_systemID({ systemID, payload: {...} })
+```
+
+**没有查询单条应用详情、也没有删除应用的接口**(只有创建/修改),这跟其他实体都不一样,
+需要删除或者查看单条详情要引导用户去网页端。
+
+**`get_products_productID_systems` 实测对部分账号会返回 `HTTP 403 Access not allowed`**——
+应用管理看起来受账号权限控制,不是所有 Token 都能查。403 时先怀疑是权限不够(提示用户
+去禅道网页确认自己有没有应用管理权限),不要当成参数错误去排查。
+
+四个必填字段里 `integrated`(是否集成应用:`0` 否 | `1` 是)和 `children`(集成应用需要
+包含哪些其他应用的 ID 列表,非集成应用传空数组 `[]`)都容易漏传——创建前先问清楚这个
+应用是不是"集成应用"(把多个子应用打包发布的那种),决定这两个字段怎么填。
+
+---
+
+## 附件改名(file)
+
+```
+put_files_fileID({ fileID, payload: { fileName } })
+```
+
+**这套 MCP 工具里附件相关能力只有改名这一个**(`zentao-shared` 已提过):上传新附件、
+删除附件都不在工具列表里,需要引导用户去网页端操作。`fileName` 必填,改名会连带更新
+附件的扩展名(如果新文件名里带了不同的后缀)。
diff --git a/plugins/zentao/skills/zentao-plan/SKILL.md b/plugins/zentao/skills/zentao-plan/SKILL.md
new file mode 100644
index 0000000..e249909
--- /dev/null
+++ b/plugins/zentao/skills/zentao-plan/SKILL.md
@@ -0,0 +1,84 @@
+---
+name: zentao-plan
+description: "禅道产品计划、版本(build)、发布(release)管理。当用户说「排个产品计划」「打个包」「建个版本」「发布上线」时使用。"
+---
+
+# 禅道产品计划 / 版本 / 发布
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节)。**
+
+---
+
+## 三者关系
+
+```
+productplan 产品计划(做什么、什么时候做)
+build 版本/构建(某次打包产出,挂在执行下)
+release 发布(把某个/某几个 build 正式对外发布)
+```
+
+三者是独立管理的实体,不是严格的父子层级——`release` 通过 `build` 字段关联具体的构建,
+`build` 通过 `executionID` 关联执行、`system` 关联应用,`productplan` 只挂产品,不关联
+执行/构建。
+
+---
+
+## 产品计划(productplan)
+
+```
+get_products_productID_productplans(productID, ...)
+get_productplans_planID(planID) 详情——注意参数叫 planID,不是 productplanID
+
+post_productplans({ payload: { productID, title, parent?, begin?, end?, branchID?, desc? } })
+put_productplans_productplanID({ productplanID, payload: {...} }) # 改用的是 productplanID
+delete_productplans_productplanID({ productplanID })
+```
+
+**详情接口和改/删接口的路径参数名不一样**(`planID` vs `productplanID`),照抄各自工具
+名对应的字面参数名。`productID`/`title` 必填,`parent` 可以挂一个父计划形成层级。
+
+---
+
+## 版本 / 构建(build)
+
+```
+get_projects_projectID_builds(projectID, ...)
+get_executions_executionID_builds(executionID, ...)
+
+post_builds({ payload: { executionID, product, name, system, builder, date,
+ scmPath?, filePath?, desc? } })
+put_builds_buildID({ buildID, payload: {...} })
+delete_builds_buildID({ buildID })
+```
+
+必填字段比较多:`executionID`(注意是 `executionID` 不是 `execution`)、`product`
+(注意是 `product` 不是 `productID`)、`name`、`system`(所属应用,需要先有
+`zentao-misc` 里的应用 ID)、`builder`(构建者)、`date`(打包日期)——**字段名在不同
+实体间不统一是这套 API 的通病**(`zentao-task` 也提过 name/title、executionID/execution
+的不一致),创建前对照该工具自己的 inputSchema 逐个字段确认,不要照抄其他实体的字段名。
+
+没有单条版本详情接口,从列表里过滤。
+
+---
+
+## 发布(release)
+
+```
+get_products_productID_releases(productID, ...)
+
+post_releases({ payload: { productID, system, name, build, date, status?, desc? } })
+put_releases_releasID({ releasID, payload: {...} }) # 注意参数叫 releasID,不是 releaseID
+delete_releases_releasID({ releasID })
+```
+
+必填:`productID`/`system`(所属应用)/`name`(应用版本号)/`build`(包含的构建,**是字符
+串数组**,可以关联多个构建,不是单个 ID)/`date`(计划发布日期)。
+
+`status` 受限枚举:`wait` 未开始 | `normal` 已发布 | `fail` 发布失败 | `terminate` 停止维护
+——不像 bug/task 有专门的状态流转端点,发布状态直接在创建/修改时传 `status` 字段设置,
+改状态就是 `put_releases_releasID({ releasID, payload: { status: "normal" } })`。
+
+**没有单条发布详情接口**,从 `get_products_productID_releases` 列表里过滤。
+
+**参数名注意**:路径参数字面拼写是 `releasID`(缺一个 `e`),跟 testcase 的 `testcasID`
+是同一类官方 API 拼写坑,照抄不要纠正。
diff --git a/plugins/zentao/skills/zentao-project/SKILL.md b/plugins/zentao/skills/zentao-project/SKILL.md
new file mode 100644
index 0000000..3215501
--- /dev/null
+++ b/plugins/zentao/skills/zentao-project/SKILL.md
@@ -0,0 +1,117 @@
+---
+name: zentao-project
+description: "禅道项目集/产品/项目/执行管理:建项目集、建产品、建项目、建执行(迭代)、查层级关系。当用户说「建个产品」「开个新迭代」「这个项目集下有哪些产品」「项目状态」时使用。"
+---
+
+# 禅道项目集 / 产品 / 项目 / 执行
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"和"ID 层级关系"两节)。**
+
+---
+
+## 层级与挂靠关系
+
+```
+program 项目集(可选层)
+ └─ product 产品(program 可选,可以不挂任何项目集独立存在)
+ └─ project 项目(products 关联字段可选,parent 挂项目集也可选)
+ └─ execution 执行/迭代(project 必填,execution 必须先有 project)
+```
+
+**只有 execution 是强制要有上级的**(`project` 字段必填);product 和 project 的上级
+关联都是可选字段,不要假设"没传 program 就建不了产品"。
+
+---
+
+## 查
+
+```
+get_programs() 项目集列表
+get_programs_programID_products(programID) 某项目集下的产品
+get_programs_programID_projects(programID) 某项目集下的项目
+get_products() 产品列表(全量)
+get_products_productID(productID) 产品详情
+get_projects() 项目列表(全量,无单条详情接口)
+get_projects_projectID_executions(projectID) 某项目下的执行/迭代
+get_executions() 执行列表(全量)
+get_executions_executionID(executionID) 执行详情
+```
+
+**没有 `get_projects_projectID`**(项目详情接口不存在,见 `zentao-shared`),要看单个
+项目信息从 `get_projects()` 列表里按 ID 过滤。
+
+---
+
+## 建项目集
+
+```
+post_programs({ payload: { name, begin, end, PM?, desc? } })
+```
+
+`name`/`begin`/`end` 必填,日期格式以字段说明为准(一般是 `YYYY-MM-DD`)。
+
+---
+
+## 建产品
+
+```
+post_products({ payload: { name, program?, line?, type?, PO?, QD?, RD?, reviewer?,
+ acl?, desc? } })
+```
+
+只有 `name` 必填。`type` 取值受限:`normal` 正常 | `branch` 多分支 | `platform` 多平台;
+`acl` 是 `open` 公开 | `private` 私有——这两个字段写错值会被后端拒绝,不要凭直觉编。
+
+---
+
+## 建项目
+
+```
+post_projects({ payload: { name, model, begin, end, workflowGroup, products?, parent?, PM? } })
+```
+
+必填字段比产品多:`name`/`model`/`begin`/`end`/`workflowGroup`。
+
+`model`(项目管理方式)取值受限,创建前跟用户确认清楚要哪种:
+`scrum` 敏捷 | `waterfall` 瀑布 | `kanban` 看板 | `agileplus` 融合敏捷 | `waterfallplus` 融合瀑布
+
+`workflowGroup`(项目流程)是付费版功能,开源版可以不传/传空。
+
+`parent` 是挂靠到哪个项目集(可选);`products` 是关联的产品(可选,接受多个)。
+
+---
+
+## 建执行(迭代)
+
+```
+post_executions({ payload: { project, name, begin, end, lifetime?, days?, products?,
+ plans?, PO?, QD?, PM?, RD?, acl? } })
+```
+
+`project`/`name`/`begin`/`end` 必填——**`project` 必填意味着建执行前必须先有一个项目
+ID**,用 `get_projects()` 查出来给用户确认要挂在哪个项目下。
+
+`lifetime`(执行类型)取值:`short` 短期 | `long` 长期 | `ops` 运维。
+
+`plans`(关联计划)如果要传,格式是"产品 ID + 计划 ID"的二维数组,不是单纯的 ID 列表,
+具体结构以工具 inputSchema 为准。
+
+---
+
+## 改 / 删
+
+```
+put_programs_programID({ programID, payload: {...} })
+put_products_productID({ productID, payload: {...} })
+put_projects_projectID({ projectID, payload: {...} })
+put_executions_executionID({ executionID, payload: {...} })
+
+delete_programs_programID({ programID }) # 删除前必须确认,不可逆
+delete_products_productID({ productID })
+delete_projects_projectID({ projectID })
+delete_executions_executionID({ executionID })
+```
+
+删除任意一层,其下挂靠的产品/项目/执行/需求/任务等大概率会受影响(具体级联行为以
+禅道后端实际处理为准,MCP 这层不做二次拦截)——删除前跟用户明确说清楚删的是哪一层、
+可能影响下面挂了什么,参考 `zentao-shared` 的"删除是不可逆操作"约定。
diff --git a/plugins/zentao/skills/zentao-shared/SKILL.md b/plugins/zentao/skills/zentao-shared/SKILL.md
new file mode 100644
index 0000000..c04ac7a
--- /dev/null
+++ b/plugins/zentao/skills/zentao-shared/SKILL.md
@@ -0,0 +1,153 @@
+---
+name: zentao-shared
+description: "禅道 MCP 共享基础:payload 包装约定、路径/查询参数规则、ID 层级关系、状态字段规则、确认约定。所有 zentao-* 技能必须先读本文件。"
+---
+
+# 禅道 MCP 共享规则
+
+所有 `zentao-*` 技能的**必读前置**。
+
+---
+
+## 一条最重要的约定:参数以工具自身的 inputSchema 为准
+
+**本文件与各技能文档都不重画完整字段表。** 每个工具的字段名、必填项、取值范围以它在 MCP
+里注册的 `inputSchema`/`description` 为唯一真相;技能只描述**调用顺序、ID 如何传递、
+payload 怎么包、哪里必须停下来等用户确认**。
+
+> 上一代其他插件(寰汐)踩过这个坑:技能文档手画参数表,字段名/枚举值/必填项跟后端
+> docstring 逐渐漂移,用户侧表现为频繁的「参数缺失」报错。工具签名变了而文档没跟上,
+> 是必然发生而非可能发生的事——禅道这边直接不画表,从源头避免。
+
+---
+
+## 工具命名
+
+Claude Code 里工具名带前缀:`mcp__zentao__post_bugs`;其他平台通常是裸名 `post_bugs`。
+本文档统一写**裸名**,实际调用时按你所在平台的约定加前缀。
+
+命名规律是 `{method}_{路径按 / 拆分拼接}`,路径参数去掉冒号:
+
+```
+GET /bugs/:bugID → get_bugs_bugID
+POST /bugs → post_bugs
+PUT /tasks/:taskID/start → put_tasks_taskID_start
+DELETE /stories/:storyID → delete_stories_storyID
+GET /products/:productID/stories → get_products_productID_stories
+```
+
+---
+
+## 路径参数完全不出现在 inputSchema 里(最隐蔽的坑)
+
+**`inputSchema` 里只有 `payload`(有请求体的话)和 query 参数,路径参数(`bugID`/
+`productID`/`taskID`……)不会被声明为任何属性**——用真实工具现场验证过:
+`get_bugs_bugID`/`delete_bugs_bugID`/`get_products_productID` 的 `inputSchema.properties`
+都是空的。想传路径参数,**必须自己从工具名最后一段解析出参数名**(`get_bugs_bugID` →
+要传 `bugID`),schema 本身看不出这个要求。
+
+**更麻烦的是不传或传错不会报错**:现场测试 `get_bugs_bugID({})`(故意不传 `bugID`)
+返回的是 `HTTP 200`、内容为空字符串,跟正常"这个 ID 查不到东西"长得一样,非常容易被
+误判成"这条数据不存在"而不是"参数传漏了"。调用返回空结果时,**先检查是不是路径参数
+传漏了或传错了名字**,再怀疑数据本身。
+
+**命名还有例外,不能全靠"猜工具名去掉动词就是参数名"**:`get_epics_storyID` /
+`get_requirements_storyID`(业务需求/用户需求详情)的路径参数**字面就叫 `storyID`**,
+不是更直觉的 `epicID`/`requirementID`——这是禅道官方 API 本身的历史命名,照抄字面
+参数名,不要自己"纠正"。
+
+---
+
+## 参数结构:payload 包装约定
+
+请求体字段也**不会摊平在 inputSchema 顶层**,而是统一包在一个 `payload` 对象里;
+路径参数(上一节说的,虽然不在 schema 里但仍要传)和 query 参数才是顶层字段。三种形态:
+
+```
+POST(新建,无路径参数):
+ post_bugs({ payload: { productID: 2, title: "...", openedBuild: ["trunk"] } })
+
+PUT(改,带路径参数):
+ put_bugs_bugID({ bugID: 123, payload: { title: "..." } })
+ put_tasks_taskID_start({ taskID: 456, payload: { realStarted: "2026-08-25" } })
+
+GET(查,query 参数摊平在顶层,没有 payload——这类工具可选参数多,其余技能文档统一用
+"函数签名速查"写法举例,不是真的按位置传参,仍然是每个字段按名字传):
+ get_products_productID_stories(productID: 2, browseType: "allstory", recPerPage: "50", pageID: "1")
+
+DELETE(删,通常只有路径参数):
+ delete_bugs_bugID({ bugID: 123 })
+```
+
+**把 body 字段错误地摊平到顶层(不包 payload)是最常见的调用失败原因**,报错通常表现为
+"参数缺失"——先检查是不是漏包了 `payload`。
+
+---
+
+## 分页
+
+**不是所有 GET 都分页**,列表类接口(约一半)才有,详情类没有。有的话固定是这两个参数:
+
+- `recPerPage`:每页数量,不超过 1000
+- `pageID`:页码,从第 1 页开始
+
+**没有 `limit` 这个参数名**——传了不存在的参数名会被静默丢弃(不报错、不生效),日志里
+`upstream_query_params` 会是空的,看起来"调用成功但没起作用",容易误判。默认页大小以
+具体工具的 inputSchema 说明为准,不要凭经验假设。
+
+---
+
+## ID 层级关系
+
+```
+program 项目集(可选,product 不强制挂靠)
+ └─ product 产品(创建只需 name 必填,program/其他都可选)
+ ├─ project 项目(创建必填 name/model/begin/end/workflowGroup;model 取值受限:
+ │ scrum 敏捷 | waterfall 瀑布 | kanban 看板 | agileplus 融合敏捷 | waterfallplus 融合瀑布)
+ │ └─ execution 执行/迭代(创建必填 project,即 execution 必须先有 project 才能建,
+ │ 不能直接挂在 product 下)
+ ├─ story 用户故事 / epic 业务需求 / requirement 用户需求(三条需求线,见 zentao-story)
+ ├─ bug
+ ├─ testcase 测试用例 / testtask 测试单
+ ├─ productplan 产品计划 / build 版本 / release 发布
+ ├─ feedback 反馈 / ticket 工单 / system 应用
+ └─ task 任务(挂在 execution 下)
+```
+
+创建下级实体前,先查上级列表拿到 ID 展示给用户确认,不要凭名字猜 ID(`get_products`
+→ `get_products_productID_stories` 这类"先列表后详情/子资源"的两步调用是常态)。
+
+**没有 `GET /projects/:projectID` 单条项目详情接口**——只有 `get_projects` 列表,要看
+某个项目的信息,从列表里按 ID 过滤,不要尝试拼一个不存在的详情工具名。
+
+---
+
+## 状态字段
+
+**禅道各实体的状态是模块内固定的字符串常量**,不是可配置的两层模型——具体取值以对应
+工具的字段说明为准(比如 story 是 `draft/active/closed/change`,bug 是否 resolved/closed
+各有专门的 `put_*_*ID_resolve` / `put_*_*ID_close` 端点)。**不要凭直觉写状态字符串**,
+这些字段大多有 DB 级约束,写错直接报错,具体状态机在各自的 zentao-story/zentao-bug/
+zentao-task 里有说明。
+
+---
+
+## 鉴权
+
+MCP 连接用的 Token 在禅道网页「头像下拉菜单 → 获取凭证」自助生成,**14 天有效期**,到期
+需要重新生成并更新插件配置里的 Token。如果调用突然全部 401,先怀疑 Token 过期。
+
+---
+
+## 全局确认约定
+
+1. **状态流转类操作必须先确认**:关闭(close)、解决(resolve)、激活(activate)、
+ 变更(change)这类端点,执行前把要提交的内容/目标状态展示给用户,等到明确确认
+ ("确认"、"关闭它"、"好的")再调。不要因为用户说了"这个 bug 修完了"就顺手把
+ resolve 也做了——修复和标记解决是两个决定。
+2. **删除是不可逆操作,必须先确认**:所有 `delete_*` 工具删的都是真实数据,没有回收站,
+ 执行前明确告知会删除什么。
+3. **先解析 ID 再操作**:需要 `productID`/`executionID`/`taskID` 这类 ID 的操作,先用
+ 对应的 `get_*` 列表工具查出来给用户看,不要凭名字或印象猜 ID。
+4. **附件目前只支持改名**:`put_files_fileID` 只能改附件文件名(`fileName` 字段),
+ 上传和删除附件不在这套 MCP 工具里,需要引导用户去网页端操作。
diff --git a/plugins/zentao/skills/zentao-story/SKILL.md b/plugins/zentao/skills/zentao-story/SKILL.md
new file mode 100644
index 0000000..703c307
--- /dev/null
+++ b/plugins/zentao/skills/zentao-story/SKILL.md
@@ -0,0 +1,129 @@
+---
+name: zentao-story
+description: "禅道需求管理:用户故事(story)、业务需求(epic)、用户需求(requirement)三条需求线的创建、变更、关闭、激活。当用户说「提个需求」「这个需求变更一下」「需求关闭了」「史诗需求」时使用。"
+---
+
+# 禅道需求管理(story / epic / requirement)
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节——本技能的
+`get_epics_storyID`/`get_requirements_storyID` 就是那条规则里点名的例外命名)。**
+
+---
+
+## 三条需求线是什么关系
+
+禅道把"需求"拆成三种独立实体,**不是同一张表的三个状态**,是三套平行的 API:
+
+| | 中文 | 路径前缀 | 典型用途 |
+|---|---|---|---|
+| story | 用户故事 | `/stories` | 敏捷开发里最常用的最小需求单元 |
+| epic | 业务需求 | `/epics` | 更粗粒度、跨多个 story 的业务目标 |
+| requirement | 用户需求 | `/requirements` | 来自用户侧的原始需求,未必等于最终交付的 story |
+
+三者创建字段结构完全一样(`productID`/`title` 必填,`pri`/`module`/`parent`/`estimate`/
+`spec`/`category`/`source`/`verify`/`assignedTo`/`reviewer` 可选),管理动作也对称
+(改 / 变更 change / 关闭 close / 激活 activate / 删除),**用户说"需求"时先确认是要
+哪一种**,不要默认都当 story 处理——三者互不包含,建错类型对方看不到。
+
+---
+
+## 详情接口的路径参数命名例外(必读)
+
+```
+get_stories_storyID(storyID) 需求详情
+get_epics_storyID(storyID) 业务需求详情 —— 参数字面叫 storyID,不是 epicID
+get_requirements_storyID(storyID) 用户需求详情 —— 参数字面叫 storyID,不是 requirementID
+```
+
+这是禅道官方 API 本身的命名(不是这个 MCP 网桥引入的),照抄传 `storyID` 就行,别自己
+"纠正"成语义上更合理的名字,传错名字会静默返回空结果(见 `zentao-shared`)。
+
+---
+
+## 查
+
+```
+get_products_productID_stories(productID, browseType?, orderBy?, recPerPage?, pageID?)
+get_projects_projectID_stories(projectID, ...)
+get_executions_executionID_stories(executionID, ...)
+get_products_productID_epics(productID, ...)
+get_products_productID_requirements(productID, ...)
+```
+
+`browseType` 常见取值:`allstory` 全部 | `assignedtome` 指派给我 | `openedbyme` 我创建 |
+`reviewbyme` 待我评审 | `draftstory` 草稿——不传默认是 `unclosed`(未关闭的)。
+
+---
+
+## 建
+
+```
+post_stories({ payload: { productID, title, pri?, module?, parent?, estimate?, spec?,
+ category?, source?, verify?, assignedTo?, reviewer?,
+ project?, execution? } })
+post_epics({ payload: { productID, title, ... 同上(无 project/execution) } })
+post_requirements({ payload: { productID, title, ... 同上(无 project/execution) } })
+```
+
+`productID`/`title` 必填,其余可选。`reviewer` 一旦设置,**该需求就必须经过评审**——
+问清楚用户是否真的需要评审流程再决定填不填。`category`/`source` 是受限枚举(类别/来源),
+取值以工具 description 为准,写错直接报错。
+
+只有 story 能挂 `project`/`execution`(关联具体项目或迭代),epic/requirement 没有这两个字段。
+
+---
+
+## 变更(change)
+
+```
+put_stories_storyID_change({ storyID, payload: { reviewer, title?, spec?, verify? } })
+put_epics_epicID_change({ epicID, payload: { reviewer, title?, spec?, verify? } })
+put_requirements_requirementID_change({ requirementID, payload: { title?, spec?, verify? } })
+```
+
+**`reviewer` 必填这条规则只对 story 和 epic 成立**——变更这两类需求必须指定评审人,
+不能跳过评审直接改。**requirement 的变更接口没有 `reviewer` 这个参数,也没有任何必填
+字段**,三者看起来对称,实际上 requirement 少一层评审约束,不要照搬 story/epic 的调用
+方式给 requirement 传 `reviewer`(传了会被当成多余字段,不生效)。
+
+注意这里改的路径参数名恢复正常(`epicID`/`requirementID`),跟上面"详情接口"那个
+`storyID` 例外命名不是一回事,两套接口的路径参数名不一样,调用前对照工具名确认。
+
+---
+
+## 关闭(close)
+
+```
+put_stories_storyID_close({ storyID, payload: { closedReason, comment? } })
+```
+
+`closedReason` 必填,受限枚举:`done` 已完成 | `subdivided` 已拆分 | `duplicate` 重复 |
+`postponed` 延期 | `willnotdo` 不做 | `cancel` 已取消 | `bydesign` 设计如此。
+
+**关闭前必须先跟用户确认关闭原因**(`zentao-shared` 的全局约定:状态流转类操作先确认),
+不要因为用户说"这个需求做完了"就自动挑一个理由关掉——`done` 和用户实际想表达的可能
+不是一回事(比如其实是想选 `subdivided` 已拆成子需求)。
+
+---
+
+## 激活(activate)
+
+```
+put_stories_storyID_activate({ storyID, payload: { assignedTo?, comment? } })
+put_epics_epicID_activate({ epicID, payload: { assignedTo?, comment? } })
+put_requirements_requirementID_activate({ requirementID, payload: { assignedTo?, comment? } })
+```
+
+三者字段结构一样,都没有必填字段,用于把已关闭/已拆分的需求重新打开。
+
+---
+
+## 删
+
+```
+delete_stories_storyID({ storyID })
+delete_epics_epicID({ epicID })
+delete_requirements_requirementID({ requirementID })
+```
+
+不可逆,删除前必须确认(`zentao-shared` 全局约定)。
diff --git a/plugins/zentao/skills/zentao-task/SKILL.md b/plugins/zentao/skills/zentao-task/SKILL.md
new file mode 100644
index 0000000..2b8dae3
--- /dev/null
+++ b/plugins/zentao/skills/zentao-task/SKILL.md
@@ -0,0 +1,103 @@
+---
+name: zentao-task
+description: "禅道任务管理:创建任务、启动、完成、关闭、激活,查执行下的任务列表。当用户说「建个任务」「这个任务开始做了」「任务做完了」「任务关掉」时使用。"
+---
+
+# 禅道任务管理
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节)。**
+
+---
+
+## 状态机
+
+```
+未开始 ──start 启动──▶ 进行中 ──finish 完成──▶ 已完成
+ │ │
+ └──────── close 关闭 ─────┘(不做了/取消,跳过完成)
+ ▲
+ activate 激活(重新打开,从已完成/已关闭回到进行中)
+```
+
+---
+
+## 字段名坑:跟 story/bug 不一致
+
+**任务标题字段叫 `name`,不是 `title`**(story/bug/epic/requirement 都是 `title`,
+只有 task 是 `name`,创建时容易写错)。
+
+**创建任务时所属执行的字段叫 `executionID`**,不是 `execution`(bug 创建时挂执行用的
+是 `execution`)——同一个"所属执行"的语义,在不同实体的创建接口里字段名不统一,
+调用前对照该工具自己的 inputSchema,不要照抄别的实体的字段名。
+
+---
+
+## 查
+
+```
+get_executions_executionID_tasks(executionID, ...) 某执行下的任务列表
+get_tasks_taskID(taskID) 任务详情
+```
+
+任务只能按"所属执行"维度查列表,没有按产品/项目查任务的接口——要看某个产品下的任务,
+先找到相关执行再查。
+
+---
+
+## 建
+
+```
+post_tasks({ payload: { name, executionID, type?, assignedTo?, estStarted?, deadline?,
+ pri?, estimate?, module?, story?, desc? } })
+```
+
+`name`/`executionID` 必填。`story` 字段可以关联到具体需求(story ID),常用于"这个
+任务是为了实现哪个需求"。
+
+---
+
+## 启动(start)
+
+```
+put_tasks_taskID_start({ taskID, payload: { realStarted, assignedTo?, consumed?, left?, comment? } })
+```
+
+`realStarted`(实际开始日期)必填。
+
+---
+
+## 完成(finish)
+
+```
+put_tasks_taskID_finish({ taskID, payload: { currentConsumed, realStarted, finishedDate,
+ assignedTo?, consumed?, comment? } })
+```
+
+**三个必填字段**:`currentConsumed`(本次消耗)、`realStarted`(实际开始,即使之前
+`start` 过也要再传一次)、`finishedDate`(实际完成日期)。完成前把这三个数字/日期跟
+用户确认清楚(`zentao-shared` 全局约定:状态流转类操作先确认),尤其 `currentConsumed`
+容易被和"总计消耗 `consumed`"搞混——前者是这一次填报的增量工时,后者是累计总工时,
+两个都传的话以工具说明为准判断二者关系,不要凭直觉认为两者相等。
+
+---
+
+## 关闭(close)/ 激活(activate)
+
+```
+put_tasks_taskID_close({ taskID, payload: { comment? } })
+put_tasks_taskID_activate({ taskID, payload: { left?, assignedTo?, comment? } })
+```
+
+都没有必填字段。`close` 用于跳过完成流程直接终止任务(比如需求取消了);`activate`
+把已完成/已关闭的任务重新打开,可以顺带更新预计剩余工时 `left`。
+
+---
+
+## 改 / 删
+
+```
+put_tasks_taskID({ taskID, payload: {...} })
+delete_tasks_taskID({ taskID })
+```
+
+删除不可逆,执行前必须确认。
diff --git a/plugins/zentao/skills/zentao-test/SKILL.md b/plugins/zentao/skills/zentao-test/SKILL.md
new file mode 100644
index 0000000..af372c8
--- /dev/null
+++ b/plugins/zentao/skills/zentao-test/SKILL.md
@@ -0,0 +1,122 @@
+---
+name: zentao-test
+description: "禅道测试管理:创建/查询/修改测试用例(含步骤设计)、创建测试单提测。当用户说「写个测试用例」「设计测试步骤」「提测」「建个测试单」「查测试用例」时使用。"
+---
+
+# 禅道测试管理(testcase / testtask)
+
+**前置:先读 `zentao-shared`(尤其"路径参数不出现在 inputSchema 里"一节)。**
+
+测试相关是高频场景——测试同学写用例、开发/测试提测,这个技能覆盖得比其他业务域更细,
+尤其是 testcase 的步骤结构,是全部 8 个技能里**唯一一处需要特别小心的嵌套/对应关系**。
+
+---
+
+## 测试用例(testcase)
+
+### 详情接口的路径参数命名例外(跟 zentao-story 的 storyID 是同类坑)
+
+```
+get_testcases_caseID(caseID) 详情:参数字面叫 caseID
+put_testcases_testcasID(...) 修改:参数字面叫 testcasID(注意,是 testcasID 不是 testcaseID,少一个 e)
+delete_testcases_testcasID(...) 删除:同上
+```
+
+三个接口的路径参数名互相都不一样(`caseID` / `testcasID`),且 `testcasID` 是官方 API
+的拼写(缺了一个 `e`),**照抄字面参数名,不要自己纠正拼写**,传错名字会静默返回空结果。
+
+### 查
+
+```
+get_products_productID_testcases(productID, ...)
+get_projects_projectID_testcases(projectID, ...)
+get_executions_executionID_testcases(executionID, ...)
+get_testcases_caseID(caseID) 详情
+```
+
+### 建——步骤是三个平行数组,靠索引位置对应
+
+```
+post_testcases({ payload: {
+ productID, title,
+ module?, story?, pri?, type?, precondition?,
+ steps?: string[], # 步骤描述,第 i 项
+ expects?: string[], # 第 i 项步骤的期望结果
+ stepType?: string[], # 第 i 项步骤的类型:step 步骤 | group 父级步骤(分组标题,无需期望结果)
+ project?, execution?
+} })
+```
+
+**这不是一个"步骤对象数组"(不是 `[{step, expect, type}, ...]`),是三个独立的字符串
+数组,`steps[i]`/`expects[i]`/`stepType[i]` 靠同一个下标 `i` 对应同一个步骤。** 三个
+数组长度必须一致,写用例时先把步骤列成一个表格跟用户确认,再按顺序拆成三个数组传,
+不要把某一步的期望结果错位对到别的步骤上——这种错位不会报错,只会在用例详情里显示
+"驴唇不对马嘴",很难事后发现。
+
+`stepType` 为 `group` 的行是分组标题(比如"登录流程"这种大标题),对应的 `expects[i]`
+一般传空字符串。
+
+举例——"登录"用例有一个分组、两个步骤:
+
+```
+steps = ["登录流程", "打开登录页,输入正确账号密码", "点击登录按钮"]
+stepType = ["group", "step", "step"]
+expects = ["", "账号密码输入框正常显示", "跳转到首页,显示欢迎语"]
+```
+
+`type`(用例类型)受限枚举:`unit` 单元测试 | `interface` 接口测试 | `feature` 功能测试 |
+`install` 安装部署 | `config` 配置相关 | `performance` 性能测试 | `security` 安全相关 |
+`other` 其他。
+
+### 改 / 删
+
+```
+put_testcases_testcasID({ testcasID, payload: {...} }) # 改步骤同样是三个数组整体替换,不是增量
+delete_testcases_testcasID({ testcasID })
+```
+
+改步骤时**传的是完整的三个新数组,不是"追加一步"**——想加一个步骤,要先 `get_testcases_caseID`
+拿到现有的 steps/expects/stepType,在末尾追加后整体传回,跟 `huanxi-task` 里
+`task_set_assignees` 整组覆盖是同一种坑,直接传一步会把其余步骤全部覆盖掉。
+
+---
+
+## 测试单(testtask,提测)
+
+**没有单条测试单详情接口**(只有创建/修改/删除,没有 `get_testtasks_testtaskID`),要看
+某个测试单的信息,从列表接口里按 ID 过滤。
+
+### 查
+
+```
+get_products_productID_testtasks(productID, ...)
+get_projects_projectID_testtasks(projectID, ...)
+get_executions_executionID_testtasks(executionID, ...)
+```
+
+### 建
+
+```
+post_testtasks({ payload: { productID, name, build, begin, end, execution?, type?,
+ owner?, status?, desc? } })
+```
+
+`productID`/`name`/`build`(提测构建/版本)/`begin`/`end` 必填。
+
+**`type`(测试类型)是字符串数组,不是单值**——一个测试单可以同时标多种测试类型,取值
+受限:`integrate` 集成测试 | `system` 系统测试 | `acceptance` 验收测试 | `performance` 性能测试
+| `safety` 安全测试。传的时候是 `type: ["integrate", "system"]` 这种数组形式,不要传成
+单个字符串 `type: "integrate"`。
+
+**跟其他实体不同,测试单的状态不是靠专门的 activate/close 端点切换,而是创建/修改时
+直接传 `status` 字段**:`wait` 未开始 | `doing` 进行中 | `done` 已关闭 | `blocked` 被阻塞。
+改状态就是 `put_testtasks_testtaskID({ testtaskID, payload: { status: "doing" } })`。
+
+### 改 / 删
+
+```
+put_testtasks_testtaskID({ testtaskID, payload: {...} })
+delete_testtasks_testtaskID({ testtaskID })
+```
+
+删除不可逆,执行前必须确认。