feat: 根目录文档、脚本、gitignore
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
---
|
||||
name: zentao-ai-channel
|
||||
description: |
|
||||
禅道 AI 通道接口技能(生产环境直连)。封装禅道 AI 通道的三个接口:
|
||||
①评估结果提交(工作量指标/验收标准写入需求扩展信息);
|
||||
②AI 批量创建任务(为需求批量建研发/测试任务,重名自动跳过);
|
||||
③文件上传并绑定(PRD/代码审查报告/工作日志/测试文档/会议纪要等上传到需求或会议)。
|
||||
三个接口可在需求生命周期中组合使用,也可独立调用任意一个。
|
||||
触发关键词:提交评估结果到禅道、禅道批量建任务/拆任务、上传文档到禅道/绑定需求、
|
||||
代码审查报告上传、验收标准提交、/zentao
|
||||
---
|
||||
|
||||
# zentao-ai-channel
|
||||
|
||||
## 定位
|
||||
|
||||
禅道系统(ITSM)AI 对接通道的统一入口。所有调用通过客户端脚本完成:
|
||||
|
||||
```
|
||||
python .agents/skills/zentao-ai-channel/zentao_client.py <子命令> [参数...]
|
||||
```
|
||||
|
||||
子命令与接口一一对应,可独立使用:`submit`(接口一)、`batch-add-tasks`(接口二)、`upload`(接口三)。
|
||||
|
||||
## 接入信息(公共)
|
||||
|
||||
- **Base URL(生产)**:`https://itsm.sino-assist.com/zentao`(脚本默认值,可用环境变量 `ZENTAO_BASE_URL` 覆盖)
|
||||
- **鉴权**:请求头 `Authorization: <token>`,**直接放 token 原文,不要加 "Bearer " 前缀**。令牌为 ai 账号永久令牌,已内置在脚本中,可用环境变量 `ZENTAO_AI_TOKEN` 覆盖
|
||||
- **统一响应**:`{"code": 0, "message": "成功", "data": {...}}`;`code=0` 成功,`code=-1` 失败(message 为失败原因)
|
||||
- 令牌问题排查:`请登录` = 未带或无效 token;`该接口仅AI框架通道可用` = 非 ai 账号令牌
|
||||
|
||||
---
|
||||
|
||||
## 接口一:评估结果提交(submit)
|
||||
|
||||
`POST /zt-story-expand/saveOrUpdate` — 需求评估完成后,将工作量评估指标与验收标准写入需求扩展信息。**同一需求重复提交 = 覆盖更新(幂等)**。
|
||||
|
||||
```bash
|
||||
python .agents/skills/zentao-ai-channel/zentao_client.py submit \
|
||||
--story-id <需求ID> \
|
||||
--number-units <S> \
|
||||
--b <单元业务复杂度B> \
|
||||
--ft <技术复杂度系数F(T)> \
|
||||
--ga <AI效率系数G(A)> \
|
||||
--w <评估工时W,人日> \
|
||||
--status <inProgress|finished> \
|
||||
[--completion-degree <0~100>] \
|
||||
[--acceptance-criteria <Markdown文本> | --acceptance-criteria-file <md文件路径>] \
|
||||
[--product-person <中文名>] [--develop-person <中文名>] [--test-person <中文名>]
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- `storyId` 为空时接口返回成功但不处理——务必确认已传
|
||||
- **`--status finished` 会锁定记录**(后续提交被拒绝,提示"该需求已完成,不可再修改")并结算当月工作量;未确认完成前一律用 `inProgress`
|
||||
- `finished` 时完成度由服务端自动置 100,无需传 `--completion-degree`
|
||||
- 验收标准为可选;若 Spec 工作区存在 `02_acceptance` 产物,建议用 `--acceptance-criteria-file` 一并提交
|
||||
|
||||
## 接口二:AI 批量创建任务(batch-add-tasks)
|
||||
|
||||
`POST /zt-task/aiBatchAdd` — 为指定需求批量创建研发/测试任务。
|
||||
|
||||
```bash
|
||||
python .agents/skills/zentao-ai-channel/zentao_client.py batch-add-tasks \
|
||||
--story-id <需求ID> \
|
||||
--tasks-json '[{"name":"后端接口开发","type":"devel","desc":"实现 xx 接口","assignedTo":"zhangsan","aiEvaluationTime":8,"planStartDate":"2026-08-10","deadline":"2026-08-11"},{"name":"接口测试","type":"test","aiEvaluationTime":4}]'
|
||||
```
|
||||
|
||||
`tasks[]` 字段:
|
||||
|
||||
| 字段 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| name | 是 | 任务名称 |
|
||||
| type | 是 | `devel`=开发 / `test`=测试,其他值整批拒绝 |
|
||||
| desc | 否 | 任务描述,写入任务描述 |
|
||||
| assignedTo | 否 | 指派人**登录账号**(非中文名) |
|
||||
| aiEvaluationTime | 否 | AI 评估工时(小时),写入任务预计工时 |
|
||||
| planStartDate / deadline | 否 | 日期格式 `yyyy-MM-dd` |
|
||||
|
||||
注意事项:
|
||||
|
||||
- **整批拒绝**:任一任务不合法(类型错误/名称为空/日期格式错误),本批全部不创建
|
||||
- **需求状态拦截**:需求为 已发布(含待验收/验收不通过,其 status 均为 `released`)/ 已完成(验收通过,`finished`)/ 已关闭(`closed`)时整批拒绝,报错"需求已发布/已完成/已关闭,不允许创建任务"(仅 AI 通道拦截,人工拆任务不受此限)
|
||||
- **防重**:同需求下已存在同名同类型(未删除)任务 → 跳过并记入响应 `skipped`,不算失败;全部命中防重时返回 `code:0, created:0`
|
||||
- 新任务初始状态"未开始",创建人显示为 ai;响应 `data.taskIds` 为新建任务 ID 列表
|
||||
|
||||
## 接口三:文件上传并绑定(upload)
|
||||
|
||||
`POST /common/uploadBind`(multipart/form-data)— 上传文件并一步绑定到需求/会议,页面即时可见,操作记录留痕。
|
||||
|
||||
```bash
|
||||
python .agents/skills/zentao-ai-channel/zentao_client.py upload \
|
||||
--file <文件路径> \
|
||||
--object-type <见下表> \
|
||||
--object-id <需求ID或会议ID> \
|
||||
[--title <文件标题>] \
|
||||
[--review-result <pass|reject>] # 仅 aiCodeReview 有效
|
||||
```
|
||||
|
||||
`--object-type` 对照表:
|
||||
|
||||
| objectType | 含义 | 绑定后效果 |
|
||||
|------------|------|-----------|
|
||||
| story | 需求文档(PRD 等) | 需求文档链接更新 |
|
||||
| aiCodeReview | 代码审查报告 | 审查报告链接更新;带 `--review-result` 时同步审查状态 |
|
||||
| aiWorkLog | 工作日志 | 工作日志链接更新 |
|
||||
| aiDocUpdate | AI 项目文档更新记录 | 更新记录链接更新 |
|
||||
| testCase | 测试用例 | 测试用例链接更新 |
|
||||
| testReport | 测试报告模版 | 报告模版链接更新 |
|
||||
| testReportSubmit | 测试报告提交 | 报告提交链接更新(SOP 流程卡点依据) |
|
||||
| testOther | 其他测试文档 | 其他文档链接更新 |
|
||||
| meeting | 会议纪要 | 会议纪要链接更新(object-id 填会议 ID) |
|
||||
|
||||
注意事项:
|
||||
|
||||
- 同一对象同一类型可多次上传形成文件列表,业务对象链接字段始终指向**最新一份**
|
||||
- objectType/objectId 错误、对象不存在均会拒绝且不落盘
|
||||
- 响应 `data.url` 为相对路径,拼在系统域名后即可访问
|
||||
|
||||
---
|
||||
|
||||
## 组合使用(需求生命周期动线)
|
||||
|
||||
三个接口常在同一会话按序使用,但每步都可独立执行:
|
||||
|
||||
1. 需求评估完成 → `submit`(status=inProgress)→ 可用 `upload --object-type story` 上传 PRD
|
||||
2. 进入开发 → `batch-add-tasks` 拆分研发/测试任务
|
||||
3. 过程中 → `upload` 上传审查报告(aiCodeReview + review-result)、工作日志(aiWorkLog)、测试文档(testCase/testReportSubmit 等)
|
||||
4. 需求完成 → `submit`(status=finished,**锁定,最后一步执行**)
|
||||
|
||||
## 常见错误速查
|
||||
|
||||
| code | message | 处理 |
|
||||
|------|---------|------|
|
||||
| -1 | 请登录 | 检查 Authorization 头 / token |
|
||||
| -1 | 该接口仅AI框架通道可用(需ai账户token) | 换用 ai 账号令牌(脚本默认已内置) |
|
||||
| -1 | 该需求已完成,不可再修改 | 需求已 finished 锁定(接口一) |
|
||||
| -1 | 需求不存在:xxx / 会议不存在:xxx | 核对 storyId / objectId |
|
||||
| -1 | uploadBind不支持的objectType:xxx | 对照接口三取值表 |
|
||||
| -1 | 任务类型仅支持devel/test:xxx | 修正 tasks[].type(接口二) |
|
||||
| -1 | 日期格式错误,应为yyyy-MM-dd:xxx | 修正日期格式(接口二) |
|
||||
@@ -0,0 +1,157 @@
|
||||
"""
|
||||
禅道 AI 通道客户端(生产环境)
|
||||
封装三个接口:
|
||||
submit 接口一:评估结果提交 POST /zt-story-expand/saveOrUpdate
|
||||
batch-add-tasks 接口二:AI批量创建任务 POST /zt-task/aiBatchAdd
|
||||
upload 接口三:文件上传并绑定 POST /common/uploadBind (multipart)
|
||||
|
||||
用法示例:
|
||||
python zentao_client.py submit --story-id 9130 --number-units 3 --b 2.2 --ft 1.4 --ga 0.55 --w 5.1 --status finished
|
||||
python zentao_client.py batch-add-tasks --story-id 9130 --tasks-json "[{\"name\":\"后端接口开发\",\"type\":\"devel\"}]"
|
||||
python zentao_client.py upload --file 报告.md --object-type aiCodeReview --object-id 9130 --review-result pass
|
||||
|
||||
环境变量覆盖:
|
||||
ZENTAO_AI_TOKEN 访问令牌(默认使用内置的 ai 账号永久令牌)
|
||||
ZENTAO_BASE_URL 服务地址(默认生产环境)
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
BASE_URL = os.environ.get("ZENTAO_BASE_URL", "https://itsm.sino-assist.com/zentao")
|
||||
# ai 账号永久令牌(来自《禅道AI通道接口文档_v1.0》,注意保管、勿外泄)
|
||||
DEFAULT_TOKEN = ("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9."
|
||||
"eyJhY2NvdW50IjoiYWkiLCJwYXNzd29yZCI6ImUxMGFkYzM5NDliYTU5YWJiZTU2ZTA1N2YyMGY4ODNlIiwidXNlclR5cGUiOjN9."
|
||||
"d43vA9pN_dhwwfeO0zg3_AaX76vEMXRYFZSc6AkYF7c")
|
||||
TOKEN = os.environ.get("ZENTAO_AI_TOKEN", DEFAULT_TOKEN)
|
||||
|
||||
TIMEOUT = 30
|
||||
|
||||
|
||||
def _headers():
|
||||
# 直接放 token 原文,不要加 "Bearer " 前缀
|
||||
return {"Authorization": TOKEN}
|
||||
|
||||
|
||||
def _print_result(resp):
|
||||
data = resp.json()
|
||||
msg = data.get("message", "")
|
||||
if isinstance(msg, str):
|
||||
try:
|
||||
msg = msg.encode("latin-1").decode("utf-8")
|
||||
except Exception:
|
||||
pass
|
||||
print(f"HTTP {resp.status_code} | code={data.get('code')} | message={msg}")
|
||||
if data.get("data") is not None:
|
||||
print(json.dumps(data["data"], ensure_ascii=False, indent=2))
|
||||
return data
|
||||
|
||||
|
||||
def submit(args):
|
||||
"""接口一:评估结果提交(同一需求重复提交为覆盖更新,幂等)"""
|
||||
payload = {
|
||||
"storyId": args.story_id,
|
||||
"numberUnits": args.number_units,
|
||||
"unitBusinessComplexity": str(args.b),
|
||||
"technicalComplexityCoefficient": str(args.ft),
|
||||
"aiEfficiencyCoefficient": str(args.ga),
|
||||
"evaluationTime": args.w,
|
||||
"workloadIndex": str(args.w),
|
||||
"requirementStatus": args.status,
|
||||
}
|
||||
if args.completion_degree is not None:
|
||||
payload["requirementCompletionDegree"] = args.completion_degree
|
||||
acceptance = args.acceptance_criteria
|
||||
if args.acceptance_criteria_file:
|
||||
with open(args.acceptance_criteria_file, encoding="utf-8") as f:
|
||||
acceptance = f.read()
|
||||
if acceptance:
|
||||
payload["acceptanceCriteria"] = acceptance
|
||||
for attr, key in [("product_person", "productPerson"),
|
||||
("develop_person", "developPerson"),
|
||||
("test_person", "testPerson")]:
|
||||
value = getattr(args, attr)
|
||||
if value is not None:
|
||||
payload[key] = value
|
||||
resp = requests.post(f"{BASE_URL}/zt-story-expand/saveOrUpdate",
|
||||
json=payload, headers=_headers(), timeout=TIMEOUT)
|
||||
return _print_result(resp)
|
||||
|
||||
|
||||
def batch_add_tasks(args):
|
||||
"""接口二:AI 批量创建任务(整批拒绝校验;同名同类型任务自动跳过防重)"""
|
||||
tasks = json.loads(args.tasks_json)
|
||||
if not isinstance(tasks, list) or not tasks:
|
||||
print("错误:--tasks-json 必须是非空 JSON 数组", file=sys.stderr)
|
||||
sys.exit(2)
|
||||
payload = {"storyId": args.story_id, "tasks": tasks}
|
||||
resp = requests.post(f"{BASE_URL}/zt-task/aiBatchAdd",
|
||||
json=payload, headers=_headers(), timeout=TIMEOUT)
|
||||
return _print_result(resp)
|
||||
|
||||
|
||||
def upload(args):
|
||||
"""接口三:文件上传并绑定(multipart/form-data)"""
|
||||
form = {"objectType": args.object_type, "objectId": str(args.object_id)}
|
||||
if args.title:
|
||||
form["title"] = args.title
|
||||
if args.review_result:
|
||||
form["reviewResult"] = args.review_result
|
||||
with open(args.file, "rb") as f:
|
||||
files = {"file": (os.path.basename(args.file), f)}
|
||||
resp = requests.post(f"{BASE_URL}/common/uploadBind",
|
||||
data=form, files=files, headers=_headers(), timeout=TIMEOUT)
|
||||
return _print_result(resp)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="禅道 AI 通道客户端(生产环境)")
|
||||
sub = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
p = sub.add_parser("submit", help="接口一:评估结果提交 saveOrUpdate")
|
||||
p.add_argument("--story-id", type=int, required=True, help="需求ID")
|
||||
p.add_argument("--number-units", type=int, required=True, help="功能单元数量 S")
|
||||
p.add_argument("--b", type=float, required=True, help="单元业务复杂度 B")
|
||||
p.add_argument("--ft", type=float, required=True, help="技术复杂度系数 F(T)")
|
||||
p.add_argument("--ga", type=float, required=True, help="AI效率系数 G(A)")
|
||||
p.add_argument("--w", type=float, required=True, help="评估工时/工作量指数 W(人日)")
|
||||
p.add_argument("--status", required=True, choices=["inProgress", "finished"],
|
||||
help="完成状态;finished 将锁定记录并结算当月工作量")
|
||||
p.add_argument("--completion-degree", default=None, help='完成度 "0"~"100"(finished 时服务端自动置 100)')
|
||||
p.add_argument("--acceptance-criteria", default=None, help="验收标准(Markdown 文本)")
|
||||
p.add_argument("--acceptance-criteria-file", default=None, help="验收标准 Markdown 文件路径(优先于 --acceptance-criteria)")
|
||||
p.add_argument("--product-person", default=None, help="产品人员(中文名)")
|
||||
p.add_argument("--develop-person", default=None, help="开发人员(中文名)")
|
||||
p.add_argument("--test-person", default=None, help="测试人员(中文名)")
|
||||
p.set_defaults(func=submit)
|
||||
|
||||
p = sub.add_parser("batch-add-tasks", help="接口二:AI 批量创建任务 aiBatchAdd")
|
||||
p.add_argument("--story-id", type=int, required=True, help="需求ID")
|
||||
p.add_argument("--tasks-json", required=True,
|
||||
help='任务列表 JSON 数组,如 [{"name":"后端接口开发","type":"devel",'
|
||||
'"desc":"实现 xx 接口","assignedTo":"zhangsan","aiEvaluationTime":8,'
|
||||
'"planStartDate":"2026-08-10","deadline":"2026-08-11"}];'
|
||||
'type 仅支持 devel/test;desc 为可选任务描述')
|
||||
p.set_defaults(func=batch_add_tasks)
|
||||
|
||||
p = sub.add_parser("upload", help="接口三:文件上传并绑定 uploadBind")
|
||||
p.add_argument("--file", required=True, help="上传文件路径")
|
||||
p.add_argument("--object-type", required=True,
|
||||
choices=["story", "aiCodeReview", "aiWorkLog", "aiDocUpdate",
|
||||
"testCase", "testReport", "testReportSubmit", "testOther", "meeting"],
|
||||
help="业务对象类型")
|
||||
p.add_argument("--object-id", type=int, required=True, help="业务对象 ID(需求 ID 或会议 ID)")
|
||||
p.add_argument("--title", default=None, help="文件标题(默认取原始文件名,支持中文)")
|
||||
p.add_argument("--review-result", default=None, choices=["pass", "reject"],
|
||||
help="仅 object-type=aiCodeReview 时有效:pass=审查通过 / reject=审查不通过")
|
||||
p.set_defaults(func=upload)
|
||||
|
||||
args = parser.parse_args()
|
||||
args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user