@clawhub-catouse-aede7dc566
以轻松聊天的方式带用户上手禅道(ZenTao)与 zentao-cli,让用户顺着自己的角色(产品经理/项目经理/测试/开发/高管)在真实禅道环境里边聊边动手,熟悉产品、需求、计划、任务、Bug、测试用例等模块的增删改查与状态流转。当用户首次接触禅道、想上手 zentao-cli、希望了解禅道能做什么,或明确提出...
--- name: zentao-tour description: 以轻松聊天的方式带用户上手禅道(ZenTao)与 zentao-cli,让用户顺着自己的角色(产品经理/项目经理/测试/开发/高管)在真实禅道环境里边聊边动手,熟悉产品、需求、计划、任务、Bug、测试用例等模块的增删改查与状态流转。当用户首次接触禅道、想上手 zentao-cli、希望了解禅道能做什么,或明确提出"带我了解禅道/给我一个禅道 tour/体验禅道"时使用本技能。 license: MIT metadata: author: Sun Hao <[email protected]> repository: https://github.com/easysoft/zentao-skills.git keywords: [zentao, 禅道, tour, onboarding, tutorial, 体验, 上手] version: 0.1.2 --- # 禅道 Tour 本技能的定位不是"教程"而是"陪逛":像同事带你在禅道里随手点点看看,顺便把 zentao-cli 的常用姿势用出来。**核心手感是"同事口吻、不要教程腔"**——别编号"任务 1/2/3",别把每个动作都包成仪式感的"小结"。但也**别让用户迷路**:开场时顺口点一下"我们大概会走这么一段路",每做完一小段顺手回顾一两句、抛个好奇心钩子接到下一段。鼓励要真诚、具体、不肉麻。 ## 第一次对话:轻轻起个头 触发技能时不要立刻抛流程,而是: 1. 用一两句话打个招呼,点明"我会陪你在你自己的禅道里随手逛一逛,顺手把 zentao-cli 几个常用招数用出来"。 2. **悄悄**做工具就绪检查(不要说"进入第 1 步")。直接跑 `zentao profile`,顺手说一句类似"我先确认下是哪个账号……" 。如果失败,参考 [overview.md](overview.md) 的指引引导安装/登录,但口气仍然是"那我们先把账号接上",不是"请完成环境检查"。 3. 确认能连上禅道后,检查已经登录的禅道账号和角色,顺势问用户一句日常化的问题来确定下一步角色: > "你在团队里平时更像哪种角色?比如想点子的、排期的、找 Bug 的、写代码的,还是看全局的?" 使用 AskQuestion 给 5 个选项(产品经理 / 项目经理 / 测试 / 开发 / 公司高管),附一个"我随便看看"的兜底项。 4. 根据选择读取对应的叙事文件: | 选择 | 读取文件 | |------|---------| | 产品经理 | [roles/pm.md](roles/pm.md) | | 项目经理 | [roles/pjm.md](roles/pjm.md) | | 测试 | [roles/test.md](roles/test.md) | | 开发 | [roles/dev.md](roles/dev.md) | | 公司高管 | [roles/executive.md](roles/executive.md) | | 我随便看看 | 先让用户描述当下最关心什么,从上面 5 个文件里选最接近的一个,但**从用户提到的那个点切入**,而不是从该文件顶部 | 5. 进入后把文件当作"线索地图"而非"剧本"。剧情要按用户当下的兴奋点走,而不是一板一眼地逐节推进。 ## 给 AI 的行为指南(用户不直接看到) 下列规范贯穿整个对话,但**不要把它们当成条款念给用户**。 ### 去教程腔,但别让用户迷路 - 不说"现在进入第 X 步 / 任务 X / 环节 X",不把流程编号。 - 进入某个角色时,用**一两句口语**把后面要走的路点一下(例:"那我们大概这样走:先一起想个点子把产品建出来,再往里面塞几条需求,然后排进一个计划里。走到哪儿你说停我就停。")。不要列 markdown 清单、不要编号——一句话带过。 - 每次最多抛一个动作或问题,让对话像聊天而不是讲课。 - 做完一小段要**自然地回顾 + 抛钩子**。不是"小结:我们完成了 X",而是"好,《XXX》(#12) 落地了,里面三条需求也挂上了——这几条一起排进同一个计划里,还是拆成两批?"让"总结"和"过渡"融在同一句话里。 ### 鼓励要具体、不肉麻 - 用户做对一件事时,**点出他做对了什么**而不是空夸"很棒": - ✅ "你这条需求写得挺扎实,目标用户和场景都在了,开发一眼就能读懂。" - ❌ "太棒了!你真厉害!" - 用户卡住时,**把难点归因到事情上**而不是他身上:"这里本来就容易卡,我多提一个例子 / 我直接给你打个样。" - 用户完成一段之后,轻轻点出"这一段你其实已经把 XXX 和 YYY 串起来了",帮他**看见自己的进展**。 ### 用 TodoWrite 追踪但不宣扬 内部可以用 TodoWrite 记录当前用户走到哪儿,但**不要主动把待办清单读给用户听**,除非用户问"我们还剩什么"。 ### 推荐要短、要贴近生活 - 需要用户做开放性选择时,给 3–4 个一眼能懂的候选(用 AskQuestion),外加一个"我自己想一个"的口子。 - 如果从上下文能看出用户的背景(技术栈、行业),优先挑贴近他的候选。 - 没思路时给用户打样一个具体例子,而不是抛一堆理论框架。 ### 写操作前先"自然地"说一声 所有 `create` / `update` / `delete` / 状态流转(`close`/`resolve`/`finish` 等)都要先征得同意,但用日常口气而非仪式感措辞。 - 不说 "我将要执行以下命令,请确认:" - 而说 "那我就用 `zentao product create --name="..."` 帮你建出来,OK?" 简写命令优先(参照 [zentao-cli 技能](../zentao-cli/SKILL.md)),不要生成冗长 JSON 除非字段特别多。 ### 出错时像朋友一样解释 | 错误码 | 口语化说法与处理 | |--------|------------------| | E1001 / E1004 | "登录像是过期了,我们重登一下:`zentao login -s ... -u ... -p ...`" | | E2001 | "这个模块名它不认,我跑个 `zentao help` 看看正确的写法" | | E2002 | "这个 ID 好像找不到对应对象,我列一下帮你挑" | | E2003 | "缺了必填字段,我看下 `zentao <module> help` 补齐" | | E2006 | "权限不够,估计得换个账号或者找管理员开一下" | | E5001 | "网络或服务超时了,我们待会儿再试 / 先确认禅道地址对不对" | 不要把错误码原样念给用户;翻译成他关心的事。 ### 自然过渡,而不是"总结-过渡"模板 转场语优先靠"联想"和"顺手",把"刚做完的"和"下一步"糅在一句话里: - "你刚才提到 XXX,其实禅道里有个更合适的地方放它——" - "既然已经有了需求,那下一次开会它就该出现在计划里,要不我们顺手挂一下?" - "好,这条 Bug 走完一生了——你看它从 `active` → `resolved` → `closed` 的路径。想不想顺手再提一条试试别的 resolution?" - "这里告一段落,想继续挖这块,还是换个视角看看?" ### 遇到用户卡住或出错时 - 不要反复追问同一个问题。换一种问法,或者直接给一个可操作的选择("我给你打个样:就拿 A 方案先建,不喜欢再改")。 - 报错时翻译成人话(见下表),并主动给下一步操作建议。 - 每条写操作前**最多**确认一次;用户已经点头后别再二次询问,干脆利落执行。 ### 切换与收尾 用户任意时刻说"够了 / 换一个"都尊重。结束时: 1. 用一两句非正式的话回顾他真正做过的动作(不要念清单)。 2. 轻轻抛一个问题:要不要换个角色再逛一圈?要不要顺手把刚才演示产生的数据清掉? 3. 如果用户想清数据,用 `zentao <module> delete <id> --yes` 帮他删,每条前再确认一次。 ## 参考资料 - [overview.md](overview.md):禅道与 zentao-cli 速览、安装、MCP 配置、就绪自检 - [禅道官网](https://www.zentao.net/) / [使用手册](https://www.zentao.net/book/zentaopms/38.html) / [版本对比](https://www.zentao.net/compare-features.html) - [zentao-cli 仓库](https://github.com/easysoft/zentao-cli) FILE:overview.md # 禅道与 zentao-cli 速览 本文档服务于 [SKILL.md](SKILL.md) 的 "第 1 步",用于向用户快速介绍禅道与 zentao-cli,并完成工具就绪检查。 ## 什么是禅道 禅道(ZenTao)是一款开源的一站式项目管理平台,覆盖研发团队的完整工作流: - **需求管理**:记录、评审、变更业务需求与用户故事 - **项目管理**:组建项目、制定计划、排期执行 - **任务管理**:拆分任务、分派开发、跟踪进度 - **Bug 管理**:提交、指派、解决、回归 Bug - **测试管理**:编写测试用例、执行测试单、记录缺陷 - **发布管理**:管理版本与发布,沉淀交付记录 核心对象之间的关系: ```mermaid flowchart LR Program[项目集] --> Product[产品] Program --> Project[项目] Product --> Story[需求] Product --> ProductPlan[产品计划] Product --> Release[发布] Project --> Execution[执行/迭代] Execution --> Task[任务] Execution --> Build[版本] Product --> Bug[Bug] Product --> TestCase[测试用例] Execution --> TestTask[测试单] ProductPlan --> Story Story --> Task ``` 简单理解:**产品承载需求**,**项目承载执行(迭代)**,**执行承载任务**;Bug 和测试用例属于产品,测试单属于执行。 ## 什么是 zentao-cli [zentao-cli](https://github.com/easysoft/zentao-cli) 是官方命令行工具,封装了禅道 RESTful API v2.0,特点: - 覆盖 20+ 模块(产品、项目、执行、需求、Bug、任务、测试用例、计划、版本、发布、反馈、工单等)的 CRUD 与状态流转 - 对 AI 友好:默认输出 Markdown 表格便于阅读,加 `--format=json` 可获取结构化数据 - 内置过滤、排序、分页、模糊搜索、字段摘取 更多命令细节见 [zentao-cli 技能文档](../zentao-cli/SKILL.md)。 ## 两种接入方式 ### 方式一:本地 CLI(推荐,快速上手) 优先使用系统中已有的包管理器全局安装: ```bash npm install -g zentao-cli # 或 bun install -g zentao-cli # 或 pnpm install -g zentao-cli # 也可免安装:npx zentao-cli ``` 首次使用登录禅道: ```bash zentao login -s https://zentao.example.com -u <账号> -p <密码> ``` 登录成功后凭证缓存在 `~/.config/zentao/zentao.json`,后续无需重复登录。 也可以通过环境变量配置(优先级低于命令行参数):`ZENTAO_URL`、`ZENTAO_ACCOUNT`、`ZENTAO_PASSWORD`、`ZENTAO_TOKEN`。 ### 方式二:配置为 MCP 服务 若用户使用的智能工具(如 Cursor、Claude Desktop 等)支持 MCP(Model Context Protocol),可将 zentao-cli 注册为 MCP 服务,直接在对话中调用禅道能力。通用配置思路: ```json { "mcpServers": { "zentao": { "command": "npx", "args": ["-y", "zentao-cli", "mcp"], "env": { "ZENTAO_URL": "https://zentao.example.com", "ZENTAO_ACCOUNT": "<账号>", "ZENTAO_TOKEN": "<token>" } } } } ``` 具体 MCP 启动方式与参数以 [zentao-cli 仓库](https://github.com/easysoft/zentao-cli) 的最新说明为准;不同智能工具的配置文件位置不同(Cursor 的 `~/.cursor/mcp.json`、Claude Desktop 的 `claude_desktop_config.json` 等)。 ## 就绪自检 正式开始前,顺手跑一下这两条,把账号和连通性确认掉: ```bash zentao profile # 确认已登录,显示当前账号 zentao product --pick=id,name # 能正常拉取产品列表 ``` 如果返回错误: - `E1001` / `E1004`:未登录或 Token 失效 → 让用户执行 `zentao login ...` - 命令找不到(`command not found`)→ 回到上文"方式一"安装 - 网络错误 / `E5001` → 检查禅道服务地址是否正确、网络是否可达 通了之后回到 SKILL.md,顺势问用户想从哪个角色切入就好。 ## 外部资料 - [禅道官网](https://www.zentao.net/) - [禅道使用手册](https://www.zentao.net/book/zentaopms/38.html) - [禅道不同版本功能对比](https://www.zentao.net/compare-features.html) - [zentao-cli 仓库](https://github.com/easysoft/zentao-cli) FILE:roles/executive.md # 高管视角 用户选了这个身份,意味着他不想看"一条记录"而想看"整体情况"。陪他从几个入口溜一圈全局数据,**只读、不写**,也**别列 4 个维度**。 ## 开场:一句话点路,再让他挑一块最关心的 示例开场: > "高管视角其实就是从不同角度扫一眼全局——项目节奏、产品健康度、发布动静、团队声音,都是入口。我们不用四个都看,挑你现在最想知道的那一块深挖就够了。 > > 你现在最想先看哪块?" 用 AskQuestion 给 4 个选项: - 项目节奏 - 产品健康度(需求 / Bug 分布) - 发布与版本 - 团队反馈与工单 根据选择从下面对应段切入。**不要全部都看一遍**,陪用户挖他真正关心的那 1–2 个就好。 ## 如果他关心项目节奏 ```bash zentao project --filter='status:doing' --pick=id,name,begin,end,progress ``` 让他扫一眼,问:"有没有哪条看着不对劲?进度慢的、日期要到的?"——挑出一个深入看: ```bash zentao execution --project=<id> --pick=id,name,status zentao task --execution=<执行ID> --pick=id,status --format=json ``` 后一条可以本地聚合一下"wait/doing/done 各多少",用一句话汇报给用户。 ## 如果他关心产品健康度 ```bash zentao product --pick=id,name,status ``` 挑他在意的那个产品: ```bash zentao story --product=<id> --pick=id,pri,stage --format=json zentao bug --product=<id> --pick=id,severity,pri,status --format=json ``` 把 JSON 结果做个简单统计(高优先级未处理需求数、严重 Bug 数),用两句话告诉用户:"《XXX》当前有 N 条高优需求还没排期,严重 Bug M 条——主要堆在这几个 severity 上。" ## 如果他关心发布与版本 ```bash zentao release --product=<id> --pick=id,name,date,status zentao build --project=<projectID> --pick=id,name,date ``` 挑最近一次已发布和最近一次待发布,用一句话把时间节点说出来。 ## 如果他关心团队声音 ```bash zentao feedback --product=<id> --pick=id,title,status,pri zentao ticket --product=<id> --pick=id,title,status,pri ``` 扫一下高频类别或未处理量,用一两句汇报热点。 ## 顺势介绍一招"驾驶舱"技巧 挖完用户关心的那块之后,顺手点一句**回顾 + 钩子**: > "刚才我们用了不到十条命令,就把《XXX》这块的健康度摸清楚了——其实这些查询加 `--format=json` 之后都能本地汇总,就是一个极简驾驶舱。你想要哪类数字定期看,我都可以帮你凑一个小脚本。" 这是钩子,不必当场实现,除非用户明确要。 ## 自然收尾 - 用户满足了——用一句话回顾他看过的那一两个维度,然后回到 [../SKILL.md](../SKILL.md) 的收尾流程。 - 用户问起操作类的事(比如"这条 Bug 谁来解")——说"这就得换开发视角 / 项目经理视角了",邀请切换。 ## 查询速查(给 AI 用) | 关注点 | 命令 | |--------|------| | 进行中的项目 | `zentao project --filter='status:doing' --pick=id,name,progress,begin,end` | | 项目下的执行 | `zentao execution --project=<id> --pick=id,name,status`| | 任务状态聚合 | `zentao task --execution=<id> --pick=status --format=json` | | 产品下需求概览 | `zentao story --product=<id> --pick=id,pri,stage --format=json` | | 产品下 Bug 概览 | `zentao bug --product=<id> --pick=id,severity,pri,status --format=json` | | 即将 / 最近发布 | `zentao release --product=<id> --pick=id,name,date,status` | | 版本 | `zentao build --project=<id> --pick=id,name,date` | | 用户反馈 | `zentao feedback --product=<id> --pick=id,title,status,pri` | | 工单 | `zentao ticket --product=<id> --pick=id,title,status,pri` | > 本视角完全只读,不要触发任何 create / update / delete。 FILE:roles/pjm.md # 项目经理视角 用户选了这个身份,意味着他关心的是"怎么把事情安排下去、推进下去"。陪他走一段"拉起一个项目 → 排一个 sprint → 把需求拆成任务、安排到人 → 跑一圈状态流转"的路子——**不要编号也不要列清单**,但开场顺口把这段路点一下,让他不迷路。 ## 开场:一句话点路,马上动手 示例开场(可变奏,别照抄): > "那我们大概这样走:先挂个项目到某个产品下面,再开一个 sprint,把两三条需求拆成任务、排到人,最后跑一遍状态流转让你感受一下节奏。随时说换或者停都行。 > > 先看看你们禅道里现在有哪些产品——项目总得挂在某个产品下面。" 顺手跑一下,让用户从现有产品里挑: ```bash zentao product --pick=id,name --limit=10 ``` 如果一条都没有,别卡住,顺手建议:"要不我们借 PM 视角先捏一个玩具产品出来?"(跳到 [pm.md](pm.md) 的建产品那段,建完回来)。 ## 拉起项目这件事,用最简几个字段就够 和用户聊清三样就可以动手: - 项目叫什么(`name`,建议与产品呼应,比如"XXX v1 研发") - 起止日期(`begin` / `end`,给 4 周 / 8 周 / 12 周 三挡让他挑) - 绑定哪个产品(`products`) 征得同意后执行: ```bash zentao project create --name="..." --begin=<YYYY-MM-DD> --end=<YYYY-MM-DD> --products=<产品ID> ``` 记下返回的项目 ID——后面 `zentao execution` 的 `--project` 要用。 用**回顾 + 钩子**的一句话过渡:"《XXX 研发》已经开张了,挂在《产品 XXX》下面——项目像个大框,还得切成一段段小冲刺才推得动。你们团队习惯两周一个 sprint 还是更长?" ## 接着把 Sprint 建出来 用户答完周期后: ```bash zentao execution create --project=<项目ID> --name="Sprint 1" --begin=... --end=... ``` 记下返回的执行 ID——后面 `zentao task create --execution=<执行ID>` 会一直用到。 一句话过渡到下一段:"Sprint 1 挂好了——空的 sprint 没啥意思,我们挑几条需求塞进来拆成任务?" ## 顺势把需求拆成任务 > "既然 sprint 立起来了,我们挑几条需求塞进去?" 先看可以塞什么: ```bash zentao story --product=<产品ID> --filter='stage:wait' --pick=id,title,pri ``` 和用户挑 2–3 条就够,别贪多。对每一条都问一句"你打算把它拆成几个任务?给谁做?预估几小时?"——用户给出一组就创建一个: ```bash zentao task create --execution=<执行ID> --name="..." --type=devel --assignedTo=<账号> --estimate=<小时> ``` 拆到第三条的时候可以主动刹车:"节奏差不多了,想不想看看现在已经排成什么样?" ## 让他看到"进度"是什么感觉 ```bash zentao task --execution=<执行ID> --pick=id,name,status,assignedTo,estimate ``` 如果用户对流转感兴趣,顺手演示一个任务从开始到完成: ```bash zentao task start <id> zentao task finish <id> --consumed=<实际小时> ``` 边演示边用一句话解释 status 从 `wait` → `doing` → `done` 的变化,就足够了。 跑完一圈之后来一句**具体的回顾**(不要空泛夸奖):"这一趟你其实已经把项目经理最核心的一条线串起来了:**产品 → 项目 → sprint → 任务 → 状态流转**。禅道里所有的进度汇总、人力统计都是从这条线长出来的。" ## 自然收尾 出现下列信号之一就可以收: - 用户开始问"那 Bug 呢 / 测试呢"——介绍测试视角的存在,邀请切换。 - 用户自己说"差不多了"——就着话头回顾:"你从一个产品拉起了项目、开了第一个 sprint、把几条需求拆成了任务,还跑了一遍状态流转。" - 对话自然淡下来——回到 [../SKILL.md](../SKILL.md) 的收尾流程,问要不要换身份或清理演示数据。 ## 写操作速查(给 AI 用) | 动作 | 命令 | |------|------| | 建项目 | `zentao project create --name= --begin= --end= --products=<产品ID>` | | 建 Sprint | `zentao execution create --project= --name= --begin= --end=` | | 建任务 | `zentao task create --execution= --name= --type=devel --assignedTo= --estimate=` | | 启动任务 | `zentao task start <id>` | | 完成任务 | `zentao task finish <id> --consumed=<小时>` | | 查执行下任务 | `zentao task --execution=<id> --pick=id,name,status,assignedTo` | > 本视角目前剧情比较轻,欢迎根据真实团队节奏补得更丰满。 FILE:roles/dev.md # 开发视角 用户选了这个身份,意味着他最熟悉的是"我今天写啥 / 还有啥 Bug 要修"。陪他走一段"认领一个任务 → 开干 → 交工 → 顺手解只 Bug"的路子——**别编号也别列清单**,但开场顺口点一下这段路会怎么走。 ## 开场:一句话点路 + 先确认身份 示例开场: > "开发视角我们大概这样走:先确认是哪个账号在开发,翻翻手头有啥任务,挑一条从 wait 推到 done,最后顺手解只 Bug 看看 resolution 都有哪些选项。随时说换或者停。 > > 先确认下身份——" ```bash zentao profile ``` 然后顺口一句:"看下手头有啥活。" ## 翻一翻"我的任务" ```bash zentao task --execution=<执行ID> --filter='assignedTo:<当前账号>,status:wait' --pick=id,name,estimate ``` 两种分支: - **有活**:让用户挑一条:"挑哪个先动手?" - **没活**:顺水推舟:"那我们去公共池里领一个。" 列未分派的任务,挑一个后用 `zentao task update <id> --assignedTo=<账号>` 认领,解释一句"认领本质上就是把 assignedTo 填成自己"。 ## 把那条任务从 wait 推到 doing 和用户确认一下预计用时(`estimate`),如果原先没填可以现在补: ```bash zentao task update <id> --estimate=<小时> zentao task start <id> ``` 口语化地说一句:"现在状态就是 `doing` 了,同事在看板上能看到你接手了。" ## 交工 聊一下"真做完 / 实际耗了多久",然后: ```bash zentao task finish <id> --consumed=<实际小时> ``` 如果用户好奇差别,用一两句解释 estimate 是预估、consumed 是真实耗时,后者会影响项目的成本统计。 ## 顺手捏一个 Bug 走完流程 > "开发视角最常打的另一个交道就是 Bug。我们看看有没有分给你的。" ```bash zentao bug --product=<产品ID> --filter='assignedTo:<当前账号>,status:active' --pick=id,title,severity,pri ``` 挑一条看详情 `zentao bug <id>`,聊两句可能的原因,然后: ```bash zentao bug resolve <id> --resolution=fixed ``` 顺带提一下其他 `resolution` 选项(fixed / duplicate / external / bydesign / notrepro / postponed / willnotfix),让用户知道不是只有"修好了"一条路。 走完后**具体回顾**一下他串起了什么:"这一趟其实已经是开发最常见的一天了:**认领任务 → wait 推到 doing → 交工 done → 顺手解只 Bug**。真实工作无非是把 estimate、consumed、resolution 这几个字段写准,后面项目经理看报表才有意义。" ## 自然收尾 - 用户问"测试那边怎么再验"——指测试视角。 - 用户满足地表示够了——简短回顾:"你认领了一个任务、把它从 wait 推到了 done,还顺手解了一个 Bug。" - 回到 [../SKILL.md](../SKILL.md) 的收尾流程。 ## 写操作速查(给 AI 用) | 动作 | 命令 | |------|------| | 看我的任务 | `zentao task --execution=<id> --filter='assignedTo:<账号>,status:wait'` | | 认领任务 | `zentao task update <id> --assignedTo=<账号>` | | 改预估 | `zentao task update <id> --estimate=<小时>` | | 开干 | `zentao task start <id>` | | 交工 | `zentao task finish <id> --consumed=<小时>` | | 看我的 Bug | `zentao bug --product=<id> --filter='assignedTo:<账号>,status:active'` | | 解决 Bug | `zentao bug resolve <id> --resolution=fixed` | > 本视角偏轻量,欢迎结合 Git / Build 联动等真实研发流程继续丰富。 FILE:roles/pm.md # 产品经理视角 这是给 AI 看的"线索地图"而非逐字剧本。用户选了产品经理的视角,意味着他关心的是"从一个点子到成型",那就陪他走一段这样的路——节奏随他,不要编号、不要列清单,但也别让他迷路。 ## 开场:先口语化点一下这段路会怎么走 说完点路,马上给一个能动手的起点,让他不至于愣着。**一句话点路 + 一个动作问题**就够了,别展开。 示例开场(可按语气变奏,别照抄): > "既然你是想点子的那种人,那我们大概这样走:先想个点子把产品建出来,再往里面塞几条能落地的需求,最后把它们排进第一个计划——你说停我就停。 > > 先问你一下:你现在手上有没有一个'想做但还没做'的小东西?没有的话我这儿有几个现成的可以玩。" 然后用 AskQuestion 给 4 个点子候选 + 1 个自定义: - 桌面便签 APP - 俄罗斯方块 3D 版 - 公司内部 IM 系统 - 在线表单收集系统 - (临时生成两个其他的点子) - 我自己有一个(让我说说) ## 把"点子"自然落成一个产品 用户给出点子后,不要立刻抛一堆字段问。挑用户最容易答的那一个切入: - 先问一句"它叫什么好呢"——让用户给个名字。 - 顺手建议一个英文代号(`code`),说"禅道里需要一个英文简写,我建议 `xxx`,你觉得 OK 吗?" - 一句话描述(`desc`)可以直接拿用户开场时的那句话当种子,问:"就用'<那句话>'当产品简介行不行?" 凑齐三样之后,用日常口气征求同意,然后执行: ```bash zentao product create --name="<名称>" --code="<代号>" --desc="<简介>" ``` 创建成功后**不要**正儿八经地"小结:产品已创建"。用**一句话回顾 + 一句话钩到下一段**,把总结和过渡糅在一起: > "好咧,《XXX》(#<id>) 落地了——名字、code、简介都齐活,后面聊的东西都会挂在它下面。光有壳子还不够有意思,我们往里面塞点灵魂?" 如果用户第一次成功建东西,顺手点一句具体的鼓励(非肉麻),例如:"code 你这个起得挺干脆,比一长串英文好记多了。" ## 顺着往下问:它到底是给谁的 承接上一段的钩子直接问下去,不要切到"任务 2"这种措辞。示例起头: > "你想啊,这东西如果真有人用,他是谁?什么时候会掏出它?" 从下面四个角度**按兴奋度**挑用户最容易聊开的一个切入(**不要按顺序逐一问**): - 目标用户与使用场景("什么人、什么时候、为什么掏出它") - 最核心的那件事("如果只能保留一个功能") - 竞品差异点("市面上有类似的吗?我们不同在哪") - 用户为什么掏钱/留下来 用户答的每一点,都顺手翻译成 1–2 条可操作的需求,嘴里说的时候像这样: > "那你说的'锁屏快捷键秒开便签'这事儿,其实就是一条很具体的需求,要不我记下来?" 等用户点头了,再写进禅道——**一次写一条**,不要攒一堆批量写: ```bash zentao story create --productID=<产品ID> --title="<标题>" --pri=<1-4> --spec="<一段话描述>" ``` 优先级 `pri` 可以根据用户自己的重视程度直接推荐:核心功能给 1 或 2,体验优化给 3,附加能力给 4。不要问用户"你想要什么优先级",而是给个建议让他点头或反对。 每写完一条,**顺嘴点一句他做对的地方**(具体、不泛泛): - "你这条把场景和动作都说清楚了,开发不用来回追问。" - "这个异常路径想得挺细——很多人第一版会漏掉。" 聊三五条之后,如果感觉用户有点累了,或者开始发散,就主动收一下,并用**一句话小回顾**引到下一段: > "先攒到这里?你已经把'什么人、干什么、为啥'都串起来了,再写下去容易糊。我们趁热把这几条挑几条排进一个计划怎样?" ## 自然滑到"那什么时候做" **不要**说"接下来是任务 3:制定计划"。用联想式过渡: > "这些东西不可能一次做完,你心里大概想先做哪几条?" 用户挑一挑之后,顺势说: > "那我们把挑出来的这波装进一个'计划'里,禅道里叫 productplan,挺像 sprint 的容器。" - 名字可以直接建议:"叫《MVP 首发》?"或者"叫《第一版》?" - 起止日期如果用户不敏感,给 2 周 / 4 周 / 8 周 三挡让他选。 确认后: ```bash zentao productplan create --productID=<产品ID> --title="<计划名>" --begin=<YYYY-MM-DD> --end=<YYYY-MM-DD> ``` 然后把用户刚刚挑出来的那几条需求挂进去,一条一条来,每条前都顺口说一声要挂哪个: ```bash zentao story update <storyID> --title="<标题必填>" --plan=<计划ID> ``` 全部挂完后,顺手列一张表给用户看成果(**不要**加"小结"这种字眼): ```bash zentao story --productID=<产品ID> --pick=id,title,pri,plan ``` 像朋友一样指着说:"喏,你看这几条都绑在《MVP 首发》上了——从一个空白的点子到这张表,其实你已经走完了产品经理最核心的一条线:**产品 → 需求 → 计划**。研发同事打开禅道就能按这个打工。" 这里的"你已经走完 XXX"就是自然的小结——**具体点出他串起了什么**,比单独说一句"恭喜完成"有分量得多。 ## 什么时候停下来 没有硬性的结束点。以下任一信号都是自然收尾时机: - 用户开始追问"那后面还能做什么"——回答之后抛一个选择:继续在产品经理视角玩、换个身份、还是到此为止。 - 用户语气变淡 / 说"差不多了"——就着他话头收: > "那我们今天就逛到这儿。你刚才从一个点子整到了《XXX》(#<id>),里面挂了 N 条需求,还排进了第一个计划。挺完整的一条线了。" - 用户问了一个越出产品经理视角的问题(比如"这些任务怎么分派")——顺手介绍项目经理视角存在,问他要不要换过去看看。 ## 收尾时可以顺口带一句 结束前留一个钩子给未来探索(只挑一个说,不要一次列清单): - "其实禅道里还有 `epic` 和 `requirement`,是给更上层战略抽象用的,以后你团队大了会用得上。" - "或者 `release`,产品真要上线那天它会登场。" 然后回到 [../SKILL.md](../SKILL.md) 的收尾流程,询问是否换个身份或清理演示数据。 ## 写操作速查(给 AI 用) | 动作 | 命令 | |------|------| | 建产品 | `zentao product create --name= --code= --desc=` | | 建需求 | `zentao story create --product= --title= --pri= --spec=` | | 改需求所属计划 | `zentao story update <id> --plan=<planID>` | | 建计划 | `zentao productplan create --product= --title= --begin= --end=` | | 查看产品下所有需求 | `zentao story --product=<id> --pick=id,title,pri,plan` | | 查看参数 | `zentao <module> help` | FILE:roles/test.md # 测试视角 用户选了这个身份,意味着他对"怎么确保东西是对的 / 怎么把问题反馈出去"更有兴趣。陪他走一段"挑个目标 → 写个用例 → 建个测试单 → 抓一只 Bug 看它走完生老病死"的路子——**别编号也别列清单**,但开场顺口把这段路点一下。 ## 开场:一句话点路 + 挑靶子 示例开场: > "测试这块我们大概这样走:先从你们产品里挑一条需求当靶子,围着它写两条用例(正向一个、异常一个),拉个测试单装起来,最后提一只 Bug 陪它走完从 active 到 closed 的全程。随时喊停。 > > 先看看产品里有哪些需求可以拿来练手——" 列几条候选让用户挑: ```bash zentao story --product=<产品ID> --pick=id,title,pri --filter='stage:wait,stage:developing' --limit=10 ``` 如果产品里没需求,顺水推舟:"要不我们借 PM 视角先捏一条?"(跳到 [pm.md](pm.md) 的建需求那段)。 ## 围着这条需求写用例 不要一上来罗列用例类型。用联想的问法: > "如果这条需求真上线,你第一个会想试什么?再想一个'要是乱搞会怎样'的场景?" 用户给出一个正向和一个异常场景,就可以各写一条用例,每条创建前把关键字段口语化报一遍: ```bash zentao testcase create --product=<产品ID> --story=<storyID> --title="..." --pri=<1-4> --type=feature ``` 如果用户想看完整字段(步骤/预期),用 `zentao testcase help` 展开,按需求补。 ## 顺势拉个测试单 > "有了用例还得有个'测试本子'把它们装起来跑,禅道里叫测试单。" ```bash zentao testtask create --product=<产品ID> --name="v1 冒烟测试" --begin=... --end=... ``` 把刚才的用例关联进来(参见 `zentao testtask help`),然后列一眼: ```bash zentao testtask --product=<产品ID> --pick=id,name,status ``` ## 然后抓一只 Bug 看它走完一生 用带点戏剧感的口气: > "假设你跑用例的时候发现点不对劲——要不我们提一个 Bug 练练手?" 和用户商量 Bug 的标题、严重度(`severity`)、优先级(`pri`)、重现步骤(`steps`)。严重度和优先级给个建议(比如"看起来能用就是有点歪,那严重 3 优先 3?"),让他点头即可。 ```bash zentao bug create --product=<产品ID> --title="..." --severity=<1-4> --pri=<1-4> --type=codeerror --steps="..." ``` 顺手演示状态流转(边执行边用一句话解释它代表开发解决了、你关掉了): ```bash zentao bug resolve <id> --resolution=fixed zentao bug close <id> ``` 走完之后**具体点一下**用户刚才串起了什么: > "你看,这一圈其实把测试最核心的一条链走完了:**需求 → 用例 → 测试单 → Bug → 状态流转**。真实工作里无非是每环都放大一些——写更多用例、加回归、跟踪遗留缺陷。骨架你已经有感觉了。" ## 自然收尾 - 用户如果开始问"开发那边怎么接 Bug"——指一下 dev 视角。 - 用户语气淡下来——顺口回顾:"你从一条需求写出了用例,拉了测试单,还提了一个 Bug 并把它送走。" - 回到 [../SKILL.md](../SKILL.md) 的收尾流程。 ## 写操作速查(给 AI 用) | 动作 | 命令 | |------|------| | 挑目标需求 | `zentao story --product=<id> --filter='stage:wait,stage:developing'` | | 建用例 | `zentao testcase create --product= --story= --title= --pri= --type=feature` | | 建测试单 | `zentao testtask create --product= --name= --begin= --end=` | | 提 Bug | `zentao bug create --product= --title= --severity= --pri= --type=codeerror --steps=` | | 解决 Bug | `zentao bug resolve <id> --resolution=fixed` | | 关闭 Bug | `zentao bug close <id>` | > 本视角目前剧情较轻,欢迎结合真实测试节奏继续扩展(例如回归、遗留缺陷分析)。
通过 zentao 命令行工具查询和操作禅道(ZenTao)数据,覆盖项目集、产品、项目、执行、需求、Bug、任务、测试用例、测试单、产品计划、版本、发布、反馈、工单、应用、用户、附件等模块的增删改查及状态流转。当用户提到禅道、zentao、查询项目进展、获取 Bug 列表、创建任务、更新需求状态等项目管理操作时...
--- name: zentao-cli description: 通过 zentao 命令行工具查询和操作禅道(ZenTao)数据,覆盖项目集、产品、项目、执行、需求、Bug、任务、测试用例、测试单、产品计划、版本、发布、反馈、工单、应用、用户、附件等模块的增删改查及状态流转。当用户提到禅道、zentao、查询项目进展、获取 Bug 列表、创建任务、更新需求状态等项目管理操作时使用本技能。 license: MIT metadata: author: Sun Hao <[email protected]> repository: https://github.com/easysoft/zentao-cli.git keywords: [zentao, 禅道, cli, project-management] version: 0.1.2 --- # 禅道 CLI 通过 `zentao` 命令行工具查询和操作禅道数据。CLI 自动处理认证、分页,支持工作区上下文和数据过滤/排序。 ## 前置准备 ### 安装 ```bash npm install -g zentao-cli # 或 bun install -g zentao-cli # 或 pnpm install -g zentao-cli # 或免安装运行:npx zentao-cli ``` 如果用户没有安装,引导用户进行全局安装使用,如果系统存在 bun 或 pnpm 则优先使用 bun 或 pnpm 进行全局安装。 ### 认证 首次执行任意 `zentao` 命令会自动提示登录。也可显式登录: ```bash zentao login -s https://zentao.example.com -u admin -p 123456 ``` 环境变量(优先级低于命令行参数): | 变量 | 说明 | |------|------| | `ZENTAO_URL` | 禅道服务地址 | | `ZENTAO_ACCOUNT` | 用户账号 | | `ZENTAO_PASSWORD` | 密码 | | `ZENTAO_TOKEN` | 直接指定 Token(有此变量可省略密码) | 登录成功后凭证缓存在 `~/.config/zentao/zentao.json`,后续无需重复登录。 ## 命令格式 使用简写方式(推荐): | 操作 | 命令 | |------|------| | 列表 | `zentao <module>` | | 详情 | `zentao <module> <id>` | | 创建 | `zentao <module> create --field=value` | | 更新 | `zentao <module> update <id> --field=value` | | 删除 | `zentao <module> delete <id>` | | 动作 | `zentao <module> <action> <id>` | | 帮助 | `zentao <module> help` | 也支持 `--data='JSON'` 传入 JSON 数据。 ## 模块与操作速查 | 模块名 | 中文 | 支持的操作 | |--------|------|-----------| | program | 项目集 | CRUD | | product | 产品 | CRUD | | project | 项目 | CRUD | | execution | 执行/迭代 | CRUD | | story | 需求 | CRUD + activate / change / close | | epic | 业务需求 | CRUD + activate / change / close | | requirement | 用户需求 | CRUD + activate / change / close | | bug | Bug | CRUD + activate / close / resolve | | task | 任务 | CRUD + activate / close / finish / start | | testcase | 测试用例 | CRUD | | testtask | 测试单 | CUD(按产品/项目/执行查列表) | | productplan | 产品计划 | CUD(按产品查列表) | | build | 版本 | CUD(按项目/执行查列表) | | release | 发布 | CUD(按产品查列表) | | feedback | 反馈 | CRUD + activate / close | | ticket | 工单 | CRUD + activate / close | | system | 应用 | CU(按产品查列表) | | user | 用户 | CRUD | | file | 附件 | 编辑名称 + 删除 | > CRUD = 列表 + 详情 + 创建 + 更新 + 删除;CUD = 无独立列表接口,需指定所属范围 ### 列表范围参数 部分模块的列表需要指定所属范围: ```bash zentao story --product=1 # 产品 #1 的需求 zentao bug --product=1 # 产品 #1 的 Bug zentao task --execution=1 # 执行 #1 的任务 zentao execution --project=5 # 项目 #5 的执行 zentao build --project=5 # 项目 #5 的版本 zentao testtask --product=1 # 产品 #1 的测试单 zentao release --product=1 # 产品 #1 的发布 zentao productplan --product=1 # 产品 #1 的计划 zentao feedback --product=1 # 产品 #1 的反馈 zentao ticket --product=1 # 产品 #1 的工单 ``` 设置工作区后可省略这些参数(见下方工作区章节)。 ## AI 使用策略 ### 输出格式 - 展示给用户:不加 `--format` 参数,默认输出 Markdown 表格(列表)或列表(单个对象) - 需要程序化处理:加 `--format=json`,返回结构化 JSON ### 交互确认 AI 场景下执行删除操作时加 `--yes` 跳过确认提示: ```bash zentao bug delete 1 --yes ``` ### 不知道 ID 时 先查列表获取 ID,再操作具体对象: ```bash zentao product --pick=id,name # 查看产品列表 zentao bug --product=1 --pick=id,title # 查看 Bug 列表 zentao bug 42 # 查看具体 Bug ``` ### 写操作前确认 执行创建、更新、删除等写操作前,先向用户确认操作内容。用户明确要求不确认时可跳过。 ## 数据处理 ### 摘取字段 ```bash zentao product --pick=id,name,status ``` ### 过滤 ```bash zentao bug --product=1 --filter='status:active' zentao bug --product=1 --filter='severity<=2,pri<=2' # AND zentao bug --product=1 --filter='status:active' --filter='status:resolved' # OR ``` 支持的运算符:`:` 等于、`!=` 不等于、`>` `<` `>=` `<=`、`~` 包含、`!~` 不包含。 ### 模糊搜索 ```bash zentao bug --product=1 --search=登录 --search-fields=title,steps ``` ### 排序 ```bash zentao bug --product=1 --sort=pri_asc,severity_asc ``` ### 分页 ```bash zentao bug --product=1 --page=1 --recPerPage=50 zentao bug --product=1 --all # 获取全部 zentao bug --product=1 --limit=10 # 只取前 10 条 ``` ## 常用操作示例 ### 查看进行中的项目和执行 ```bash zentao project --filter='status:doing' --pick=id,name,status zentao execution --project=5 --pick=id,name,status ``` ### 创建需求并关联计划 ```bash zentao story create --product=1 --title="需求标题" --assignedTo=admin --pri=3 zentao story update 11 --title="需求标题" --plan=1 ``` ### 创建并解决 Bug ```bash zentao bug create --product=1 --title="Bug标题" --severity=2 --pri=2 --type=codeerror --openedBuild=trunk zentao bug resolve 42 ``` ### 创建、启动并完成任务 ```bash zentao task create --execution=1 --name="任务名" --type=devel --assignedTo=admin --estimate=4 zentao task start 100 zentao task finish 100 --consumed=4 ``` ### 查看帮助 ```bash zentao bug help # 查看 Bug 模块的参数和操作 zentao story update help # 查看需求更新操作的参数和操作 zentao help # 查看所有命令 ``` ## 意图识别 | 用户意图 | CLI 命令 | |---------|---------| | 所有产品/项目/项目集 | `zentao product` / `zentao project` / `zentao program` | | 进行中的项目 | `zentao project --filter='status:doing'` | | 某产品的 Bug | `zentao bug --product=<id>` | | 某执行的任务 | `zentao task --execution=<id>` | | 创建/新增 Bug | `zentao bug create ...` | | 解决 Bug | `zentao bug resolve <id>` | | 关闭 Bug | `zentao bug close <id>` | | 激活 Bug | `zentao bug activate <id>` | | 创建需求 | `zentao story create ...` | | 变更/关闭/激活需求 | `zentao story change/close/activate <id>` | | 业务需求 | `zentao epic ...`(同 story) | | 用户需求 | `zentao requirement ...`(同 story) | | 创建/启动/完成/关闭任务 | `zentao task create/start/finish/close ...` | | 测试用例 | `zentao testcase ...` | | 测试单 | `zentao testtask ...` | | 产品计划 | `zentao productplan ...` | | 版本/Build | `zentao build ...` | | 发布 | `zentao release ...` | | 反馈 | `zentao feedback ...` | | 工单 | `zentao ticket ...` | | 用户列表 | `zentao user` | | 当前用户信息 | `zentao profile` | ## 错误处理 | 错误码 | 含义 | 处理方式 | |--------|------|---------| | E1001 | 未登录/凭证缺失 | 执行 `zentao login` | | E1004 | Token 失效 | 执行 `zentao login` 重新登录 | | E2001 | 模块不存在 | 执行 `zentao help` 查看可用模块 | | E2002 | 对象不存在 | 检查 ID 是否正确 | | E2003 | 缺少必要参数 | 执行 `zentao <module> help` 查看参数 | | E2006 | 无权限 | 提示用户检查权限 | | E5001 | 请求超时 | 检查网络或禅道服务状态 | ## 注意事项 - 不确定模块参数时,先执行 `zentao <module> help` 查看帮助 - `browseType` 常用值:`all`(全部)、`doing`(进行中)、`closed`(已关闭) - 静默模式:`--silent` 只输出错误信息 - 多账号切换:`zentao profile` 查看和切换账号
调用禅道(ZenTao)RESTful API v2.0 完成用户请求,覆盖项目集、产品、项目、执行、需求(Story/Epic/Requirement)、Bug、任务、测试用例、测试单、产品计划、版本、发布、反馈、工单、应用、用户、文件等 20 个模块的增删改查及状态流转操作。当用户提到禅道、zentao、查询...
--- name: zentao-api description: 调用禅道(ZenTao)RESTful API v2.0 完成用户请求,覆盖项目集、产品、项目、执行、需求(Story/Epic/Requirement)、Bug、任务、测试用例、测试单、产品计划、版本、发布、反馈、工单、应用、用户、文件等 20 个模块的增删改查及状态流转操作。当用户提到禅道、zentao、查询项目进展、获取 Bug 列表、更新需求状态、创建任务等项目管理相关操作时使用本技能。 metadata: author: Sun Hao <[email protected]> repository: https://github.com/easysoft/zentao-skills.git keywords: [zentao, 禅道, api, project-management] version: 1.0.4 --- # 禅道 API v2.0 ## 配置 优先级从高到低: | 变量 | 说明 | |------|------| | `ZENTAO_URL` | 服务器地址,如 `http://zentao.example.com` | | `ZENTAO_TOKEN` | 直接指定 token,跳过登录和缓存(最高优先级),仍需提供服务器地址 | | `ZENTAO_ACCOUNT` | 登录账号,有 token 时可选,但提供可更好回答与当前用户相关的问题 | | `ZENTAO_PASSWORD` | 登录密码,有 token 时无需提供 | **首次登录后 `ZENTAO_URL`、`ZENTAO_TOKEN`、`ZENTAO_ACCOUNT` 写入 `~/.zentao-token.json`,后续无需重复设置**。 若必要变量缺失,提示用户并给出 `export` 命令。用户直接提供服务器、账号和密码时直接使用,同时告知尽量设为环境变量。 ## 认证流程 所有业务 API 需在 Header 携带 `token`。运行 `scripts/get-token.sh` 自动获取: ```bash eval "$(bash scripts/get-token.sh)" # 执行后可直接使用 $ZENTAO_URL、$ZENTAO_TOKEN、$ZENTAO_ACCOUNT ``` 脚本依赖:`curl`、`node` 后续所有请求 Header 携带:`token: $ZENTAO_TOKEN` ## 执行 API 调用的步骤 1. 运行 `eval "$(bash scripts/get-token.sh)"` 获取凭证(自动处理缓存;仍缺失时提示用户) 2. 根据用户意图选择正确的 API 端点(参见 [api-reference.md](api-reference.md)) 3. 若为 PUT 编辑操作且用户未提供全部必填字段,先调用对应 GET 详情接口取回当前数据,再将用户指定的字段覆盖进去 4. 构造请求(方法、URL、Header、Body)并向用户确认写操作内容 5. 执行请求,解析响应 6. 以清晰易读的格式向用户展示结果 ## 模块总览 API 基础路径:`$ZENTAO_URL/api.php/v2` | 模块 | 资源路径 | 支持操作 | |------|---------|---------| | 项目集 Program | `/programs` | CRUD + 关联产品/项目列表 | | 产品 Product | `/products` | CRUD + 关联需求/Bug/用例/计划/发布/反馈/工单/测试单/应用 | | 项目 Project | `/projects` | CUD + 关联执行/需求/Bug/用例/版本/测试单 | | 执行 Execution | `/executions` | CRUD + 关联需求/任务/Bug/用例/版本/测试单 | | 需求 Story | `/stories` | CRUD + change/close/activate | | 业务需求 Epic | `/epics` | CRUD + change/close/activate | | 用户需求 Requirement | `/requirements` | CRUD + change/close/activate | | Bug | `/bugs` | CRUD + resolve/close/activate | | 任务 Task | `/tasks` | CRUD + start/finish/close/activate | | 测试用例 Testcase | `/testcases` | CRUD | | 产品计划 Productplan | `/productplans` | CUD + 按产品查列表 | | 版本 Build | `/builds` | CUD + 按项目/执行查列表 | | 发布 Release | `/releases` | CUD + 按产品查列表 | | 测试单 Testtask | `/testtasks` | CUD + 按产品/项目/执行查列表 | | 反馈 Feedback | `/feedbacks` | CRUD + close/activate | | 工单 Ticket | `/tickets` | CRUD + close/activate | | 应用 System | `/systems` | CU + 按产品查列表 | | 用户 User | `/users` | CRUD | | 文件 File | `/files` | 编辑名称 + 删除 | > CRUD = 创建(POST) + 读取(GET) + 更新(PUT) + 删除(DELETE);CUD = 无独立全局列表接口 ## 分页与筛选 所有列表接口支持统一的查询参数: | 参数 | 说明 | |------|------| | `browseType` 或 `status` | 筛选状态,如 `all`, `doing`, `unclosed`, `undone` 等(不同模块参数名和可选值不同,详见 [api-reference.md](api-reference.md)) | | `orderBy` | 排序,格式 `字段_asc` 或 `字段_desc`,如 `id_desc`, `title_asc` | | `recPerPage` | 每页数量,最大 1000 | | `pageID` | 页码,从 1 开始 | **筛选参数名不一致**:Program 列表、Execution 全局列表、Task 列表用 `status`,其余用 `browseType`。 ## 常用操作示例 ### 获取进行中的项目及其执行 ```bash curl -s "$ZENTAO_URL/api.php/v2/projects?browseType=doing&recPerPage=100" -H "token: $ZENTAO_TOKEN" curl -s "$ZENTAO_URL/api.php/v2/projects/{projectID}/executions?browseType=doing" -H "token: $ZENTAO_TOKEN" ``` ### 创建需求(必填:productID, title) ```bash curl -s -X POST "$ZENTAO_URL/api.php/v2/stories" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"productID": 1, "title": "需求标题", "grade": 1, "pri": 3, "assignedTo": "admin", "spec": "需求描述"}' ``` ### 创建业务需求(Epic) ```bash curl -s -X POST "$ZENTAO_URL/api.php/v2/epics" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"productID": 1, "title": "业务需求标题", "grade": 1, "pri": 3, "reviewer": ["admin"]}' ``` ### 创建用户需求(Requirement) ```bash curl -s -X POST "$ZENTAO_URL/api.php/v2/requirements" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"productID": 1, "title": "用户需求标题", "parent": 1001, "grade": 1, "pri": 3, "reviewer": ["admin"]}' ``` ### 创建 Bug(必填:productID, title, openedBuild) ```bash curl -s -X POST "$ZENTAO_URL/api.php/v2/bugs" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"productID": 1, "title": "Bug标题", "openedBuild": ["trunk"], "severity": 2, "type": "codeerror"}' ``` ### 解决 Bug(必填:resolution) ```bash curl -s -X PUT "$ZENTAO_URL/api.php/v2/bugs/{bugID}/resolve" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"resolution": "fixed"}' ``` ### 创建任务(必填:name, executionID) ```bash curl -s -X POST "$ZENTAO_URL/api.php/v2/tasks" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"executionID": 1, "name": "任务名", "type": "devel", "assignedTo": "admin", "estimate": 4}' ``` ### 完成任务(必填:currentConsumed, realStarted, finishedDate) ```bash curl -s -X PUT "$ZENTAO_URL/api.php/v2/tasks/{taskID}/finish" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"currentConsumed": 4, "realStarted": "2026-03-25", "finishedDate": "2026-03-25"}' ``` ### 关闭需求(必填:closedReason) ```bash curl -s -X PUT "$ZENTAO_URL/api.php/v2/stories/{storyID}/close" \ -H "token: $ZENTAO_TOKEN" -H "Content-Type: application/json" \ -d '{"closedReason": "done"}' ``` ## 常用枚举值速查 | 字段 | 可选值 | |------|-------| | 项目模式 `model` | `scrum`, `waterfall`, `kanban`, `agileplus`, `waterfallplus` | | Bug 类型 `type` | `codeerror`, `config`, `install`, `security`, `performance`, `standard`, `automation`, `designdefect`, `others` | | Bug 解决方案 `resolution` | `fixed`, `notrepro`, `bydesign`, `duplicate`, `external`, `postponed`, `willnotfix`, `tostory` | | 需求关闭原因 `closedReason` | `done`, `subdivided`, `duplicate`, `postponed`, `willnotdo`, `cancel`, `bydesign` | | 需求来源 `source` | `customer`, `user`, `po`, `market`, `service`, `operation`, `support`, `competitor`, `partner`, `dev`, `tester`, `bug`, `forum`, `other` | | 需求类别 `category` | `feature`, `interface`, `performance`, `safe`, `experience`, `improve`, `other` | | 用例类型 `type` | `unit`, `interface`, `feature`, `install`, `config`, `performance`, `security`, `other` | | 测试单类型 `type` | `integrate`, `system`, `acceptance`, `performance`, `safety` | | 发布状态 `status` | `wait`, `normal`, `fail`, `terminate` | | 反馈关闭原因 `closedReason` | `commented`, `repeat`, `refuse` | | 工单类型 `type` | `code`, `data`, `stuck`, `security`, `affair` | | 产品类型 `type` | `normal`, `branch`, `platform` | | 产品访问控制 `acl` | `open`, `private` | | 执行类型 `lifetime` | `short`, `long`, `ops` | ## 意图识别规则 | 用户意图关键词 | 对应操作 | |--------------|---------| | 进行中的执行/迭代/Sprint | GET /projects?browseType=doing → GET /projects/{id}/executions | | 获取所有产品/项目/项目集 | GET /products, /projects, /programs | | 某产品/项目/执行的 Bug | GET /products/{id}/bugs, /projects/{id}/bugs, /executions/{id}/bugs | | 创建/新增 Bug | POST /bugs(必填:productID, title, openedBuild) | | 更新/修改 Bug | PUT /bugs/{id} | | 解决 Bug | PUT /bugs/{id}/resolve(必填:resolution) | | 关闭 Bug | PUT /bugs/{id}/close | | 激活 Bug | PUT /bugs/{id}/activate | | 创建需求 | POST /stories(必填:productID, title) | | 关闭/激活/变更需求 | PUT /stories/{id}/close, /activate, /change | | 业务需求 | /epics(同 stories 结构) | | 用户需求 | /requirements(同 stories 结构) | | 创建任务 | POST /tasks(必填:name, executionID) | | 启动任务 | PUT /tasks/{id}/start(必填:realStarted) | | 完成任务 | PUT /tasks/{id}/finish(必填:currentConsumed, realStarted, finishedDate) | | 关闭任务 | PUT /tasks/{id}/close | | 测试用例 | /testcases(CRUD) | | 测试单 | /testtasks(CUD + 按产品/项目/执行查列表) | | 产品计划 | /productplans(CUD + 按产品查列表) | | 版本/Build | /builds(CUD + 按项目/执行查列表) | | 发布 | /releases(CUD + 按产品查列表) | | 反馈 | /feedbacks(CRUD + close/activate) | | 工单 | /tickets(CRUD + close/activate) | | 应用/系统 | /systems(CU + 按产品查列表) | | 获取用户列表 | GET /users | ## 注意事项 - URL 中的 `{id}` 需替换为实际 ID;不知道 ID 时先调列表接口获取 - **创建 Epic / Requirement / Story 时,建议始终显式传 `grade`,不要依赖接口默认值。** 已有用户反馈某些禅道实例在未传 `grade` 时会把需求层级写成 `0`,导致界面中 BR / UR / SR 标签显示异常。 - **PUT 编辑接口**:先 GET 详情获取当前完整数据,再将用户修改的字段覆盖进去一并提交 - **状态流转操作** (resolve/close/activate/start/finish/change) 通常有独立的必填字段,不需要先 GET 详情 - 写操作前向用户确认,用户明确要求不确认则直接执行 - 401 响应表示 token 已失效,执行 `rm ~/.zentao-token.json` 清除缓存后重新运行 - **字段名不一致注意**:POST builds 用 `executionID`,PUT builds 用 `execution`;PUT testcases 的模块字段为 `moudule`(规范中的拼写) ## 完整 API 参考 详细的端点列表、必填/可选字段、枚举值和查询参数见 [api-reference.md](api-reference.md)。 ## 备用资源 - 禅道 API 2.0 官方文档:https://www.zentao.net/book/api/2309.html - 1.0 API 文档(备用):https://www.zentao.net/book/api/1397.html FILE:api-reference.md # 禅道 API v2.0 端点参考 基础路径:`{ZENTAO_URL}/api.php/v2` 认证方式:所有接口(除登录外)需在 Header 携带 `token: <token值>` 写操作需额外携带 `Content-Type: application/json` **字段标注说明**:**粗体** = 必填,普通 = 可选 --- ## 认证(Token) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/users/login` | 登录获取 token | 请求体:**account**(string), **password**(string) 响应:`{"status":"success","token":"..."}` --- ## 用户(User) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/users` | 创建用户 | | GET | `/users` | 获取用户列表 | | GET | `/users/{userID}` | 获取用户详情 | | PUT | `/users/{userID}` | 修改用户信息 | | DELETE | `/users/{userID}` | 删除用户 | **POST 创建**:**account**(string), **realname**(string), **password**(string) **PUT 修改**:realname, dept(int), join(date), group(string[]), email, visions(string[] rnd|lite), mobile, weixin, password **GET 列表参数**:browseType(`inside`|`outside`), orderBy(`id`|`realname`|`account`+`_asc/_desc`), recPerPage, pageID --- ## 项目集(Program) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/programs` | 创建项目集 | | GET | `/programs` | 获取项目集列表 | | GET | `/programs/{programID}` | 获取项目集详情 | | PUT | `/programs/{programID}` | 修改项目集 | | DELETE | `/programs/{programID}` | 删除项目集 | | GET | `/programs/{programID}/products` | 获取项目集的产品列表 | | GET | `/programs/{programID}/projects` | 获取项目集的项目列表 | **POST/PUT**:**name**(string), **begin**(date), **end**(date), PM(string), desc(string) **GET 列表参数**:status(`all`|`unclosed`|`wait`|`doing`|`suspended`|`delayed`|`closed`), orderBy(`id`|`name`|`begin`|`end`+`_asc/_desc`), recPerPage, pageID --- ## 产品(Product) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/products` | 创建产品 | | GET | `/products` | 获取产品列表 | | GET | `/products/{productID}` | 获取产品详情 | | PUT | `/products/{productID}` | 修改产品 | | DELETE | `/products/{productID}` | 删除产品 | **产品关联资源列表:** | GET 路径 | 说明 | |---------|------| | `/products/{id}/stories` | 需求列表 | | `/products/{id}/epics` | 业务需求列表 | | `/products/{id}/requirements` | 用户需求列表 | | `/products/{id}/bugs` | Bug 列表 | | `/products/{id}/testcases` | 测试用例列表 | | `/products/{id}/productplans` | 产品计划列表 | | `/products/{id}/releases` | 发布列表 | | `/products/{id}/testtasks` | 测试单列表 | | `/products/{id}/feedbacks` | 反馈列表 | | `/products/{id}/tickets` | 工单列表 | | `/products/{id}/systems` | 应用列表 | **POST/PUT**:**name**(string), program(int), line(int), type(`normal`|`branch`|`platform`), PO(string), reviewer(string[]), desc(string[]), QD(string), RD(string), acl(`open`|`private`) **GET 列表参数**:browseType(`all`|`noclosed`|`closed`), orderBy(`id`|`title`|`begin`|`end`+`_asc/_desc`), recPerPage, pageID --- ## 项目(Project) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/projects` | 创建项目 | | GET | `/projects` | 获取项目列表 | | PUT | `/projects/{projectID}` | 修改项目 | | DELETE | `/projects/{projectID}` | 删除项目 | **项目关联资源列表:** | GET 路径 | 说明 | |---------|------| | `/projects/{id}/executions` | 执行列表 | | `/projects/{id}/stories` | 需求列表 | | `/projects/{id}/bugs` | Bug 列表 | | `/projects/{id}/testcases` | 测试用例列表 | | `/projects/{id}/builds` | 版本列表 | | `/projects/{id}/testtasks` | 测试单列表 | **POST/PUT**:**name**(string), **model**(`scrum`|`waterfall`|`kanban`|`agileplus`|`waterfallplus`), **begin**(date), **end**(date), **workflowGroup**(int, 付费版功能开源版可不填), products(string[]), parent(int), PM(string) **GET /projects 参数**:browseType(`all`|`undone`|`wait`|`doing`,默认`undone`), orderBy(`id`|`name`|`begin`|`end`+`_asc/_desc`), recPerPage, pageID **GET /programs/{id}/projects 参数**:同上 **GET /projects/{id}/executions 参数**:browseType(`all`|`undone`|`wait`|`doing`,默认`undone`), orderBy(`rawID`|`nameCol`|`begin`|`end`+`_asc/_desc`), recPerPage, pageID --- ## 执行(Execution / Sprint) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/executions` | 创建执行 | | GET | `/executions` | 获取执行列表 | | GET | `/executions/{executionID}` | 获取执行详情 | | PUT | `/executions/{executionID}` | 修改执行 | | DELETE | `/executions/{executionID}` | 删除执行 | **执行关联资源列表:** | GET 路径 | 说明 | |---------|------| | `/executions/{id}/stories` | 需求列表 | | `/executions/{id}/tasks` | 任务列表 | | `/executions/{id}/bugs` | Bug 列表 | | `/executions/{id}/testcases` | 测试用例列表 | | `/executions/{id}/builds` | 版本列表 | | `/executions/{id}/testtasks` | 测试单列表 | **POST 创建**:**project**(int), **name**(string), **begin**(date), **end**(date), lifetime(`short`|`long`|`ops`), days(int), products(string[]), plans(string[]), PO(string), QD(string), PM(string), RD(string), acl(`open`|`private`) **PUT 修改**:同上但 project 变为可选 **GET /executions 参数**:status(`all`|`undone`|`wait`|`doing`,默认`undone`), orderBy(`rawID`|`nameCol`|`begin`|`end`+`_asc/_desc`), recPerPage, pageID --- ## 需求(Story) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/stories` | 创建需求 | | GET | `/stories/{storyID}` | 获取需求详情 | | PUT | `/stories/{storyID}` | 修改需求 | | DELETE | `/stories/{storyID}` | 删除需求 | | PUT | `/stories/{storyID}/change` | 变更需求 | | PUT | `/stories/{storyID}/close` | 关闭需求 | | PUT | `/stories/{storyID}/activate` | 激活需求 | 列表通过父资源获取:`/products/{id}/stories`, `/projects/{id}/stories`, `/executions/{id}/stories` **POST 创建**:**productID**(int), **title**(string), grade(int 需求层级,建议显式传入), pri(int, 默认3), module(int), parent(int), estimate(float), spec(string 需求描述), category(`feature`|`interface`|`performance`|`safe`|`experience`|`improve`|`other`), source(`customer`|`user`|`po`|`market`|`service`|`operation`|`support`|`competitor`|`partner`|`dev`|`tester`|`bug`|`forum`|`other`), verify(string 验收标准), assignedTo(string), reviewer(string[]), project(int), execution(int) **PUT 修改**:**title**(string),其余字段可选 **PUT change 变更**:**reviewer**(string[]), title(string), spec(string), verify(string) **PUT close 关闭**:**closedReason**(`done`|`subdivided`|`duplicate`|`postponed`|`willnotdo`|`cancel`|`bydesign`), comment(string) **PUT activate 激活**:assignedTo(string), comment(string) **GET 列表参数**:browseType(`allstory`|`assignedtome`|`openedbyme`|`reviewbyme`|`draftstory`,默认`unclosed`), orderBy(`id`|`title`|`status`+`_asc/_desc`), recPerPage, pageID **建议**:创建 Story / Epic / Requirement 时显式传 `grade`,不要依赖接口默认值;在某些禅道实例中,不传 `grade` 可能会把需求层级写成 `0`。 --- ## 业务需求(Epic) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/epics` | 创建业务需求 | | GET | `/epics/{storyID}` | 获取业务需求详情 | | PUT | `/epics/{epicID}` | 修改业务需求 | | DELETE | `/epics/{epicID}` | 删除业务需求 | | PUT | `/epics/{epicID}/change` | 变更业务需求 | | PUT | `/epics/{epicID}/close` | 关闭业务需求 | | PUT | `/epics/{epicID}/activate` | 激活业务需求 | 列表:`/products/{id}/epics` 字段结构同 Story,差异:无 project/execution 字段,parent 指父业务需求。 **POST 创建建议**:沿用 Story 的字段结构,并显式传 `grade`。典型 BR 场景可传 `grade: 1`。 **GET 列表参数**:同 Story --- ## 用户需求(Requirement) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/requirements` | 创建用户需求 | | GET | `/requirements/{storyID}` | 获取用户需求详情 | | PUT | `/requirements/{requirementID}` | 修改用户需求 | | DELETE | `/requirements/{requirementID}` | 删除用户需求 | | PUT | `/requirements/{requirementID}/change` | 变更用户需求 | | PUT | `/requirements/{requirementID}/close` | 关闭用户需求 | | PUT | `/requirements/{requirementID}/activate` | 激活用户需求 | 列表:`/products/{id}/requirements` 字段结构同 Story,差异:无 project/execution 字段,change 操作中 reviewer 为可选(非必填)。 **POST 创建建议**:沿用 Story 的字段结构,并显式传 `grade`。典型 UR 场景可传 `grade: 1`。 **GET 列表参数**:同 Story --- ## Bug | 方法 | 路径 | 说明 | |------|------|------| | POST | `/bugs` | 创建 Bug | | GET | `/bugs/{bugID}` | 获取 Bug 详情 | | PUT | `/bugs/{bugID}` | 修改 Bug | | DELETE | `/bugs/{bugID}` | 删除 Bug | | PUT | `/bugs/{bugID}/resolve` | 解决 Bug | | PUT | `/bugs/{bugID}/close` | 关闭 Bug | | PUT | `/bugs/{bugID}/activate` | 激活 Bug | 列表通过父资源获取:`/products/{id}/bugs`, `/projects/{id}/bugs`, `/executions/{id}/bugs` **POST 创建**:**productID**(int), **title**(string), **openedBuild**(string[], 如`["trunk"]`), project(int), execution(int), severity(int, 默认3), pri(int, 默认3), type(`codeerror`|`config`|`install`|`security`|`performance`|`standard`|`automation`|`designdefect`|`others`), steps(string), story(int) **PUT 修改**:所有字段均为可选 **PUT resolve 解决**:**resolution**(`fixed`|`notrepro`|`bydesign`|`duplicate`|`external`|`postponed`|`willnotfix`|`tostory`), resolvedDate(string), resolvedBuild(string), assignedTo(string), comment(string) **PUT close 关闭**:comment(string) **PUT activate 激活**:openedBuild(string[]), assignedTo(string), comment(string) **GET /products/{id}/bugs 参数**:browseType(`all`|`unclosed`|`assignedtome`|`openedbyme`|`assignedbyme`,默认`unclosed`), orderBy(`id`|`title`|`status`+`_asc/_desc`), recPerPage, pageID **GET /projects/{id}/bugs 和 /executions/{id}/bugs 参数**:browseType(`all`|`unresolved`,默认`all`), orderBy 同上 --- ## 任务(Task) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/tasks` | 创建任务 | | GET | `/tasks/{taskID}` | 获取任务详情 | | PUT | `/tasks/{taskID}` | 修改任务 | | DELETE | `/tasks/{taskID}` | 删除任务 | | PUT | `/tasks/{taskID}/start` | 启动任务 | | PUT | `/tasks/{taskID}/finish` | 完成任务 | | PUT | `/tasks/{taskID}/close` | 关闭任务 | | PUT | `/tasks/{taskID}/activate` | 激活任务 | 列表:`/executions/{id}/tasks` **POST 创建**:**name**(string), **executionID**(int), type(string), assignedTo(string), estStarted(date), deadline(date), pri(int), estimate(float), module(int), story(int), desc(string) **PUT 修改**:所有字段可选 **PUT start 启动**:**realStarted**(date), assignedTo(string), consumed(float), left(float), comment(string) **PUT finish 完成**:**currentConsumed**(float), **realStarted**(date), **finishedDate**(date), assignedTo(string), consumed(float), comment(string) **PUT close 关闭**:comment(string) **PUT activate 激活**:left(float), assignedTo(string), comment(string) **GET 列表参数**:status(`all`|`unclosed`|`assignedtome`|`myinvolved`|`assignedbyme`,默认`unclosed`), orderBy(`id`|`name`|`status`+`_asc/_desc`), recPerPage, pageID --- ## 测试用例(Testcase) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/testcases` | 创建测试用例 | | GET | `/testcases/{caseID}` | 获取测试用例详情 | | PUT | `/testcases/{testcasID}` | 修改测试用例 | | DELETE | `/testcases/{testcasID}` | 删除测试用例 | 列表通过父资源获取:`/products/{id}/testcases`, `/projects/{id}/testcases`, `/executions/{id}/testcases` **POST 创建**:**productID**(int), **title**(string), module(int), story(int), pri(int), type(`unit`|`interface`|`feature`|`install`|`config`|`performance`|`security`|`other`), precondition(string), steps(string[]), expects(string[]), stepType(string[] `step`|`group`), project(int), execution(int) **PUT 修改**:**title**(string),其余可选(注意:模块字段名为 `moudule`) **GET 列表参数**:browseType(`all`|`wait`|`needconfirm`,默认`all`), orderBy(`id`|`title`|`status`或`pri`+`_asc/_desc`), recPerPage, pageID --- ## 产品计划(Productplan) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/productplans` | 创建产品计划 | | GET | `/productplans/{planID}` | 获取产品计划详情 | | PUT | `/productplans/{productplanID}` | 修改产品计划 | | DELETE | `/productplans/{productplanID}` | 删除产品计划 | 列表:`/products/{id}/productplans` **POST 创建**:**productID**(int), **title**(string), parent(int), begin(date), end(date), branchID(int), desc(string) **PUT 修改**:**title**(string),productID 不再必填,其余可选 **GET 列表参数**:browseType(`all`|`undone`|`wait`|`doing`,默认`undone`), orderBy(`id`|`title`|`begin`|`end`|`status`+`_asc/_desc`), recPerPage, pageID --- ## 版本(Build) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/builds` | 创建版本 | | PUT | `/builds/{buildID}` | 修改版本 | | DELETE | `/builds/{buildID}` | 删除版本 | 列表:`/projects/{id}/builds`, `/executions/{id}/builds`(无查询参数) **POST 创建**:**executionID**(int), **product**(int), **name**(string), **system**(int), **builder**(string), **date**(date), scmPath(string), filePath(string), desc(string) **PUT 修改**:同上但字段名为 **execution**(int)(非 executionID) --- ## 发布(Release) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/releases` | 创建发布 | | PUT | `/releases/{releasID}` | 修改发布 | | DELETE | `/releases/{releasID}` | 删除发布 | 列表:`/products/{id}/releases`(无查询参数) **POST 创建**:**productID**(int), **system**(int), **name**(string), **build**(string[]), **date**(date), status(`wait`|`normal`|`fail`|`terminate`), desc(string) **PUT 修改**:productID 不再必填,其余同上 > 路径参数名为 `releasID`(非 releaseID) --- ## 测试单(Testtask) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/testtasks` | 创建测试单 | | PUT | `/testtasks/{testtaskID}` | 修改测试单 | | DELETE | `/testtasks/{testtaskID}` | 删除测试单 | 列表:`/products/{id}/testtasks`, `/projects/{id}/testtasks`, `/executions/{id}/testtasks`(无查询参数) **POST 创建**:**productID**(int), **name**(string), **build**(int), **begin**(date), **end**(date), execution(int), type(string[] `integrate`|`system`|`acceptance`|`performance`|`safety`), owner(string), status(`wait`|`doing`|`done`|`blocked`), desc(string) **PUT 修改**:productID 不再必填,其余同上 --- ## 反馈(Feedback) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/feedbacks` | 创建反馈 | | GET | `/feedbacks/{feedbackID}` | 获取反馈详情 | | PUT | `/feedbacks/{feedbackID}` | 修改反馈 | | DELETE | `/feedbacks/{feedbackID}` | 删除反馈 | | PUT | `/feedbacks/{feedbackID}/close` | 关闭反馈 | | PUT | `/feedbacks/{feedbackID}/activate` | 激活反馈 | 列表:`/products/{id}/feedbacks` **POST/PUT**:**product**(int), **title**(string), module(int), type(`story`|`task`|`bug`|`todo`|`advice`|`issue`|`risk`|`opportunity`), desc(string), feedbackBy(string), source(string) **PUT close 关闭**:**closedReason**(`commented`|`repeat`|`refuse`), comment(string) **PUT activate 激活**:assignedTo(string), comment(string) **GET 列表参数**:browseType(`all`|`wait`|`doing`|`toclosed`|`review`|`assigntome`|`openedbyme`,默认`wait`), orderBy(`id`|`title`|`status`+`_asc/_desc`), recPerPage, pageID --- ## 工单(Ticket) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/tickets` | 创建工单 | | GET | `/tickets/{ticketID}` | 获取工单详情 | | PUT | `/tickets/{ticketID}` | 修改工单 | | DELETE | `/tickets/{ticketID}` | 删除工单 | | PUT | `/tickets/{ticketID}/close` | 关闭工单 | | PUT | `/tickets/{ticketID}/activate` | 激活工单 | 列表:`/products/{id}/tickets` **POST 创建**:**product**(int), **title**(string), module(int), type(`code`|`data`|`stuck`|`security`|`affair`), desc(string), assignedTo(string), deadline(date), openedBuild(string[]) **PUT 修改**:所有字段可选 **PUT close 关闭**:**closedReason**(`commented`|`repeat`|`refuse`), **comment**(string) **PUT activate 激活**:assignedTo(string), comment(string) **GET 列表参数**:browseType(`all`|`unclosed`|`wait`|`doing`|`done`|`finishedbyme`|`assigntome`|`openedbyme`,默认`wait`), orderBy(`id`|`title`|`status`+`_asc/_desc`), recPerPage, pageID --- ## 应用(System) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/systems` | 创建应用 | | PUT | `/systems/{systemID}` | 修改应用 | 列表:`/products/{id}/systems`(无查询参数) **POST 创建**:**productID**(int), **integrated**(int, 0=否 1=是), **children**(string[], 非集成传[]), **name**(string), desc(string) **PUT 修改**:**name**(string), **children**(string[]), desc(string) --- ## 文件(File) | 方法 | 路径 | 说明 | |------|------|------| | POST | `/files` | 编辑附件名称 | | DELETE | `/files/{fileID}` | 删除附件 | **POST**:**fileName**(string) FILE:scripts/get-token.sh #!/usr/bin/env bash # 获取禅道 API 调用所需的 URL、token 和用户名,按优先级:缓存文件 > 环境变量 > 账号密码登录。 # 用法:eval "$(bash get-token.sh)" → 设置 ZENTAO_URL、ZENTAO_TOKEN、ZENTAO_ACCOUNT # 依赖:curl, node # 缓存文件 ~/.zentao-token.json 保存 token、url、account,下次可免密直接使用。 # 注:禅道 token 永久有效;如需切换账号/服务器,删除缓存文件后重新运行即可。 set -euo pipefail CACHE_FILE="HOME/.zentao-token.json" # 输出三元组(KEY=VALUE 格式,可直接 eval)并退出 output_and_exit() { local url="$1" token="$2" account="$3" echo "ZENTAO_URL=url" echo "ZENTAO_TOKEN=token" echo "ZENTAO_ACCOUNT=account" exit 0 } # ── 1. 优先:从缓存文件读取 url、token、account(单次 node 调用)──────────── if [[ -f "$CACHE_FILE" ]]; then _cache_url='' _cache_token='' _cache_account='' { IFS= read -r _cache_url IFS= read -r _cache_token IFS= read -r _cache_account } < <(node -e " try { const d = JSON.parse(require('fs').readFileSync(process.argv[1], 'utf8')); process.stdout.write((d.url||'') + '\n' + (d.token||'') + '\n' + (d.account||'') + '\n'); } catch(e) { process.stdout.write('\n\n\n'); } " "$CACHE_FILE" 2>/dev/null || printf '\n\n\n') # 用缓存补全缺失的环境变量 [[ -z "-" && -n "$_cache_url" ]] && ZENTAO_URL="$_cache_url" [[ -z "-" && -n "$_cache_account" ]] && ZENTAO_ACCOUNT="$_cache_account" # 缓存 token 有效且 url/account 均匹配,直接输出三元组(无需密码) if [[ -n "$_cache_token" \ && "-" == "$_cache_url" \ && ( -z "-" || "-" == "$_cache_account" ) ]]; then output_and_exit "$_cache_url" "$_cache_token" "$_cache_account" fi fi # ── 2. 其次:从环境变量读取 token(仍需 ZENTAO_URL)──────────────────────── if [[ -n "-" ]]; then if [[ -z "-" ]]; then echo "错误:设置了 ZENTAO_TOKEN 但缺少 ZENTAO_URL,请同时提供服务器地址。" >&2 exit 1 fi # 写入缓存,方便下次无需环境变量直接使用 node - "$CACHE_FILE" "ZENTAO_TOKEN" "ZENTAO_URL" "-" <<'JSEOF' const [,, cachePath, token, url, account] = process.argv; const fs = require('fs'); fs.writeFileSync(cachePath, JSON.stringify({ token, url, account }, null, 2)); JSEOF output_and_exit "ZENTAO_URL" "ZENTAO_TOKEN" "-" fi # ── 3. 再次:用账号密码重新登录(需 ZENTAO_URL、ZENTAO_ACCOUNT、ZENTAO_PASSWORD) if [[ -z "-" || -z "-" || -z "-" ]]; then echo "错误:Token 获取失败。请通过以下任一方式提供鉴权信息:" >&2 echo " · 缓存文件 ~/.zentao-token.json(含 url、token、account 字段)" >&2 echo " · 环境变量 ZENTAO_TOKEN + ZENTAO_URL(直接提供 token 和服务器地址)" >&2 echo " · 环境变量 ZENTAO_URL、ZENTAO_ACCOUNT、ZENTAO_PASSWORD(账号密码登录)" >&2 exit 1 fi RESPONSE=$(curl -s -X POST "ZENTAO_URL/api.php/v2/users/login" \ -H "Content-Type: application/json" \ -d "{\"account\": \"ZENTAO_ACCOUNT\", \"password\": \"ZENTAO_PASSWORD\"}") TOKEN=$(echo "$RESPONSE" | node -e " const chunks = []; process.stdin.on('data', d => chunks.push(d)); process.stdin.on('end', () => { try { const data = JSON.parse(chunks.join('')); const token = (data.data && data.data.token) || data.token || ''; if (!token) { process.stderr.write('登录失败,服务器响应:' + JSON.stringify(data) + '\n'); process.exit(1); } process.stdout.write(token); } catch (e) { process.stderr.write('解析登录响应失败:' + e.message + '\n'); process.exit(1); } }); ") || { echo "错误:登录失败,请查看上方错误信息" >&2; exit 1; } # ── 4. 缓存:写入 token、url、account ──────────────────────────────────────── node - "$CACHE_FILE" "$TOKEN" "$ZENTAO_URL" "$ZENTAO_ACCOUNT" <<'JSEOF' const [,, cachePath, token, url, account] = process.argv; const fs = require('fs'); fs.writeFileSync(cachePath, JSON.stringify({ token, url, account }, null, 2)); JSEOF output_and_exit "$ZENTAO_URL" "$TOKEN" "$ZENTAO_ACCOUNT"