新增 AutoRDO 需求清洗工作台与消息中枢,迭代 OneOS V2 设计规范及租赁合同/工作台/车辆等原型,同步云效技能与导航注册;并归档一批 legacy 原型快照。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
113
.claude/skills/YunxiaoPM/SKILL.md
Normal file
113
.claude/skills/YunxiaoPM/SKILL.md
Normal file
@@ -0,0 +1,113 @@
|
||||
---
|
||||
name: YunxiaoPM
|
||||
description: >-
|
||||
产品经理云效(Projex)自动化:记录需求(压缩点选 1a2b3a4d:类型/项目/优先级/标签)、
|
||||
实时点选云效项目、推进 待处理→已确认→分析中→设计中→设计完成→待开发,
|
||||
交付树【交付】ASSOCIATED /【分析】【设计】TASK_SUB,无单快轨与编号直推交棒何斐,
|
||||
创建迭代并挂【交付】(不挂需求)。用户说 YunxiaoPM、YunxiaoPMapp、需求任务、记录需求、受理确认、开始分析、
|
||||
开始设计、设计完成、交棒开发、快轨待开发、编号直推、创建迭代 时使用。
|
||||
不建【开发】/【测试】。凡写云效先 Plan 确认再一口气 apply;禁止对齐
|
||||
yunxiao-requirement-lifecycle。
|
||||
---
|
||||
|
||||
# 需求任务(YunxiaoPM)
|
||||
|
||||
产品部云效自动化。斜杠调起 **`/YunxiaoPM`**;对外中文名 **需求任务**(原名 YunxiaoPMapp)。本 Skill **自洽成篇**;**禁止** fork / include / 「对齐」`yunxiao-requirement-lifecycle`。
|
||||
|
||||
产品经理会话**不要**同时挂载旧 lifecycle Skill,避免双建任务。
|
||||
|
||||
## Plan 模式门禁(强制 · 凡写云效)
|
||||
|
||||
凡会改云效的操作(建单、改状态、建/改任务、打标、传附件、改负责人、建迭代等),Agent **第一步**必须:
|
||||
|
||||
1. `SwitchMode` → **plan**(说明:先对齐参数与执行清单,确认后再一口气 apply)。
|
||||
2. **新建**依赖项目空间时:先按 [references/project-selection.md](references/project-selection.md) **实时拉项目列表并点选(门禁 PJ)**;禁止静默使用 `runtime-ids.json` 默认 `spaceIdentifier`。口令/选项**无法对应**时:**自动重拉项目列表一次**再匹配;仍失败则停请用户重选(禁止第 3 次空转拉取)。
|
||||
3. Plan 写清:**已选项目名 + spaceId**、目标需求/任务**编号**、将改状态、将建/复用的交付·分析·设计编号策略、迭代版本类型(若适用)、§0.1① 占位风险勾选(若适用)、**不会做的事**(不建【开发】/【测试】、不按标题查重)。**记录需求**须按 [compact-select.md](references/compact-select.md) 给出 1–4 题字母表,接受压缩答复如 `1a2b3a4d`。
|
||||
4. **用户确认 / 批准 Plan / 「执行」之前**:禁止 apply;项目未点选同禁。压缩串须先解析回显,再等「执行」。
|
||||
5. 确认后切回 Agent,**同一轮按清单一口气执行到底**,再一次性校验回报;中途缺参才停下。
|
||||
|
||||
**例外(可读可不进 Plan):** 仅查状态 / 为什么没流转 / 给我方案。
|
||||
|
||||
**禁止:** 以「参数已齐」「速度路径」「用户很熟」跳过 Plan;「批准计划」若清单未点齐关键参数(含 **PJ 项目**),仍视为未完成门禁。
|
||||
|
||||
## 真相源模型
|
||||
|
||||
```text
|
||||
需求状态 = 阶段看板唯一真相
|
||||
【交付】 = 每需求最多 1 条容器(ASSOCIATED→需求)
|
||||
【分析】/【设计】 = TASK_SUB→交付(交付「子项」必可见;与 ASSOCIATED 同 create 互斥)
|
||||
【开发】/【测试】 = 不进本 Skill
|
||||
查重/复用唯一渠道 = 任务编号(ONEOS-xx);禁止按标题
|
||||
```
|
||||
|
||||
编号权威:需求描述 `## 工作项编号(系统)`(见 [references/workitem-ids.md](references/workitem-ids.md))。
|
||||
操作顺序:口令显式编号 > 读该区块 > ASSOCIATED/SUB 校验;冲突则停。
|
||||
|
||||
## 外置调用(禁止本 Skill 内嵌对方全文)
|
||||
|
||||
| 时机 | 调用 |
|
||||
|---|---|
|
||||
| 入库清洗聊天/录音 | **`$AutoRDO`**(独立 Skill;路径 `AutoRDO/SKILL.md`;不内嵌清洗细则) |
|
||||
| 设计完成 PRD + 对象存储链接 + 回填【交付】 | **`$oneos-autoprd`(AutoPRD)**;创建【交付】仍占位,设计完成才灌 MD |
|
||||
| 人员 / 状态 / 字段 ID;项目 catalog 仅缓存 | [assets/runtime-ids.json](assets/runtime-ids.json) |
|
||||
| **PJ 云效项目点选**(新建必选;实时列表) | [references/project-selection.md](references/project-selection.md) · [scripts/list_projects.py](scripts/list_projects.py) |
|
||||
| **压缩点选 `1a2b3a4d`**(类型/项目/优先级/标签) | [references/compact-select.md](references/compact-select.md) · [scripts/list_tags.py](scripts/list_tags.py) |
|
||||
| 阶段日历工时 | [references/work-hours.md](references/work-hours.md) + [assets/cn-workday-calendar.json](assets/cn-workday-calendar.json) + [scripts/workday_hours.py](scripts/workday_hours.py) |
|
||||
|
||||
## 路由(按需完整阅读)
|
||||
|
||||
| 场景 | 模块 |
|
||||
|---|---|
|
||||
| 交付树、关联约定、禁止项 | [references/model.md](references/model.md) |
|
||||
| 描述双段 · AutoRDO / 占位 / AutoPRD | [references/description-split.md](references/description-split.md) |
|
||||
| 步骤 0–5 标准路径 | [references/stage-flow.md](references/stage-flow.md) |
|
||||
| 无单快轨到待开发 | [references/fast-track.md](references/fast-track.md) |
|
||||
| 编号直推交棒 | [references/number-push.md](references/number-push.md) |
|
||||
| 交棒门禁 · 回退最小集 | [references/handoff-and-rollback.md](references/handoff-and-rollback.md) |
|
||||
| 计划开始/完成 · 阶段日历工时 | [references/work-hours.md](references/work-hours.md) |
|
||||
| Make 导出 ZIP + 复制截图 | [references/make-export-attach.md](references/make-export-attach.md) |
|
||||
| 创建迭代 · V主.副.子 · 只挂交付 | [references/sprint.md](references/sprint.md) |
|
||||
| 口令面 | [references/commands.md](references/commands.md) |
|
||||
| 记录需求元字段(优先级/标签/提交部门/提交人) | [references/record-meta-fields.md](references/record-meta-fields.md) |
|
||||
| 云效项目点选 PJ | [references/project-selection.md](references/project-selection.md) |
|
||||
| 验收清单 · 回报模板 | [references/acceptance.md](references/acceptance.md) |
|
||||
| 交接契约(开发 Skill 入口) | [references/handoff-contract.md](references/handoff-contract.md) |
|
||||
| 已验证实写 API · 极速建单 | [references/live-api.md](references/live-api.md) · [scripts/live_create_fast.py](scripts/live_create_fast.py) |
|
||||
| 2026-07-23 复盘与耗时对比 | [references/live-perf-2026-07-23.md](references/live-perf-2026-07-23.md) |
|
||||
|
||||
## 口令速查
|
||||
|
||||
```text
|
||||
记录需求:…;项目=(Plan 点选,勿默认);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨)
|
||||
受理确认:ONEOS-xx
|
||||
开始分析:ONEOS-xx
|
||||
开始设计:ONEOS-xx;交付任务=…;分析任务=…
|
||||
设计完成:ONEOS-xx;设计任务=…;原型=…
|
||||
交棒开发:ONEOS-xx;交付任务=…
|
||||
快轨待开发:ONEOS-xx
|
||||
编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c / 交付任务=ONEOS-a
|
||||
创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=…
|
||||
```
|
||||
|
||||
**PJ 项目**:新建前必须从云效实时列表点选(见 [project-selection.md](references/project-selection.md));口令带项目名仅作预填建议。
|
||||
**压缩点选**:记录需求 Plan 展示 `1.类型 2.项目 3.优先级 4.标签` 字母表;你可回 `1a2b3a4d`(见 [compact-select.md](references/compact-select.md))。标签未命中会自动重拉一次标签候选并重生选项。
|
||||
**记录需求**元字段(优先级/标签/提交部门/提交人):与压缩点选并用;缺项用字母表补齐。见 [references/record-meta-fields.md](references/record-meta-fields.md)。
|
||||
|
||||
后续口令**优先带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。
|
||||
|
||||
## 本 Skill 终点
|
||||
|
||||
交棒完成(需求=待开发;【交付】负责人=何斐)后结束。可一句:「请技术经理使用开发 Skill」。
|
||||
例外:交棒后「创建迭代并关联交付」仍属 YunxiaoPM。
|
||||
|
||||
**明确不做:** 创建【开发】/【测试】、挂仓库、开分支、提测、写用例。
|
||||
|
||||
## §0.1 五条补齐(摘要)
|
||||
|
||||
1. **交棒占位**:标准路径下交付仍为 `等待设计任务完成后自动填入` 时**允许**交棒,但 Plan 必须勾选风险,回报首行标红。**快轨**有手工/原型时禁止占位(见 [fast-track.md](references/fast-track.md))。
|
||||
2. **预计工时**:标准路径 = **阶段日历工时**(工作日×8);**快轨待开发**需求默认预计/实际各 **2**。
|
||||
3. **编号真相源**在需求「工作项编号(系统)」;新建后立即 PATCH 该区块。
|
||||
4. **无单快轨**:【设计】描述同步需求;计划起止=当日;TASK_SUB→交付后补 ASSOCIATED→需求;【交付】描述手工同步或 AutoPRD;交付/设计与需求同标签;设计当日完成态。
|
||||
5. **描述双段**不互相覆盖;迭代**只挂【交付】**(需求不挂迭代);回退重做设计则**新开设计编号**,交付计划开始不改。
|
||||
|
||||
细则见各 references。
|
||||
49
.claude/skills/YunxiaoPM/acceptance.md
Normal file
49
.claude/skills/YunxiaoPM/acceptance.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 验收清单与回报
|
||||
|
||||
## apply 后必须自检
|
||||
|
||||
| # | 项 | 通过标准 |
|
||||
|---|---|---|
|
||||
| 1 | 需求编号 | 回报含 ONEOS-xx |
|
||||
| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 |
|
||||
| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 |
|
||||
| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) |
|
||||
| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) |
|
||||
| 6 | 计划开始 | 未误覆盖已有值 |
|
||||
| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 |
|
||||
| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) |
|
||||
| 9 | 占位风险 | 交付仍占位时首行标红 |
|
||||
| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 |
|
||||
|
||||
设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。
|
||||
|
||||
迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。
|
||||
|
||||
## 回报模板(精简)
|
||||
|
||||
```text
|
||||
【YunxiaoPMapp】
|
||||
风险:(若有占位交棒则首行标红)
|
||||
需求:ONEOS-xx | 状态=…
|
||||
交付:ONEOS-a | 负责人=…
|
||||
分析:ONEOS-b | …(无则写无)
|
||||
设计:ONEOS-c | …(无则写无)
|
||||
阶段日历工时:分析 Hh / 设计 Hh(若本轮写入)
|
||||
附件:…(若本轮)
|
||||
迭代:…(若本轮)
|
||||
下一步:请技术经理使用开发 Skill(交棒后)
|
||||
```
|
||||
|
||||
## 适合全自动 vs 人工门禁
|
||||
|
||||
| 适合全自动 | 建议半自动/人工门禁 |
|
||||
|---|---|
|
||||
| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | 受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 |
|
||||
|
||||
## 实写性能(2026-07-23 复盘)
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) |
|
||||
| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` |
|
||||
| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 |
|
||||
4
.claude/skills/YunxiaoPM/agents/openai.yaml
Normal file
4
.claude/skills/YunxiaoPM/agents/openai.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "需求任务"
|
||||
short_description: "云效:压缩点选记录需求→分析/设计→交棒待开发→挂迭代"
|
||||
default_prompt: "按需求任务(/YunxiaoPM)处理:先 Plan 出压缩点选字母表并确认项目,用户回 1a2b3a4d 与「执行」后再 apply。"
|
||||
95
.claude/skills/YunxiaoPM/assets/cn-workday-calendar.json
Normal file
95
.claude/skills/YunxiaoPM/assets/cn-workday-calendar.json
Normal file
@@ -0,0 +1,95 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"timezone": "Asia/Shanghai",
|
||||
"note": "法定放假日 holidays;国务院公布的周末调休补班日 workdays_on_weekend。缺年则禁止静默按仅去周末计算。",
|
||||
"source": {
|
||||
"2025": "国办发明电〔2024〕12号 https://www.gov.cn/zhengce/zhengceku/202411/content_6986383.htm",
|
||||
"2026": "国办发明电〔2025〕7号 https://www.gov.cn/zhengce/content/202511/content_7047090.htm"
|
||||
},
|
||||
"years": {
|
||||
"2025": {
|
||||
"holidays": [
|
||||
"2025-01-01",
|
||||
"2025-01-28",
|
||||
"2025-01-29",
|
||||
"2025-01-30",
|
||||
"2025-01-31",
|
||||
"2025-02-01",
|
||||
"2025-02-02",
|
||||
"2025-02-03",
|
||||
"2025-02-04",
|
||||
"2025-04-04",
|
||||
"2025-04-05",
|
||||
"2025-04-06",
|
||||
"2025-05-01",
|
||||
"2025-05-02",
|
||||
"2025-05-03",
|
||||
"2025-05-04",
|
||||
"2025-05-05",
|
||||
"2025-05-31",
|
||||
"2025-06-01",
|
||||
"2025-06-02",
|
||||
"2025-10-01",
|
||||
"2025-10-02",
|
||||
"2025-10-03",
|
||||
"2025-10-04",
|
||||
"2025-10-05",
|
||||
"2025-10-06",
|
||||
"2025-10-07",
|
||||
"2025-10-08"
|
||||
],
|
||||
"workdays_on_weekend": [
|
||||
"2025-01-26",
|
||||
"2025-02-08",
|
||||
"2025-04-27",
|
||||
"2025-09-28",
|
||||
"2025-10-11"
|
||||
]
|
||||
},
|
||||
"2026": {
|
||||
"holidays": [
|
||||
"2026-01-01",
|
||||
"2026-01-02",
|
||||
"2026-01-03",
|
||||
"2026-02-15",
|
||||
"2026-02-16",
|
||||
"2026-02-17",
|
||||
"2026-02-18",
|
||||
"2026-02-19",
|
||||
"2026-02-20",
|
||||
"2026-02-21",
|
||||
"2026-02-22",
|
||||
"2026-02-23",
|
||||
"2026-04-04",
|
||||
"2026-04-05",
|
||||
"2026-04-06",
|
||||
"2026-05-01",
|
||||
"2026-05-02",
|
||||
"2026-05-03",
|
||||
"2026-05-04",
|
||||
"2026-05-05",
|
||||
"2026-06-19",
|
||||
"2026-06-20",
|
||||
"2026-06-21",
|
||||
"2026-09-25",
|
||||
"2026-09-26",
|
||||
"2026-09-27",
|
||||
"2026-10-01",
|
||||
"2026-10-02",
|
||||
"2026-10-03",
|
||||
"2026-10-04",
|
||||
"2026-10-05",
|
||||
"2026-10-06",
|
||||
"2026-10-07"
|
||||
],
|
||||
"workdays_on_weekend": [
|
||||
"2026-01-04",
|
||||
"2026-02-14",
|
||||
"2026-02-28",
|
||||
"2026-05-09",
|
||||
"2026-09-20",
|
||||
"2026-10-10"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
221
.claude/skills/YunxiaoPM/assets/runtime-ids.json
Normal file
221
.claude/skills/YunxiaoPM/assets/runtime-ids.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"skill": "YunxiaoPM",
|
||||
"verified_at": "2026-07-25",
|
||||
"note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.",
|
||||
"project": {
|
||||
"selection_mode": "user_pick_required",
|
||||
"name": null,
|
||||
"name_aliases": [
|
||||
"01_ONEOS",
|
||||
"ONEOS",
|
||||
"统一运营管理平台",
|
||||
"统一运营管理平台PC端",
|
||||
"统一运营管理平台 PC 端"
|
||||
],
|
||||
"spaceIdentifier": null,
|
||||
"customCode": null,
|
||||
"spaceType": "Project",
|
||||
"organizationIdentifier": "697c54a19df7fdfa65466405",
|
||||
"default_sprint_name_prefix": "统一运营管理平台PC端",
|
||||
"list_api": "GET /projex/api/workspace/project/search/list",
|
||||
"last_selected": {
|
||||
"name": "01_ONEOS",
|
||||
"spaceIdentifier": "1280be963a5a2cc126a4118dca",
|
||||
"customCode": "ONEOS",
|
||||
"note": "历史常用项;仅作预填建议,禁止未点选即使用"
|
||||
},
|
||||
"refreshed_at": "2026-07-25"
|
||||
},
|
||||
"projects_catalog": [
|
||||
{
|
||||
"name": "05_羚牛碳资产平台",
|
||||
"identifier": "ff104a3bce09463da136a97999",
|
||||
"customCode": "CARBON"
|
||||
},
|
||||
{
|
||||
"name": "06_对外客户项目",
|
||||
"identifier": "baca8b4d676c5af67e475caec0",
|
||||
"customCode": "CUST"
|
||||
},
|
||||
{
|
||||
"name": "07_LNBOX",
|
||||
"identifier": "35e1e915a052379bbdfb993aac",
|
||||
"customCode": "LNBOX"
|
||||
},
|
||||
{
|
||||
"name": "04_AI应用",
|
||||
"identifier": "d9002ea72c6c3a1a97b02be750",
|
||||
"customCode": "AIAPP"
|
||||
},
|
||||
{
|
||||
"name": "03_数据中台",
|
||||
"identifier": "a801cef5c9a68fa051c07432c7",
|
||||
"customCode": "DATA"
|
||||
},
|
||||
{
|
||||
"name": "02_小羚羚APP",
|
||||
"identifier": "db771a2cca07bed43b369af077",
|
||||
"customCode": "XLLAPP"
|
||||
},
|
||||
{
|
||||
"name": "01_ONEOS",
|
||||
"identifier": "1280be963a5a2cc126a4118dca",
|
||||
"customCode": "ONEOS"
|
||||
},
|
||||
{
|
||||
"name": "敏捷研发示例项目",
|
||||
"identifier": "65eca0c2e16a23939081e19e14",
|
||||
"customCode": "DEMO"
|
||||
}
|
||||
],
|
||||
"people": {
|
||||
"wangmian": {
|
||||
"displayName": "王冕",
|
||||
"identifier": "6811df000601d2fea60144a9",
|
||||
"role": "产品创建人/默认负责人"
|
||||
},
|
||||
"hefei": {
|
||||
"displayName": "何斐",
|
||||
"identifier": "695f0400562f09713f9c3a93",
|
||||
"role": "待开发交棒【交付】负责人"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"故障管理": "ceb526a7343995577645317e9a",
|
||||
"还车应结款": "4204960ce658c94b17abb7c5f6",
|
||||
"工作台": "b62d21beac55c0b43389915eab",
|
||||
"交车管理": "b6947b5aca82a8c759612ba039",
|
||||
"合同管理": "23873be81931cb0dc1bf87a456",
|
||||
"安全培训": "76988b5f73ef515de5930d59b9",
|
||||
"证照管理": "11f4c2ec65901a8f3199c81706",
|
||||
"还车管理": "1e0fce2d6929ff4ac13b310e97"
|
||||
},
|
||||
"status": {
|
||||
"req": {
|
||||
"待处理": "100005",
|
||||
"已确认": "32",
|
||||
"分析中": "154395",
|
||||
"设计中": "156603",
|
||||
"设计完成": "307012",
|
||||
"待开发": "1582fc929d429111b925309493"
|
||||
},
|
||||
"task": {
|
||||
"待处理": "100005",
|
||||
"已完成": "100014"
|
||||
},
|
||||
"transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit",
|
||||
"fast_handoff_hops": [
|
||||
"设计完成",
|
||||
"待开发"
|
||||
],
|
||||
"note": "见 references/live-api.md;禁止再用 updateStatus"
|
||||
},
|
||||
"workitem_types": {
|
||||
"product_req": {
|
||||
"name": "产品类需求",
|
||||
"category": "Req",
|
||||
"identifier": "9uy29901re573f561d69jn40"
|
||||
},
|
||||
"task": {
|
||||
"name": "任务",
|
||||
"category": "Task",
|
||||
"identifier": "ba102e46bc6a8483d9b7f25c"
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"紧急": "646004e97f54bb77fec7b455df",
|
||||
"高": "95b89e0a524d9693e1f335ffe5",
|
||||
"中": "fa155d1214f9f8db222d39db3b",
|
||||
"低": "92924feff9c1085891e7511872"
|
||||
},
|
||||
"fields": {
|
||||
"plan_start": {
|
||||
"fieldIdentifier": "79",
|
||||
"name": "计划开始时间",
|
||||
"value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted",
|
||||
"example": "2026-07-27 12:00:00",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON"
|
||||
},
|
||||
"plan_end": {
|
||||
"fieldIdentifier": "80",
|
||||
"name": "计划完成时间",
|
||||
"value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON"
|
||||
},
|
||||
"estimated_hours": {
|
||||
"fieldIdentifier": "101586",
|
||||
"name": "预计工时",
|
||||
"note": "不可直接改字段;须工时预估登记",
|
||||
"create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier",
|
||||
"delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}",
|
||||
"list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier="
|
||||
},
|
||||
"actual_hours": {
|
||||
"name": "实际工时",
|
||||
"note": "快轨待开发默认 2",
|
||||
"create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)",
|
||||
"list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=",
|
||||
"fast_track_default": 2,
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"fast_track": {
|
||||
"req_estimated_hours": 2,
|
||||
"req_actual_hours": 2,
|
||||
"design_plan_start_end_same_day": true,
|
||||
"design_description": "copy_req_document",
|
||||
"delivery_description": "manual_sync_or_autoprd",
|
||||
"delivery_design_same_tags_as_req": true,
|
||||
"design_associated_to_req_after_task_sub": true,
|
||||
"document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}"
|
||||
},
|
||||
"submit_department": {
|
||||
"fieldIdentifier": "3132597a9718d1c282b7ba5a0c",
|
||||
"name": "提交部门",
|
||||
"format": "string input",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON",
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"submitter": {
|
||||
"fieldIdentifier": "9e01269e96f91fbb97d36bf5b3",
|
||||
"name": "提交人",
|
||||
"format": "string input",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON",
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"tag": {
|
||||
"fieldIdentifier": "tag",
|
||||
"name": "标签",
|
||||
"propertyKey": "tag"
|
||||
}
|
||||
},
|
||||
"assignee_rules": {
|
||||
"待开发_交付任务": "hefei",
|
||||
"分析中_交付与分析": "creator",
|
||||
"设计中_设计": "creator"
|
||||
},
|
||||
"relation": {
|
||||
"delivery_to_req": "ASSOCIATED",
|
||||
"stage_to_delivery": "TASK_SUB",
|
||||
"create_field": "createWorkitemRelationInfo",
|
||||
"forbid": [
|
||||
"PARENT_as_关联项",
|
||||
"ASSOCIATED_only_for_stage_tasks_without_TASK_SUB"
|
||||
],
|
||||
"note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md"
|
||||
},
|
||||
"delivery_placeholder": "等待设计任务完成后自动填入",
|
||||
"task_title_prefixes": {
|
||||
"delivery": "【交付】",
|
||||
"analysis": "【分析】",
|
||||
"design": "【设计】"
|
||||
},
|
||||
"publish_url": {
|
||||
"shape": "{baseUrl}/{prototype-id}/index.html",
|
||||
"forbid_prototypes_prefix": true,
|
||||
"require_index_html": true
|
||||
}
|
||||
}
|
||||
317
.claude/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md
Normal file
317
.claude/skills/YunxiaoPM/docs-YunxiaoPMapp-实现原理-开发Skill对接.md
Normal file
@@ -0,0 +1,317 @@
|
||||
# YunxiaoPMapp 实现原理说明
|
||||
|
||||
> 供**开发部门**设计 / 制作「开发侧 Skill」(暂称 **YunxiaoDevapp**)时对齐契约。
|
||||
> 本文描述产品侧 Skill **YunxiaoPMapp** 的模型、边界、数据契约与交棒接口;**不是**对 `yunxiao-requirement-lifecycle` 的兼容说明。
|
||||
> 技能包:https://github.com/15810879921-coder/oneos-pm-skills · `skills/YunxiaoPMapp/`
|
||||
> 文档版本:2026-07-24
|
||||
|
||||
---
|
||||
|
||||
## 1. 为什么拆成两个 Skill
|
||||
|
||||
| | YunxiaoPMapp(产品) | 开发 Skill(待建) |
|
||||
|---|---|---|
|
||||
| 职责 | 需求从「待处理」到「待开发」交棒 | 从「待开发」到开发完成 / 提测前 |
|
||||
| 任务类型 | 【交付】【分析】【设计】 | 【开发】(可多条)、可选挂仓库/分支 |
|
||||
| 负责人交接 | 交棒时【交付】→ **何斐** | 拆【开发】子任务并指派研发 |
|
||||
| 明确不做 | 建【开发】/【测试】、开分支、提测 | 不建【分析】/【设计】、不改 AutoRDO 段 |
|
||||
|
||||
**硬规则:** 两个 Skill **禁止互相 include / fork 全文**;只认本文第 6 章「交棒契约」。产品会话与开发会话不要同时挂载旧 `yunxiao-requirement-lifecycle`,避免双建任务树。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心真相源(必须先统一)
|
||||
|
||||
```text
|
||||
需求状态 = 阶段看板唯一真相(分析中 / 设计中 / 待开发 …)
|
||||
【交付】任务 = 每需求最多 1 条「交付容器」
|
||||
【分析】【设计】 = 【交付】下的阶段子项(产品侧建)
|
||||
【开发】【测试】 = 开发 / 测试 Skill 建(产品侧永不创建)
|
||||
查重 / 复用 = 只认云效任务编号 ONEOS-xx,禁止按标题
|
||||
```
|
||||
|
||||
### 2.1 关系模型(云效)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
Req["产品类需求 ONEOS-R"]
|
||||
DEL["【交付】ONEOS-a"]
|
||||
AN["【分析】ONEOS-b"]
|
||||
DE["【设计】ONEOS-c"]
|
||||
DEV["【开发】… 开发 Skill"]
|
||||
TEST["【测试】… 测试 Skill"]
|
||||
|
||||
DEL -->|"ASSOCIATED 关联项"| REQ
|
||||
AN -->|"TASK_SUB 子项"| DEL
|
||||
DE -->|"TASK_SUB 子项"| DEL
|
||||
DEV -.->|"建议:SUB→交付 或 ASSOCIATED→需求+交付"| DEL
|
||||
TEST -.->|"测试 Skill"| REQ
|
||||
```
|
||||
|
||||
| 关系 | 谁写 | 验收 |
|
||||
|---|---|---|
|
||||
| `ASSOCIATED` | **仅【交付】→ 需求** | 交付详情「关联项」可见需求 |
|
||||
| `TASK_SUB` | 【分析】/【设计】→【交付】 | 交付详情「子项」可见阶段任务 |
|
||||
| (开发侧建议) | 【开发】→【交付】或需求 | 由开发 Skill 自定,但须可编号查重 |
|
||||
|
||||
**踩坑(已复现):** 对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`,交付「子项」会为空。正确:create 时 `createWorkitemRelationInfo=TASK_SUB` + `parent`/`parentIdentifier`=交付。同一 create **只能**带一条关系,ASSOCIATED 与 TASK_SUB 互斥。
|
||||
|
||||
### 2.2 标题只是展示
|
||||
|
||||
- 形式:`【交付】`/`【分析】`/`【设计】` + 需求标题
|
||||
- **不参与查重**;改标题不影响幂等
|
||||
- 开发侧【开发】标题建议 `【开发】` + 范围简述,同样**只认编号**
|
||||
|
||||
---
|
||||
|
||||
## 3. 编号权威源
|
||||
|
||||
需求描述固定区块(机器可解析):
|
||||
|
||||
```markdown
|
||||
## 工作项编号(系统)
|
||||
- 交付:ONEOS-a
|
||||
- 分析:ONEOS-b(无则写无)
|
||||
- 设计:ONEOS-c(无则写无)
|
||||
```
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 新建后立即 PATCH | 只改本区块,不动 AutoRDO / AutoPRD |
|
||||
| 操作顺序 | 口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查 |
|
||||
| 冲突 | 停下人工;**禁止按标题猜** |
|
||||
| 多交付编号 | 停止并列号请人合并 |
|
||||
|
||||
**开发 Skill 入口建议:** 口令必须带 `需求=ONEOS-R` + `交付任务=ONEOS-a`;缺则读本区块;仍无则询问。
|
||||
|
||||
---
|
||||
|
||||
## 4. 需求描述双段(产品写 · 开发只读)
|
||||
|
||||
```markdown
|
||||
## 原始诉求(AutoRDO)
|
||||
(聊天/录音清洗稿;设计完成也不删)
|
||||
|
||||
## 产品说明(AutoPRD)
|
||||
(设计完成才灌满;含对象存储预览链接)
|
||||
|
||||
## 工作项编号(系统)
|
||||
- 交付 / 分析 / 设计
|
||||
```
|
||||
|
||||
| 段 | 谁写 | 开发侧用法 |
|
||||
|---|---|---|
|
||||
| AutoRDO | `$AutoRDO` → 产品记需求时 | 占位交棒时**唯一可信**业务原文 |
|
||||
| AutoPRD | `$oneos-autoprd` → 设计完成时 | 正式需求说明;缺则有权要求产品补设计完成 |
|
||||
| 编号区块 | YunxiaoPMapp | 定位交付树 |
|
||||
|
||||
**【交付】描述:** 设计完成前固定占位文案 `等待设计任务完成后自动填入`;设计完成后替换为 AutoPRD 正文。
|
||||
**允许占位交棒**,但产品回报必须标红风险;开发 Skill 应检测占位并提示「材料不齐,可凭 AutoRDO 开工或退回补设计」。
|
||||
|
||||
对象存储预览 URL 形态:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 产品侧阶段状态机(0→5)
|
||||
|
||||
```text
|
||||
0 创建需求(待处理) ─AutoRDO→ 仅需求,不建任务
|
||||
1 受理确认(已确认) 仍不建任务
|
||||
2 分析中 建【交付】+【分析】
|
||||
3 设计中 建【设计】;收口【分析】计划完成
|
||||
4 设计完成 收口【设计】;AutoPRD+附件;灌【交付】描述
|
||||
5 待开发交棒 ★ 需求→待开发;【交付】负责人→何斐
|
||||
★ = YunxiaoPMapp 终点 / 开发 Skill 起点
|
||||
```
|
||||
|
||||
### 5.1 各步要点(开发需知道的副作用)
|
||||
|
||||
| 步骤 | 需求状态 | 任务侧关键动作 |
|
||||
|---|---|---|
|
||||
| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;描述=占位;计划开始仅空写当日且**此后不可改**;新建【分析】TASK_SUB→交付 |
|
||||
| 3 设计中 | 设计中 | 新建【设计】;【分析】计划完成=当日 + 阶段日历工时 |
|
||||
| 4 设计完成 | 设计完成 | 【设计】完成态;需求+交付挂 ZIP/截图;交付描述换正式说明 |
|
||||
| 5 交棒 | **待开发** | **只改交付负责人→何斐**;不新建分析/设计;不写交付计划完成 |
|
||||
|
||||
### 5.2 两条旁路(仍交到同一终点)
|
||||
|
||||
| 路径 | 前提 | 行为 |
|
||||
|---|---|---|
|
||||
| **无单快轨** | 尚无分析路径 / 明确跳过分析 | 待开发 + 新建交付+设计(设计当日收口);**不建分析**;默认可不跑 AutoPRD |
|
||||
| **编号直推** | 已有分析/设计**编号** | 收口空计划完成 → 待开发 + 交付交棒何斐;不新建冗余单 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 交棒契约(开发 Skill 必须实现)
|
||||
|
||||
### 6.1 入口条件(产品已完成)
|
||||
|
||||
```text
|
||||
需求状态 = 待开发
|
||||
【交付】任务编号 = ONEOS-a(唯一)
|
||||
【交付】负责人 = 何斐
|
||||
【交付】ASSOCIATED → 该需求
|
||||
可选:分析/设计编号在「工作项编号(系统)」中
|
||||
```
|
||||
|
||||
### 6.2 开发 Skill 建议入口口令
|
||||
|
||||
```text
|
||||
接手开发:需求=ONEOS-R;交付任务=ONEOS-a
|
||||
拆分开发:交付任务=ONEOS-a;范围=前端|后端|…;负责人=…
|
||||
开始开发:开发任务=ONEOS-d
|
||||
开发完成:开发任务=ONEOS-d
|
||||
```
|
||||
|
||||
### 6.3 开发 Skill 建议职责边界
|
||||
|
||||
| 应做 | 不应做 |
|
||||
|---|---|
|
||||
| 在【交付】下建一条或多条【开发】(编号幂等) | 再建【分析】/【设计】或第二套【交付】 |
|
||||
| 正式关联:建议 SUB→交付,或同时 ASSOCIATED→需求 | 只按标题「【开发】xxx」查重 |
|
||||
| 挂仓库 / 分支 / MR(按你们 Codeup 规范) | 覆盖需求 AutoRDO 段 |
|
||||
| 改【开发】状态与负责人 | 擅自把需求从待开发退回(除非产品授权回退口令) |
|
||||
| 检测交付描述是否仍为占位并提示 | 假装 AutoPRD 已齐全 |
|
||||
| 全部【开发】完成后通知测试 Skill / 建【测试】 | 在产品 Skill 会话里混跑 |
|
||||
|
||||
### 6.4 共享常量(可引用,勿整包加载)
|
||||
|
||||
路径均在 `skills/YunxiaoPMapp/assets/`:
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| `runtime-ids.json` | 项目 spaceId、何斐 ID、工作项类型、状态 transit、字段 79/80 |
|
||||
| `cn-workday-calendar.json` | 法定节假日 / 调休(阶段日历工时) |
|
||||
|
||||
开发侧可复制一份到自己的 `assets/`,或只读引用短路径;**不要**把 YunxiaoPMapp 的 references 全文 include 进开发 Skill。
|
||||
|
||||
---
|
||||
|
||||
## 7. 计划时间与「阶段日历工时」
|
||||
|
||||
产品侧口径(开发侧若写计划时间建议对齐语义):
|
||||
|
||||
| 对象 | 计划开始 | 计划完成 | 预计工时 |
|
||||
|---|---|---|---|
|
||||
| 【交付】 | 首次分析中空则写当日,**永不覆盖** | 产品侧不写(留给上线) | 一般不推全周期 |
|
||||
| 【分析】【设计】 | 创建时空则写当日,**永不覆盖** | 阶段收口日 | `工作日×8` |
|
||||
|
||||
```text
|
||||
预计工时 = 阶段日历工时(Lead Time)≠ 人力投入人天
|
||||
算法:起止日含首尾,扣周末与法定假,计入调休补班;×8 小时
|
||||
禁止:(结束−开始+1)×8 的自然日算法
|
||||
```
|
||||
|
||||
脚注固定:`【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估`
|
||||
脚本:`scripts/workday_hours.py`
|
||||
|
||||
**建议:** 开发 Skill 若给【开发】写预计工时,在文档中明确是「人力投入预估」还是「阶段日历工时」,避免与产品侧混称。
|
||||
|
||||
---
|
||||
|
||||
## 8. Agent 运行时原理(制作 Skill 时照抄模式)
|
||||
|
||||
### 8.1 Plan 门禁
|
||||
|
||||
凡写云效:`SwitchMode → plan` → 用户确认 → 一口气 apply → 一次校验回报。
|
||||
禁止用「参数已齐 / 速度路径」跳过 Plan。
|
||||
|
||||
### 8.2 模块化路由
|
||||
|
||||
`SKILL.md` 只做路由与门禁;细则在 `references/*.md`;常量在 `assets/`;脚本在 `scripts/`。
|
||||
外置能力(AutoRDO、AutoPRD)**调用对方 Skill**,不内嵌对方全文。
|
||||
|
||||
### 8.3 幂等
|
||||
|
||||
```text
|
||||
幂等键 = 项目 + 需求编号 + 任务类型角色(交付/分析/设计/开发…)+ 已登记任务编号
|
||||
命中编号 → 复用并补缺字段;禁止新建第二条同角色主容器(交付唯一)
|
||||
```
|
||||
|
||||
### 8.4 验收与回报
|
||||
|
||||
每次 apply 后自检(产品侧清单见 `references/acceptance.md`)。开发侧建议至少回报:
|
||||
|
||||
```text
|
||||
【YunxiaoDevapp】
|
||||
需求:ONEOS-R | 状态=待开发|开发中|…
|
||||
交付:ONEOS-a | 负责人=…
|
||||
开发:ONEOS-d1, ONEOS-d2 | …
|
||||
仓库/分支:…
|
||||
下一步:…
|
||||
```
|
||||
|
||||
### 8.5 已验证写路径(可复用思路)
|
||||
|
||||
| 动作 | 约定(见 live-api.md) |
|
||||
|---|---|
|
||||
| 改状态 | `POST …/status/transit`(勿用错误 updateStatus) |
|
||||
| 改负责人 | `PATCH …/{id}` + `propertyKey=assignedTo` |
|
||||
| 建单关系 | create 带 `createWorkitemRelationInfo` |
|
||||
| 极速复测 | `scripts/live_create_fast.py`(可参考,开发侧另写自己的脚本) |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回退最小集(开发需配合)
|
||||
|
||||
| 场景 | 产品侧规则 | 开发侧注意 |
|
||||
|---|---|---|
|
||||
| 待开发 → 退回设计中 | 需求回退;交付负责人可改回产品;**交付计划开始不改**;重做设计则**新开设计编号** | 已建【开发】是否取消 / 暂停由开发 Skill 定义;勿静默删编号 |
|
||||
| 设计完成后需求大变 | Plan 问是否回退;更新 AutoPRD;AutoRDO 保留+变更纪要 | 勿覆盖 AutoRDO |
|
||||
| 取消需求 | 任务标取消;不删编号区块 | 同步取消未完成【开发】 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 建议的「YunxiaoDevapp」目录骨架
|
||||
|
||||
```text
|
||||
YunxiaoDevapp/
|
||||
├── SKILL.md # 门禁 + 路由 + 边界(引用本文契约,不 include PMapp)
|
||||
├── assets/
|
||||
│ └── runtime-ids.json # 可从 PMapp 复制/裁剪
|
||||
├── references/
|
||||
│ ├── model.md # 【开发】与交付/需求的关系约定
|
||||
│ ├── handoff-intake.md # 认交付编号 + 占位检测
|
||||
│ ├── split-dev-tasks.md # 拆前端/后端多【开发】
|
||||
│ ├── codeup.md # 分支 / MR / 关联
|
||||
│ ├── commands.md # 口令面
|
||||
│ └── acceptance.md
|
||||
└── scripts/ # 可选极速 API
|
||||
```
|
||||
|
||||
`SKILL.md` description 建议写明:触发词「接手开发 / 拆分开发 / 开始开发」;**Does NOT create 【分析】/【设计】**;入口条件需求=待开发。
|
||||
|
||||
---
|
||||
|
||||
## 11. 对照检查表(开发 Skill 评审用)
|
||||
|
||||
- [ ] 是否只认「需求编号 + 交付任务编号」,禁止标题查重
|
||||
- [ ] 是否拒绝创建第二套【交付】或【分析】【设计】
|
||||
- [ ] 占位交棒时是否提示风险且不假装 PRD 齐全
|
||||
- [ ] 【开发】是否可编号幂等、可挂到交付树
|
||||
- [ ] 是否与 YunxiaoPMapp **会话隔离**(不同 Skill、不同口令)
|
||||
- [ ] 计划开始字段是否遵守「已有值不覆盖」(若沿用同一字段)
|
||||
- [ ] 提测 / 【测试】是否交给测试 Skill,本包边界清晰
|
||||
|
||||
---
|
||||
|
||||
## 12. 相关文档索引
|
||||
|
||||
| 主题 | 路径(仓库内) |
|
||||
|---|---|
|
||||
| Skill 入口 | `skills/YunxiaoPMapp/SKILL.md` |
|
||||
| 交付树模型 | `skills/YunxiaoPMapp/references/model.md` |
|
||||
| 交棒契约 | `skills/YunxiaoPMapp/references/handoff-contract.md` |
|
||||
| 标准路径 0–5 | `skills/YunxiaoPMapp/references/stage-flow.md` |
|
||||
| 交棒门禁 / 回退 | `skills/YunxiaoPMapp/references/handoff-and-rollback.md` |
|
||||
| 描述双段 | `skills/YunxiaoPMapp/references/description-split.md` |
|
||||
| 编号区块 | `skills/YunxiaoPMapp/references/workitem-ids.md` |
|
||||
| 快轨 / 编号直推 | `references/fast-track.md` · `number-push.md` |
|
||||
| 工时算法 | `references/work-hours.md` |
|
||||
| 实写 API | `references/live-api.md` |
|
||||
|
||||
安装产品 Skill:
|
||||
|
||||
```bash
|
||||
npx skills add 15810879921-coder/oneos-pm-skills --skill YunxiaoPMapp -a cursor -g -y
|
||||
```
|
||||
179
.claude/skills/YunxiaoPM/docs-产品部流转流程图.md
Normal file
179
.claude/skills/YunxiaoPM/docs-产品部流转流程图.md
Normal file
@@ -0,0 +1,179 @@
|
||||
# 产品部流转:YunxiaoPMapp(到交棒为止)
|
||||
|
||||
> 用途:先把**产品部**在云效上的工作流转说清楚;开发 / 测试 / 发版不在本 Skill 内,仅在文末标出交接点。
|
||||
> 依据:`references/stage-flow.md` · `model.md` · `fast-track.md` · `handoff-contract.md`
|
||||
> 日期:2026-07-24
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品部管到哪里
|
||||
|
||||
```text
|
||||
产品部(YunxiaoPMapp)终点 = 需求状态「待开发」+【交付】负责人「何斐」
|
||||
之后 = 请技术经理使用开发 Skill(不建【开发】/【测试】)
|
||||
```
|
||||
|
||||
| 谁 | 做什么 | 不做什么 |
|
||||
|---|---|---|
|
||||
| 产品 / YunxiaoPMapp | 建需求、打标签、建【交付】【分析】【设计】、推进状态、设计完成灌 AutoPRD、交棒 | 建【开发】【测试】、开分支、提测、发版 |
|
||||
| 技术经理(接手) | 从「待开发」起拆开发 | 不改写 AutoRDO;不新建第二套【交付】 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 总览流程图(标准路径)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph inputs [材料入口]
|
||||
Chat[聊天/录音/口述]
|
||||
AutoRDO["$AutoRDO 清洗"]
|
||||
Proto[原型页 Make]
|
||||
end
|
||||
|
||||
subgraph pm [产品部 · YunxiaoPMapp]
|
||||
S0["0 创建需求\n状态=待处理\n只写 AutoRDO\n不建任务"]
|
||||
S1["1 受理确认\n状态=已确认\n仍不建任务"]
|
||||
S2["2 分析中\n建【交付】+【分析】"]
|
||||
S3["3 设计中\n建【设计】\n收口【分析】"]
|
||||
S4["4 设计完成\n收口【设计】\nAutoPRD+附件\n灌【交付】描述"]
|
||||
S5["5 待开发交棒\n交付负责人→何斐\n★ 产品终点"]
|
||||
end
|
||||
|
||||
subgraph after [产品之后 · 不在本 Skill]
|
||||
Dev["开发 Skill\n拆【开发】…"]
|
||||
Test["测试 Skill"]
|
||||
Rel["发版"]
|
||||
end
|
||||
|
||||
Chat --> AutoRDO --> S0
|
||||
Proto -.-> S4
|
||||
S0 --> S1 --> S2 --> S3 --> S4 --> S5
|
||||
S5 -->|"交棒契约:需求编号+交付编号"| Dev
|
||||
Dev --> Test --> Rel
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 各步说明(产品侧)
|
||||
|
||||
| 步骤 | 需求状态 | 云效任务动作 | 描述写什么 | 口令示例 |
|
||||
|---|---|---|---|---|
|
||||
| 0 创建 | 待处理 | 无 | `## 原始诉求(AutoRDO)`;编号区块待建 | `记录需求:…;推进至=暂不推进` |
|
||||
| 1 受理 | 已确认 | 仍无 | 不动 | `受理确认:ONEOS-xx` |
|
||||
| 2 分析中 | 分析中 | 新建【交付】ASSOCIATED→需求;新建【分析】TASK_SUB→交付 | 交付=占位文案 | `开始分析:ONEOS-xx` |
|
||||
| 3 设计中 | 设计中 | 新建【设计】TASK_SUB→交付;分析计划完成=当日+阶段日历工时 | — | `开始设计:ONEOS-xx;交付=…;分析=…` |
|
||||
| 4 设计完成 | 设计完成 | 设计完成态;需求+交付挂 ZIP/截图;交付描述换 AutoPRD | `$oneos-autoprd` 灌 `## 产品说明` | `设计完成:ONEOS-xx;设计=…;原型=…` |
|
||||
| 5 交棒 | **待开发** | **只**把【交付】负责人改为何斐 | 若仍占位须标红风险 | `交棒开发:ONEOS-xx;交付=…` |
|
||||
|
||||
**编号权威:** 需求描述 `## 工作项编号(系统)`(交付 / 分析 / 设计)。后续口令优先带 `ONEOS-xx`,禁止按标题查重。
|
||||
|
||||
---
|
||||
|
||||
## 4. 交付树(产品建出来的结构)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
REQ["产品类需求 ONEOS-R\n状态=阶段真相"]
|
||||
DEL["【交付】ONEOS-a\nASSOCIATED→需求"]
|
||||
AN["【分析】ONEOS-b"]
|
||||
DE["【设计】ONEOS-c"]
|
||||
|
||||
DEL -->|"关联项 ASSOCIATED"| REQ
|
||||
AN -->|"子项 TASK_SUB"| DEL
|
||||
DE -->|"子项 TASK_SUB"| DEL
|
||||
```
|
||||
|
||||
验收口诀:
|
||||
- 打开【交付】→「关联项」能看到需求
|
||||
- 打开【交付】→「子项」能看到分析/设计(必须 `TASK_SUB`,不能只写 parentIdentifier)
|
||||
|
||||
---
|
||||
|
||||
## 5. 两条旁路(仍交到同一终点)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph standard [标准]
|
||||
A1[分析中] --> A2[设计中] --> A3[设计完成] --> A4[待开发]
|
||||
end
|
||||
subgraph fast [无单快轨]
|
||||
B1[待处理/已确认等] --> B2["待开发\n建交付+设计当日收口\n不建分析"]
|
||||
end
|
||||
subgraph push [编号直推]
|
||||
C1[已有分析/设计编号] --> C2[收口空计划完成] --> C3[待开发+交棒何斐]
|
||||
end
|
||||
```
|
||||
|
||||
| 路径 | 何时用 | 注意 |
|
||||
|---|---|---|
|
||||
| 标准 0→5 | 正常产品节奏 | 设计完成才灌 AutoPRD |
|
||||
| 快轨 | 明确跳过分析、急交棒 | 默认可不跑 AutoPRD;交付常占位→回报标红 |
|
||||
| 编号直推 | 树上已有阶段任务编号 | 不新建冗余单;只认编号 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 产品部人机边界(Plan 门禁)
|
||||
|
||||
```text
|
||||
凡写云效:Plan 对齐 → 人确认 → 一口气 apply → 一次校验回报
|
||||
```
|
||||
|
||||
| 适合全自动 | 建议人点一下 |
|
||||
|---|---|
|
||||
| 建单、打标、建树、改状态、交棒负责人、幂等复用 | 优先级、标签、是否快轨、设计是否真可开发、占位是否接受交棒 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 交棒给下游时交出什么
|
||||
|
||||
产品回报至少包含:
|
||||
|
||||
```text
|
||||
【YunxiaoPMapp】
|
||||
风险:(占位交棒则首行标红)
|
||||
需求:ONEOS-R | 状态=待开发
|
||||
交付:ONEOS-a | 负责人=何斐
|
||||
分析:ONEOS-b 或 无
|
||||
设计:ONEOS-c 或 快轨占位说明
|
||||
下一步:请技术经理使用开发 Skill
|
||||
```
|
||||
|
||||
下游入口条件见 `references/handoff-contract.md`:认 **需求编号 + 交付任务编号**。
|
||||
|
||||
---
|
||||
|
||||
## 8. 和全链路的关系(本文件边界)
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PM[产品部本文] --> Handoff[待开发交棒]
|
||||
Handoff --> Dev[开发]
|
||||
Dev --> QA[测试]
|
||||
QA --> Rel[发版]
|
||||
```
|
||||
|
||||
全链路(含谢佳伟 / 时生亮)见仓库原型:
|
||||
`src/prototypes/yunxiao-pipeline-handbook/`
|
||||
产品 Skill 对接开发说明:
|
||||
`docs-YunxiaoPMapp-实现原理-开发Skill对接.md`
|
||||
|
||||
---
|
||||
|
||||
## 9. 相关 references
|
||||
|
||||
| 主题 | 路径 |
|
||||
|---|---|
|
||||
| 标准 0–5 | `references/stage-flow.md` |
|
||||
| 交付树 / 子项 | `references/model.md` |
|
||||
| 快轨 | `references/fast-track.md` |
|
||||
| 编号直推 | `references/number-push.md` |
|
||||
| 交棒门禁 | `references/handoff-and-rollback.md` |
|
||||
| 交棒契约 | `references/handoff-contract.md` |
|
||||
| 口令 | `references/commands.md` |
|
||||
|
||||
---
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- 四角色泳道(产品/开发/测试/发版):`docs-四角色泳道流程图.md`
|
||||
- 开发侧草案:`docs-开发侧流转草案.md`
|
||||
318
.claude/skills/YunxiaoPM/docs-全流程分步说明.md
Normal file
318
.claude/skills/YunxiaoPM/docs-全流程分步说明.md
Normal file
@@ -0,0 +1,318 @@
|
||||
# 全流程分步说明:产品 → 开发 → 测试 → 发版
|
||||
|
||||
> 把整条链路拆成**按顺序执行的步骤**,每步说清:谁做、做什么、云效上变成什么、下一步谁接。
|
||||
> 读完应能照着口令/动作走完一轮(标准路径)。
|
||||
> 日期:2026-07-24
|
||||
|
||||
---
|
||||
|
||||
## 先记住三件事
|
||||
|
||||
1. **需求状态 = 阶段真相**
|
||||
看需求状态就知道现在卡在哪一段(分析中 / 待开发 / 开发中 / 待测试…)。
|
||||
2. **每条需求只有 1 个【交付】容器**
|
||||
分析、设计、开发、测试都挂在这个交付下面(子项),不要再建第二个【交付】。
|
||||
3. **编号说话,不靠标题查**
|
||||
口令尽量带 `ONEOS-xx`;改标题不影响关联。
|
||||
|
||||
```text
|
||||
需求(状态主轴)
|
||||
└─【交付】(容器,关联到需求)
|
||||
├─【分析】【设计】 ← 产品建
|
||||
├─【开发】… ← 开发建
|
||||
└─【测试】 ← 开发提测时建
|
||||
【发版】另建,挂迭代 + 范围内需求
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总步骤一览(共 18 步)
|
||||
|
||||
| 阶段 | 步骤 | 一句话 |
|
||||
|---|---|---|
|
||||
| **产品** | 1~6 | 从记诉求到交棒给何斐 |
|
||||
| **开发** | 7~12 | 从拆任务到提测 |
|
||||
| **测试** | 13~15 | 从接测到测试完成 |
|
||||
| **发版** | 16~17 | 从建发版到发布结果 |
|
||||
| **收口** | 18 | 产品验收关闭 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 A|产品(步骤 1~6)
|
||||
|
||||
### 步骤 1|记录需求
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 / AI(YunxiaoPMapp) |
|
||||
| **做什么** | 把聊天、录音、口述先洗成 AutoRDO,再建一条**产品类需求** |
|
||||
| **需求状态** | **待处理** |
|
||||
| **云效任务** | 不建任何任务 |
|
||||
| **描述里写** | `## 原始诉求(AutoRDO)`;编号区先空着 |
|
||||
| **口令例** | `记录需求:…;推进至=暂不推进` |
|
||||
| **为什么** | 先落盘,避免口头需求丢;还没确认值不值得做 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 2|受理确认
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 |
|
||||
| **做什么** | 确认要做:定优先级、打标签(从云效标签里点选) |
|
||||
| **需求状态** | **已确认** |
|
||||
| **云效任务** | 仍不建任务 |
|
||||
| **口令例** | `受理确认:ONEOS-xx` |
|
||||
| **为什么** | 和「还没想清楚」的待处理分开;确认后才进分析/设计 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 3|开始分析
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 / AI |
|
||||
| **做什么** | ① 建 **【交付】**(整条需求的唯一容器,关联到需求)② 建 **【分析】**(挂在交付**子项**下) |
|
||||
| **需求状态** | **分析中** |
|
||||
| **云效任务** | 【交付】+【分析】 |
|
||||
| **口令例** | `开始分析:ONEOS-xx` |
|
||||
| **验收** | 打开【交付】→「关联项」有需求;「子项」有【分析】 |
|
||||
| **为什么** | 分析要有落点;后续设计/开发/测试都挂同一棵树 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 4|开始设计
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 / AI |
|
||||
| **做什么** | 建 **【设计】**(子项挂交付);收口【分析】计划时间 |
|
||||
| **需求状态** | **设计中** |
|
||||
| **云效任务** | 新增【设计】 |
|
||||
| **口令例** | `开始设计:ONEOS-xx;交付=…;分析=…` |
|
||||
| **为什么** | 进入原型/方案设计;分析阶段收口 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 5|设计完成
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 / AI |
|
||||
| **做什么** | 收口【设计】;跑 AutoPRD,把**产品说明**灌进【交付】描述;挂原型 ZIP/截图等附件 |
|
||||
| **需求状态** | **设计完成** |
|
||||
| **口令例** | `设计完成:ONEOS-xx;设计=…;原型=…` |
|
||||
| **为什么** | 开发接手时看的是【交付】里的产品说明,不是聊天记录 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 6|交棒开发(产品终点)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 / AI |
|
||||
| **做什么** | 需求改为待开发;**【交付】负责人改为何斐**(不新建开发/测试任务) |
|
||||
| **需求状态** | **待开发** ★ |
|
||||
| **交出什么** | 需求编号 +【交付】编号 |
|
||||
| **口令例** | `交棒开发:ONEOS-xx;交付=…` |
|
||||
| **为什么** | 产品部到此结束;技术经理从这里拆开发 |
|
||||
|
||||
> 旁路:急单可用**快轨**(跳过分析、直接待开发),仍交到同一终点「待开发 + 交付→何斐」。
|
||||
|
||||
---
|
||||
|
||||
## 阶段 B|开发(步骤 7~12)
|
||||
|
||||
### 步骤 7|分配开发任务
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 开发主管何斐 / 开发 Skill |
|
||||
| **做什么** | 输入**交付任务编号** → 自动建 **【开发】**;标题=`【开发】`+需求标题;**描述人工补**;指定开发人;自动写计划时间 |
|
||||
| **关联** | 子项挂【交付】;关联项挂需求 |
|
||||
| **需求状态** | 仍为 **待开发**(确认即可,一般不用再改) |
|
||||
| **口令例** | `分配任务:交付=ONEOS-a;开发人=张三` |
|
||||
| **为什么** | 一条需求可拆多个开发任务;都挂在同一交付下 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 8|开始开发
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 开发人员 |
|
||||
| **做什么** | 口令带上**开发任务编号** |
|
||||
| **开发任务** | → **处理中** |
|
||||
| **需求状态** | → **开发中** |
|
||||
| **口令例** | `开始开发:ONEOS-d` |
|
||||
| **为什么** | 看板一眼能看出「正在写代码」 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 9|写代码 / 提 MR
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 开发人员 |
|
||||
| **做什么** | 按【交付】里的产品说明实现;分支/MR 建议挂上开发任务编号与需求编号 |
|
||||
| **需求状态** | 仍为 **开发中** |
|
||||
| **为什么** | 代码可追溯到哪条开发任务、哪条需求 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 10|AI 自测门禁(开发侧草案)
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | AI(开发完成后触发) |
|
||||
| **做什么** | 找与**需求同标题**的测试计划 → 按用例自测 → 不过则自动提缺陷(标题含开发任务编号)并尝试修复,直到本计划用例全过 |
|
||||
| **需求状态** | 仍为 **开发中**(未全过前) |
|
||||
| **为什么** | 在正式提测前先挡住明显问题(属草案,规则可再收紧) |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 11|开发任务完成
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 系统 / AI(用例全过之后) |
|
||||
| **做什么** | 该条【开发】标为**已完成** |
|
||||
| **需求状态** | → **开发完成**(若有多条开发,建议等**全部**完成再改,避免过早) |
|
||||
| **为什么** | 标记「代码+门禁」这一段结束 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 12|完成开发 · 正式提测
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 开发人员(自测也完成) |
|
||||
| **做什么** | 口令「完成开发」→ 自动建 **【测试】**(标题=`【测试】`+任务名);子项挂【交付】;建议再关联需求;指派测试(如谢佳伟) |
|
||||
| **需求状态** | → **待测试** ★ |
|
||||
| **口令例** | `完成开发:ONEOS-d` |
|
||||
| **为什么** | 开发交棒给测试;测试侧开始接单 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 C|测试(步骤 13~15)
|
||||
|
||||
### 步骤 13|接测试任务
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 测试(谢佳伟等) |
|
||||
| **做什么** | 打开【测试】任务;确认关联的需求与交付;准备/核对测试计划与用例包(建议按**需求编号**分包,比「同标题」更稳) |
|
||||
| **需求状态** | **待测试** → 开始执行后改为 **测试中** |
|
||||
| **为什么** | 正式测试以计划用例为准,不靠口头「测过了」 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 14|执行用例 · 缺陷回流
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 测试执行;失败时回流开发修 |
|
||||
| **做什么** | 按用例执行;失败建缺陷 → 开发修复 → 再测 |
|
||||
| **需求状态** | **测试中** |
|
||||
| **为什么** | 质量关口;未绿不能进发版 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 15|测试完成
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 测试 / AI 辅助判定 |
|
||||
| **做什么** | **本需求**相关用例全绿 →【测试】收口;通知发版对接人 |
|
||||
| **需求状态** | → **测试完成** ★ |
|
||||
| **注意** | 只卡本需求,不卡同迭代里别的需求 |
|
||||
| **为什么** | 发版范围以「测试完成」的需求为准 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 D|发版(步骤 16~17)
|
||||
|
||||
### 步骤 16|建发版任务
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 发版对接人(时生亮)· 手工或 AI |
|
||||
| **做什么** | 建 **【发版】**;关联**迭代** + 本期内要上的需求(建议再挂交付/测试);汇总更新说明 |
|
||||
| **需求状态** | 进入发布流程时 → **发布中** |
|
||||
| **为什么** | 一次发版可能含多条需求;要有明确范围清单 |
|
||||
|
||||
---
|
||||
|
||||
### 步骤 17|发布结果回写
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 发版对接人 / 流水线 |
|
||||
| **做什么** | 执行发布 |
|
||||
| **结果** | 成功 → **发布完成**;失败 → **发布失败**(可重试再进发布中) |
|
||||
| **为什么** | 成败要回写云效,避免「以为发了其实没发」 |
|
||||
|
||||
---
|
||||
|
||||
## 阶段 E|收口(步骤 18)
|
||||
|
||||
### 步骤 18|业务验收 · 关闭
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **谁** | 产品 |
|
||||
| **做什么** | 业务验收通过后,关闭需求,并收口【交付】 |
|
||||
| **需求状态** | → **已关闭** |
|
||||
| **为什么** | 全链路结束;树上任务与需求一致收口 |
|
||||
|
||||
---
|
||||
|
||||
## 状态主链(串起来看)
|
||||
|
||||
```text
|
||||
待处理 → 已确认 → 分析中 → 设计中 → 设计完成
|
||||
→ 待开发 ← 产品交棒给何斐
|
||||
→ 开发中 → 开发完成
|
||||
→ 待测试 → 测试中 → 测试完成
|
||||
→ 发布中 → 发布完成(或发布失败可重试)
|
||||
→ 已关闭 ← 产品验收关单
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 四个交棒口(最重要)
|
||||
|
||||
| 交棒 | 交出人 | 接棒人 | 交出物 | 状态落点 |
|
||||
|---|---|---|---|---|
|
||||
| ① 产品→开发 | 产品 | 何斐 | 需求号 + 交付号 | 待开发 |
|
||||
| ② 开发→测试 | 开发人 | 测试 | 【测试】任务已建好 | 待测试 |
|
||||
| ③ 测试→发版 | 测试 | 发版 | 本需求用例全绿 | 测试完成 |
|
||||
| ④ 发版→产品 | 发版 | 产品 | 发布完成 / 更新说明 | 发布完成 → 已关闭 |
|
||||
|
||||
---
|
||||
|
||||
## 谁在什么时候动(对照表)
|
||||
|
||||
| 步骤 | 产品 | 开发主管 | 开发人 | 测试 | 发版 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1~2 记需求/确认 | ● | | | | |
|
||||
| 3~5 分析设计 | ● | | | | |
|
||||
| 6 交棒 | ● | 接 | | | |
|
||||
| 7 分配任务 | | ● | | | |
|
||||
| 8~9 开始写码 | | | ● | | |
|
||||
| 10~11 AI门禁/完成任务 | | | ●/AI | | |
|
||||
| 12 提测 | | | ● | 接 | |
|
||||
| 13~15 测试 | | | 修缺陷 | ● | |
|
||||
| 16~17 发版 | | | | | ● |
|
||||
| 18 关闭 | ● | | | | |
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档 | 用途 |
|
||||
|---|---|
|
||||
| `docs-四角色泳道流程图.md` | 泳道图总览 |
|
||||
| `docs-产品部流转流程图.md` | 产品 1~6 细规 |
|
||||
| `docs-开发侧流转草案.md` | 开发 7~12 细规与待拍板 |
|
||||
| `yunxiao-pipeline-handbook` | 云效规则与配置 |
|
||||
181
.claude/skills/YunxiaoPM/docs-四角色泳道流程图.md
Normal file
181
.claude/skills/YunxiaoPM/docs-四角色泳道流程图.md
Normal file
@@ -0,0 +1,181 @@
|
||||
# 四角色泳道图:产品 · 开发 · 测试 · 发版
|
||||
|
||||
> 用途:一张图看清从建需求到发版关闭,四条泳道各自做什么、在哪交棒。
|
||||
> 依据:产品部流转 · 开发侧草案 · 生产线手册(测试/发版)
|
||||
> 日期:2026-07-24
|
||||
> 说明:开发泳道含「AI 用例门禁」草案;测试泳道为正式提测后的人/计划执行。
|
||||
|
||||
---
|
||||
|
||||
## 1. 泳道总览(推荐主图)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph PM["泳道① 产品"]
|
||||
direction TB
|
||||
P0["建需求 · 待处理<br/>只写 AutoRDO"]
|
||||
P1["受理确认 · 已确认"]
|
||||
P2["分析中<br/>建【交付】+【分析】"]
|
||||
P3["设计中<br/>建【设计】"]
|
||||
P4["设计完成<br/>AutoPRD 灌交付"]
|
||||
P5["交棒 · 待开发<br/>交付负责人→何斐"]
|
||||
P6["业务验收后<br/>关闭需求+交付"]
|
||||
P0 --> P1 --> P2 --> P3 --> P4 --> P5
|
||||
end
|
||||
|
||||
subgraph DEV["泳道② 开发"]
|
||||
direction TB
|
||||
D1["何斐:分配任务<br/>建【开发】SUB→交付"]
|
||||
D2["开发人:开始开发<br/>任务处理中 · 需求开发中"]
|
||||
D3["写代码 / 提 MR"]
|
||||
D4["AI 按同标题测试计划自测<br/>不过→提缺陷并修"]
|
||||
D5["开发任务已完成<br/>需求→开发完成"]
|
||||
D6["完成开发(含自测)<br/>建【测试】· 需求待测试"]
|
||||
D1 --> D2 --> D3 --> D4 --> D5 --> D6
|
||||
end
|
||||
|
||||
subgraph QA["泳道③ 测试"]
|
||||
direction TB
|
||||
T1["接【测试】任务<br/>需求=待测试"]
|
||||
T2["建/挂测试计划与用例<br/>(建议按需求编号分包)"]
|
||||
T3["测试中 · 执行用例"]
|
||||
T4["缺陷回流开发修复"]
|
||||
T5["本需求用例全绿<br/>需求→测试完成"]
|
||||
T1 --> T2 --> T3
|
||||
T3 -.->|失败| T4
|
||||
T4 -.->|再测| T3
|
||||
T3 -->|通过| T5
|
||||
end
|
||||
|
||||
subgraph REL["泳道④ 发版"]
|
||||
direction TB
|
||||
R1["建【发版】任务<br/>挂迭代+范围内需求"]
|
||||
R2["汇总更新说明"]
|
||||
R3["发布中"]
|
||||
R4{"发布结果"}
|
||||
R5["发布完成"]
|
||||
R6["发布失败 · 可重试"]
|
||||
R1 --> R2 --> R3 --> R4
|
||||
R4 -->|成功| R5
|
||||
R4 -->|失败| R6
|
||||
R6 -.-> R3
|
||||
end
|
||||
|
||||
P5 -->|"交棒契约<br/>需求编号+交付编号"| D1
|
||||
D6 -->|"提测<br/>【测试】已挂交付"| T1
|
||||
T5 -->|"交棒发版"| R1
|
||||
R5 -->|"业务可验收"| P6
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 横向时间轴版(谁在哪一阶段动)
|
||||
|
||||
从左到右是需求状态主链;上下是四个角色。空格表示该角色此阶段无主动动作。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph S_PM["产品阶段"]
|
||||
direction TB
|
||||
sp0["待处理→已确认"]
|
||||
sp1["分析/设计/设计完成"]
|
||||
sp2["待开发交棒"]
|
||||
sp3["已关闭"]
|
||||
end
|
||||
|
||||
subgraph S_DEV["开发阶段"]
|
||||
direction TB
|
||||
sd0["拆【开发】"]
|
||||
sd1["开发中"]
|
||||
sd2["AI门禁→开发完成"]
|
||||
sd3["建【测试】→待测试"]
|
||||
end
|
||||
|
||||
subgraph S_QA["测试阶段"]
|
||||
direction TB
|
||||
sq0["待测试"]
|
||||
sq1["测试中"]
|
||||
sq2["测试完成"]
|
||||
end
|
||||
|
||||
subgraph S_REL["发版阶段"]
|
||||
direction TB
|
||||
sr0["建【发版】"]
|
||||
sr1["发布中"]
|
||||
sr2["完成/失败"]
|
||||
end
|
||||
|
||||
sp0 --> sp1 --> sp2
|
||||
sp2 --> sd0 --> sd1 --> sd2 --> sd3
|
||||
sd3 --> sq0 --> sq1 --> sq2
|
||||
sq2 --> sr0 --> sr1 --> sr2
|
||||
sr2 --> sp3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 交接点一览
|
||||
|
||||
| # | 从 → 到 | 交出什么 | 需求状态落点 |
|
||||
|---|---|---|---|
|
||||
| 1 | 产品 → 开发 | 需求编号 +【交付】编号;交付负责人=何斐 | **待开发** |
|
||||
| 2 | 开发 → 测试 | 【测试】任务(子项挂交付);用例计划可先备好 | **待测试** |
|
||||
| 3 | 测试 → 发版 | 本需求用例全绿;范围内需求清单 | **测试完成** |
|
||||
| 4 | 发版 → 产品 | 发布完成证据 / 更新说明 | **发布完成** → 产品关单 **已关闭** |
|
||||
|
||||
---
|
||||
|
||||
## 4. 各泳道职责边界(一句话)
|
||||
|
||||
| 角色 | 管什么 | 不管什么 |
|
||||
|---|---|---|
|
||||
| **产品** | 建需求、【交付】【分析】【设计】、AutoPRD、交棒待开发、验收关闭 | 不建【开发】【测试】【发版】、不开分支 |
|
||||
| **开发** | 拆【开发】、写码、AI 自测门禁、建【测试】提测 | 不改写产品 AutoRDO;不建第二套【交付】 |
|
||||
| **测试** | 接【测试】、跑计划用例、缺陷回流、判测试完成 | 不擅自改需求状态到开发中;不建【发版】(可催) |
|
||||
| **发版** | 建【发版】、挂迭代与范围、发布与回写成败 | 不替代测试判绿;不关闭需求(交给产品) |
|
||||
|
||||
---
|
||||
|
||||
## 5. 云效工作项树(跨角色)
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
REQ["需求 · 状态=阶段真相"]
|
||||
DEL["【交付】ASSOCIATED→需求"]
|
||||
AN["【分析】"]
|
||||
DE["【设计】"]
|
||||
DV["【开发】"]
|
||||
TE["【测试】"]
|
||||
RE["【发版】ASSOCIATED→迭代/需求"]
|
||||
|
||||
DEL --> REQ
|
||||
AN -->|TASK_SUB| DEL
|
||||
DE -->|TASK_SUB| DEL
|
||||
DV -->|TASK_SUB| DEL
|
||||
TE -->|TASK_SUB| DEL
|
||||
DV -.->|ASSOCIATED| REQ
|
||||
TE -.->|ASSOCIATED| REQ
|
||||
RE -.->|关联范围| REQ
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 典型负责人(手册约定,可按项目改)
|
||||
|
||||
| 泳道 | 典型对接人 |
|
||||
|---|---|
|
||||
| 产品 | 王冕 · YunxiaoPMapp / AI |
|
||||
| 开发 | 何斐(拆任务)→ 王雨昊 / 李振等(执行) |
|
||||
| 测试 | 谢佳伟 |
|
||||
| 发版 | 时生亮 |
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档 | 说明 |
|
||||
|---|---|
|
||||
| `docs-全流程分步说明.md` | **整条链路逐步说明(推荐先读)** |
|
||||
| `docs-产品部流转流程图.md` | 产品 0→5 细图 |
|
||||
| `docs-开发侧流转草案.md` | 开发 D1–D5 细图 |
|
||||
| `yunxiao-pipeline-handbook` | 全链路配置与规则 |
|
||||
230
.claude/skills/YunxiaoPM/docs-开发侧流转草案.md
Normal file
230
.claude/skills/YunxiaoPM/docs-开发侧流转草案.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# 开发侧流转草案(产品交棒之后)
|
||||
|
||||
> 来源:产品经理口述规则整理(2026-07-24)
|
||||
> 上游终点:产品部 `docs-产品部流转流程图.md` → 需求=**待开发**,【交付】负责人=**何斐**
|
||||
> 本文件目标:把「何斐拆任务 → 开发执行 → AI 用例门禁 → 提测」画清,供后续 **YunxiaoDevapp** Skill 定稿
|
||||
> 状态:**草案**(文末有待拍板项)
|
||||
|
||||
---
|
||||
|
||||
## 1. 总览流程图
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
subgraph handoff [产品已交棒]
|
||||
Ready["需求=待开发\n【交付】负责人=何斐"]
|
||||
end
|
||||
|
||||
subgraph d1 [D1 分配开发]
|
||||
Assign["何斐:分配任务 + 交付任务编号"]
|
||||
CreateDev["自动建【开发】+需求标题\n描述人工补\n指定开发人\n写计划时间"]
|
||||
Link["子项 TASK_SUB→交付\n关联项 ASSOCIATED→需求"]
|
||||
StReady["需求保持/确认为 待开发"]
|
||||
end
|
||||
|
||||
subgraph d2 [D2 开始开发]
|
||||
Start["开发人:开始开发 + 开发任务编号"]
|
||||
DevDoing["开发任务→处理中"]
|
||||
ReqDoing["需求→开发中"]
|
||||
end
|
||||
|
||||
subgraph d3 [D3 AI 用例门禁]
|
||||
CodeDone["代码开发完成触发"]
|
||||
FindPlan["按需求同标题找测试计划"]
|
||||
AITest["AI 按用例自测"]
|
||||
Fail["未通过→自动提缺陷\n标题含开发任务编号\nAI 写标题描述"]
|
||||
Fix["自动修复→再测"]
|
||||
PassGate["用例全部通过"]
|
||||
end
|
||||
|
||||
subgraph d4 [D4 开发任务完成]
|
||||
DevDone["该条【开发】→已完成"]
|
||||
ReqDevDone["需求→开发完成"]
|
||||
end
|
||||
|
||||
subgraph d5 [D5 完成开发·提测]
|
||||
Finish["开发人:完成开发\n含自测完成"]
|
||||
CreateTest["自动建【测试】+任务名\n子项→交付"]
|
||||
PendingTest["需求→待测试"]
|
||||
end
|
||||
|
||||
Ready --> Assign --> CreateDev --> Link --> StReady
|
||||
StReady --> Start --> DevDoing --> ReqDoing
|
||||
ReqDoing --> CodeDone --> FindPlan --> AITest
|
||||
AITest -->|未通过| Fail --> Fix --> AITest
|
||||
AITest -->|全通过| PassGate --> DevDone --> ReqDevDone
|
||||
ReqDevDone --> Finish --> CreateTest --> PendingTest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 分步说明(按你的 5 条)
|
||||
|
||||
### D1|何斐分配任务
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 触发 | Skill 口令:`分配任务` + **交付任务编号** |
|
||||
| 新建 | 【开发】任务;标题 = `【开发】` + **需求标题** |
|
||||
| 描述 | **人工添加**(Skill 不代写正文,或只留空模板) |
|
||||
| 负责人 | 口令指定开发人员 |
|
||||
| 计划时间 | **自动创建**计划开始/计划完成(算法待定:阶段日历工时 or 人力预估,见待拍板) |
|
||||
| 关联 | **子项** `TASK_SUB` →【交付】;**关联项** `ASSOCIATED` → 需求 |
|
||||
| 需求状态 | 改为 / 确认为 **待开发**(见待拍板:与产品交棒是否重复) |
|
||||
|
||||
### D2|开始开发
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 触发 | `开始开发` + **开发任务编号** |
|
||||
| 开发任务 | → **处理中** |
|
||||
| 需求 | → **开发中**(有 ≥1 条开发进入处理中即可,或本条触发即改,见待拍板) |
|
||||
|
||||
### D3|代码完成后 · AI 按测试计划自测
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 触发 | 「代码开发完成」(MR 合并 / 口令 / Webhook,触发源待定) |
|
||||
| 找计划 | **自动寻找与需求同标题的测试计划** |
|
||||
| 执行 | AI 按该计划中用例自行测试 |
|
||||
| 失败 | 自动提缺陷:标题含 **开发任务编号** + AI 生成标题/描述;再自动修复,循环直到用例全过 |
|
||||
|
||||
### D4|用例全通过
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 该条【开发】 | → **已完成** |
|
||||
| 需求 | → **开发完成** |
|
||||
|
||||
### D5|开发人「完成开发」(自测也完成)
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 触发 | `完成开发` +(建议带)开发任务编号 |
|
||||
| 新建 | 【测试】任务;标题 = `【测试】` + 任务名称(建议=需求标题) |
|
||||
| 关联 | 子项 →【交付】(建议同时 ASSOCIATED→需求) |
|
||||
| 需求 | → **待测试** |
|
||||
|
||||
---
|
||||
|
||||
## 3. 与产品部的衔接
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
PM["产品 YunxiaoPMapp\n待开发 + 交付→何斐"] --> DevSkill["开发 Skill 草案\n本文 D1–D5"]
|
||||
DevSkill --> QA["测试执行人/Skill\n待测试之后"]
|
||||
```
|
||||
|
||||
| 上游已具备 | 开发侧依赖 |
|
||||
|---|---|
|
||||
| 需求编号 +【交付】编号 | D1 入口只认编号,不按标题拆第二套交付 |
|
||||
| 交付 ASSOCIATED→需求 | D1 开发任务再 ASSOCIATED→需求、SUB→交付 |
|
||||
| 产品不建【开发】【测试】 | 仅开发 Skill 建这两类 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 待拍板(建议你先定这几条)
|
||||
|
||||
### 4.1 状态顺序是否自洽
|
||||
|
||||
你现在的顺序是:
|
||||
|
||||
```text
|
||||
D3 AI 用例全过 → 开发任务已完成 + 需求「开发完成」
|
||||
D5 完成开发 → 建【测试】+ 需求「待测试」
|
||||
```
|
||||
|
||||
与现有手册常见顺序对比:
|
||||
|
||||
```text
|
||||
手册常见:开发人完成自测 → 待测试 →(测试执行)→ 测试完成
|
||||
你草案: AI 先跑测试计划用例过关 → 开发完成 → 人再点完成开发 → 待测试
|
||||
```
|
||||
|
||||
需要确认:
|
||||
1. D3 的「测试计划」是 **开发自测门禁**,正式【测试】任务仍留给谢佳伟?
|
||||
2. 还是 D3 已等同正式测试,那 D5 再建【测试】是否重复?
|
||||
|
||||
### 4.2 「需求→待开发」写在 D1
|
||||
|
||||
产品交棒时需求**已经是待开发**。D1 再改一次通常多余。建议改为:
|
||||
- D1 **校验**需求已是待开发;若不是则停下或只允许从设计完成快轨补交棒
|
||||
- 有开发任务进入处理中时,需求才变为 **开发中**(即你的 D2)
|
||||
|
||||
### 4.3 「与需求同标题的测试计划」
|
||||
|
||||
标题易改、易撞名。更稳的是:
|
||||
- 测试计划关联 **需求编号 / 迭代**,或
|
||||
- 用例包命名 `CP-{需求编号}-…`
|
||||
|
||||
否则同名需求/改标题会导致找错计划。
|
||||
|
||||
### 4.4 AI 自动提缺陷并自动修复
|
||||
|
||||
风险高:无上限循环、误报、改回旧逻辑。建议门禁:
|
||||
- 最多 N 轮;失败转人工
|
||||
- 缺陷必须关联 **开发任务编号 + 需求编号 + 用例 ID**
|
||||
- 与「防回归」清单联动,禁止静默改无关模块
|
||||
|
||||
### 4.5 多条【开发】时需求状态
|
||||
|
||||
| 场景 | 建议 |
|
||||
|---|---|
|
||||
| 任一条开始开发 | 需求 → 开发中 |
|
||||
| 需求 → 开发完成 | **全部**未取消【开发】均为已完成(与手册 Y21 一致) |
|
||||
| 需求 → 待测试 | 全部【开发】完成后,由末位「完成开发」或系统自动建【测试】 |
|
||||
|
||||
你原文 D4 写「该条」通过就把需求改成开发完成——多开发人并行时会过早。建议改成「全员开发任务完成」。
|
||||
|
||||
### 4.6 关联写法(与产品侧一致)
|
||||
|
||||
同一 create 只能带一条 `createWorkitemRelationInfo`:
|
||||
- 优先保证【开发】/【测试】**子项挂在交付**(交付详情「子项」可见)
|
||||
- ASSOCIATED→需求若同 create 互斥,需约定:先 SUB,再补 ASSOCIATED,或 OpenAPI 双挂
|
||||
|
||||
---
|
||||
|
||||
## 5. 建议口令面(草案)
|
||||
|
||||
```text
|
||||
分配任务:交付任务=ONEOS-a;开发人=张三,李四;计划开始=…;计划完成=…
|
||||
开始开发:开发任务=ONEOS-d
|
||||
代码完成:开发任务=ONEOS-d → 触发 AI 用例门禁(或 Webhook)
|
||||
完成开发:开发任务=ONEOS-d → 建【测试】;需求→待测试
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 推荐定稿后的状态主链(供你勾选)
|
||||
|
||||
**方案 A(贴近你原文,AI 测作开发门禁)**
|
||||
|
||||
```text
|
||||
待开发 → 开发中 →(AI 用例门禁)→ 开发完成 → 待测试 → 测试中 → 测试完成 → …
|
||||
```
|
||||
|
||||
**方案 B(贴近现有生产线手册)**
|
||||
|
||||
```text
|
||||
待开发 → 开发中 → 开发完成(人自测+MR)→ 待测试 → 测试中(人/AI 跑计划)→ 测试完成 → …
|
||||
```
|
||||
|
||||
AI 跑测试计划放在 **待测试之后**,与【测试】任务负责人(如谢佳伟)对齐。
|
||||
|
||||
---
|
||||
|
||||
## 7. 下一步
|
||||
|
||||
1. 你拍板:**方案 A 或 B**,以及多【开发】时「开发完成 / 待测试」的聚合规则
|
||||
2. 我按定稿输出 `YunxiaoDevapp` 的 `SKILL.md` 骨架 + 与产品交棒契约对齐段落
|
||||
3. 再补一版「仅开发侧」验收清单(10 条以内)
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
| 文档 | 路径 |
|
||||
|---|---|
|
||||
| 产品部流转 | `docs-产品部流转流程图.md` |
|
||||
| 产品↔开发契约 | `docs-YunxiaoPMapp-实现原理-开发Skill对接.md` |
|
||||
| 全链路手册 | `src/prototypes/yunxiao-pipeline-handbook/` |
|
||||
47
.claude/skills/YunxiaoPM/fast-track.md
Normal file
47
.claude/skills/YunxiaoPM/fast-track.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 无单快轨到待开发
|
||||
|
||||
适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。
|
||||
|
||||
## 动作表
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 |
|
||||
| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 |
|
||||
| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 |
|
||||
| 【分析】 | **禁止创建** |
|
||||
|
||||
## 交付描述双路径(A1)
|
||||
|
||||
| 口令/材料 | 【交付】描述 |
|
||||
|---|---|
|
||||
| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) |
|
||||
| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 |
|
||||
| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 |
|
||||
|
||||
有手工或原型时**禁止**交棒占位。
|
||||
|
||||
## AutoPRD
|
||||
|
||||
- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。
|
||||
- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。
|
||||
|
||||
## 查重
|
||||
|
||||
只认任务编号。已有分析单不得因「快轨」被删除。
|
||||
|
||||
## 回报必含
|
||||
|
||||
- 需求 / 交付 / 设计编号
|
||||
- 「快轨:未建分析;设计已同步需求描述并当日收口」
|
||||
- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红)
|
||||
- 需求预计工时=2、实际工时=2
|
||||
- 设计关联项含需求编号;交付子项含设计编号
|
||||
|
||||
## 与标准 / 编号直推对比
|
||||
|
||||
```text
|
||||
标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒
|
||||
快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2)
|
||||
编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单)
|
||||
```
|
||||
242
.claude/skills/YunxiaoPM/live-api.md
Normal file
242
.claude/skills/YunxiaoPM/live-api.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 云效实写 API(YunxiaoPMapp 已验证)
|
||||
|
||||
`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。
|
||||
|
||||
本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。
|
||||
|
||||
## 认证
|
||||
|
||||
- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage)
|
||||
- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后)
|
||||
- `Origin` / `Referer`:`https://devops.aliyun.com`
|
||||
|
||||
## 建单
|
||||
|
||||
`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8`
|
||||
|
||||
创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。
|
||||
|
||||
## 改负责人(已通)
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8
|
||||
{"propertyKey":"assignedTo","propertyValue":"<userId>","operateType":"COVER"}
|
||||
```
|
||||
|
||||
交棒:【交付】`propertyValue` = 何斐 ID。
|
||||
|
||||
## 打标签(已通)
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8
|
||||
{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"<tagId>[,<tagId>]","operateType":"COVER"}
|
||||
```
|
||||
|
||||
## 改状态(已通 · 唯一推荐)
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8
|
||||
{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"}
|
||||
```
|
||||
|
||||
成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。
|
||||
|
||||
### 需求状态 ID(本项目)
|
||||
|
||||
| 显示名 | identifier |
|
||||
|---|---|
|
||||
| 待处理 | `100005` |
|
||||
| 已确认 | `32` |
|
||||
| 分析中 | `154395` |
|
||||
| 设计中 | `156603` |
|
||||
| 设计完成 | `307012` |
|
||||
| 待开发 | `1582fc929d429111b925309493` |
|
||||
|
||||
### 任务状态 ID
|
||||
|
||||
| 显示名 | identifier |
|
||||
|---|---|
|
||||
| 待处理 | `100005` |
|
||||
| 已完成 | `100014` |
|
||||
|
||||
### 极速交棒跳转(工作流允许)
|
||||
|
||||
从「待处理」菜单可见直达「设计完成」;推荐最少跳:
|
||||
|
||||
```text
|
||||
待处理 → 设计完成 → 待开发
|
||||
```
|
||||
|
||||
标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。
|
||||
|
||||
### 禁止(已证伪)
|
||||
|
||||
| 写法 | 结果 |
|
||||
|---|---|
|
||||
| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` |
|
||||
| `PATCH …/{id}` + `propertyKey=status` | `property not found` |
|
||||
| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 |
|
||||
|
||||
仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。
|
||||
|
||||
## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订)
|
||||
|
||||
**通用字段写入(已通):**
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}]
|
||||
```
|
||||
|
||||
| 字段 | fieldIdentifier | value |
|
||||
|---|---|---|
|
||||
| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 |
|
||||
| 计划完成 | `80` | 同上 |
|
||||
| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 |
|
||||
| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 |
|
||||
|
||||
**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记:
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8
|
||||
{"workitemIdentifier":"<id>","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"<userId>","forCreate":false,"containsRestDay":false}
|
||||
```
|
||||
|
||||
删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}`
|
||||
列表:`GET …/time/estimate/list?workitemIdentifier=`
|
||||
|
||||
旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。
|
||||
|
||||
## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先)
|
||||
|
||||
### 关联项(【交付】强制 · ASSOCIATED)
|
||||
|
||||
任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"createWorkitemRelationInfo": {
|
||||
"relatedWorkitemIdentifier": "<需求id>",
|
||||
"relatedToRelationIdentifier": "ASSOCIATED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
校验:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true
|
||||
```
|
||||
|
||||
`result` 含该需求即通过。
|
||||
|
||||
### 禁止(关联项)
|
||||
|
||||
| 写法 | 结果 |
|
||||
|---|---|
|
||||
| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** |
|
||||
| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) |
|
||||
| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 |
|
||||
| `createWorkitemRelationList` | 不落 ASSOCIATED |
|
||||
|
||||
### 子项(【分析】/【设计】强制 · TASK_SUB)
|
||||
|
||||
「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**:
|
||||
|
||||
```json
|
||||
{
|
||||
"parent": "<交付id>",
|
||||
"parentIdentifier": "<交付id>",
|
||||
"createWorkitemRelationInfo": {
|
||||
"relatedWorkitemIdentifier": "<交付id>",
|
||||
"relatedToRelationIdentifier": "TASK_SUB"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
同一 create 只能带一条 `createWorkitemRelationInfo`。
|
||||
`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。
|
||||
|
||||
**产品优先级:交付「子项」tab > 阶段任务「关联项」。**
|
||||
交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。
|
||||
|
||||
**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8
|
||||
{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"}
|
||||
```
|
||||
|
||||
**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。
|
||||
**可行路径:**
|
||||
|
||||
| 目标 | 做法 |
|
||||
|---|---|
|
||||
| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` |
|
||||
| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) |
|
||||
| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 |
|
||||
|
||||
产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。
|
||||
|
||||
校验子项:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true
|
||||
```
|
||||
|
||||
结果须含对应分析/设计 identifier。
|
||||
|
||||
校验设计关联项:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true
|
||||
```
|
||||
|
||||
`result` 须含原始需求 identifier。
|
||||
|
||||
### 无单快轨字段默认(2026-07-27)
|
||||
|
||||
| 对象 | 规则 |
|
||||
|---|---|
|
||||
| 【设计】描述 | 复制需求 document HTML |
|
||||
| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) |
|
||||
| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 |
|
||||
| 【交付】`79` | 创建当日;不写 `80` |
|
||||
| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` |
|
||||
| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) |
|
||||
| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` |
|
||||
| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"<html>","formatType":"RICHTEXT"}` |
|
||||
|
||||
## 迭代挂接(已通 · 2026-07-27 · 只挂交付)
|
||||
|
||||
创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。
|
||||
|
||||
挂【交付】到迭代:
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8
|
||||
{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"}
|
||||
```
|
||||
|
||||
清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。
|
||||
|
||||
**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败):
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8
|
||||
→ result.sprint[].identifier / name
|
||||
```
|
||||
|
||||
产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。
|
||||
|
||||
### 极速建单注意
|
||||
|
||||
1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。
|
||||
2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。
|
||||
3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。
|
||||
4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。
|
||||
5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。
|
||||
6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。
|
||||
7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。
|
||||
660
.claude/skills/YunxiaoPM/live_create_fast.py
Normal file
660
.claude/skills/YunxiaoPM/live_create_fast.py
Normal file
@@ -0,0 +1,660 @@
|
||||
#!/usr/bin/env python3
|
||||
"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.parse
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import browser_cookie3
|
||||
except ImportError: # pragma: no cover
|
||||
browser_cookie3 = None
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text())
|
||||
STATUS = RUNTIME["status"]["req"]
|
||||
TASK_STATUS = RUNTIME["status"]["task"]
|
||||
FAST = RUNTIME.get("fields", {}).get("fast_track") or {}
|
||||
|
||||
SPACE = (
|
||||
RUNTIME.get("project", {}).get("spaceIdentifier")
|
||||
or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier")
|
||||
)
|
||||
if not SPACE:
|
||||
raise RuntimeError(
|
||||
"未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected"
|
||||
)
|
||||
REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"]
|
||||
TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"]
|
||||
HEFEI = RUNTIME["people"]["hefei"]["identifier"]
|
||||
WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9")
|
||||
PRI = RUNTIME["priority"]["中"]
|
||||
TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a")
|
||||
PLACEHOLDER = RUNTIME["delivery_placeholder"]
|
||||
PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html"
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
TODAY = datetime.now(TZ).strftime("%Y-%m-%d")
|
||||
NOON = f"{TODAY} 12:00:00"
|
||||
EOD = f"{TODAY} 23:59:59"
|
||||
NOON_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
START_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
END_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
FAST_EST = int(FAST.get("req_estimated_hours", 2))
|
||||
FAST_ACT = int(FAST.get("req_actual_hours", 2))
|
||||
|
||||
PENDING = STATUS["待处理"]
|
||||
DESIGN_DONE = STATUS["设计完成"]
|
||||
PENDING_DEV = STATUS["待开发"]
|
||||
TASK_DONE = TASK_STATUS["已完成"]
|
||||
|
||||
_COOKIE = ""
|
||||
_XSRF = ""
|
||||
|
||||
|
||||
def load_auth() -> None:
|
||||
global _COOKIE, _XSRF
|
||||
jar: dict[str, str] = {}
|
||||
if browser_cookie3:
|
||||
for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"):
|
||||
try:
|
||||
for c in browser_cookie3.chrome(domain_name=domain):
|
||||
jar[c.name] = c.value
|
||||
except Exception:
|
||||
pass
|
||||
if not jar:
|
||||
p = Path("/tmp/yunxiao_cookies.json")
|
||||
if p.exists():
|
||||
raw = json.loads(p.read_text())
|
||||
jar = (
|
||||
raw
|
||||
if isinstance(raw, dict) and "XSRF-TOKEN" in raw
|
||||
else {c["name"]: c["value"] for c in raw.get("cookies", [])}
|
||||
)
|
||||
_COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items())
|
||||
x = jar.get("XSRF-TOKEN", "")
|
||||
_XSRF = urllib.parse.unquote(x) if "%" in x else x
|
||||
if not _XSRF:
|
||||
raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com")
|
||||
|
||||
|
||||
def session() -> requests.Session:
|
||||
s = requests.Session()
|
||||
s.headers.update(
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"Cookie": _COOKIE,
|
||||
"x-xsrf-token": _XSRF,
|
||||
"X-XSRF-TOKEN": _XSRF,
|
||||
"Origin": "https://devops.aliyun.com",
|
||||
"Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req",
|
||||
"accept": "application/json",
|
||||
"User-Agent": "YunxiaoPM-live_create_fast/5.0",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict:
|
||||
r = s.request(method, url, json=body, timeout=60)
|
||||
try:
|
||||
return r.json()
|
||||
except Exception:
|
||||
r.raise_for_status()
|
||||
raise
|
||||
|
||||
|
||||
def create(s: requests.Session, payload: dict) -> dict:
|
||||
url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8"
|
||||
j = api(s, "POST", url, payload)
|
||||
r = j.get("result") or {}
|
||||
if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"):
|
||||
return r
|
||||
j = api(s, "PUT", url, payload)
|
||||
r = j.get("result") or {}
|
||||
if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"):
|
||||
return r
|
||||
raise RuntimeError(f"create failed: {j}")
|
||||
|
||||
|
||||
def get(s: requests.Session, wid: str) -> dict:
|
||||
return api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8",
|
||||
)["result"]
|
||||
|
||||
|
||||
def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"PATCH",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"propertyKey": "tag",
|
||||
"propertyValue": tag_id,
|
||||
"operateType": "COVER",
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"tag failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_document(s: requests.Session, wid: str, html: str) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"PATCH",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8",
|
||||
{"content": html, "formatType": "RICHTEXT"},
|
||||
)
|
||||
if j.get("code") != 200 or j.get("errorMsg"):
|
||||
raise RuntimeError(f"document failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None:
|
||||
data = urllib.parse.urlencode(
|
||||
{
|
||||
"fieldValueList": json.dumps(
|
||||
[{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False
|
||||
)
|
||||
}
|
||||
)
|
||||
r = s.post(
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=60,
|
||||
)
|
||||
j = r.json()
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"field/value failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None:
|
||||
est = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}",
|
||||
).get("result") or []
|
||||
for row in est:
|
||||
eid = row.get("identifier")
|
||||
if eid:
|
||||
api(
|
||||
s,
|
||||
"DELETE",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}",
|
||||
)
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"spentTime": hours,
|
||||
"type": "develop",
|
||||
"description": "快轨默认预计工时",
|
||||
"recordUserIdentifier": user,
|
||||
"forCreate": False,
|
||||
"containsRestDay": False,
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"estimate failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"actualTime": hours,
|
||||
"type": "develop",
|
||||
"description": "快轨默认实际工时",
|
||||
"recordUserIdentifier": user,
|
||||
"gmtStart": START_MS,
|
||||
"gmtEnd": END_MS,
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200 or not j.get("result"):
|
||||
raise RuntimeError(f"actual time failed {wid}: {j}")
|
||||
|
||||
|
||||
def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool:
|
||||
"""建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。"""
|
||||
bodies = [
|
||||
{"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id},
|
||||
{
|
||||
"relationIdentifier": "ASSOCIATED",
|
||||
"fromWorkitemIdentifier": stage_id,
|
||||
"toWorkitemIdentifier": req_id,
|
||||
},
|
||||
]
|
||||
for body in bodies:
|
||||
for url in (
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8",
|
||||
):
|
||||
j = api(s, "POST", url, body)
|
||||
if j.get("code") == 200 and j.get("result") not in (None, False):
|
||||
if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)):
|
||||
rows = list_associated(s, stage_id)
|
||||
if req_id in {r.get("identifier") for r in rows}:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None:
|
||||
if from_status == to_status:
|
||||
return
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8",
|
||||
{"fromStatus": from_status, "toStatus": to_status},
|
||||
)
|
||||
if not (j.get("code") == 200 and j.get("result") is True):
|
||||
raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}")
|
||||
|
||||
|
||||
def md_to_html(md: str) -> str:
|
||||
parts = []
|
||||
for line in md.splitlines():
|
||||
if line.startswith("## "):
|
||||
parts.append(f"<h2>{line[3:]}</h2>")
|
||||
elif line.startswith("### "):
|
||||
parts.append(f"<h3>{line[4:]}</h3>")
|
||||
elif line.startswith("- "):
|
||||
parts.append(f"<p>• {line[2:]}</p>")
|
||||
elif line.strip():
|
||||
parts.append(f"<p>{line}</p>")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def req_document_html(s: requests.Session, rid: str) -> str:
|
||||
w = get(s, rid)
|
||||
return ((w.get("document") or {}).get("content") or w.get("description") or "").strip()
|
||||
|
||||
|
||||
def req_payload(subject: str, html: str) -> dict:
|
||||
return {
|
||||
"subject": subject,
|
||||
"description": html,
|
||||
"formatType": "RICHTEXT",
|
||||
"document": {"content": html, "formatType": "RICHTEXT"},
|
||||
"spaceIdentifier": SPACE,
|
||||
"space": SPACE,
|
||||
"spaceType": "Project",
|
||||
"workitemTypeIdentifier": REQ_TYPE,
|
||||
"workitemType": REQ_TYPE,
|
||||
"categoryIdentifier": "Req",
|
||||
"category": "Req",
|
||||
"assignedTo": WANG,
|
||||
"fieldValueList": [
|
||||
{"fieldIdentifier": "priority", "value": PRI},
|
||||
{"fieldIdentifier": "assignedTo", "value": WANG},
|
||||
],
|
||||
"attachmentIdList": [],
|
||||
"cloneFrom": None,
|
||||
"createWorkitemRelationList": [],
|
||||
}
|
||||
|
||||
|
||||
def task_payload(
|
||||
subject: str,
|
||||
html: str,
|
||||
assignee: str,
|
||||
*,
|
||||
plan_start: bool = True,
|
||||
associated_req: str | None = None,
|
||||
parent_delivery: str | None = None,
|
||||
) -> dict:
|
||||
fvl = [
|
||||
{"fieldIdentifier": "priority", "value": PRI},
|
||||
{"fieldIdentifier": "assignedTo", "value": assignee},
|
||||
]
|
||||
if plan_start:
|
||||
fvl.append({"fieldIdentifier": "79", "value": NOON_MS})
|
||||
payload: dict[str, Any] = {
|
||||
"subject": subject,
|
||||
"description": html,
|
||||
"formatType": "RICHTEXT",
|
||||
"spaceIdentifier": SPACE,
|
||||
"space": SPACE,
|
||||
"spaceType": "Project",
|
||||
"workitemTypeIdentifier": TASK_TYPE,
|
||||
"workitemType": TASK_TYPE,
|
||||
"categoryIdentifier": "Task",
|
||||
"category": "Task",
|
||||
"assignedTo": assignee,
|
||||
"fieldValueList": fvl,
|
||||
"attachmentIdList": [],
|
||||
"cloneFrom": None,
|
||||
}
|
||||
if parent_delivery:
|
||||
payload["parent"] = parent_delivery
|
||||
payload["parentIdentifier"] = parent_delivery
|
||||
payload["createWorkitemRelationInfo"] = {
|
||||
"relatedWorkitemIdentifier": parent_delivery,
|
||||
"relatedToRelationIdentifier": "TASK_SUB",
|
||||
}
|
||||
else:
|
||||
if not associated_req:
|
||||
raise ValueError("associated_req required for delivery: ASSOCIATED→需求")
|
||||
payload["createWorkitemRelationInfo"] = {
|
||||
"relatedWorkitemIdentifier": associated_req,
|
||||
"relatedToRelationIdentifier": "ASSOCIATED",
|
||||
}
|
||||
return payload
|
||||
|
||||
|
||||
def list_associated(s: requests.Session, wid: str) -> list[dict]:
|
||||
j = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true",
|
||||
)
|
||||
return j.get("result") or []
|
||||
|
||||
|
||||
def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None:
|
||||
rows = list_associated(s, wid)
|
||||
ids = {r.get("identifier") for r in rows}
|
||||
if req_id not in ids:
|
||||
raise RuntimeError(
|
||||
f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}"
|
||||
)
|
||||
|
||||
|
||||
AUTO_RDO = """## 原始诉求(AutoRDO)
|
||||
|
||||
运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致
|
||||
|
||||
待确认:
|
||||
- 本期是否含真实短信/邮件通道(现口径一般为演示模板)
|
||||
|
||||
## 工作项编号(系统)
|
||||
- 交付:待建
|
||||
- 分析:待建
|
||||
- 设计:待建
|
||||
"""
|
||||
|
||||
|
||||
def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict:
|
||||
return {
|
||||
"serial": w.get("serialNumber"),
|
||||
"id": w.get("identifier"),
|
||||
"subject": w.get("subject"),
|
||||
"status": status,
|
||||
"assignee": assignee_name,
|
||||
"parent": w.get("parentIdentifier"),
|
||||
}
|
||||
|
||||
|
||||
def build_normal() -> dict:
|
||||
t0 = time.perf_counter()
|
||||
s = session()
|
||||
title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)"
|
||||
req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n")))
|
||||
rid = req["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_r = pool.submit(apply_tag, session(), rid)
|
||||
f_deliv = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【交付】{title}",
|
||||
f"<p>{PLACEHOLDER}</p>",
|
||||
HEFEI,
|
||||
associated_req=rid,
|
||||
),
|
||||
)
|
||||
deliv = f_deliv.result()
|
||||
f_tag_r.result()
|
||||
did = deliv["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=3) as pool:
|
||||
f_tag_d = pool.submit(apply_tag, session(), did)
|
||||
f_ana = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【分析】{title}",
|
||||
"<p>分析阶段:故障处置台账、挂起归档与证据链。</p>",
|
||||
WANG,
|
||||
associated_req=rid,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
f_des = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【设计】{title}",
|
||||
f"<p>设计阶段:对齐原型 {PROTO}</p>",
|
||||
WANG,
|
||||
associated_req=rid,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
ana = f_ana.result()
|
||||
des = f_des.result()
|
||||
f_tag_d.result()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=3) as pool:
|
||||
list(
|
||||
as_completed(
|
||||
[
|
||||
pool.submit(transit, session(), rid, PENDING, DESIGN_DONE),
|
||||
pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE),
|
||||
pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE),
|
||||
]
|
||||
)
|
||||
)
|
||||
transit(s, rid, DESIGN_DONE, PENDING_DEV)
|
||||
|
||||
return {
|
||||
"path": "normal",
|
||||
"elapsed_s": round(time.perf_counter() - t0, 3),
|
||||
"req": summarize_from_create(req, status="待开发", assignee_name="王冕"),
|
||||
"delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"),
|
||||
"analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"),
|
||||
"design": summarize_from_create(des, status="已完成", assignee_name="王冕"),
|
||||
}
|
||||
|
||||
|
||||
def build_fast(
|
||||
*,
|
||||
tag_id: str = TAG_FAULT,
|
||||
has_prototype: bool = True,
|
||||
delivery_html: str | None = None,
|
||||
) -> dict:
|
||||
"""无单快轨。
|
||||
|
||||
- 设计描述 = 需求 document
|
||||
- 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位
|
||||
- 设计 79/80=当日;交付 79=当日
|
||||
- 需求/交付/设计同标签
|
||||
- 需求预计/实际工时各 2
|
||||
- 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求
|
||||
"""
|
||||
t0 = time.perf_counter()
|
||||
s = session()
|
||||
title = "【新增】故障处置(YunxiaoPM快轨·极速v5)"
|
||||
req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n"))
|
||||
req = create(s, req_payload(title, req_html))
|
||||
rid = req["identifier"]
|
||||
# 以落库 document 为准(与手动建需后读需求一致)
|
||||
req_html = req_document_html(s, rid) or req_html
|
||||
|
||||
if delivery_html:
|
||||
deliv_html = delivery_html
|
||||
desc_source = "autoprd"
|
||||
elif req_html.strip():
|
||||
deliv_html = req_html
|
||||
desc_source = "manual_sync"
|
||||
else:
|
||||
deliv_html = f"<p>{PLACEHOLDER}</p>"
|
||||
desc_source = "placeholder"
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_r = pool.submit(apply_tag, session(), rid, tag_id)
|
||||
f_deliv = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【交付】{title}",
|
||||
deliv_html,
|
||||
HEFEI,
|
||||
associated_req=rid,
|
||||
),
|
||||
)
|
||||
deliv = f_deliv.result()
|
||||
f_tag_r.result()
|
||||
did = deliv["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_d = pool.submit(apply_tag, session(), did, tag_id)
|
||||
f_des = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【设计】{title}",
|
||||
req_html,
|
||||
WANG,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
des = f_des.result()
|
||||
f_tag_d.result()
|
||||
des_id = des["identifier"]
|
||||
apply_tag(s, des_id, tag_id)
|
||||
|
||||
# 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固)
|
||||
set_fields(s, des_id, [("79", NOON), ("80", EOD)])
|
||||
set_fields(s, did, [("79", NOON)])
|
||||
|
||||
# 设计关联项补挂需求(可能失败,回报 risk)
|
||||
design_assoc_ok = try_associate_to_req(s, des_id, rid)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
list(
|
||||
as_completed(
|
||||
[
|
||||
pool.submit(transit, session(), rid, PENDING, DESIGN_DONE),
|
||||
pool.submit(transit, session(), des_id, PENDING, TASK_DONE),
|
||||
]
|
||||
)
|
||||
)
|
||||
transit(s, rid, DESIGN_DONE, PENDING_DEV)
|
||||
|
||||
set_estimate_hours(s, rid, FAST_EST)
|
||||
set_actual_hours(s, rid, FAST_ACT)
|
||||
|
||||
risk = None
|
||||
if desc_source == "placeholder":
|
||||
risk = "交付描述仍为占位"
|
||||
if not design_assoc_ok:
|
||||
risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底"
|
||||
|
||||
return {
|
||||
"path": "fast",
|
||||
"elapsed_s": round(time.perf_counter() - t0, 3),
|
||||
"req": summarize_from_create(req, status="待开发", assignee_name="王冕"),
|
||||
"delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"),
|
||||
"design": summarize_from_create(des, status="已完成", assignee_name="王冕"),
|
||||
"analysis": None,
|
||||
"delivery_desc_source": desc_source,
|
||||
"design_associated_ok": design_assoc_ok,
|
||||
"risk": risk,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
auth0 = time.perf_counter()
|
||||
load_auth()
|
||||
auth_s = round(time.perf_counter() - auth0, 3)
|
||||
|
||||
wall0 = time.perf_counter()
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_fast = pool.submit(build_fast)
|
||||
f_normal = pool.submit(build_normal)
|
||||
fast = f_fast.result()
|
||||
normal = f_normal.result()
|
||||
build_s = round(time.perf_counter() - wall0, 3)
|
||||
|
||||
s = session()
|
||||
assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery")
|
||||
assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery")
|
||||
|
||||
def assert_sub(delivery_id: str, child_id: str, label: str) -> None:
|
||||
rows = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true",
|
||||
).get("result") or []
|
||||
ids = {r.get("identifier") for r in rows}
|
||||
if child_id not in ids:
|
||||
raise RuntimeError(
|
||||
f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}"
|
||||
)
|
||||
|
||||
assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis")
|
||||
assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design")
|
||||
assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design")
|
||||
verify = {
|
||||
"normal_req": get(s, normal["req"]["id"])["status"]["displayName"],
|
||||
"fast_req": get(s, fast["req"]["id"])["status"]["displayName"],
|
||||
"normal_delivery_assignee": (
|
||||
get(s, normal["delivery"]["id"]).get("assignedTo") or {}
|
||||
).get("displayName"),
|
||||
"fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get(
|
||||
"displayName"
|
||||
),
|
||||
"delivery_associated_ok": True,
|
||||
"stage_tasks_as_sub_ok": True,
|
||||
"fast_design_associated_ok": fast.get("design_associated_ok"),
|
||||
}
|
||||
out = {
|
||||
"normal": normal,
|
||||
"fast": fast,
|
||||
"verify": verify,
|
||||
"auth_elapsed_s": auth_s,
|
||||
"wall_elapsed_s": build_s,
|
||||
"created_at": datetime.now(TZ).isoformat(),
|
||||
"mode": "live_create_fast_v5_fast_track_rules",
|
||||
"opts": {
|
||||
"create_includes_plan_start": True,
|
||||
"skip_plan_end_on_create": True,
|
||||
"tracked_transit_no_get": True,
|
||||
"delivery_assignee_hefei_at_create": True,
|
||||
"overlap_tag_with_create": True,
|
||||
"fast_req_hours": f"{FAST_EST}+{FAST_ACT}",
|
||||
"relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req",
|
||||
"http": "requests.Session keep-alive",
|
||||
},
|
||||
}
|
||||
Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text(
|
||||
json.dumps(out, ensure_ascii=False, indent=2)
|
||||
)
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
60
.claude/skills/YunxiaoPM/model.md
Normal file
60
.claude/skills/YunxiaoPM/model.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 交付树模型与关联约定
|
||||
|
||||
## 真相源
|
||||
|
||||
```text
|
||||
需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…)
|
||||
【交付】任务 = 该需求的交付容器(每需求最多 1 条)
|
||||
【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项
|
||||
【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包
|
||||
ASSOCIATED = 横向挂钩(【交付】 ↔ 需求)
|
||||
SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计)
|
||||
```
|
||||
|
||||
## 关系验收
|
||||
|
||||
| 关系 | 用在哪里 | 验收 | 优先级 |
|
||||
|---|---|---|---|
|
||||
| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 |
|
||||
| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 |
|
||||
|
||||
建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`):
|
||||
|
||||
| 对象 | `createWorkitemRelationInfo` | 另写字段 |
|
||||
|---|---|---|
|
||||
| 【交付】 | `ASSOCIATED` → 需求 | — |
|
||||
| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 |
|
||||
|
||||
**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。
|
||||
勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。
|
||||
|
||||
## 禁止
|
||||
|
||||
| 禁止 | 说明 |
|
||||
|---|---|
|
||||
| 创建或描述【开发】/【测试】 | 口令与回报均不出现 |
|
||||
| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 |
|
||||
| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 |
|
||||
| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 |
|
||||
| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 |
|
||||
|
||||
## 任务标题(仅展示)
|
||||
|
||||
- 【交付】+ 需求标题
|
||||
- 【分析】+ 需求标题
|
||||
- 【设计】+ 需求标题
|
||||
|
||||
标题可改,**不参与查重**。
|
||||
|
||||
## 任务编号唯一性
|
||||
|
||||
**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。
|
||||
|
||||
| 场景 | 正确 | 错误 |
|
||||
|---|---|---|
|
||||
| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 |
|
||||
| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 |
|
||||
| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 |
|
||||
| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 |
|
||||
|
||||
按编号命中则复用;**计划开始已有值禁止改写**。
|
||||
49
.claude/skills/YunxiaoPM/references/acceptance.md
Normal file
49
.claude/skills/YunxiaoPM/references/acceptance.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 验收清单与回报
|
||||
|
||||
## apply 后必须自检
|
||||
|
||||
| # | 项 | 通过标准 |
|
||||
|---|---|---|
|
||||
| 1 | 需求编号 | 回报含 ONEOS-xx |
|
||||
| 2 | 任务编号 | 交付/分析/设计凡新建或操作均回报编号;禁止只报标题 |
|
||||
| 3 | 编号区块 | 需求「工作项编号(系统)」与实际一致 |
|
||||
| 4 | ASSOCIATED | **仅交付**详情关联项可见需求(`createWorkitemRelationInfo=ASSOCIATED`;禁止 PARENT 冒充) |
|
||||
| 5 | SUB | **必过**:分析/设计 create 用 `TASK_SUB→交付`(含 `parent`+`parentIdentifier`);交付「子项」tab 须可见。与 ASSOCIATED 同 create 互斥,阶段任务关联项可空(见 live-api.md) |
|
||||
| 6 | 计划开始 | 未误覆盖已有值 |
|
||||
| 7 | 阶段日历工时 | 有计划完成时已写入;脚注存在;回报用语正确 |
|
||||
| 8 | 交棒 | 需求=待开发;交付负责人=何斐(交棒场景) |
|
||||
| 9 | 占位风险 | 交付仍占位时首行标红 |
|
||||
| 10 | 负向 | 本轮**无**【开发】/【测试】任务;未按标题查重 |
|
||||
|
||||
设计完成额外:AutoPRD 段已写;交付非占位(除非失败停下);ZIP+截图已挂或已列出缺项。
|
||||
|
||||
迭代额外:只挂【交付】(需求不挂);版本号符合递增规则。
|
||||
|
||||
## 回报模板(精简)
|
||||
|
||||
```text
|
||||
【YunxiaoPM】
|
||||
风险:(若有占位交棒则首行标红)
|
||||
需求:ONEOS-xx | 状态=…
|
||||
交付:ONEOS-a | 负责人=…
|
||||
分析:ONEOS-b | …(无则写无)
|
||||
设计:ONEOS-c | …(无则写无)
|
||||
阶段日历工时:分析 Hh / 设计 Hh(若本轮写入)
|
||||
附件:…(若本轮)
|
||||
迭代:…(若本轮)
|
||||
下一步:请技术经理使用开发 Skill(交棒后)
|
||||
```
|
||||
|
||||
## 适合全自动 vs 人工门禁
|
||||
|
||||
| 适合全自动 | 建议半自动/人工门禁 |
|
||||
|---|---|
|
||||
| 建单、打标签、挂迭代、写描述、建交付树、改状态、交棒负责人、幂等查重 | **PJ 云效项目点选**、受理(已确认)、是否快轨、设计完成是否真可开发、跨需求优先级与迭代容量、回退与取消 |
|
||||
|
||||
## 实写性能(2026-07-23 复盘)
|
||||
|
||||
| 项 | 要求 |
|
||||
|---|---|
|
||||
| 改状态 | 只用 `status/transit`(见 [live-api.md](live-api.md)) |
|
||||
| 改【交付】负责人 | 只用 `PATCH …/{id}` + `propertyKey=assignedTo` |
|
||||
| 极速复测 | `scripts/live_create_fast.py`;默认不开浏览器 |
|
||||
22
.claude/skills/YunxiaoPM/references/commands.md
Normal file
22
.claude/skills/YunxiaoPM/references/commands.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 口令面
|
||||
|
||||
```text
|
||||
记录需求:…;项目=(必选·Plan 点选);优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=暂不推进|已确认|分析中|设计中|设计完成|待开发|待开发(快轨)
|
||||
受理确认:ONEOS-xx
|
||||
开始分析:ONEOS-xx → 【交付】+【分析】并回报编号
|
||||
开始设计:ONEOS-xx;交付任务=…;分析任务=… → 【设计】+收口分析
|
||||
设计完成:ONEOS-xx;设计任务=…;原型=…
|
||||
交棒开发:ONEOS-xx;交付任务=… → 待开发 + 该编号负责人=何斐
|
||||
快轨待开发:ONEOS-xx → 待开发 +【交付】+【设计】(无【分析】)+ 交付负责人=何斐
|
||||
编号直推:分析任务=ONEOS-b / 设计任务=ONEOS-c → 收口未完成计划完成 + 需求待开发 + 交付交棒何斐
|
||||
创建迭代:版本类型=主|副|子;交付任务=ONEOS-a,ONEOS-b,…;名称前缀=…
|
||||
AutoRDO:…(粘贴聊天/附录音)→ 再记录需求
|
||||
```
|
||||
|
||||
版本自动规则:取同前缀下最大 `Vx.y.z`(旧 `Vx.y` 视为 `Vx.y.0`)再按主/副/子递增;迭代名=`{前缀}{新版本}`。
|
||||
|
||||
**PJ 项目**:新建需求/迭代前须点选云效项目(实时列表);禁止默认直指。见 [project-selection.md](project-selection.md)。
|
||||
|
||||
后续推进口令**优先显式带任务编号**;未带则读「工作项编号(系统)」;仍无则询问;**禁止按标题补全**。
|
||||
|
||||
YunxiaoPM 回报止于交棒;可一句「请技术经理使用开发 Skill」。
|
||||
95
.claude/skills/YunxiaoPM/references/compact-select.md
Normal file
95
.claude/skills/YunxiaoPM/references/compact-select.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# 压缩点选(`1a2b3a4d`)
|
||||
|
||||
记录需求 Plan **默认**用编号题 + 字母选项;用户可用一行压缩答复确认。
|
||||
|
||||
## Plan 展示模板
|
||||
|
||||
```text
|
||||
请回复压缩点选,例:1a2b3a4d
|
||||
(数字=题号,字母=选项;大小写不敏感;可写 1A2B3A4D)
|
||||
|
||||
1. 类型
|
||||
A. 新增
|
||||
B. 优化
|
||||
|
||||
2. 项目(云效实时列表;建议项可标★)
|
||||
A. 01_ONEOS(ONEOS)★
|
||||
B. 02_小羚羚APP(XLLAPP)
|
||||
C. …
|
||||
|
||||
3. 优先级
|
||||
A. 紧急
|
||||
B. 高
|
||||
C. 中
|
||||
D. 低
|
||||
|
||||
4. 标签(云效候选;建议项可标★)
|
||||
A. 故障管理★
|
||||
B. 还车应结款
|
||||
C. …
|
||||
```
|
||||
|
||||
可选续题(口令已齐则可写入清单、压缩串可不含):
|
||||
|
||||
```text
|
||||
5. 推进至 …
|
||||
6. 迭代 …
|
||||
```
|
||||
|
||||
## 解析规则
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| 形态 | `(题号)(字母)` 连续拼接,如 `1a2b3a4d` |
|
||||
| 题号 | `1`=类型 `2`=项目 `3`=优先级 `4`=标签;可扩展 `5` `6` |
|
||||
| 字母 | `a`→第 1 项 … `z`→第 26 项;超出选项数 → 非法,停 |
|
||||
| 缺题 | 该题若口令已唯一预填且列表唯一命中,可用预填;否则停并追问缺题 |
|
||||
| 多答同题 | 后写覆盖先写 |
|
||||
| 非法字符 | 停,重贴模板 |
|
||||
|
||||
解析成功后 Plan 回显人话确认一行,例如:
|
||||
|
||||
```text
|
||||
已解析 1a2b3a4d → 类型=新增;项目=02_小羚羚APP;优先级=紧急;标签=(D 项名)
|
||||
确认后回复「执行」
|
||||
```
|
||||
|
||||
用户再回「执行」才 apply(仍守 Plan 门禁)。
|
||||
|
||||
## 与口令预填
|
||||
|
||||
口令已写 `类型:新增;项目:01_ONEOS;优先级:高;标签:故障管理` 时:
|
||||
|
||||
- 对应选项标 ★,并给出**建议压缩串**(如 `1a2a3b4a`),用户可直接改字母或整段重答。
|
||||
- **不得**因有预填而跳过展示字母表;压缩确认(或逐题点选)仍是门禁。
|
||||
|
||||
## 标签未命中 → 自动重拉一次候选并重生选项
|
||||
|
||||
「标签无法对应」:口令标签名在**当前标签候选**中 0 命中(或多命中无法唯一)。
|
||||
|
||||
| 步骤 | 动作 |
|
||||
|---|---|
|
||||
| 1 | **自动重拉一次**标签候选(见下「标签候选来源」;强制刷新,不用旧缓存) |
|
||||
| 2 | 用新列表**重新生成 4.A/B/C…** 选择项 |
|
||||
| 3 | 回报:`已自动重拉标签列表(因无法对应)`;请用户重答 `4x` 或整串 |
|
||||
| 4 | 仍无法对应 → 停;**禁止**第 3 次空转;**禁止**猜 tagId |
|
||||
|
||||
> 说明:此处重拉的是**标签候选列表**,不是项目列表。项目无法对应仍走 [project-selection.md](project-selection.md) 的「自动重拉一次」。
|
||||
|
||||
### 标签候选来源(当前已验证路径)
|
||||
|
||||
1. `runtime-ids.json` → `tags`
|
||||
2. 目标项目(或预填项目)下近期工作项 `tag` 字段聚合去重
|
||||
3. 重拉 = 再请求工作项列表聚合 + 合并 runtime;命中后可回写 `tags` 缓存
|
||||
|
||||
(专用 `tag/list` Cookie API 尚未稳定;有稳定端点后改写入 `live-api.md` 并切换。)
|
||||
|
||||
## 项目列表顺序
|
||||
|
||||
生成 2. 选项时:口令预填/建议项目置 **A** 并标 ★,其余按云效返回顺序接 B/C/…。
|
||||
|
||||
## Agent 义务
|
||||
|
||||
1. 记录需求进 Plan 时必须输出本模板(至少 1–4 题)。
|
||||
2. 收到压缩串先解析回显,再等「执行」。
|
||||
3. 标签/项目无法对应:各自动重拉**一次**并刷新对应题选项。
|
||||
69
.claude/skills/YunxiaoPM/references/description-split.md
Normal file
69
.claude/skills/YunxiaoPM/references/description-split.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# 需求描述 vs 交付描述
|
||||
|
||||
两类描述职责不同;禁止混写;禁止建【交付】时把 AutoPRD 六大块提前塞进交付描述。
|
||||
|
||||
## A. 需求描述 · AutoRDO 清洗
|
||||
|
||||
| 项 | 规则 |
|
||||
|---|---|
|
||||
| 触发 | 对话建需求 / 碎片材料入库 |
|
||||
| 调用 | **必须**使用独立 Skill「**`$AutoRDO`**」(`AutoRDO/SKILL.md`;本 Skill 不内嵌清洗细则) |
|
||||
| 输入 | 聊天记录、录音、口述材料 |
|
||||
| 处理 | 保留原意;书面化;去口头禅;**去除结尾句号**(细则在 AutoRDO `references/rules.md`) |
|
||||
| 输出 | 写入 `## 原始诉求(AutoRDO)` |
|
||||
| 不做 | 本阶段不写 AutoPRD 六大块;不要求已有原型;不覆盖已有「产品说明」 |
|
||||
|
||||
口令:`AutoRDO:…` → 确认后 `记录需求:【新增】标题;描述=整理稿;推进至=…`
|
||||
|
||||
## B. 交付描述
|
||||
|
||||
### B1. 标准路径(分析中起建交付)
|
||||
|
||||
| 时机 | 【交付】描述 |
|
||||
|---|---|
|
||||
| 首次创建起至设计完成前 | 固定文案:`等待设计任务完成后自动填入` |
|
||||
| 设计完成时 | 用 AutoPRD「产品说明」正文替换占位 |
|
||||
|
||||
### B2. 无单快轨建交付(覆盖 B1)
|
||||
|
||||
| 口令/材料 | 【交付】描述 |
|
||||
|---|---|
|
||||
| 手工描述(无指定原型) | **同步需求**当前手工/AutoRDO 正文;禁止占位 |
|
||||
| 指定原型页面 | `$oneos-autoprd` 从原型生成写入;禁止占位 |
|
||||
| 既无正文又无原型 | 才允许占位并标红风险 |
|
||||
|
||||
快轨【设计】描述始终复制需求当前描述正文(见 [fast-track.md](fast-track.md))。
|
||||
|
||||
## C. 需求描述双段模板(不可互相覆盖)
|
||||
|
||||
```markdown
|
||||
## 原始诉求(AutoRDO)
|
||||
(清洗稿;设计完成也不删除)
|
||||
|
||||
## 产品说明(AutoPRD)
|
||||
(六大块+对象存储链接;未设计完成前可无此节或写「待设计完成后填入」)
|
||||
|
||||
## 工作项编号(系统)
|
||||
- 交付:…
|
||||
- 分析:…
|
||||
- 设计:…
|
||||
```
|
||||
|
||||
## D. 设计完成 · AutoPRD + 附件
|
||||
|
||||
前提:设计任务完成且已关联对应原型页。
|
||||
|
||||
1. 调用 **`$oneos-autoprd`(AutoPRD)**,产出并落盘 `.spec/requirements-prd.md` + 标注同步:
|
||||
- 对象存储预览链接:`{baseUrl}/{prototype-id}/index.html`(禁止加 `prototypes/` 前缀、禁止去掉 `index.html`)
|
||||
- 产品说明 Markdown(总览/角色/流程/状态/风险/交付等,见 AutoPRD 模板)
|
||||
2. 写入需求 `## 产品说明(AutoPRD)`;**禁止覆盖** `## 原始诉求(AutoRDO)` / `## 工作项编号(系统)`。
|
||||
3. 【交付】描述:按**交付任务编号**用产品说明正文**替换**占位(细则见 AutoPRD `references/yunxiao-delivery-sync.md`);可附「原始诉求见需求描述」。**创建【交付】时不得提前灌 MD。**
|
||||
4. 附件(需求 +【交付】均挂;失败则不得声称成功):
|
||||
- Make「导出 HTML(含源码)」ZIP → [make-export-attach.md](make-export-attach.md)
|
||||
- Make「复制截图」全交互页 → 同上
|
||||
|
||||
缺原型 / AutoPRD 失败 / 导出或截图失败 → **不得**报到设计完成并宣称附件齐全;停下并列出缺项。
|
||||
|
||||
执行顺序:先 AutoPRD 落盘与附件就绪 → 再改需求/交付描述与设计计划完成 → 最后改需求状态。
|
||||
|
||||
**禁止**加载 `yunxiao-requirement-lifecycle`;阶段任务树只由本 Skill(YunxiaoPMapp)创建。
|
||||
47
.claude/skills/YunxiaoPM/references/fast-track.md
Normal file
47
.claude/skills/YunxiaoPM/references/fast-track.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# 无单快轨到待开发
|
||||
|
||||
适用:口令明确「快轨」或「推进至待开发」且当前需求尚无标准分析路径任务(或用户确认跳过分析)。
|
||||
|
||||
## 动作表
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 需求 | 状态 → **待开发**;**预计工时=2**、**实际工时=2**(覆盖同日 8h 日历工时默认);标签按口令;更新编号区块 |
|
||||
| 【交付】 | 无则新建;ASSOCIATED→需求(勿用 PARENT);负责人→何斐;**计划开始=创建当日**(`79`);**不写**计划完成;**描述双路径**(见下);**标签与需求相同**;更新编号区块 |
|
||||
| 【设计】 | **必须新建**;**TASK_SUB→交付**(交付「子项」须可见);**描述=复制需求当前描述正文**;计划开始/完成均=当日(create 后 `field/value` 写 `79`+`80`);任务→完成态;**标签与需求相同**;建后再尝试 **ASSOCIATED→原始需求**(Cookie 常失败,见 live-api;失败须标红,**子项优先于关联项**);更新编号区块 |
|
||||
| 【分析】 | **禁止创建** |
|
||||
|
||||
## 交付描述双路径(A1)
|
||||
|
||||
| 口令/材料 | 【交付】描述 |
|
||||
|---|---|
|
||||
| **手工描述**(无指定原型) | 同步需求当前手工/AutoRDO 正文(与设计一致可复制 document) |
|
||||
| **指定原型页面** | 调用 `$oneos-autoprd`,从原型生成 AutoPRD「产品说明」写入【交付】 |
|
||||
| 既无手工可同步正文、又无原型 | 才允许占位 `等待设计任务完成后自动填入`,Plan 勾选交棒占位风险,回报首行标红 |
|
||||
|
||||
有手工或原型时**禁止**交棒占位。
|
||||
|
||||
## AutoPRD
|
||||
|
||||
- 快轨默认可不跑 AutoPRD;**例外**:口令指定原型 → 交付走 AutoPRD 路径(上表)。
|
||||
- 口令同时「设计完成 + 原型」时仍按步骤 4 灌需求产品说明段。
|
||||
|
||||
## 查重
|
||||
|
||||
只认任务编号。已有分析单不得因「快轨」被删除。
|
||||
|
||||
## 回报必含
|
||||
|
||||
- 需求 / 交付 / 设计编号
|
||||
- 「快轨:未建分析;设计已同步需求描述并当日收口」
|
||||
- 交付描述来源:手工同步 / AutoPRD / 占位(若占位则首行标红)
|
||||
- 需求预计工时=2、实际工时=2
|
||||
- 设计关联项含需求编号;交付子项含设计编号
|
||||
|
||||
## 与标准 / 编号直推对比
|
||||
|
||||
```text
|
||||
标准:分析中→交付+分析 → 设计中→设计+收口分析 → 设计完成→收口设计 → 待开发交棒
|
||||
快轨(无既有阶段单):→ 待开发:新建交付+设计+交棒何斐(无分析;设计当日收口;需求工时 2+2)
|
||||
编号直推:已有分析/设计编号 → 收口空计划完成 → 待开发+交付交棒(不新建冗余单)
|
||||
```
|
||||
20
.claude/skills/YunxiaoPM/references/handoff-and-rollback.md
Normal file
20
.claude/skills/YunxiaoPM/references/handoff-and-rollback.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# 交棒门禁与回退最小集
|
||||
|
||||
## 交棒门禁(待开发)
|
||||
|
||||
| 情形 | 规则 |
|
||||
|---|---|
|
||||
| 【交付】描述已是 AutoPRD 正式说明(非占位) | 允许交棒;Plan 正常列交付任务编号 + 负责人→何斐 |
|
||||
| 【交付】描述仍为 `等待设计任务完成后自动填入` | **仍允许交棒**,但 Plan **必须**勾选风险项「交付描述仍为占位,技术侧仅可凭需求 AutoRDO 稿理解」;回报**首行标红**该风险;**禁止**静默交棒假装材料齐全 |
|
||||
| 口令同时带原型且要求设计完成 | 先走步骤 4(AutoPRD+附件)成功,再交棒,不再勾占位风险 |
|
||||
|
||||
## 回退 / 变更(最小集 · 兼容计划开始不篡改)
|
||||
|
||||
| 场景 | 规则 |
|
||||
|---|---|
|
||||
| 待开发 → 退回设计中 | 需求状态回退;【交付】负责人可改回创建人/产品;**交付计划开始不改**;若需重做设计 → **新开**设计任务编号(旧设计保持已收口,不改其计划开始);更新「工作项编号(系统)」中的设计编号为新号 |
|
||||
| 设计完成后需求大变 | 不自动改状态;Plan 询问是否回退设计中并新开设计编号;更新 AutoPRD 段;AutoRDO 段保留并追加「变更纪要」 |
|
||||
| 取消需求 | 关联任务标取消/废止;不删编号区块;不改已写计划开始 |
|
||||
| 度量含义 | 交付计划开始保留 = 全周期仍从首次开工起算;回退重做会拉长日历工时——回报注明「含回退重做」 |
|
||||
|
||||
凡写云效的回退仍须走 Plan 门禁。
|
||||
14
.claude/skills/YunxiaoPM/references/handoff-contract.md
Normal file
14
.claude/skills/YunxiaoPM/references/handoff-contract.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 交接契约(开发 Skill 入口)
|
||||
|
||||
YunxiaoPMapp 与开发 Skill **不要**互相 include 全文;仅认下列契约。
|
||||
|
||||
```text
|
||||
PM 完成交棒 → 需求=待开发;【交付】任务编号=ONEOS-xx;负责人=何斐;ASSOCIATED 需求
|
||||
若交付描述仍为占位 → 回报已标红风险
|
||||
开发 Skill 入口 → 认「需求编号 + 交付任务编号」
|
||||
占位时只可信需求「原始诉求(AutoRDO)」并有权要求产品补设计完成
|
||||
```
|
||||
|
||||
共享常量(项目 ID、何斐 ID、节假日日历)可引用本 Skill 的 `assets/` 短路径,勿加载整份对方规则。
|
||||
|
||||
测试 Skill 另开;本契约不覆盖提测/缺陷。
|
||||
242
.claude/skills/YunxiaoPM/references/live-api.md
Normal file
242
.claude/skills/YunxiaoPM/references/live-api.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# 云效实写 API(YunxiaoPMapp 已验证)
|
||||
|
||||
`verified_at`: 2026-07-25 · **项目须门禁 PJ 点选**(见 [project-selection.md](project-selection.md));历史验证样本项目为 `01_ONEOS` / 原「统一运营管理平台」(`last_selected.spaceIdentifier` 见 `assets/runtime-ids.json`,禁止未点选即使用)。
|
||||
|
||||
本文件只记**已跑通**的写法;禁止再盲试 `updateStatus` / 错误 `updateFieldValue` POST。
|
||||
|
||||
## 认证
|
||||
|
||||
- Cookie:Chrome 域 `.aliyun.com` / `devops.aliyun.com`(`browser_cookie3` 或 Playwright storage)
|
||||
- Header:`x-xsrf-token` = cookie `XSRF-TOKEN`(URL 解码后)
|
||||
- `Origin` / `Referer`:`https://devops.aliyun.com`
|
||||
|
||||
## 建单
|
||||
|
||||
`POST|PUT /projex/api/workitem/workitem?_input_charset=utf-8`
|
||||
|
||||
创建响应 `result.identifier` / `serialNumber` 即编号真相;**禁止按标题查重**。
|
||||
|
||||
## 改负责人(已通)
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8
|
||||
{"propertyKey":"assignedTo","propertyValue":"<userId>","operateType":"COVER"}
|
||||
```
|
||||
|
||||
交棒:【交付】`propertyValue` = 何斐 ID。
|
||||
|
||||
## 打标签(已通)
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{id}?_input_charset=utf-8
|
||||
{"workitemIdentifier":"{id}","propertyKey":"tag","propertyValue":"<tagId>[,<tagId>]","operateType":"COVER"}
|
||||
```
|
||||
|
||||
## 改状态(已通 · 唯一推荐)
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/{id}/status/transit?_input_charset=utf-8
|
||||
{"fromStatus":"<当前status.identifier>","toStatus":"<目标status.identifier>"}
|
||||
```
|
||||
|
||||
成功:`code=200` 且 `result=true`。失败时 `errorMsg` 含「不能流转」。
|
||||
|
||||
### 需求状态 ID(本项目)
|
||||
|
||||
| 显示名 | identifier |
|
||||
|---|---|
|
||||
| 待处理 | `100005` |
|
||||
| 已确认 | `32` |
|
||||
| 分析中 | `154395` |
|
||||
| 设计中 | `156603` |
|
||||
| 设计完成 | `307012` |
|
||||
| 待开发 | `1582fc929d429111b925309493` |
|
||||
|
||||
### 任务状态 ID
|
||||
|
||||
| 显示名 | identifier |
|
||||
|---|---|
|
||||
| 待处理 | `100005` |
|
||||
| 已完成 | `100014` |
|
||||
|
||||
### 极速交棒跳转(工作流允许)
|
||||
|
||||
从「待处理」菜单可见直达「设计完成」;推荐最少跳:
|
||||
|
||||
```text
|
||||
待处理 → 设计完成 → 待开发
|
||||
```
|
||||
|
||||
标准路径若需看板留痕,可走完整链:已确认→分析中→设计中→设计完成→待开发(仍用本 API,勿开 UI)。
|
||||
|
||||
### 禁止(已证伪)
|
||||
|
||||
| 写法 | 结果 |
|
||||
|---|---|
|
||||
| `PATCH …/updateStatus` + `statusIdentifier` | `400 不能为空` |
|
||||
| `PATCH …/{id}` + `propertyKey=status` | `property not found` |
|
||||
| Playwright 点左侧/列表上的状态色块(`x<1100`) | 假成功、状态不落库 |
|
||||
|
||||
仅当 `status/transit` 不可用时,才用 UI:右侧详情状态钮(`getBoundingClientRect().x > 1100`)+ `.next-menu-item`。
|
||||
|
||||
## 计划开始/完成 · 提交部门/人 · 预计工时(2026-07-27 修订)
|
||||
|
||||
**通用字段写入(已通):**
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/field/value/{workitemId}?_input_charset=utf-8
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
fieldValueList=[{"fieldIdentifier":"79","value":"2026-07-27 12:00:00"},{"fieldIdentifier":"3132597a9718d1c282b7ba5a0c","value":"业务管理部"},{"fieldIdentifier":"9e01269e96f91fbb97d36bf5b3","value":"何苗苗"}]
|
||||
```
|
||||
|
||||
| 字段 | fieldIdentifier | value |
|
||||
|---|---|---|
|
||||
| 计划开始 | `79` | `YYYY-MM-DD HH:mm:ss`(推荐正午)或 epoch ms 字符串 |
|
||||
| 计划完成 | `80` | 同上 |
|
||||
| 提交部门 | `3132597a9718d1c282b7ba5a0c` | 纯文本 |
|
||||
| 提交人 | `9e01269e96f91fbb97d36bf5b3` | 纯文本 |
|
||||
|
||||
**预计工时 `101586`:禁止直接改字段**(报「不可直接修改」)。须登记:
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/time/estimate?_input_charset=utf-8
|
||||
{"workitemIdentifier":"<id>","spentTime":8,"type":"develop","description":"阶段日历工时","recordUserIdentifier":"<userId>","forCreate":false,"containsRestDay":false}
|
||||
```
|
||||
|
||||
删除多余预估:`DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}`
|
||||
列表:`GET …/time/estimate/list?workitemIdentifier=`
|
||||
|
||||
旧写法 `PATCH …/updateWorkitemFieldValue` 对上述自定义字段常 `400 不能为空`,勿再优先使用。
|
||||
|
||||
## 父子 / 子项 / 关联项(已通 · 2026-07-23 修订 · 子项优先)
|
||||
|
||||
### 关联项(【交付】强制 · ASSOCIATED)
|
||||
|
||||
任务详情「关联项」只认 `ASSOCIATED`。**仅【交付】**建单时 `createWorkitemRelationInfo` 必须指向**需求**:
|
||||
|
||||
```json
|
||||
{
|
||||
"createWorkitemRelationInfo": {
|
||||
"relatedWorkitemIdentifier": "<需求id>",
|
||||
"relatedToRelationIdentifier": "ASSOCIATED"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
校验:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true
|
||||
```
|
||||
|
||||
`result` 含该需求即通过。
|
||||
|
||||
### 禁止(关联项)
|
||||
|
||||
| 写法 | 结果 |
|
||||
|---|---|
|
||||
| `relatedToRelationIdentifier=PARENT` 把交付挂需求 | 详情可能有 parent,**关联项仍为空** |
|
||||
| 分析/设计只用 `ASSOCIATED→需求` + `parentIdentifier` | 关联项可能有,**交付子项仍为空**(ONEOS-246/247) |
|
||||
| 建后再 `POST …/relation/record` 补关系 | Cookie 路径下常报「不能关联相同的工作项」 |
|
||||
| `createWorkitemRelationList` | 不落 ASSOCIATED |
|
||||
|
||||
### 子项(【分析】/【设计】强制 · TASK_SUB)
|
||||
|
||||
「子项」页读 `PARENT_SUB` / `TASK_SUB`,分析/设计**必须**:
|
||||
|
||||
```json
|
||||
{
|
||||
"parent": "<交付id>",
|
||||
"parentIdentifier": "<交付id>",
|
||||
"createWorkitemRelationInfo": {
|
||||
"relatedWorkitemIdentifier": "<交付id>",
|
||||
"relatedToRelationIdentifier": "TASK_SUB"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
同一 create 只能带一条 `createWorkitemRelationInfo`。
|
||||
`ASSOCIATED→需求` 与 `TASK_SUB→交付` **不能同时写**。
|
||||
|
||||
**产品优先级:交付「子项」tab > 阶段任务「关联项」。**
|
||||
交付本身仍必须 `ASSOCIATED→需求`。标准路径下分析/设计的「关联项」允许为空。
|
||||
|
||||
**无单快轨例外(设计双挂):** create 用 `TASK_SUB→交付` 后,须再补 **ASSOCIATED→原始需求**,使设计详情「关联项」可见需求。
|
||||
|
||||
```http
|
||||
POST /projex/api/workitem/workitem/{设计id}/relation/record?_input_charset=utf-8
|
||||
{"relationIdentifier":"ASSOCIATED","toWorkitemIdentifier":"<需求id>"}
|
||||
```
|
||||
|
||||
**已证伪(Cookie · 2026-07-27):** 凡工作项**已创建**后再 `relation/record` 补挂(含 ASSOCIATED / TASK_SUB),常报「不能关联相同的工作项」;与是否已有父项无关。`createWorkitemRelationList` 亦不落 ASSOCIATED。
|
||||
**可行路径:**
|
||||
|
||||
| 目标 | 做法 |
|
||||
|---|---|
|
||||
| 交付「子项」可见设计(优先) | create 带 `TASK_SUB→交付` |
|
||||
| 设计「关联项」可见需求 | create 带 `ASSOCIATED→需求`(与上互斥,同 create 只能一条) |
|
||||
| 双挂 | 需个人 `x-yunxiao-token` OpenAPI;Cookie 路径**不得**声称成功 |
|
||||
|
||||
产品默认:**子项优先**;关联项失败须在回报中标红并列出缺项。
|
||||
|
||||
校验子项:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{交付id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true
|
||||
```
|
||||
|
||||
结果须含对应分析/设计 identifier。
|
||||
|
||||
校验设计关联项:
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/v2/workitem/{设计id}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true
|
||||
```
|
||||
|
||||
`result` 须含原始需求 identifier。
|
||||
|
||||
### 无单快轨字段默认(2026-07-27)
|
||||
|
||||
| 对象 | 规则 |
|
||||
|---|---|
|
||||
| 【设计】描述 | 复制需求 document HTML |
|
||||
| 【设计】`79`/`80` | 当日 `12:00:00` / `23:59:59`,create 后 `field/value`(勿 create 同时带 79+80) |
|
||||
| 【交付】描述 | 手工同步需求正文,或原型→AutoPRD;禁止无故占位 |
|
||||
| 【交付】`79` | 创建当日;不写 `80` |
|
||||
| 【交付】/【设计】标签 | 与需求相同,`PATCH propertyKey=tag` |
|
||||
| 需求预计工时 | `time/estimate` **spentTime=2**(先删多余预估) |
|
||||
| 需求实际工时 | `POST …/workitem/time`,body 用 **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd` epoch ms 字符串;见 `runtime-ids.json` `fields.actual_hours` |
|
||||
| 描述更新 | `PATCH …/workitem/{id}/document`,`{"content":"<html>","formatType":"RICHTEXT"}` |
|
||||
|
||||
## 迭代挂接(已通 · 2026-07-27 · 只挂交付)
|
||||
|
||||
创建迭代:`POST /projex/api/workspace/sprint`(必填 `staffIds`;可写 `capacityHours`)。
|
||||
|
||||
挂【交付】到迭代:
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8
|
||||
{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"}
|
||||
```
|
||||
|
||||
清空误挂(如需求):`propertyValue:""` + `operateType:"COVER"`。
|
||||
|
||||
**校验**必须读 `/extra`(详情主接口常不含 sprint 字段,禁止据此判失败):
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8
|
||||
→ result.sprint[].identifier / name
|
||||
```
|
||||
|
||||
产品规则:**只挂【交付】**;需求 / 分析 / 设计默认不挂(除非口令显式)。
|
||||
|
||||
### 极速建单注意
|
||||
|
||||
1. Cookie 只刷一次;全程纯 HTTP,默认**不开浏览器**。
|
||||
2. 交付建完后,【分析】与【设计】**并行**创建(均 TASK_SUB→交付);标准/快轨两树可并行。
|
||||
3. 状态用 `transit` + **本地追踪 fromStatus**(禁止每次 GET);负责人在交棒场景下**创建时即何斐**。
|
||||
4. 建单 `fieldValueList` 可带计划开始 `79`;**不要**在 create 同时写 `79+80`(同日会 400)。
|
||||
5. 标签必须 PATCH(create 带 tag 不落库);可与建子任务重叠;快轨交付/设计须与需求同标签。
|
||||
6. `requests.Session` keep-alive;**禁止**对共享 opener 加全局锁。
|
||||
7. 脚本入口:`scripts/live_create_fast.py`(v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂)。
|
||||
59
.claude/skills/YunxiaoPM/references/live-perf-2026-07-23.md
Normal file
59
.claude/skills/YunxiaoPM/references/live-perf-2026-07-23.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 2026-07-23 真实建单复盘与极速优化
|
||||
|
||||
## 测试结论
|
||||
|
||||
标准 + 快轨均可建到「待开发」交棒;交付树、标签「故障管理」、快轨无【分析】均正确。
|
||||
|
||||
| 轮次 | 编号 |
|
||||
|---|---|
|
||||
| 第一轮(探测+UI) | ONEOS-141~147 |
|
||||
| 极速 v1 | ONEOS-148~154 |
|
||||
| 极速 v2 | ONEOS-164~177(含中间探针) |
|
||||
|
||||
## 过程问题(已修)
|
||||
|
||||
| # | 问题 | 根因 | 修复 |
|
||||
|---|---|---|---|
|
||||
| 1 | 状态改不动 / 假成功 | 误用 `updateStatus`;UI 点列表区 | `POST …/status/transit` |
|
||||
| 2 | 负责人改不成 | `updateFieldValue` 错 | `PATCH …/{id}` + `assignedTo` |
|
||||
| 3 | 状态 ID 不全 | 未抓网络 | 写入 `runtime-ids.json` |
|
||||
| 4 | 极慢 | Playwright + 串行探测 | 纯 HTTP + 并行 |
|
||||
| 5 | v1 仍偏慢 | 多余 GET、串行 PATCH 计划、交棒再改负责人 | 见下节 v2 |
|
||||
|
||||
## 性能对比
|
||||
|
||||
| 口径 | 优化前(第一轮) | 极速 v1 | 极速 v2(本轮) |
|
||||
|---|---|---|---|
|
||||
| 会话墙钟 | ≈ **17.9 分钟** | — | — |
|
||||
| 两路径并行建单墙钟 | ≈4.1 分钟自动化 | **4.4 s** | **2.2 s** |
|
||||
| 标准单路径 | 失败反复 | 3.7 s | **2.2 s** |
|
||||
| 快轨单路径 | 失败反复 | 3.7 s | **2.1 s** |
|
||||
| 浏览器 | 多次 | 无 | 无 |
|
||||
|
||||
相对第一轮会话 ≈ **490×**;相对 v1 再快约 **2×**。
|
||||
|
||||
## v2 压榨点(已落地)
|
||||
|
||||
1. `status/transit` **本地追踪** `fromStatus`,跳过每次 GET。
|
||||
2. 建单 `fieldValueList` 带**计划开始(79)**;计划完成不在 create 写(同日会 400)。
|
||||
3. 【交付】创建时负责人直接 **何斐**(省 PATCH)。
|
||||
4. `requests.Session` keep-alive;**去掉全局锁**(否则并行失效)。
|
||||
5. 打标签与建子任务 **重叠**;两路径并行。
|
||||
6. 交棒后汇总用创建响应,校验 GET 移出主路径计时。
|
||||
|
||||
## 探针结论(未采用)
|
||||
|
||||
| 尝试 | 结果 |
|
||||
|---|---|
|
||||
| create `fieldValueList` 带 tag | 建单成功但标签不落库 → 仍须 PATCH |
|
||||
| create 同时写 79+80 | `400 计划…转化异常` |
|
||||
| urllib 全局 lock + 单 opener | 并行被串行化,单路径 ≈4 s |
|
||||
|
||||
## 仍可再压(收益变小)
|
||||
|
||||
1. 标签 API 若将来支持 create 落库,可再少 2~4 次 PATCH。
|
||||
2. HTTP/2 或连接预热(DNS/TLS 复用到进程级)。
|
||||
3. 业务允许「建单即待开发」且平台支持初始状态 → 少 2 次 transit。
|
||||
4. 计划完成改为交棒后异步补写(当前故意跳过)。
|
||||
|
||||
脚本:`scripts/live_create_fast.py`(mode=`live_create_fast_v2`)。
|
||||
53
.claude/skills/YunxiaoPM/references/make-export-attach.md
Normal file
53
.claude/skills/YunxiaoPM/references/make-export-attach.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Make 导出与截图附件
|
||||
|
||||
设计完成(步骤 4)且已关联原型页时:需求与【交付】均挂附件。任一侧失败 → 不得声称设计完成附件齐全。
|
||||
|
||||
## 1. 导出 HTML(含源码)ZIP
|
||||
|
||||
与 Make「发布 → 导出 HTML(含源码)」对齐。
|
||||
|
||||
优先本地 Make Admin:
|
||||
|
||||
```http
|
||||
GET {adminOrigin}/api/export-html?path={prototypePath}&projectId={projectId}&includeSource=true
|
||||
```
|
||||
|
||||
| 参数 | 要求 |
|
||||
|---|---|
|
||||
| `path` | 原型路径(如 `prototypes/oneos-h5-vehicle-assets`) |
|
||||
| `includeSource` | **必须** `true` |
|
||||
| 响应 | ZIP(`PK` 头);文件名建议 `{prototype-id}-html-source.zip` |
|
||||
|
||||
失败时:提示用户在 Make 客户端对该原型执行「发布 → 导出 HTML(含源码)」,把 ZIP 路径发回。
|
||||
**禁止**用「仅对象存储链接」冒充已附带源码包。
|
||||
|
||||
## 2. 复制截图(全交互页)
|
||||
|
||||
1. Make「发布 → **复制截图**」(或等价:导出该原型主界面及所有交互页截图)。
|
||||
2. 上传到需求附件与【交付】附件。
|
||||
3. 缺页/失败 → 列出缺项,不得报成功。
|
||||
|
||||
## 3. 挂载范围
|
||||
|
||||
同一 ZIP / 同一批截图:
|
||||
|
||||
1. 上传到需求 identifier
|
||||
2. 上传到【交付】任务 identifier
|
||||
|
||||
优先 API;未知 upload 端点时用已登录浏览器在详情页「附件」上传。
|
||||
|
||||
## 4. 与对象存储的关系
|
||||
|
||||
| 产物 | 用途 |
|
||||
|---|---|
|
||||
| `{baseUrl}/{id}/index.html` | AutoPRD 描述内可点预览链接 |
|
||||
| 导出 ZIP(含源码) | 附件,供开发离线打开 |
|
||||
| 交互页截图 | 附件,供评审/开发对照 |
|
||||
|
||||
三者职责不同,不可互相替代。
|
||||
|
||||
## 负向
|
||||
|
||||
- 不得导出错误原型页(须 Plan 确认原型路径/名称)。
|
||||
- 不得把别的需求的旧 ZIP 复用到新需求。
|
||||
- 快轨交棒默认不跑本附件流程(除非口令同时设计完成+原型)。
|
||||
60
.claude/skills/YunxiaoPM/references/model.md
Normal file
60
.claude/skills/YunxiaoPM/references/model.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# 交付树模型与关联约定
|
||||
|
||||
## 真相源
|
||||
|
||||
```text
|
||||
需求状态 = 阶段看板唯一真相(分析中/设计中/待开发…)
|
||||
【交付】任务 = 该需求的交付容器(每需求最多 1 条)
|
||||
【分析】/【设计】 = YunxiaoPMapp 在交付下建的阶段子项
|
||||
【开发】/【测试】 = 另属开发 Skill / 测试 Skill,不进本包
|
||||
ASSOCIATED = 横向挂钩(【交付】 ↔ 需求)
|
||||
SUB / TASK_SUB = 纵向拆解(交付 → 分析/设计)
|
||||
```
|
||||
|
||||
## 关系验收
|
||||
|
||||
| 关系 | 用在哪里 | 验收 | 优先级 |
|
||||
|---|---|---|---|
|
||||
| ASSOCIATED | **仅【交付】** ↔ 需求 | 交付详情「关联项」能看到需求(**强制**) | 交付必过 |
|
||||
| TASK_SUB | 【分析】/【设计】 → 【交付】 | 交付详情「子项」能看到阶段任务(**强制**) | 阶段任务必过 |
|
||||
|
||||
建单规则(Cookie 路径 · 同一 create 只能一条 `createWorkitemRelationInfo`):
|
||||
|
||||
| 对象 | `createWorkitemRelationInfo` | 另写字段 |
|
||||
|---|---|---|
|
||||
| 【交付】 | `ASSOCIATED` → 需求 | — |
|
||||
| 【分析】/【设计】 | `TASK_SUB` → 交付 | `parent` + `parentIdentifier` = 交付 |
|
||||
|
||||
**禁止**对分析/设计只写 `parentIdentifier` + `ASSOCIATED→需求`:关联项可能有,但交付「子项」仍为空(ONEOS-246/247 已复现)。
|
||||
勿用 `PARENT` 冒充关联项。细则见 [live-api.md](live-api.md)。
|
||||
|
||||
## 禁止
|
||||
|
||||
| 禁止 | 说明 |
|
||||
|---|---|
|
||||
| 创建或描述【开发】/【测试】 | 口令与回报均不出现 |
|
||||
| 分析/设计未挂成交付子项(无 TASK_SUB) | 交付「子项」为 0,交棒验收失败 |
|
||||
| 用任务名称/标题做唯一性或查重 | 不得 `subject` 匹配复用 |
|
||||
| 同一需求认定多个交付任务编号 | 列出编号请人合并后再继续 |
|
||||
| 功能模块标签用「分析/设计/交付」 | 模块标签打在需求(可选交付)上 |
|
||||
|
||||
## 任务标题(仅展示)
|
||||
|
||||
- 【交付】+ 需求标题
|
||||
- 【分析】+ 需求标题
|
||||
- 【设计】+ 需求标题
|
||||
|
||||
标题可改,**不参与查重**。
|
||||
|
||||
## 任务编号唯一性
|
||||
|
||||
**唯一允许的查重/复用渠道:云效任务编号**(如 `ONEOS-99` / `serialNumber`)。
|
||||
|
||||
| 场景 | 正确 | 错误 |
|
||||
|---|---|---|
|
||||
| 首次创建【交付】 | 建单成功后回报并写入「工作项编号(系统)」 | 下次用标题搜 |
|
||||
| 复用【交付】 | 口令或编号区块带 `交付任务=ONEOS-xx` | list 后按 subject 相等 |
|
||||
| 判断是否已有交付 | 编号区块或交付 ASSOCIATED 编号集合 | 数【交付】开头标题 |
|
||||
| 分析/设计幂等 | 仅已登记编号 | 按【分析】+需求标题 |
|
||||
|
||||
按编号命中则复用;**计划开始已有值禁止改写**。
|
||||
35
.claude/skills/YunxiaoPM/references/number-push.md
Normal file
35
.claude/skills/YunxiaoPM/references/number-push.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# 编号直推到待开发
|
||||
|
||||
适用:标准路径已建过【分析】和/或【设计】,用**任务编号**一口气推到待开发交棒。
|
||||
|
||||
## 入口(只认编号)
|
||||
|
||||
```text
|
||||
交棒开发:需求=ONEOS-xx;交付任务=ONEOS-a;分析任务=ONEOS-b;设计任务=ONEOS-c
|
||||
交棒开发:分析任务=ONEOS-b → 由分析 ASSOCIATED/SUB 反查需求与交付
|
||||
交棒开发:设计任务=ONEOS-c → 同上反查
|
||||
```
|
||||
|
||||
禁止用标题猜。编号对不上关联树 → 停下列出关联,不瞎改。
|
||||
|
||||
## 动作表
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 需求 | 状态 → **待开发**(按云效连跳) |
|
||||
| 【交付】 | **必须**用交付任务编号定位;负责人→何斐;不改计划开始;不写计划完成;描述仍占位则保持(除非同时设计完成+原型+AutoPRD) |
|
||||
| 【分析】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 |
|
||||
| 【设计】 | 若有编号:计划完成若为空 → 写当日并推工时;**不新建**第二条 |
|
||||
| 仅有分析、尚无设计 | **不强制补建【设计】**(与无单快轨区分);回报注明「无设计任务编号」 |
|
||||
| 仅有设计、无分析 | 收口设计 + 交棒;**不补建分析** |
|
||||
|
||||
## 与无单快轨区别
|
||||
|
||||
| | 无单快轨 | 编号直推 |
|
||||
|---|---|---|
|
||||
| 前提 | 尚无分析/设计阶段单(或明确跳过分析) | 已有分析和/或设计**任务编号** |
|
||||
| 【分析】 | 禁止创建 | 有编号则收口,无则不建 |
|
||||
| 【设计】 | 必须新建且当日收口 | 有编号则收口;仅分析无设计时不强制新建 |
|
||||
| 定位 | 新建后登记编号 | **仅任务编号**定位/反查 |
|
||||
|
||||
仍不创建【开发】/【测试】。交棒占位风险见 [handoff-and-rollback.md](handoff-and-rollback.md)。
|
||||
73
.claude/skills/YunxiaoPM/references/project-selection.md
Normal file
73
.claude/skills/YunxiaoPM/references/project-selection.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 门禁 PJ · 云效项目选择(强制点选)
|
||||
|
||||
凡 **新建需求 / 新建任务树 / 创建迭代 / 其它写入依赖 `spaceIdentifier`** 的操作,项目必须由用户从云效实时列表**点选**。禁止静默使用 `runtime-ids.json` 里的默认 `project.spaceIdentifier`。
|
||||
|
||||
## 何时触发
|
||||
|
||||
| 场景 | 是否必须选项目 |
|
||||
|---|---|
|
||||
| 记录需求 / 无单快轨新建 | **必须** |
|
||||
| 创建迭代(新挂项目空间) | **必须**(与需求同项目时沿用已锁定项,仍须在 Plan 写出项目名+ID) |
|
||||
| 仅推进已有编号(受理确认、开始分析…) | **不必重选**;以该编号所在项目为准,Plan 写出项目名 |
|
||||
| 只读查询 | 不强制 |
|
||||
|
||||
## 执行顺序(建单前)
|
||||
|
||||
1. **实时拉取**云效项目列表(Cookie + XSRF):
|
||||
|
||||
```http
|
||||
GET /projex/api/workspace/project/search/list
|
||||
?category=Project&scope=all&toPage=1&pageSize=100
|
||||
&conditions={"conditionGroups":[[]]}
|
||||
&extraConditions=…(可与页面一致:当前用户参与 + public)
|
||||
&orderBy={"fieldIdentifier":"gmtCreate","format":"input","order":"desc","className":"date"}
|
||||
&_input_charset=utf-8
|
||||
```
|
||||
|
||||
2. 将结果整理为单选列表:`{name}({customCode})`;选项 id 用 `identifier`(spaceId)。
|
||||
3. **AskQuestion / Plan 单选**「PJ. 云效项目」;未点选 → **禁止 create**。
|
||||
4. 「批准 Plan」≠ 已选项目;Plan 里仍是空选项或写「默认 01_ONEOS」而未点选 → 停。
|
||||
5. 用户点选后:本轮 apply 全程使用该 `spaceIdentifier`;回报写清项目名 + customCode + spaceId。
|
||||
6. 拉取成功后可回写 `assets/runtime-ids.json` → `projects_catalog`(缓存);**缓存不得当作已选项**。
|
||||
|
||||
## 无法对应 → 自动重拉一次
|
||||
|
||||
「无法对应」任一成立即触发:
|
||||
|
||||
| # | 情形 |
|
||||
|---|---|
|
||||
| 1 | 口令/预填的项目名、customCode、别名在**当前列表**中 0 命中 |
|
||||
| 2 | 口令给出的 spaceId / identifier 不在当前列表 |
|
||||
| 3 | 用户点选的选项 id 在 apply 前校验时已不在列表(列表过期) |
|
||||
| 4 | 仅命中 `projects_catalog` 缓存、与本次实时列表不一致 |
|
||||
| 5 | 首次拉取失败后改用了缓存,用户按缓存点选后 create 报项目/空间无效 |
|
||||
|
||||
**动作(固定一次):**
|
||||
|
||||
1. **立即再调一次**同一 `project/search/list`(强制网络,不用缓存)。
|
||||
2. 用新列表重新做匹配 / 刷新 Plan 单选选项;可回写 `projects_catalog`。
|
||||
3. 回报注明:`已自动重拉项目列表(因无法对应)`。
|
||||
4. **仍无法对应** → 停,展示最新列表请用户重选;**禁止**再自动拉第 3 次;**禁止**猜一个 spaceId 继续建单。
|
||||
|
||||
匹配规则(名/码):忽略大小写;`name` 全等或包含;`customCode` 全等;`name_aliases` 全等。多命中视为无法唯一对应 → 重拉后仍多命中则请用户点选,不自动选定。
|
||||
|
||||
## 禁止
|
||||
|
||||
- 未询问就写入 `project.spaceIdentifier`(即便 catalog 标了 `suggested`)
|
||||
- 按口令里的「统一运营管理平台」字符串静默映射而不展示列表点选(口令仅作**预填建议**,仍须用户确认点选)
|
||||
- API 失败时擅自沿用上次默认;应展示 `projects_catalog` 缓存并标明「离线缓存,请确认」,仍须点选;用户确认后若仍无法对应 → 走上一节「自动重拉一次」
|
||||
- 多项目并行建单却共用一个未确认的 spaceId
|
||||
- 「无法对应」时循环重拉超过 1 次,或跳过点选直接建单
|
||||
|
||||
## 口令预填
|
||||
|
||||
```text
|
||||
记录需求:…;项目=01_ONEOS;…
|
||||
```
|
||||
|
||||
若口令已含项目名/编号前缀且在实时列表中**唯一命中**:Plan 可预勾该选项,但仍须用户确认;0 命中或多命中 → **先自动重拉一次**再匹配;仍 0/多 → 不预勾,只展示最新列表。
|
||||
|
||||
## 脚本
|
||||
|
||||
`scripts/list_projects.py`:stdout JSON `projects[]`。
|
||||
带查询时:`python3 scripts/list_projects.py --match '01_ONEOS'` —— 0 命中/多命中则自动重拉一次再匹配(`refetched: true`)。
|
||||
58
.claude/skills/YunxiaoPM/references/record-meta-fields.md
Normal file
58
.claude/skills/YunxiaoPM/references/record-meta-fields.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 记录需求 · 元字段(优先级 / 标签 / 提交部门 / 提交人)
|
||||
|
||||
建单前消费网页 / AutoRDO / 口令中的四类元信息。细则与 `assets/runtime-ids.json` 对齐;**禁止臆造未验证的 fieldIdentifier**。
|
||||
|
||||
## 口令形态
|
||||
|
||||
```text
|
||||
记录需求:…;优先级=紧急|高|中|低;标签=…;提交部门=…;提交人=…;推进至=…
|
||||
```
|
||||
|
||||
四字段均可选出现;网页或 AutoRDO 提示里已带的值与口令等价。
|
||||
|
||||
## Plan 是否追问
|
||||
|
||||
记录需求 **一律**走 [compact-select.md](compact-select.md) 字母表(类型/项目/优先级/标签),即使用户口令已写齐;给出建议压缩串,等用户回 `1a2b3a4d`(或改字母)并「执行」。
|
||||
|
||||
| 条件 | 行为 |
|
||||
|---|---|
|
||||
| 口令已含类型/项目/优先级/标签 | 对应选项标 ★ + 建议压缩串;**仍须**压缩确认或显式点选 |
|
||||
| 标签名 0 命中 / 多命中 | **自动重拉一次**标签候选并重生 4.A/B/C…;仍失败则停 |
|
||||
| 仅缺提交部门/提交人 | 压缩 1–4 确认后,缺则追问或写入描述页脚 |
|
||||
| 仅缺「推进至」等 | 按既有口令规则;可作 5. 题字母表 |
|
||||
|
||||
## 写入策略
|
||||
|
||||
### 优先级(已验证)
|
||||
|
||||
- 映射:`runtime-ids.json` → `priority`(紧急 / 高 / 中 / 低)。
|
||||
- create 时写入 `fieldIdentifier: "priority"`(与 [live-api.md](live-api.md) / `live_create_fast.py` 一致)。
|
||||
- 未给优先级时:Plan 追问;脚本默认「中」仅作既有兜底,口令/网页有值时以用户值为准。
|
||||
|
||||
### 标签(已验证)
|
||||
|
||||
- 映射:`runtime-ids.json` → `tags`(按显示名取 tagId)。
|
||||
- 标签须 **PATCH** `propertyKey: "tag"`(create 带 tag 不落库)。
|
||||
- 口令/网页给出的标签名若不在候选:按 [compact-select.md](compact-select.md) **自动重拉一次**标签列表并重生选项;仍无则停在 Plan,勿猜 ID。
|
||||
|
||||
### 提交部门 / 提交人(已验证 · 2026-07-27 · ONEOS-293)
|
||||
|
||||
| 字段 | fieldIdentifier |
|
||||
|---|---|
|
||||
| 提交部门 | `3132597a9718d1c282b7ba5a0c` |
|
||||
| 提交人 | `9e01269e96f91fbb97d36bf5b3` |
|
||||
|
||||
| 规则 | 说明 |
|
||||
|---|---|
|
||||
| **写入** | `POST /projex/api/workitem/workitem/field/value/{workitemId}`,`Content-Type: application/x-www-form-urlencoded`,参数 `fieldValueList` = JSON 数组字符串 `[{"fieldIdentifier","value"}]` |
|
||||
| **形态** | 均为普通文本 input(非人员选择器);口令值原样写入 |
|
||||
| **页脚** | 仍可在描述页脚重复一份,便于检索;**不能替代**字段写入 |
|
||||
| **计划开始** | 字段 `79`,同一 `field/value` API;推荐 `YYYY-MM-DD 12:00:00`。快轨:【交付】/【设计】创建当日写入;【设计】另写计划完成 `80`=`当日 23:59:59` |
|
||||
| **预计工时** | 字段 `101586` **禁止**直接 PATCH;用 `POST …/time/estimate`(`spentTime`)登记;删多余用 `DELETE …/time/estimate/{workitemId}/{estimateId}` |
|
||||
| **实际工时** | `POST …/workitem/time`,body **`actualTime`**(非 spentTime)+ `gmtStart`/`gmtEnd`(epoch ms 字符串);快轨待开发需求默认 **2** |
|
||||
| **描述更新** | `PATCH …/workitem/{id}/document`,`{"content","formatType":"RICHTEXT"}` |
|
||||
| **快轨标签** | 【交付】【设计】建单后 `PATCH propertyKey=tag`,`propertyValue` 与需求 tagId 一致(多标签逗号拼接) |
|
||||
|
||||
## 与描述双段的关系
|
||||
|
||||
页脚元信息写在「原始诉求」段末或整篇描述末尾均可,**不得覆盖** `## 产品说明(AutoPRD)` / `## 工作项编号(系统)` 区块(见 [description-split.md](description-split.md))。
|
||||
63
.claude/skills/YunxiaoPM/references/sprint.md
Normal file
63
.claude/skills/YunxiaoPM/references/sprint.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 创建迭代并关联交付
|
||||
|
||||
产品经理可将已交棒(或已有编号)的多条【交付】任务打进同一云效迭代。
|
||||
|
||||
## 口令
|
||||
|
||||
```text
|
||||
创建迭代:版本类型=副;交付任务=ONEOS-a,ONEOS-b,ONEOS-c;名称前缀=统一运营管理平台PC端
|
||||
创建迭代:版本类型=子;交付任务=ONEOS-99;名称前缀=统一运营管理平台PC端
|
||||
```
|
||||
|
||||
| 参数 | 规则 |
|
||||
|---|---|
|
||||
| 交付任务 | **1..N 个任务编号**(逗号分隔);禁止用标题凑数 |
|
||||
| 版本类型 | 主 / 副 / 子;未点选则停下询问,禁止默认猜 |
|
||||
| 名称前缀 | 可选;缺省 `统一运营管理平台PC端`。最终名=`{前缀}{版本号}` |
|
||||
|
||||
## 版本号 `V{主}.{副}.{子}`
|
||||
|
||||
| 版本类型 | 含义 | 递增 |
|
||||
|---|---|---|
|
||||
| **主** | 功能重制 | 主+1,副→0,子→0(V1.3.2→V2.0.0) |
|
||||
| **副** | 新功能上线 | 副+1,子→0(V1.3.2→V1.4.0) |
|
||||
| **子** | bug/优化 | 子+1(V1.3.2→V1.3.3) |
|
||||
|
||||
### 取基线再递增
|
||||
|
||||
1. 拉取当前项目云效迭代列表。
|
||||
2. 从迭代**名称**解析 `V数字.数字.数字`;旧名 `V数字.数字` 视为 `.0`。
|
||||
3. 同名称前缀内取**最大**版本为基线(主→副→子比较);避免 PC 与小程序串号。
|
||||
4. 按用户点选类型递增,写入新迭代名称。
|
||||
5. 无一可解析版本:基线 `V0.0.0` 再递增(主→V1.0.0,副→V0.1.0,子→V0.0.1);或口令显式给起始版本。
|
||||
|
||||
禁止:手填与规则冲突的版本号却声称自动生成;禁止用交付标题推断版本类型。
|
||||
|
||||
## 创建与关联
|
||||
|
||||
| 步骤 | 动作 |
|
||||
|---|---|
|
||||
| 1 | 算出新版本 → 拼迭代全名 |
|
||||
| 2 | 创建迭代(起止日期口令未给则询问或用项目默认,禁止瞎填) |
|
||||
| 3 | **只挂【交付】**:N 个交付任务挂迭代;交付失败不得报完成 |
|
||||
| 4 | 幂等:同名迭代已存在 → 不新建,补挂尚未关联的交付,回报「复用迭代」 |
|
||||
| 5 | 回报:迭代名、版本号、identifier、已关联交付编号、失败编号 |
|
||||
|
||||
**禁止**把需求挂进迭代(需求侧「迭代」字段保持空)。不挂【分析】/【设计】除非口令显式点名。
|
||||
**不**在本步创建【开发】/【测试】。仍须 Plan 门禁。
|
||||
|
||||
### 挂接 API(已通 · 校验走 `/extra`)
|
||||
|
||||
```http
|
||||
PATCH /projex/api/workitem/workitem/{交付id}?_input_charset=utf-8
|
||||
{"workitemIdentifier":"{交付id}","propertyKey":"sprint","propertyValue":"{sprintId}","operateType":"COVER"}
|
||||
```
|
||||
|
||||
校验(详情主接口**不**含 sprint,勿用其判空):
|
||||
|
||||
```http
|
||||
GET /projex/api/workitem/workitem/{id}/extra?_input_charset=utf-8
|
||||
→ result.sprint[].identifier
|
||||
```
|
||||
|
||||
清空误挂的需求迭代:同上 PATCH,`propertyValue:""` + `operateType:"COVER"`(或 `CLEAR`)。
|
||||
63
.claude/skills/YunxiaoPM/references/stage-flow.md
Normal file
63
.claude/skills/YunxiaoPM/references/stage-flow.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 标准路径:待处理 → 待开发交棒
|
||||
|
||||
人员/状态等常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。**项目空间**须按 [project-selection.md](project-selection.md) 点选,禁止默认直指。查重只用任务编号。
|
||||
|
||||
## 步骤 0|创建需求
|
||||
|
||||
| 云效动作 | 落地 |
|
||||
|---|---|
|
||||
| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` |
|
||||
| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD |
|
||||
| 默认状态 | **待处理**;不建任务、不改负责人 |
|
||||
| 可选推进 | 口令点选目标状态;未选则停在待处理 |
|
||||
|
||||
## 步骤 1|受理 → 已确认
|
||||
|
||||
| 云效动作 | 落地 |
|
||||
|---|---|
|
||||
| 状态 | 待处理 → **已确认** |
|
||||
| 任务 | **仍不建**交付/分析/设计 |
|
||||
|
||||
若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。
|
||||
|
||||
## 步骤 2|分析中
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 |
|
||||
| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 |
|
||||
|
||||
| 需求 | 状态=`分析中` |
|
||||
|
||||
## 步骤 3|设计中
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 |
|
||||
| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 |
|
||||
| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) |
|
||||
| 需求 | 状态=`设计中` |
|
||||
|
||||
## 步骤 4|设计完成
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【设计】 | 计划完成=当日;推算工时;任务→完成态 |
|
||||
| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` |
|
||||
| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 |
|
||||
|
||||
顺序与失败门禁见 [description-split.md](description-split.md)。
|
||||
|
||||
## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径)
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 需求 | 状态=`待开发` |
|
||||
| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 |
|
||||
| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 |
|
||||
|
||||
执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。
|
||||
|
||||
**不做:** 【开发】/【测试】、仓库、分支、提测。
|
||||
|
||||
快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。
|
||||
73
.claude/skills/YunxiaoPM/references/work-hours.md
Normal file
73
.claude/skills/YunxiaoPM/references/work-hours.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# 计划时间与阶段日历工时
|
||||
|
||||
适用于【交付】/【分析】/【设计】统一口径。
|
||||
|
||||
## 用途
|
||||
|
||||
| 对象 | 计划开始→计划完成 |
|
||||
|---|---|
|
||||
| 【交付】 | 需求开工→上线全周期(本 Skill **不**写交付计划完成;上线由后续段) |
|
||||
| 【分析】 | 分析阶段时长 |
|
||||
| 【设计】 | 设计阶段时长 |
|
||||
|
||||
## 计划开始(不可篡改)
|
||||
|
||||
| 对象 | 首次写入 | 之后 |
|
||||
|---|---|---|
|
||||
| 【交付】 | 首次进入分析中且字段为空 → 当日 | **禁止覆盖** |
|
||||
| 【分析】 | 首次创建且为空 → 当日 | **禁止覆盖** |
|
||||
| 【设计】 | 首次创建且为空 → 当日 | **禁止覆盖** |
|
||||
|
||||
日期写入形态:中国时区正午 epoch ms 字符串,见 [../assets/runtime-ids.json](../assets/runtime-ids.json) `fields.plan_start`。
|
||||
|
||||
## 计划完成
|
||||
|
||||
| 环节 | 写计划完成的对象 |
|
||||
|---|---|
|
||||
| 推进到设计中 | 【分析】= 状态更新日 |
|
||||
| 推进到设计完成 | 【设计】= 状态更新日 |
|
||||
| 无单快轨建设计 | 【设计】= 当日(同轮收口) |
|
||||
| 编号直推收口 | 口令涉及的分析/设计若计划完成为空 → 当日 |
|
||||
| 上线/关闭 | 【交付】计划完成(**本 Skill 暂不写**) |
|
||||
|
||||
## 预计工时 = 阶段日历工时(Lead Time)
|
||||
|
||||
有计划完成且已有计划开始时,写入该对象云效「预计工时」:
|
||||
|
||||
```text
|
||||
workdays = 0
|
||||
从计划开始日到计划完成日(含首尾)逐日:
|
||||
周六或周日 → 跳过(除非该日是调休补班)
|
||||
法定放假日 → 跳过
|
||||
调休补班日(含周末上班)→ 计入
|
||||
其余周一~周五 → 计入
|
||||
预计工时(小时) = workdays × 8
|
||||
```
|
||||
|
||||
**禁止**用「(结束日 − 开始日 + 1)× 8」日历天数算法。
|
||||
|
||||
### 语义与脚注(强制)
|
||||
|
||||
- 正式名称:**阶段日历工时**,**不是**投入人天/排期负荷。
|
||||
- 任务描述末尾固定脚注:
|
||||
`【系统】预计工时=阶段日历工时(工作日×8),非人力投入预估`
|
||||
- 回报写「阶段日历工时 Hh」,**禁止**写「需投入 Hh」。
|
||||
- 本 Skill **不自动写**「人力投入预估」。
|
||||
|
||||
### 日历数据源
|
||||
|
||||
- 使用 [../assets/cn-workday-calendar.json](../assets/cn-workday-calendar.json)(按自然年;跨年须覆盖起止两侧年份)。
|
||||
- 缺当年日历 → **停下**提示补日历;**禁止**静默按「仅去周末」算完并报成功。
|
||||
- 计算脚本:[../scripts/workday_hours.py](../scripts/workday_hours.py)
|
||||
|
||||
```bash
|
||||
python3 scripts/workday_hours.py 2026-03-06 2026-03-09
|
||||
```
|
||||
|
||||
缺计划开始则只写结束、不推工时,回报提示。
|
||||
|
||||
## 示例
|
||||
|
||||
- 开始=周五、结束=下周一(无节假日)→ 五+一 → 16h
|
||||
- 区间全在法定放假内 → 0h
|
||||
- 某周日为调休补班且落在区间内 → 该日计入 8h
|
||||
22
.claude/skills/YunxiaoPM/references/workitem-ids.md
Normal file
22
.claude/skills/YunxiaoPM/references/workitem-ids.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 工作项编号(系统)
|
||||
|
||||
## 权威源
|
||||
|
||||
需求描述固定区块(机器可解析;人工只读即可):
|
||||
|
||||
```markdown
|
||||
## 工作项编号(系统)
|
||||
- 交付:ONEOS-xx
|
||||
- 分析:ONEOS-yy(无则写无)
|
||||
- 设计:ONEOS-zz(无则写无)
|
||||
```
|
||||
|
||||
## 规则
|
||||
|
||||
1. **禁止**仅靠本地会话映射当唯一真相。
|
||||
2. 每次新建/登记交付·分析·设计编号后,**立即 PATCH 更新该区块**(只改本区块,不动 AutoRDO / AutoPRD 正文)。
|
||||
3. 操作顺序:口令显式编号 > 读本区块 > ASSOCIATED/SUB 反查校验。
|
||||
4. 三者冲突 → 停下人工;**仍禁止按标题猜**。
|
||||
5. 本地缓存仅加速,启动以云效该区块为准。
|
||||
6. 区块出现两个交付编号 → 停止并列号请人合并。
|
||||
7. 取消需求:关联任务标取消/废止;**不删**本区块(留痕)。
|
||||
221
.claude/skills/YunxiaoPM/runtime-ids.json
Normal file
221
.claude/skills/YunxiaoPM/runtime-ids.json
Normal file
@@ -0,0 +1,221 @@
|
||||
{
|
||||
"schema_version": 1,
|
||||
"skill": "YunxiaoPM",
|
||||
"verified_at": "2026-07-25",
|
||||
"note": "YunxiaoPM constants. Project space MUST be user-selected (PJ gate); project.spaceIdentifier is last-known cache only — never auto-apply. See references/project-selection.md.",
|
||||
"project": {
|
||||
"selection_mode": "user_pick_required",
|
||||
"name": null,
|
||||
"name_aliases": [
|
||||
"01_ONEOS",
|
||||
"ONEOS",
|
||||
"统一运营管理平台",
|
||||
"统一运营管理平台PC端",
|
||||
"统一运营管理平台 PC 端"
|
||||
],
|
||||
"spaceIdentifier": null,
|
||||
"customCode": null,
|
||||
"spaceType": "Project",
|
||||
"organizationIdentifier": "697c54a19df7fdfa65466405",
|
||||
"default_sprint_name_prefix": "统一运营管理平台PC端",
|
||||
"list_api": "GET /projex/api/workspace/project/search/list",
|
||||
"last_selected": {
|
||||
"name": "01_ONEOS",
|
||||
"spaceIdentifier": "1280be963a5a2cc126a4118dca",
|
||||
"customCode": "ONEOS",
|
||||
"note": "历史常用项;仅作预填建议,禁止未点选即使用"
|
||||
},
|
||||
"refreshed_at": "2026-07-25"
|
||||
},
|
||||
"projects_catalog": [
|
||||
{
|
||||
"name": "05_羚牛碳资产平台",
|
||||
"identifier": "ff104a3bce09463da136a97999",
|
||||
"customCode": "CARBON"
|
||||
},
|
||||
{
|
||||
"name": "06_对外客户项目",
|
||||
"identifier": "baca8b4d676c5af67e475caec0",
|
||||
"customCode": "CUST"
|
||||
},
|
||||
{
|
||||
"name": "07_LNBOX",
|
||||
"identifier": "35e1e915a052379bbdfb993aac",
|
||||
"customCode": "LNBOX"
|
||||
},
|
||||
{
|
||||
"name": "04_AI应用",
|
||||
"identifier": "d9002ea72c6c3a1a97b02be750",
|
||||
"customCode": "AIAPP"
|
||||
},
|
||||
{
|
||||
"name": "03_数据中台",
|
||||
"identifier": "a801cef5c9a68fa051c07432c7",
|
||||
"customCode": "DATA"
|
||||
},
|
||||
{
|
||||
"name": "02_小羚羚APP",
|
||||
"identifier": "db771a2cca07bed43b369af077",
|
||||
"customCode": "XLLAPP"
|
||||
},
|
||||
{
|
||||
"name": "01_ONEOS",
|
||||
"identifier": "1280be963a5a2cc126a4118dca",
|
||||
"customCode": "ONEOS"
|
||||
},
|
||||
{
|
||||
"name": "敏捷研发示例项目",
|
||||
"identifier": "65eca0c2e16a23939081e19e14",
|
||||
"customCode": "DEMO"
|
||||
}
|
||||
],
|
||||
"people": {
|
||||
"wangmian": {
|
||||
"displayName": "王冕",
|
||||
"identifier": "6811df000601d2fea60144a9",
|
||||
"role": "产品创建人/默认负责人"
|
||||
},
|
||||
"hefei": {
|
||||
"displayName": "何斐",
|
||||
"identifier": "695f0400562f09713f9c3a93",
|
||||
"role": "待开发交棒【交付】负责人"
|
||||
}
|
||||
},
|
||||
"tags": {
|
||||
"故障管理": "ceb526a7343995577645317e9a",
|
||||
"还车应结款": "4204960ce658c94b17abb7c5f6",
|
||||
"工作台": "b62d21beac55c0b43389915eab",
|
||||
"交车管理": "b6947b5aca82a8c759612ba039",
|
||||
"合同管理": "23873be81931cb0dc1bf87a456",
|
||||
"安全培训": "76988b5f73ef515de5930d59b9",
|
||||
"证照管理": "11f4c2ec65901a8f3199c81706",
|
||||
"还车管理": "1e0fce2d6929ff4ac13b310e97"
|
||||
},
|
||||
"status": {
|
||||
"req": {
|
||||
"待处理": "100005",
|
||||
"已确认": "32",
|
||||
"分析中": "154395",
|
||||
"设计中": "156603",
|
||||
"设计完成": "307012",
|
||||
"待开发": "1582fc929d429111b925309493"
|
||||
},
|
||||
"task": {
|
||||
"待处理": "100005",
|
||||
"已完成": "100014"
|
||||
},
|
||||
"transit_api": "POST /projex/api/workitem/workitem/{id}/status/transit",
|
||||
"fast_handoff_hops": [
|
||||
"设计完成",
|
||||
"待开发"
|
||||
],
|
||||
"note": "见 references/live-api.md;禁止再用 updateStatus"
|
||||
},
|
||||
"workitem_types": {
|
||||
"product_req": {
|
||||
"name": "产品类需求",
|
||||
"category": "Req",
|
||||
"identifier": "9uy29901re573f561d69jn40"
|
||||
},
|
||||
"task": {
|
||||
"name": "任务",
|
||||
"category": "Task",
|
||||
"identifier": "ba102e46bc6a8483d9b7f25c"
|
||||
}
|
||||
},
|
||||
"priority": {
|
||||
"紧急": "646004e97f54bb77fec7b455df",
|
||||
"高": "95b89e0a524d9693e1f335ffe5",
|
||||
"中": "fa155d1214f9f8db222d39db3b",
|
||||
"低": "92924feff9c1085891e7511872"
|
||||
},
|
||||
"fields": {
|
||||
"plan_start": {
|
||||
"fieldIdentifier": "79",
|
||||
"name": "计划开始时间",
|
||||
"value_shape": "YYYY-MM-DD HH:mm:ss China wall time preferred; epoch ms string also accepted",
|
||||
"example": "2026-07-27 12:00:00",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON"
|
||||
},
|
||||
"plan_end": {
|
||||
"fieldIdentifier": "80",
|
||||
"name": "计划完成时间",
|
||||
"value_shape": "YYYY-MM-DD HH:mm:ss or epoch ms string",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON"
|
||||
},
|
||||
"estimated_hours": {
|
||||
"fieldIdentifier": "101586",
|
||||
"name": "预计工时",
|
||||
"note": "不可直接改字段;须工时预估登记",
|
||||
"create_api": "POST /projex/api/workitem/workitem/time/estimate body spentTime,type,recordUserIdentifier,workitemIdentifier",
|
||||
"delete_api": "DELETE /projex/api/workitem/workitem/time/estimate/{workitemId}/{estimateId}",
|
||||
"list_api": "GET /projex/api/workitem/workitem/time/estimate/list?workitemIdentifier="
|
||||
},
|
||||
"actual_hours": {
|
||||
"name": "实际工时",
|
||||
"note": "快轨待开发默认 2",
|
||||
"create_api": "POST /projex/api/workitem/workitem/time body actualTime,type,recordUserIdentifier,workitemIdentifier,gmtStart,gmtEnd (epoch ms string)",
|
||||
"list_api": "GET /projex/api/workitem/workitem/time/list?workitemIdentifier=",
|
||||
"fast_track_default": 2,
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"fast_track": {
|
||||
"req_estimated_hours": 2,
|
||||
"req_actual_hours": 2,
|
||||
"design_plan_start_end_same_day": true,
|
||||
"design_description": "copy_req_document",
|
||||
"delivery_description": "manual_sync_or_autoprd",
|
||||
"delivery_design_same_tags_as_req": true,
|
||||
"design_associated_to_req_after_task_sub": true,
|
||||
"document_update_api": "PATCH /projex/api/workitem/workitem/{id}/document {content,formatType:RICHTEXT}"
|
||||
},
|
||||
"submit_department": {
|
||||
"fieldIdentifier": "3132597a9718d1c282b7ba5a0c",
|
||||
"name": "提交部门",
|
||||
"format": "string input",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON",
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"submitter": {
|
||||
"fieldIdentifier": "9e01269e96f91fbb97d36bf5b3",
|
||||
"name": "提交人",
|
||||
"format": "string input",
|
||||
"update_api": "POST /projex/api/workitem/workitem/field/value/{workitemId} form fieldValueList=JSON",
|
||||
"verified_at": "2026-07-27",
|
||||
"verified_on": "ONEOS-293"
|
||||
},
|
||||
"tag": {
|
||||
"fieldIdentifier": "tag",
|
||||
"name": "标签",
|
||||
"propertyKey": "tag"
|
||||
}
|
||||
},
|
||||
"assignee_rules": {
|
||||
"待开发_交付任务": "hefei",
|
||||
"分析中_交付与分析": "creator",
|
||||
"设计中_设计": "creator"
|
||||
},
|
||||
"relation": {
|
||||
"delivery_to_req": "ASSOCIATED",
|
||||
"stage_to_delivery": "TASK_SUB",
|
||||
"create_field": "createWorkitemRelationInfo",
|
||||
"forbid": [
|
||||
"PARENT_as_关联项",
|
||||
"ASSOCIATED_only_for_stage_tasks_without_TASK_SUB"
|
||||
],
|
||||
"note": "交付必须 ASSOCIATED→需求。分析/设计默认 TASK_SUB→交付(子项 tab)。二者同 create 互斥。见 references/live-api.md"
|
||||
},
|
||||
"delivery_placeholder": "等待设计任务完成后自动填入",
|
||||
"task_title_prefixes": {
|
||||
"delivery": "【交付】",
|
||||
"analysis": "【分析】",
|
||||
"design": "【设计】"
|
||||
},
|
||||
"publish_url": {
|
||||
"shape": "{baseUrl}/{prototype-id}/index.html",
|
||||
"forbid_prototypes_prefix": true,
|
||||
"require_index_html": true
|
||||
}
|
||||
}
|
||||
227
.claude/skills/YunxiaoPM/scripts/list_projects.py
Normal file
227
.claude/skills/YunxiaoPM/scripts/list_projects.py
Normal file
@@ -0,0 +1,227 @@
|
||||
#!/usr/bin/env python3
|
||||
"""拉取云效项目列表(供 YunxiaoPMapp 门禁 PJ 点选)。stdout = JSON。
|
||||
|
||||
无法对应时自动重拉一次:
|
||||
python3 scripts/list_projects.py --match '01_ONEOS'
|
||||
python3 scripts/list_projects.py --match-id 1280be963a5a2cc126a4118dca
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import urllib.parse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text())
|
||||
ORG = RUNTIME.get("project", {}).get("organizationIdentifier", "697c54a19df7fdfa65466405")
|
||||
WANG = RUNTIME.get("people", {}).get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9")
|
||||
ALIASES = [a.lower() for a in (RUNTIME.get("project", {}).get("name_aliases") or [])]
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def load_jar() -> dict[str, str]:
|
||||
jar: dict[str, str] = {}
|
||||
try:
|
||||
import browser_cookie3
|
||||
|
||||
for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"):
|
||||
try:
|
||||
for c in browser_cookie3.chrome(domain_name=domain):
|
||||
jar[c.name] = c.value
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
if not jar.get("XSRF-TOKEN"):
|
||||
p = Path("/tmp/yunxiao_cookies.json")
|
||||
if p.exists():
|
||||
raw = json.loads(p.read_text())
|
||||
jar = (
|
||||
raw
|
||||
if isinstance(raw, dict) and "XSRF-TOKEN" in raw
|
||||
else {c["name"]: c["value"] for c in raw.get("cookies", [])}
|
||||
)
|
||||
if not jar.get("XSRF-TOKEN"):
|
||||
raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com")
|
||||
return jar
|
||||
|
||||
|
||||
def fetch_projects() -> list[dict[str, Any]]:
|
||||
jar = load_jar()
|
||||
x = jar.get("XSRF-TOKEN", "")
|
||||
xsrf = urllib.parse.unquote(x) if "%" in x else x
|
||||
cookie = "; ".join(f"{k}={v}" for k, v in jar.items())
|
||||
headers = {
|
||||
"Cookie": cookie,
|
||||
"x-xsrf-token": xsrf,
|
||||
"X-XSRF-TOKEN": xsrf,
|
||||
"Origin": "https://devops.aliyun.com",
|
||||
"Referer": "https://devops.aliyun.com/projex",
|
||||
"accept": "application/json",
|
||||
}
|
||||
extra = json.dumps(
|
||||
{
|
||||
"conditionGroups": [
|
||||
[
|
||||
{
|
||||
"className": "user",
|
||||
"fieldIdentifier": "users",
|
||||
"format": "multiList",
|
||||
"operator": "CONTAINS",
|
||||
"value": [WANG],
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"className": "string",
|
||||
"fieldIdentifier": "scope",
|
||||
"format": "list",
|
||||
"operator": "CONTAINS",
|
||||
"value": ["public"],
|
||||
}
|
||||
],
|
||||
]
|
||||
},
|
||||
separators=(",", ":"),
|
||||
)
|
||||
params = {
|
||||
"extraConditions": extra,
|
||||
"conditions": json.dumps({"conditionGroups": [[]]}, separators=(",", ":")),
|
||||
"orderBy": json.dumps(
|
||||
{
|
||||
"fieldIdentifier": "gmtCreate",
|
||||
"format": "input",
|
||||
"order": "desc",
|
||||
"className": "date",
|
||||
},
|
||||
separators=(",", ":"),
|
||||
),
|
||||
"scope": "all",
|
||||
"category": "Project",
|
||||
"toPage": 1,
|
||||
"pageSize": 100,
|
||||
"_input_charset": "utf-8",
|
||||
}
|
||||
r = requests.get(
|
||||
"https://devops.aliyun.com/projex/api/workspace/project/search/list",
|
||||
headers=headers,
|
||||
params=params,
|
||||
timeout=30,
|
||||
)
|
||||
r.raise_for_status()
|
||||
rows = r.json().get("result") or []
|
||||
return [
|
||||
{
|
||||
"name": p.get("name"),
|
||||
"identifier": p.get("identifier"),
|
||||
"customCode": p.get("customCode"),
|
||||
"logicalStatus": p.get("logicalStatus"),
|
||||
"status": (p.get("status") or {}).get("displayName"),
|
||||
"scope": p.get("scope"),
|
||||
}
|
||||
for p in rows
|
||||
]
|
||||
|
||||
|
||||
def match_projects(
|
||||
projects: list[dict[str, Any]],
|
||||
*,
|
||||
query: str | None = None,
|
||||
space_id: str | None = None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if space_id:
|
||||
sid = space_id.strip()
|
||||
return [p for p in projects if p.get("identifier") == sid]
|
||||
if not query:
|
||||
return []
|
||||
q = query.strip().lower()
|
||||
hits: list[dict[str, Any]] = []
|
||||
for p in projects:
|
||||
name = (p.get("name") or "").lower()
|
||||
code = (p.get("customCode") or "").lower()
|
||||
if q == name or q == code or q in name or name in q:
|
||||
hits.append(p)
|
||||
continue
|
||||
if q in ALIASES and (code == "oneos" or "oneos" in name or "运营" in name):
|
||||
hits.append(p)
|
||||
# de-dupe by identifier
|
||||
seen: set[str] = set()
|
||||
out: list[dict[str, Any]] = []
|
||||
for p in hits:
|
||||
i = p.get("identifier") or ""
|
||||
if i in seen:
|
||||
continue
|
||||
seen.add(i)
|
||||
out.append(p)
|
||||
return out
|
||||
|
||||
|
||||
def list_projects(
|
||||
*,
|
||||
match: str | None = None,
|
||||
match_id: str | None = None,
|
||||
) -> dict:
|
||||
projects = fetch_projects()
|
||||
payload: dict[str, Any] = {
|
||||
"fetched_at": datetime.now(TZ).isoformat(),
|
||||
"organizationIdentifier": ORG,
|
||||
"count": len(projects),
|
||||
"projects": projects,
|
||||
"selection_required": True,
|
||||
"refetched": False,
|
||||
"note": "禁止默认直指;须 AskQuestion/Plan 点选后再写入 spaceIdentifier",
|
||||
}
|
||||
if not match and not match_id:
|
||||
return payload
|
||||
|
||||
hits = match_projects(projects, query=match, space_id=match_id)
|
||||
if len(hits) == 1:
|
||||
payload["matches"] = hits
|
||||
payload["match_status"] = "unique"
|
||||
return payload
|
||||
|
||||
# 无法对应(0 或多)→ 自动重拉一次
|
||||
projects2 = fetch_projects()
|
||||
hits2 = match_projects(projects2, query=match, space_id=match_id)
|
||||
payload["projects"] = projects2
|
||||
payload["count"] = len(projects2)
|
||||
payload["refetched"] = True
|
||||
payload["refetch_reason"] = "无法对应" if len(hits) != 1 else "unexpected"
|
||||
if len(hits) == 0:
|
||||
payload["refetch_reason"] = "首次0命中"
|
||||
elif len(hits) > 1:
|
||||
payload["refetch_reason"] = "首次多命中"
|
||||
payload["matches"] = hits2
|
||||
if len(hits2) == 1:
|
||||
payload["match_status"] = "unique_after_refetch"
|
||||
elif len(hits2) == 0:
|
||||
payload["match_status"] = "none_after_refetch"
|
||||
payload["note"] = "已自动重拉一次仍无法对应;请用户从最新列表点选;禁止再拉第3次"
|
||||
else:
|
||||
payload["match_status"] = "ambiguous_after_refetch"
|
||||
payload["note"] = "已自动重拉一次仍多命中;请用户点选;禁止自动选定"
|
||||
payload["fetched_at"] = datetime.now(TZ).isoformat()
|
||||
return payload
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="YunxiaoPMapp 项目列表 / 匹配(无法对应则重拉一次)")
|
||||
ap.add_argument("--match", help="按项目名或 customCode 匹配")
|
||||
ap.add_argument("--match-id", help="按 spaceIdentifier 匹配")
|
||||
args = ap.parse_args()
|
||||
print(
|
||||
json.dumps(
|
||||
list_projects(match=args.match, match_id=args.match_id),
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
190
.claude/skills/YunxiaoPM/scripts/list_tags.py
Normal file
190
.claude/skills/YunxiaoPM/scripts/list_tags.py
Normal file
@@ -0,0 +1,190 @@
|
||||
#!/usr/bin/env python3
|
||||
"""标签候选列表(聚合工作项 tag + runtime);未命中可 --match 后自动重拉一次。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import urllib.parse
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text())
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
|
||||
|
||||
def load_jar() -> dict[str, str]:
|
||||
jar: dict[str, str] = {}
|
||||
try:
|
||||
import browser_cookie3
|
||||
|
||||
for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"):
|
||||
try:
|
||||
for c in browser_cookie3.chrome(domain_name=domain):
|
||||
jar[c.name] = c.value
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
if not jar.get("XSRF-TOKEN"):
|
||||
p = Path("/tmp/yunxiao_cookies.json")
|
||||
if p.exists():
|
||||
raw = json.loads(p.read_text())
|
||||
jar = (
|
||||
raw
|
||||
if isinstance(raw, dict) and "XSRF-TOKEN" in raw
|
||||
else {c["name"]: c["value"] for c in raw.get("cookies", [])}
|
||||
)
|
||||
if not jar.get("XSRF-TOKEN"):
|
||||
raise RuntimeError("缺少 XSRF-TOKEN")
|
||||
return jar
|
||||
|
||||
|
||||
def session() -> requests.Session:
|
||||
jar = load_jar()
|
||||
x = jar.get("XSRF-TOKEN", "")
|
||||
xsrf = urllib.parse.unquote(x) if "%" in x else x
|
||||
s = requests.Session()
|
||||
s.headers.update(
|
||||
{
|
||||
"Cookie": "; ".join(f"{k}={v}" for k, v in jar.items()),
|
||||
"x-xsrf-token": xsrf,
|
||||
"X-XSRF-TOKEN": xsrf,
|
||||
"Origin": "https://devops.aliyun.com",
|
||||
"Referer": "https://devops.aliyun.com/projex",
|
||||
"accept": "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
def fetch_tags(space_id: str) -> list[dict[str, str]]:
|
||||
s = session()
|
||||
by_id: dict[str, str] = {}
|
||||
for name, tid in (RUNTIME.get("tags") or {}).items():
|
||||
by_id[tid] = name
|
||||
body = {
|
||||
"spaceIdentifier": space_id,
|
||||
"spaceType": "Project",
|
||||
"category": "Req",
|
||||
"toPage": 1,
|
||||
"pageSize": 100,
|
||||
"conditions": json.dumps({"conditionGroups": [[]]}),
|
||||
}
|
||||
rows = (
|
||||
s.post(
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8",
|
||||
json=body,
|
||||
timeout=30,
|
||||
)
|
||||
.json()
|
||||
.get("result")
|
||||
or []
|
||||
)
|
||||
for row in rows:
|
||||
for t in row.get("tag") or []:
|
||||
tid = t.get("identifier")
|
||||
name = t.get("name") or t.get("displayName")
|
||||
if tid and name:
|
||||
by_id[tid] = name
|
||||
# Task 再扫一页,补标签
|
||||
body["category"] = "Task"
|
||||
rows = (
|
||||
s.post(
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/list?_input_charset=utf-8",
|
||||
json=body,
|
||||
timeout=30,
|
||||
)
|
||||
.json()
|
||||
.get("result")
|
||||
or []
|
||||
)
|
||||
for row in rows:
|
||||
for t in row.get("tag") or []:
|
||||
tid = t.get("identifier")
|
||||
name = t.get("name") or t.get("displayName")
|
||||
if tid and name:
|
||||
by_id[tid] = name
|
||||
return [{"name": n, "identifier": i} for i, n in sorted(by_id.items(), key=lambda x: x[1])]
|
||||
|
||||
|
||||
def match_tags(tags: list[dict[str, str]], query: str) -> list[dict[str, str]]:
|
||||
q = query.strip().lower()
|
||||
hits = []
|
||||
for t in tags:
|
||||
name = (t.get("name") or "").lower()
|
||||
if q == name or q in name or name in q:
|
||||
hits.append(t)
|
||||
return hits
|
||||
|
||||
|
||||
def list_tags(*, space_id: str, match: str | None = None, prefer: str | None = None) -> dict[str, Any]:
|
||||
tags = fetch_tags(space_id)
|
||||
# prefer 置顶
|
||||
if prefer:
|
||||
prefer_l = prefer.strip().lower()
|
||||
tags = sorted(
|
||||
tags,
|
||||
key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""),
|
||||
)
|
||||
out: dict[str, Any] = {
|
||||
"fetched_at": datetime.now(TZ).isoformat(),
|
||||
"spaceIdentifier": space_id,
|
||||
"count": len(tags),
|
||||
"tags": tags,
|
||||
"refetched": False,
|
||||
"note": "标签未命中须自动重拉一次并重生 4.A/B/C 选项;见 compact-select.md",
|
||||
}
|
||||
if not match:
|
||||
return out
|
||||
hits = match_tags(tags, match)
|
||||
if len(hits) == 1:
|
||||
out["matches"] = hits
|
||||
out["match_status"] = "unique"
|
||||
return out
|
||||
# 无法对应 → 重拉一次
|
||||
tags2 = fetch_tags(space_id)
|
||||
if prefer:
|
||||
prefer_l = prefer.strip().lower()
|
||||
tags2 = sorted(
|
||||
tags2,
|
||||
key=lambda t: (0 if (t.get("name") or "").lower() == prefer_l else 1, t.get("name") or ""),
|
||||
)
|
||||
hits2 = match_tags(tags2, match)
|
||||
out["tags"] = tags2
|
||||
out["count"] = len(tags2)
|
||||
out["refetched"] = True
|
||||
out["refetch_reason"] = "首次0命中" if len(hits) == 0 else "首次多命中"
|
||||
out["matches"] = hits2
|
||||
if len(hits2) == 1:
|
||||
out["match_status"] = "unique_after_refetch"
|
||||
elif len(hits2) == 0:
|
||||
out["match_status"] = "none_after_refetch"
|
||||
out["note"] = "已自动重拉标签列表仍无法对应;请用户点选 4.x;禁止再拉第3次"
|
||||
else:
|
||||
out["match_status"] = "ambiguous_after_refetch"
|
||||
out["fetched_at"] = datetime.now(TZ).isoformat()
|
||||
return out
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--space", required=True, help="spaceIdentifier")
|
||||
ap.add_argument("--match", help="标签名")
|
||||
ap.add_argument("--prefer", help="置顶标签名")
|
||||
args = ap.parse_args()
|
||||
print(
|
||||
json.dumps(
|
||||
list_tags(space_id=args.space, match=args.match, prefer=args.prefer),
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
660
.claude/skills/YunxiaoPM/scripts/live_create_fast.py
Normal file
660
.claude/skills/YunxiaoPM/scripts/live_create_fast.py
Normal file
@@ -0,0 +1,660 @@
|
||||
#!/usr/bin/env python3
|
||||
"""YunxiaoPM 极速真实建单 v5:快轨描述/计划/标签/工时 2+2/设计 ASSOCIATED 补挂。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
import urllib.parse
|
||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
try:
|
||||
import browser_cookie3
|
||||
except ImportError: # pragma: no cover
|
||||
browser_cookie3 = None
|
||||
|
||||
import requests
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
RUNTIME = json.loads((ROOT / "assets" / "runtime-ids.json").read_text())
|
||||
STATUS = RUNTIME["status"]["req"]
|
||||
TASK_STATUS = RUNTIME["status"]["task"]
|
||||
FAST = RUNTIME.get("fields", {}).get("fast_track") or {}
|
||||
|
||||
SPACE = (
|
||||
RUNTIME.get("project", {}).get("spaceIdentifier")
|
||||
or (RUNTIME.get("project", {}).get("last_selected") or {}).get("spaceIdentifier")
|
||||
)
|
||||
if not SPACE:
|
||||
raise RuntimeError(
|
||||
"未选定项目 spaceIdentifier:须先走门禁 PJ 点选,或设置 runtime project.last_selected"
|
||||
)
|
||||
REQ_TYPE = RUNTIME["workitem_types"]["product_req"]["identifier"]
|
||||
TASK_TYPE = RUNTIME["workitem_types"]["task"]["identifier"]
|
||||
HEFEI = RUNTIME["people"]["hefei"]["identifier"]
|
||||
WANG = RUNTIME["people"].get("wangmian", {}).get("identifier", "6811df000601d2fea60144a9")
|
||||
PRI = RUNTIME["priority"]["中"]
|
||||
TAG_FAULT = RUNTIME.get("tags", {}).get("故障管理", "ceb526a7343995577645317e9a")
|
||||
PLACEHOLDER = RUNTIME["delivery_placeholder"]
|
||||
PROTO = "https://prototype.lnoneos.com/vehicle-fault-handling/index.html"
|
||||
TZ = timezone(timedelta(hours=8))
|
||||
TODAY = datetime.now(TZ).strftime("%Y-%m-%d")
|
||||
NOON = f"{TODAY} 12:00:00"
|
||||
EOD = f"{TODAY} 23:59:59"
|
||||
NOON_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=12, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
START_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=9, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
END_MS = str(
|
||||
int(datetime.now(TZ).replace(hour=11, minute=0, second=0, microsecond=0).timestamp() * 1000)
|
||||
)
|
||||
FAST_EST = int(FAST.get("req_estimated_hours", 2))
|
||||
FAST_ACT = int(FAST.get("req_actual_hours", 2))
|
||||
|
||||
PENDING = STATUS["待处理"]
|
||||
DESIGN_DONE = STATUS["设计完成"]
|
||||
PENDING_DEV = STATUS["待开发"]
|
||||
TASK_DONE = TASK_STATUS["已完成"]
|
||||
|
||||
_COOKIE = ""
|
||||
_XSRF = ""
|
||||
|
||||
|
||||
def load_auth() -> None:
|
||||
global _COOKIE, _XSRF
|
||||
jar: dict[str, str] = {}
|
||||
if browser_cookie3:
|
||||
for domain in (".aliyun.com", "devops.aliyun.com", ".devops.aliyun.com"):
|
||||
try:
|
||||
for c in browser_cookie3.chrome(domain_name=domain):
|
||||
jar[c.name] = c.value
|
||||
except Exception:
|
||||
pass
|
||||
if not jar:
|
||||
p = Path("/tmp/yunxiao_cookies.json")
|
||||
if p.exists():
|
||||
raw = json.loads(p.read_text())
|
||||
jar = (
|
||||
raw
|
||||
if isinstance(raw, dict) and "XSRF-TOKEN" in raw
|
||||
else {c["name"]: c["value"] for c in raw.get("cookies", [])}
|
||||
)
|
||||
_COOKIE = "; ".join(f"{k}={v}" for k, v in jar.items())
|
||||
x = jar.get("XSRF-TOKEN", "")
|
||||
_XSRF = urllib.parse.unquote(x) if "%" in x else x
|
||||
if not _XSRF:
|
||||
raise RuntimeError("缺少 XSRF-TOKEN:请先在 Chrome 登录 devops.aliyun.com")
|
||||
|
||||
|
||||
def session() -> requests.Session:
|
||||
s = requests.Session()
|
||||
s.headers.update(
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"Cookie": _COOKIE,
|
||||
"x-xsrf-token": _XSRF,
|
||||
"X-XSRF-TOKEN": _XSRF,
|
||||
"Origin": "https://devops.aliyun.com",
|
||||
"Referer": f"https://devops.aliyun.com/projex/project/{SPACE}/req",
|
||||
"accept": "application/json",
|
||||
"User-Agent": "YunxiaoPM-live_create_fast/5.0",
|
||||
"Connection": "keep-alive",
|
||||
}
|
||||
)
|
||||
return s
|
||||
|
||||
|
||||
def api(s: requests.Session, method: str, url: str, body: Any = None) -> dict:
|
||||
r = s.request(method, url, json=body, timeout=60)
|
||||
try:
|
||||
return r.json()
|
||||
except Exception:
|
||||
r.raise_for_status()
|
||||
raise
|
||||
|
||||
|
||||
def create(s: requests.Session, payload: dict) -> dict:
|
||||
url = "https://devops.aliyun.com/projex/api/workitem/workitem?_input_charset=utf-8"
|
||||
j = api(s, "POST", url, payload)
|
||||
r = j.get("result") or {}
|
||||
if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"):
|
||||
return r
|
||||
j = api(s, "PUT", url, payload)
|
||||
r = j.get("result") or {}
|
||||
if j.get("code") == 200 and isinstance(r, dict) and r.get("identifier"):
|
||||
return r
|
||||
raise RuntimeError(f"create failed: {j}")
|
||||
|
||||
|
||||
def get(s: requests.Session, wid: str) -> dict:
|
||||
return api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8",
|
||||
)["result"]
|
||||
|
||||
|
||||
def apply_tag(s: requests.Session, wid: str, tag_id: str = TAG_FAULT) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"PATCH",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"propertyKey": "tag",
|
||||
"propertyValue": tag_id,
|
||||
"operateType": "COVER",
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"tag failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_document(s: requests.Session, wid: str, html: str) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"PATCH",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/document?_input_charset=utf-8",
|
||||
{"content": html, "formatType": "RICHTEXT"},
|
||||
)
|
||||
if j.get("code") != 200 or j.get("errorMsg"):
|
||||
raise RuntimeError(f"document failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_fields(s: requests.Session, wid: str, pairs: list[tuple[str, str]]) -> None:
|
||||
data = urllib.parse.urlencode(
|
||||
{
|
||||
"fieldValueList": json.dumps(
|
||||
[{"fieldIdentifier": k, "value": v} for k, v in pairs], ensure_ascii=False
|
||||
)
|
||||
}
|
||||
)
|
||||
r = s.post(
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/field/value/{wid}?_input_charset=utf-8",
|
||||
data=data,
|
||||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||||
timeout=60,
|
||||
)
|
||||
j = r.json()
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"field/value failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_estimate_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None:
|
||||
est = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/list?workitemIdentifier={wid}",
|
||||
).get("result") or []
|
||||
for row in est:
|
||||
eid = row.get("identifier")
|
||||
if eid:
|
||||
api(
|
||||
s,
|
||||
"DELETE",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate/{wid}/{eid}",
|
||||
)
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/time/estimate?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"spentTime": hours,
|
||||
"type": "develop",
|
||||
"description": "快轨默认预计工时",
|
||||
"recordUserIdentifier": user,
|
||||
"forCreate": False,
|
||||
"containsRestDay": False,
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200:
|
||||
raise RuntimeError(f"estimate failed {wid}: {j}")
|
||||
|
||||
|
||||
def set_actual_hours(s: requests.Session, wid: str, hours: int, user: str = WANG) -> None:
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
"https://devops.aliyun.com/projex/api/workitem/workitem/time?_input_charset=utf-8",
|
||||
{
|
||||
"workitemIdentifier": wid,
|
||||
"actualTime": hours,
|
||||
"type": "develop",
|
||||
"description": "快轨默认实际工时",
|
||||
"recordUserIdentifier": user,
|
||||
"gmtStart": START_MS,
|
||||
"gmtEnd": END_MS,
|
||||
},
|
||||
)
|
||||
if j.get("code") != 200 or not j.get("result"):
|
||||
raise RuntimeError(f"actual time failed {wid}: {j}")
|
||||
|
||||
|
||||
def try_associate_to_req(s: requests.Session, stage_id: str, req_id: str) -> bool:
|
||||
"""建后补 ASSOCIATED→需求。Cookie 下常失败,成功返回 True。"""
|
||||
bodies = [
|
||||
{"relationIdentifier": "ASSOCIATED", "toWorkitemIdentifier": req_id},
|
||||
{
|
||||
"relationIdentifier": "ASSOCIATED",
|
||||
"fromWorkitemIdentifier": stage_id,
|
||||
"toWorkitemIdentifier": req_id,
|
||||
},
|
||||
]
|
||||
for body in bodies:
|
||||
for url in (
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{stage_id}/relation/record?_input_charset=utf-8",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{stage_id}/relation/record?_input_charset=utf-8",
|
||||
):
|
||||
j = api(s, "POST", url, body)
|
||||
if j.get("code") == 200 and j.get("result") not in (None, False):
|
||||
if not (isinstance(j.get("result"), dict) and j["result"].get("status") in (404, 405)):
|
||||
rows = list_associated(s, stage_id)
|
||||
if req_id in {r.get("identifier") for r in rows}:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def transit(s: requests.Session, wid: str, from_status: str, to_status: str) -> None:
|
||||
if from_status == to_status:
|
||||
return
|
||||
j = api(
|
||||
s,
|
||||
"POST",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/workitem/{wid}/status/transit?_input_charset=utf-8",
|
||||
{"fromStatus": from_status, "toStatus": to_status},
|
||||
)
|
||||
if not (j.get("code") == 200 and j.get("result") is True):
|
||||
raise RuntimeError(f"transit {wid} {from_status}->{to_status}: {j}")
|
||||
|
||||
|
||||
def md_to_html(md: str) -> str:
|
||||
parts = []
|
||||
for line in md.splitlines():
|
||||
if line.startswith("## "):
|
||||
parts.append(f"<h2>{line[3:]}</h2>")
|
||||
elif line.startswith("### "):
|
||||
parts.append(f"<h3>{line[4:]}</h3>")
|
||||
elif line.startswith("- "):
|
||||
parts.append(f"<p>• {line[2:]}</p>")
|
||||
elif line.strip():
|
||||
parts.append(f"<p>{line}</p>")
|
||||
return "".join(parts)
|
||||
|
||||
|
||||
def req_document_html(s: requests.Session, rid: str) -> str:
|
||||
w = get(s, rid)
|
||||
return ((w.get("document") or {}).get("content") or w.get("description") or "").strip()
|
||||
|
||||
|
||||
def req_payload(subject: str, html: str) -> dict:
|
||||
return {
|
||||
"subject": subject,
|
||||
"description": html,
|
||||
"formatType": "RICHTEXT",
|
||||
"document": {"content": html, "formatType": "RICHTEXT"},
|
||||
"spaceIdentifier": SPACE,
|
||||
"space": SPACE,
|
||||
"spaceType": "Project",
|
||||
"workitemTypeIdentifier": REQ_TYPE,
|
||||
"workitemType": REQ_TYPE,
|
||||
"categoryIdentifier": "Req",
|
||||
"category": "Req",
|
||||
"assignedTo": WANG,
|
||||
"fieldValueList": [
|
||||
{"fieldIdentifier": "priority", "value": PRI},
|
||||
{"fieldIdentifier": "assignedTo", "value": WANG},
|
||||
],
|
||||
"attachmentIdList": [],
|
||||
"cloneFrom": None,
|
||||
"createWorkitemRelationList": [],
|
||||
}
|
||||
|
||||
|
||||
def task_payload(
|
||||
subject: str,
|
||||
html: str,
|
||||
assignee: str,
|
||||
*,
|
||||
plan_start: bool = True,
|
||||
associated_req: str | None = None,
|
||||
parent_delivery: str | None = None,
|
||||
) -> dict:
|
||||
fvl = [
|
||||
{"fieldIdentifier": "priority", "value": PRI},
|
||||
{"fieldIdentifier": "assignedTo", "value": assignee},
|
||||
]
|
||||
if plan_start:
|
||||
fvl.append({"fieldIdentifier": "79", "value": NOON_MS})
|
||||
payload: dict[str, Any] = {
|
||||
"subject": subject,
|
||||
"description": html,
|
||||
"formatType": "RICHTEXT",
|
||||
"spaceIdentifier": SPACE,
|
||||
"space": SPACE,
|
||||
"spaceType": "Project",
|
||||
"workitemTypeIdentifier": TASK_TYPE,
|
||||
"workitemType": TASK_TYPE,
|
||||
"categoryIdentifier": "Task",
|
||||
"category": "Task",
|
||||
"assignedTo": assignee,
|
||||
"fieldValueList": fvl,
|
||||
"attachmentIdList": [],
|
||||
"cloneFrom": None,
|
||||
}
|
||||
if parent_delivery:
|
||||
payload["parent"] = parent_delivery
|
||||
payload["parentIdentifier"] = parent_delivery
|
||||
payload["createWorkitemRelationInfo"] = {
|
||||
"relatedWorkitemIdentifier": parent_delivery,
|
||||
"relatedToRelationIdentifier": "TASK_SUB",
|
||||
}
|
||||
else:
|
||||
if not associated_req:
|
||||
raise ValueError("associated_req required for delivery: ASSOCIATED→需求")
|
||||
payload["createWorkitemRelationInfo"] = {
|
||||
"relatedWorkitemIdentifier": associated_req,
|
||||
"relatedToRelationIdentifier": "ASSOCIATED",
|
||||
}
|
||||
return payload
|
||||
|
||||
|
||||
def list_associated(s: requests.Session, wid: str) -> list[dict]:
|
||||
j = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{wid}/relation/workitem/list/by-relation-category?category=ASSOCIATED&isForward=true",
|
||||
)
|
||||
return j.get("result") or []
|
||||
|
||||
|
||||
def assert_associated_to_req(s: requests.Session, wid: str, req_id: str, label: str) -> None:
|
||||
rows = list_associated(s, wid)
|
||||
ids = {r.get("identifier") for r in rows}
|
||||
if req_id not in ids:
|
||||
raise RuntimeError(
|
||||
f"{label} 关联项未挂需求:期望 {req_id},实际 {[r.get('serialNumber') for r in rows]}"
|
||||
)
|
||||
|
||||
|
||||
AUTO_RDO = """## 原始诉求(AutoRDO)
|
||||
|
||||
运维需在故障处置页承接机器人上报的故障,完成处置、挂起与归档,并保留证据链;工作台相关统计口径需与处置页一致
|
||||
|
||||
待确认:
|
||||
- 本期是否含真实短信/邮件通道(现口径一般为演示模板)
|
||||
|
||||
## 工作项编号(系统)
|
||||
- 交付:待建
|
||||
- 分析:待建
|
||||
- 设计:待建
|
||||
"""
|
||||
|
||||
|
||||
def summarize_from_create(w: dict, *, status: str, assignee_name: str) -> dict:
|
||||
return {
|
||||
"serial": w.get("serialNumber"),
|
||||
"id": w.get("identifier"),
|
||||
"subject": w.get("subject"),
|
||||
"status": status,
|
||||
"assignee": assignee_name,
|
||||
"parent": w.get("parentIdentifier"),
|
||||
}
|
||||
|
||||
|
||||
def build_normal() -> dict:
|
||||
t0 = time.perf_counter()
|
||||
s = session()
|
||||
title = "【新增】故障处置(YunxiaoPMapp标准·极速v2)"
|
||||
req = create(s, req_payload(title, md_to_html(AUTO_RDO + f"\n原型:{PROTO}\n")))
|
||||
rid = req["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_r = pool.submit(apply_tag, session(), rid)
|
||||
f_deliv = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【交付】{title}",
|
||||
f"<p>{PLACEHOLDER}</p>",
|
||||
HEFEI,
|
||||
associated_req=rid,
|
||||
),
|
||||
)
|
||||
deliv = f_deliv.result()
|
||||
f_tag_r.result()
|
||||
did = deliv["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=3) as pool:
|
||||
f_tag_d = pool.submit(apply_tag, session(), did)
|
||||
f_ana = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【分析】{title}",
|
||||
"<p>分析阶段:故障处置台账、挂起归档与证据链。</p>",
|
||||
WANG,
|
||||
associated_req=rid,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
f_des = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【设计】{title}",
|
||||
f"<p>设计阶段:对齐原型 {PROTO}</p>",
|
||||
WANG,
|
||||
associated_req=rid,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
ana = f_ana.result()
|
||||
des = f_des.result()
|
||||
f_tag_d.result()
|
||||
|
||||
with ThreadPoolExecutor(max_workers=3) as pool:
|
||||
list(
|
||||
as_completed(
|
||||
[
|
||||
pool.submit(transit, session(), rid, PENDING, DESIGN_DONE),
|
||||
pool.submit(transit, session(), ana["identifier"], PENDING, TASK_DONE),
|
||||
pool.submit(transit, session(), des["identifier"], PENDING, TASK_DONE),
|
||||
]
|
||||
)
|
||||
)
|
||||
transit(s, rid, DESIGN_DONE, PENDING_DEV)
|
||||
|
||||
return {
|
||||
"path": "normal",
|
||||
"elapsed_s": round(time.perf_counter() - t0, 3),
|
||||
"req": summarize_from_create(req, status="待开发", assignee_name="王冕"),
|
||||
"delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"),
|
||||
"analysis": summarize_from_create(ana, status="已完成", assignee_name="王冕"),
|
||||
"design": summarize_from_create(des, status="已完成", assignee_name="王冕"),
|
||||
}
|
||||
|
||||
|
||||
def build_fast(
|
||||
*,
|
||||
tag_id: str = TAG_FAULT,
|
||||
has_prototype: bool = True,
|
||||
delivery_html: str | None = None,
|
||||
) -> dict:
|
||||
"""无单快轨。
|
||||
|
||||
- 设计描述 = 需求 document
|
||||
- 交付描述 = 手工同步需求正文(无原型)或传入 AutoPRD HTML;禁止无故占位
|
||||
- 设计 79/80=当日;交付 79=当日
|
||||
- 需求/交付/设计同标签
|
||||
- 需求预计/实际工时各 2
|
||||
- 设计 TASK_SUB→交付后尝试 ASSOCIATED→需求
|
||||
"""
|
||||
t0 = time.perf_counter()
|
||||
s = session()
|
||||
title = "【新增】故障处置(YunxiaoPM快轨·极速v5)"
|
||||
req_html = md_to_html(AUTO_RDO + (f"\n原型:{PROTO}\n" if has_prototype else "\n"))
|
||||
req = create(s, req_payload(title, req_html))
|
||||
rid = req["identifier"]
|
||||
# 以落库 document 为准(与手动建需后读需求一致)
|
||||
req_html = req_document_html(s, rid) or req_html
|
||||
|
||||
if delivery_html:
|
||||
deliv_html = delivery_html
|
||||
desc_source = "autoprd"
|
||||
elif req_html.strip():
|
||||
deliv_html = req_html
|
||||
desc_source = "manual_sync"
|
||||
else:
|
||||
deliv_html = f"<p>{PLACEHOLDER}</p>"
|
||||
desc_source = "placeholder"
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_r = pool.submit(apply_tag, session(), rid, tag_id)
|
||||
f_deliv = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【交付】{title}",
|
||||
deliv_html,
|
||||
HEFEI,
|
||||
associated_req=rid,
|
||||
),
|
||||
)
|
||||
deliv = f_deliv.result()
|
||||
f_tag_r.result()
|
||||
did = deliv["identifier"]
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_tag_d = pool.submit(apply_tag, session(), did, tag_id)
|
||||
f_des = pool.submit(
|
||||
create,
|
||||
session(),
|
||||
task_payload(
|
||||
f"【设计】{title}",
|
||||
req_html,
|
||||
WANG,
|
||||
parent_delivery=did,
|
||||
),
|
||||
)
|
||||
des = f_des.result()
|
||||
f_tag_d.result()
|
||||
des_id = des["identifier"]
|
||||
apply_tag(s, des_id, tag_id)
|
||||
|
||||
# 计划时间:设计起止当日;交付开始当日(create 已带 79,再 field/value 加固)
|
||||
set_fields(s, des_id, [("79", NOON), ("80", EOD)])
|
||||
set_fields(s, did, [("79", NOON)])
|
||||
|
||||
# 设计关联项补挂需求(可能失败,回报 risk)
|
||||
design_assoc_ok = try_associate_to_req(s, des_id, rid)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
list(
|
||||
as_completed(
|
||||
[
|
||||
pool.submit(transit, session(), rid, PENDING, DESIGN_DONE),
|
||||
pool.submit(transit, session(), des_id, PENDING, TASK_DONE),
|
||||
]
|
||||
)
|
||||
)
|
||||
transit(s, rid, DESIGN_DONE, PENDING_DEV)
|
||||
|
||||
set_estimate_hours(s, rid, FAST_EST)
|
||||
set_actual_hours(s, rid, FAST_ACT)
|
||||
|
||||
risk = None
|
||||
if desc_source == "placeholder":
|
||||
risk = "交付描述仍为占位"
|
||||
if not design_assoc_ok:
|
||||
risk = (risk + ";" if risk else "") + "设计 ASSOCIATED→需求补挂失败(Cookie),须 UI/OpenAPI 兜底"
|
||||
|
||||
return {
|
||||
"path": "fast",
|
||||
"elapsed_s": round(time.perf_counter() - t0, 3),
|
||||
"req": summarize_from_create(req, status="待开发", assignee_name="王冕"),
|
||||
"delivery": summarize_from_create(deliv, status="待处理", assignee_name="何斐"),
|
||||
"design": summarize_from_create(des, status="已完成", assignee_name="王冕"),
|
||||
"analysis": None,
|
||||
"delivery_desc_source": desc_source,
|
||||
"design_associated_ok": design_assoc_ok,
|
||||
"risk": risk,
|
||||
}
|
||||
|
||||
|
||||
def main() -> None:
|
||||
auth0 = time.perf_counter()
|
||||
load_auth()
|
||||
auth_s = round(time.perf_counter() - auth0, 3)
|
||||
|
||||
wall0 = time.perf_counter()
|
||||
with ThreadPoolExecutor(max_workers=2) as pool:
|
||||
f_fast = pool.submit(build_fast)
|
||||
f_normal = pool.submit(build_normal)
|
||||
fast = f_fast.result()
|
||||
normal = f_normal.result()
|
||||
build_s = round(time.perf_counter() - wall0, 3)
|
||||
|
||||
s = session()
|
||||
assert_associated_to_req(s, normal["delivery"]["id"], normal["req"]["id"], "normal.delivery")
|
||||
assert_associated_to_req(s, fast["delivery"]["id"], fast["req"]["id"], "fast.delivery")
|
||||
|
||||
def assert_sub(delivery_id: str, child_id: str, label: str) -> None:
|
||||
rows = api(
|
||||
s,
|
||||
"GET",
|
||||
f"https://devops.aliyun.com/projex/api/workitem/v2/workitem/{delivery_id}/relation/workitem/list/by-relation-category?category=PARENT_SUB&isForward=true",
|
||||
).get("result") or []
|
||||
ids = {r.get("identifier") for r in rows}
|
||||
if child_id not in ids:
|
||||
raise RuntimeError(
|
||||
f"{label} 未出现在交付子项:期望 {child_id},实际 {[r.get('serialNumber') for r in rows]}"
|
||||
)
|
||||
|
||||
assert_sub(normal["delivery"]["id"], normal["analysis"]["id"], "normal.analysis")
|
||||
assert_sub(normal["delivery"]["id"], normal["design"]["id"], "normal.design")
|
||||
assert_sub(fast["delivery"]["id"], fast["design"]["id"], "fast.design")
|
||||
verify = {
|
||||
"normal_req": get(s, normal["req"]["id"])["status"]["displayName"],
|
||||
"fast_req": get(s, fast["req"]["id"])["status"]["displayName"],
|
||||
"normal_delivery_assignee": (
|
||||
get(s, normal["delivery"]["id"]).get("assignedTo") or {}
|
||||
).get("displayName"),
|
||||
"fast_delivery_assignee": (get(s, fast["delivery"]["id"]).get("assignedTo") or {}).get(
|
||||
"displayName"
|
||||
),
|
||||
"delivery_associated_ok": True,
|
||||
"stage_tasks_as_sub_ok": True,
|
||||
"fast_design_associated_ok": fast.get("design_associated_ok"),
|
||||
}
|
||||
out = {
|
||||
"normal": normal,
|
||||
"fast": fast,
|
||||
"verify": verify,
|
||||
"auth_elapsed_s": auth_s,
|
||||
"wall_elapsed_s": build_s,
|
||||
"created_at": datetime.now(TZ).isoformat(),
|
||||
"mode": "live_create_fast_v5_fast_track_rules",
|
||||
"opts": {
|
||||
"create_includes_plan_start": True,
|
||||
"skip_plan_end_on_create": True,
|
||||
"tracked_transit_no_get": True,
|
||||
"delivery_assignee_hefei_at_create": True,
|
||||
"overlap_tag_with_create": True,
|
||||
"fast_req_hours": f"{FAST_EST}+{FAST_ACT}",
|
||||
"relation": "delivery ASSOCIATED→req; analysis/design TASK_SUB→delivery; design post ASSOCIATED→req",
|
||||
"http": "requests.Session keep-alive",
|
||||
},
|
||||
}
|
||||
Path("/tmp/yunxiao_pmapp_fast_v2_result.json").write_text(
|
||||
json.dumps(out, ensure_ascii=False, indent=2)
|
||||
)
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
92
.claude/skills/YunxiaoPM/scripts/workday_hours.py
Executable file
92
.claude/skills/YunxiaoPM/scripts/workday_hours.py
Executable file
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Compute YunxiaoPMapp stage calendar hours = workdays × 8.
|
||||
|
||||
Usage:
|
||||
python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD
|
||||
python3 workday_hours.py YYYY-MM-DD YYYY-MM-DD --calendar /path/to/cn-workday-calendar.json
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def parse_day(s: str) -> date:
|
||||
return datetime.strptime(s, "%Y-%m-%d").date()
|
||||
|
||||
|
||||
def load_calendar(path: Path) -> dict:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
years = data.get("years") or {}
|
||||
holidays: set[str] = set()
|
||||
makeup: set[str] = set()
|
||||
for _y, block in years.items():
|
||||
holidays.update(block.get("holidays") or [])
|
||||
makeup.update(block.get("workdays_on_weekend") or [])
|
||||
return {"holidays": holidays, "makeup": makeup, "years": set(years.keys())}
|
||||
|
||||
|
||||
def ensure_years_covered(start: date, end: date, years: set[str]) -> None:
|
||||
y = start.year
|
||||
while y <= end.year:
|
||||
if str(y) not in years:
|
||||
raise SystemExit(
|
||||
f"缺少 {y} 年日历:请在 assets/cn-workday-calendar.json 补全后再计算。"
|
||||
"禁止仅去周末静默估算。"
|
||||
)
|
||||
y += 1
|
||||
|
||||
|
||||
def is_workday(d: date, holidays: set[str], makeup: set[str]) -> bool:
|
||||
key = d.isoformat()
|
||||
if key in holidays:
|
||||
return False
|
||||
if key in makeup:
|
||||
return True
|
||||
return d.weekday() < 5 # Mon=0 .. Fri=4
|
||||
|
||||
|
||||
def count_workdays(start: date, end: date, holidays: set[str], makeup: set[str]) -> int:
|
||||
if end < start:
|
||||
raise SystemExit("结束日早于开始日")
|
||||
n = 0
|
||||
cur = start
|
||||
while cur <= end:
|
||||
if is_workday(cur, holidays, makeup):
|
||||
n += 1
|
||||
cur += timedelta(days=1)
|
||||
return n
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="阶段日历工时 = 工作日×8")
|
||||
parser.add_argument("start", help="计划开始 YYYY-MM-DD")
|
||||
parser.add_argument("end", help="计划完成 YYYY-MM-DD")
|
||||
parser.add_argument(
|
||||
"--calendar",
|
||||
default=str(Path(__file__).resolve().parent.parent / "assets" / "cn-workday-calendar.json"),
|
||||
help="日历 JSON 路径",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
start = parse_day(args.start)
|
||||
end = parse_day(args.end)
|
||||
cal = load_calendar(Path(args.calendar))
|
||||
ensure_years_covered(start, end, cal["years"])
|
||||
days = count_workdays(start, end, cal["holidays"], cal["makeup"])
|
||||
hours = days * 8
|
||||
out = {
|
||||
"start": start.isoformat(),
|
||||
"end": end.isoformat(),
|
||||
"workdays": days,
|
||||
"stage_calendar_hours": hours,
|
||||
"label": "阶段日历工时(工作日×8),非人力投入预估",
|
||||
}
|
||||
print(json.dumps(out, ensure_ascii=False, indent=2))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
63
.claude/skills/YunxiaoPM/stage-flow.md
Normal file
63
.claude/skills/YunxiaoPM/stage-flow.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# 标准路径:待处理 → 待开发交棒
|
||||
|
||||
项目常量见 [../assets/runtime-ids.json](../assets/runtime-ids.json)。查重只用任务编号。
|
||||
|
||||
## 步骤 0|创建需求
|
||||
|
||||
| 云效动作 | 落地 |
|
||||
|---|---|
|
||||
| 新建产品类需求 | POST 建单;标题 `【新增】`/`【优化】` |
|
||||
| 需求描述 | **先** AutoRDO 清洗再写入 `## 原始诉求(AutoRDO)`;本步不写 AutoPRD |
|
||||
| 默认状态 | **待处理**;不建任务、不改负责人 |
|
||||
| 可选推进 | 口令点选目标状态;未选则停在待处理 |
|
||||
|
||||
## 步骤 1|受理 → 已确认
|
||||
|
||||
| 云效动作 | 落地 |
|
||||
|---|---|
|
||||
| 状态 | 待处理 → **已确认** |
|
||||
| 任务 | **仍不建**交付/分析/设计 |
|
||||
|
||||
若用户只「创建」未受理:只提示「受理后请推进至已确认」,不自动跳。
|
||||
|
||||
## 步骤 2|分析中
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【交付】 | 无则新建;标题 `【交付】`+需求标题;**ASSOCIATED→需求**(勿用 PARENT);负责人=创建人/产品;**计划开始仅空时写当日**;不写计划完成;描述=`等待设计任务完成后自动填入`;更新编号区块 |
|
||||
| 【分析】 | 新建;**TASK_SUB→交付**(`parent`+`parentIdentifier`+`createWorkitemRelationInfo=TASK_SUB`);交付「子项」须可见;计划开始=当日(仅空时);负责人默认可=创建人;更新编号区块 |
|
||||
|
||||
| 需求 | 状态=`分析中` |
|
||||
|
||||
## 步骤 3|设计中
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【交付】 | 已存在则按编号复用;没有则补建;**不改交付计划开始**;不写交付计划完成 |
|
||||
| 【设计】 | 新建;**TASK_SUB→交付**(同分析);计划开始=当日(仅空时);更新编号区块 |
|
||||
| 【分析】 | **计划完成**=状态更新日;推算阶段日历工时(见 work-hours) |
|
||||
| 需求 | 状态=`设计中` |
|
||||
|
||||
## 步骤 4|设计完成
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 【设计】 | 计划完成=当日;推算工时;任务→完成态 |
|
||||
| 需求 | AutoPRD 写入 `## 产品说明`;挂 ZIP+截图;状态=`设计完成` |
|
||||
| 【交付】 | 不换负责人;不改计划开始;不写计划完成;描述改为 AutoPRD 正文;同样挂附件 |
|
||||
|
||||
顺序与失败门禁见 [description-split.md](description-split.md)。
|
||||
|
||||
## 步骤 5|待开发交棒(本 Skill 终点 · 标准路径)
|
||||
|
||||
| 对象 | 动作 |
|
||||
|---|---|
|
||||
| 需求 | 状态=`待开发` |
|
||||
| 【交付】 | 按**任务编号**定位;**负责人→何斐**;不改计划开始;不写计划完成 |
|
||||
| 【分析】/【设计】 | 不新建;不改已有计划开始;前序未收口按步骤 3/4 处理 |
|
||||
|
||||
执行 [handoff-and-rollback.md](handoff-and-rollback.md) §交棒门禁。
|
||||
|
||||
**不做:** 【开发】/【测试】、仓库、分支、提测。
|
||||
|
||||
快轨 / 编号直推见专文。创建迭代见 [sprint.md](sprint.md)。
|
||||
Reference in New Issue
Block a user