From 00f2dc98ddec3ad3aac465d791640cf6ad706846 Mon Sep 17 00:00:00 2001 From: YANG JIANKUAN Date: Tue, 20 Oct 2026 11:44:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(skill):=20zentao-bug=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=20assign=20=E5=91=BD=E4=BB=A4=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8C=87=E6=B4=BE=20Bug=20=E5=B9=B6=E9=99=84=E5=A4=87=E6=B3=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 等同网页「指派」表单:--to 接受账号或姓名,状态保持激活,提交后回读校验指派人; 多行备注用 --comment-file,换行转
以便网页分行显示。 SKILL.md 补充用法,约定转给后端时备注照搬《后端接口对接问题清单》对应行。 Co-Authored-By: Claude Opus 5.5 --- .claude/skills/zentao-bug/SKILL.md | 16 +++++++- .claude/skills/zentao-bug/zentao.py | 61 +++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/.claude/skills/zentao-bug/SKILL.md b/.claude/skills/zentao-bug/SKILL.md index db0e74d..fd1e32c 100644 --- a/.claude/skills/zentao-bug/SKILL.md +++ b/.claude/skills/zentao-bug/SKILL.md @@ -1,6 +1,6 @@ --- name: zentao-bug -description: 读取禅道(njlkairport.chandao.net,开源版 18.8)中的 Bug 单——我的 Bug 列表、单个 Bug 的标题/描述/截图/评论/修改历史/附件——并按 AirLogistics 项目规范定位代码完成修复。禅道 18.8 没有 API 2.0,官方 zentao-cli / MCP 不可用,必须用本 skill 的 zentao.py。当用户提到"禅道"、"bug 单"、"我的 bug"、"修 bug"、"处理 bug"、给出 Bug 编号(如 "#632"、"bug 632")或禅道链接(bug-view-xxx.html / my-work-bug.html)时使用。 +description: 读取禅道(njlkairport.chandao.net,开源版 18.8)中的 Bug 单——我的 Bug 列表、单个 Bug 的标题/描述/截图/评论/修改历史/附件——并按 AirLogistics 项目规范定位代码完成修复,也可在禅道上解决、指派 Bug(如转给后端并附问题说明)。禅道 18.8 没有 API 2.0,官方 zentao-cli / MCP 不可用,必须用本 skill 的 zentao.py。当用户提到"禅道"、"bug 单"、"我的 bug"、"修 bug"、"处理 bug"、给出 Bug 编号(如 "#632"、"bug 632")或禅道链接(bug-view-xxx.html / my-work-bug.html)时使用。 --- # 禅道 Bug 读取与修复 @@ -61,11 +61,23 @@ python3 $Z resolve 621 622 --comment 已解决 # 提交 - 接口提交**不会**自动补表单默认值,所以先 GET 解决表单 HTML 读出 `resolvedDate` 与 `assignedTo` 的选中项再原样提交; 解决日期不能用本机时间:本机可能在美西时区,禅道服务器是北京时间(#621 曾因此写成本机时间) +### 指派(转给后端等) + +```bash +python3 $Z assign 627 --to 陶雨佳 --comment-file /path/c627.txt --dry-run # 先预览 +python3 $Z assign 627 --to 陶雨佳 --comment-file /path/c627.txt # 提交 +``` + +等同网页「指派」表单:`--to` 接受账号(如 `TYJ`)或姓名,状态保持激活,提交后回读校验指派人。 +需后端修复的单子转给后端时,备注照搬 `documents/后端接口对接问题清单.xlsx` 对应行 +(接口地址 / 问题类型 / 问题描述 / 期望调整 / 前端现状),并注明清单序号;多行备注用 `--comment-file`,换行会转成 `
`。 + ## 实现说明 - 认证:`POST /api.php/v1/tokens` 换 Token;数据走页面 JSON 模式(`.html` → `.json`),Cookie `zentaosid=`。 - 接口:`/my-work-bug--0-id_desc-0--.json`、`/bug-view-.json`、`/file-read-.`; 解决:GET `/bug-resolve-.json` 取版本/用户选项,POST `/bug-resolve-.html?onlybody=yes` 表单 - (resolution/resolvedBuild/resolvedDate/assignedTo/status=resolved/comment)。 + (resolution/resolvedBuild/resolvedDate/assignedTo/status=resolved/comment); + 指派:GET `/bug-assignTo-.json` 取用户,POST `/bug-assignTo-.html?onlybody=yes` 表单(assignedTo/status/mailto[]/comment)。 - REST v1 的 `/products//bugs` 只返回激活中的 Bug,不能替代"我的 Bug"页面。 - 凭据:Token 在 `~/.config/zentao-lite/config.json`(0600),密码只在钥匙串(服务名 `zentao-lite`)。`logout` 一并清除。 diff --git a/.claude/skills/zentao-bug/zentao.py b/.claude/skills/zentao-bug/zentao.py index e94dfd0..6001dca 100644 --- a/.claude/skills/zentao-bug/zentao.py +++ b/.claude/skills/zentao-bug/zentao.py @@ -569,6 +569,59 @@ def cmd_resolve(args): sys.exit(1) +def resolve_user(users, who): + """按账号或姓名定位用户:先精确匹配账号,再按「姓名」匹配(禅道显示名形如 T:陶雨佳)。""" + if who in users: + return who + hits = [k for k, v in users.items() if k and v.split(":", 1)[-1] == who] + if len(hits) == 1: + return hits[0] + die(f"找不到唯一的用户「{who}」" + (f",候选:{', '.join(hits)}" if hits else "")) + + +def comment_html(text): + """备注是富文本,纯文本换行在网页上会被合并,逐行转义后用
连接。""" + return "
".join(html.escape(line) for line in text.splitlines()) + + +def cmd_assign(args): + """指派 Bug:等同网页「指派」表单(指派给 + 备注),状态保持激活;提交后回读校验指派人。""" + client = Client(load_config()) + comment = args.comment + if args.comment_file: + comment = Path(args.comment_file).read_text(encoding="utf-8").strip() + failed = 0 + for bug_id in args.ids: + form = client.page(f"/bug-assignTo-{bug_id}.json") + bug = (form or {}).get("bug") + if not bug: + print(f"#{bug_id} 跳过:不存在或无权查看") + failed += 1 + continue + users = form.get("users") or {} + account = resolve_user(users, args.to) + title = html.unescape(bug["title"]) + desc = f"#{bug_id} {title} → 指派给 {users.get(account, account)}" + if args.dry_run: + print(f"[dry-run] {desc}") + if comment: + print(" 备注:\n " + comment.replace("\n", "\n ")) + continue + fields = {"assignedTo": account, "status": bug["status"], "mailto[]": "", + "comment": comment_html(comment) if comment else ""} + resp = client.post_form(f"/bug-assignTo-{bug_id}.html?onlybody=yes", fields) + after = client.page(f"/bug-view-{bug_id}.json").get("bug") or {} + if after.get("assignedTo") == account: + print(f"已指派 {desc}") + else: + alerts = re.findall(r"alert\((['\"])(.*?)\1\)", resp) + print(f"#{bug_id} 失败:提交后指派人仍为 {after.get('assignedTo')}" + + (f",禅道提示:{'; '.join(html.unescape(a[1]) for a in alerts)}" if alerts else "")) + failed += 1 + if failed: + sys.exit(1) + + def main(): ap = argparse.ArgumentParser(description="禅道 18.x Bug 读取工具") sub = ap.add_subparsers(dest="cmd", required=True) @@ -604,6 +657,14 @@ def main(): p.add_argument("--dry-run", action="store_true", help="只打印将要做的修改,不提交") p.set_defaults(func=cmd_resolve) + p = sub.add_parser("assign", help="指派 Bug 并附备注(写操作,会改动禅道线上状态)") + p.add_argument("ids", type=int, nargs="+") + p.add_argument("--to", required=True, help="指派给:账号(如 TYJ)或姓名(如 陶雨佳)") + p.add_argument("--comment", default="", help="备注,多行用换行分隔") + p.add_argument("--comment-file", help="从文件读取备注(多行内容推荐用这个)") + p.add_argument("--dry-run", action="store_true", help="只打印将要做的修改,不提交") + p.set_defaults(func=cmd_assign) + args = ap.parse_args() try: args.func(args)