ai-engineering-init 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/code-reviewer.md +3 -130
- package/.claude/hooks/skill-forced-eval.js +2 -0
- package/.claude/hooks/stop.js +24 -1
- package/.claude/skills/codex-code-review/SKILL.md +327 -0
- package/.claude/skills/leniu-report-customization/SKILL.md +82 -2
- package/.claude/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.claude/skills/loki-log-query/SKILL.md +400 -0
- package/.claude/skills/mysql-debug/SKILL.md +58 -22
- package/.claude/skills/skill-creator/LICENSE.txt +202 -0
- package/.claude/skills/skill-creator/SKILL.md +479 -0
- package/.claude/skills/skill-creator/agents/analyzer.md +274 -0
- package/.claude/skills/skill-creator/agents/comparator.md +202 -0
- package/.claude/skills/skill-creator/agents/grader.md +223 -0
- package/.claude/skills/skill-creator/assets/eval_review.html +146 -0
- package/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.claude/skills/skill-creator/references/schemas.md +430 -0
- package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
- package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.claude/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.claude/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.claude/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.claude/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.claude/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.claude/skills/skill-creator/scripts/utils.py +47 -0
- package/.claude/skills/sync-back-merge/SKILL.md +66 -0
- package/.claude/skills/yunxiao-task-management/SKILL.md +489 -0
- package/.codex/skills/leniu-report-customization/SKILL.md +82 -2
- package/.codex/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.codex/skills/loki-log-query/SKILL.md +400 -0
- package/.codex/skills/loki-log-query/environments.json +45 -0
- package/.codex/skills/mysql-debug/SKILL.md +58 -22
- package/.codex/skills/skill-creator/LICENSE.txt +202 -0
- package/.codex/skills/skill-creator/SKILL.md +479 -0
- package/.codex/skills/skill-creator/agents/analyzer.md +274 -0
- package/.codex/skills/skill-creator/agents/comparator.md +202 -0
- package/.codex/skills/skill-creator/agents/grader.md +223 -0
- package/.codex/skills/skill-creator/assets/eval_review.html +146 -0
- package/.codex/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.codex/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.codex/skills/skill-creator/references/schemas.md +430 -0
- package/.codex/skills/skill-creator/scripts/__init__.py +0 -0
- package/.codex/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.codex/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.codex/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.codex/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.codex/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.codex/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.codex/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.codex/skills/skill-creator/scripts/utils.py +47 -0
- package/.codex/skills/sync-back-merge/SKILL.md +66 -0
- package/.codex/skills/yunxiao-task-management/SKILL.md +489 -0
- package/.cursor/hooks/stop.js +23 -1
- package/.cursor/skills/leniu-report-customization/SKILL.md +82 -2
- package/.cursor/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/.cursor/skills/loki-log-query/SKILL.md +400 -0
- package/.cursor/skills/loki-log-query/environments.json +45 -0
- package/.cursor/skills/mysql-debug/SKILL.md +58 -22
- package/.cursor/skills/skill-creator/LICENSE.txt +202 -0
- package/.cursor/skills/skill-creator/SKILL.md +479 -0
- package/.cursor/skills/skill-creator/agents/analyzer.md +274 -0
- package/.cursor/skills/skill-creator/agents/comparator.md +202 -0
- package/.cursor/skills/skill-creator/agents/grader.md +223 -0
- package/.cursor/skills/skill-creator/assets/eval_review.html +146 -0
- package/.cursor/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/.cursor/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/.cursor/skills/skill-creator/references/schemas.md +430 -0
- package/.cursor/skills/skill-creator/scripts/__init__.py +0 -0
- package/.cursor/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/.cursor/skills/skill-creator/scripts/generate_report.py +326 -0
- package/.cursor/skills/skill-creator/scripts/improve_description.py +248 -0
- package/.cursor/skills/skill-creator/scripts/package_skill.py +136 -0
- package/.cursor/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/.cursor/skills/skill-creator/scripts/run_eval.py +310 -0
- package/.cursor/skills/skill-creator/scripts/run_loop.py +332 -0
- package/.cursor/skills/skill-creator/scripts/utils.py +47 -0
- package/.cursor/skills/sync-back-merge/SKILL.md +66 -0
- package/.cursor/skills/yunxiao-task-management/SKILL.md +489 -0
- package/bin/index.js +606 -24
- package/package.json +1 -1
- package/src/platform-map.json +4 -0
- package/src/skills/codex-code-review/SKILL.md +261 -69
- package/src/skills/leniu-report-customization/SKILL.md +82 -2
- package/src/skills/leniu-report-standard-customization/SKILL.md +65 -2
- package/src/skills/loki-log-query/SKILL.md +400 -0
- package/src/skills/loki-log-query/environments.json +45 -0
- package/src/skills/mysql-debug/SKILL.md +58 -22
- package/src/skills/skill-creator/LICENSE.txt +202 -0
- package/src/skills/skill-creator/SKILL.md +479 -0
- package/src/skills/skill-creator/agents/analyzer.md +274 -0
- package/src/skills/skill-creator/agents/comparator.md +202 -0
- package/src/skills/skill-creator/agents/grader.md +223 -0
- package/src/skills/skill-creator/assets/eval_review.html +146 -0
- package/src/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/src/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/src/skills/skill-creator/references/schemas.md +430 -0
- package/src/skills/skill-creator/scripts/__init__.py +0 -0
- package/src/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/src/skills/skill-creator/scripts/generate_report.py +326 -0
- package/src/skills/skill-creator/scripts/improve_description.py +248 -0
- package/src/skills/skill-creator/scripts/package_skill.py +136 -0
- package/src/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/src/skills/skill-creator/scripts/run_eval.py +310 -0
- package/src/skills/skill-creator/scripts/run_loop.py +332 -0
- package/src/skills/skill-creator/scripts/utils.py +47 -0
- package/src/skills/sync-back-merge/SKILL.md +66 -0
- package/src/skills/yunxiao-task-management/SKILL.md +489 -0
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yunxiao-task-management
|
|
3
|
+
description: |
|
|
4
|
+
阿里云云效(Yunxiao)项目协作任务管理技能。通过云效 Open API 直接操作工作项:查询任务、修改状态、编辑描述、添加评论、记录工时、创建子任务、搜索项目等。
|
|
5
|
+
支持完整开发工作流:读取任务 → 查找父需求 → 读取需求详情 → 创建提测单 → 编辑提测单 → 转给测试。
|
|
6
|
+
当用户提到"云效"、"任务管理"、"工作项"、"修改状态"、"添加评论"、"记录工时"、"创建子任务"、"查询任务"、"项目任务"、"提测"、"提测单"、"父需求"、"需求详情"、"SARW"、"EZML"、"IXXP"或其他云效项目编号时使用此技能。
|
|
7
|
+
即使用户只是简单说"把XXX改为开发完成"、"帮我查一下我的待处理任务"、"读取EZML-1878"、"创建提测单",也应该触发此技能。
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 云效任务管理 Skill
|
|
11
|
+
|
|
12
|
+
通过阿里云云效 Open API 管理项目工作项。所有操作使用 Node.js fetch 直接调用 API。
|
|
13
|
+
|
|
14
|
+
## 前置配置
|
|
15
|
+
|
|
16
|
+
从 `.claude/settings.json` 的 `mcpServers.yunxiao.env` 中读取 Token:
|
|
17
|
+
```
|
|
18
|
+
YUNXIAO_ACCESS_TOKEN: 个人访问令牌
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**已知用户信息**:
|
|
22
|
+
- 组织 ID: `61dbcd725356b19beeb1dc03`
|
|
23
|
+
- 用户: 徐嘉骏(ID: `66286d4b06679a65daed4d28`)
|
|
24
|
+
- 常用项目: SARW(`4574cb1c653fe873335b6c4716`)、EZML(`6f99a4e627fa88d6f8cb541e6c`)
|
|
25
|
+
|
|
26
|
+
## API 基础信息
|
|
27
|
+
|
|
28
|
+
| 项目 | 值 |
|
|
29
|
+
|------|-----|
|
|
30
|
+
| Base URL | `https://openapi-rdc.aliyuncs.com` |
|
|
31
|
+
| 认证方式 | 请求头 `x-yunxiao-token: {token}` |
|
|
32
|
+
| 内容类型 | `application/json` |
|
|
33
|
+
| 成功响应 | 200(有 body)或 204(无 body) |
|
|
34
|
+
|
|
35
|
+
## 通用请求模板
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
async function yunxiaoReq(path, opts = {}) {
|
|
39
|
+
const TOKEN = "从settings.json读取";
|
|
40
|
+
const BASE = "https://openapi-rdc.aliyuncs.com";
|
|
41
|
+
const r = await fetch(BASE + path, {
|
|
42
|
+
method: opts.method || "GET",
|
|
43
|
+
headers: {
|
|
44
|
+
"Accept": "application/json",
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
"x-yunxiao-token": TOKEN
|
|
47
|
+
},
|
|
48
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined
|
|
49
|
+
});
|
|
50
|
+
if (r.status === 204) return { success: true };
|
|
51
|
+
const text = await r.text();
|
|
52
|
+
try { return JSON.parse(text); } catch { return text; }
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## API 操作清单
|
|
57
|
+
|
|
58
|
+
### 1. 搜索项目
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
POST /oapi/v1/projex/organizations/{orgId}/projects:search?page={page}&perPage={perPage}
|
|
62
|
+
Body: {} 或 { keyword: "关键词" }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
返回数组,每个项目包含 `id`、`name`、`customCode`(如 SARW、IXXP)。
|
|
66
|
+
|
|
67
|
+
**按成员过滤项目**(仅获取用户参与的项目,大幅减少搜索范围):
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"conditions": "{\"conditionGroups\":[[{\"className\":\"user\",\"fieldIdentifier\":\"members\",\"format\":\"list\",\"operator\":\"CONTAINS\",\"toValue\":null,\"value\":[\"用户ID\"]}]]}"
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2. 搜索工作项
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
POST /oapi/v1/projex/organizations/{orgId}/workitems:search
|
|
78
|
+
Body: {
|
|
79
|
+
spaceIdentifier: "项目ID",
|
|
80
|
+
spaceId: "项目ID",
|
|
81
|
+
category: "Task", // Task | Req | Bug | Risk
|
|
82
|
+
page: 1,
|
|
83
|
+
perPage: 200
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**⚠️ 必须指定 spaceId/spaceIdentifier**,不支持跨项目搜索。
|
|
88
|
+
|
|
89
|
+
返回数组。每个工作项关键字段:
|
|
90
|
+
- `serialNumber`: 编号(如 SARW-87)
|
|
91
|
+
- `id`: 工作项 ID
|
|
92
|
+
- `subject`: 标题
|
|
93
|
+
- `status`: `{ name, id }` — 状态名和状态 ID
|
|
94
|
+
- `assignedTo`: `{ name, id }` — 负责人
|
|
95
|
+
|
|
96
|
+
#### conditions 高级过滤(重要!)
|
|
97
|
+
|
|
98
|
+
搜索工作项支持 `conditions` 参数进行服务端过滤,避免全量拉取再本地筛选。`conditions` 是 **JSON 字符串**,格式:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"conditionGroups": [[
|
|
103
|
+
{ "className": "类型", "fieldIdentifier": "字段", "format": "格式", "operator": "操作符", "toValue": null, "value": ["值"] }
|
|
104
|
+
]]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**按负责人过滤**(最常用):
|
|
109
|
+
```json
|
|
110
|
+
{ "className": "user", "fieldIdentifier": "assignedTo", "format": "list", "operator": "CONTAINS", "toValue": null, "value": ["用户ID"] }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**排除已完成/已取消状态**(只查未完成的任务,最实用):
|
|
114
|
+
```json
|
|
115
|
+
{ "className": "status", "fieldIdentifier": "status", "format": "list", "operator": "NOT_CONTAINS", "toValue": null, "value": ["100014", "141230"] }
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
> `statusStage` 字段在 API 返回中为 undefined,不可用于过滤。用 `status` + `NOT_CONTAINS` 排除特定状态 ID 代替。
|
|
119
|
+
|
|
120
|
+
**按创建人过滤**:
|
|
121
|
+
```json
|
|
122
|
+
{ "className": "user", "fieldIdentifier": "creator", "format": "list", "operator": "CONTAINS", "toValue": null, "value": ["用户ID"] }
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
**按优先级过滤**:
|
|
126
|
+
```json
|
|
127
|
+
{ "className": "option", "fieldIdentifier": "priority", "format": "list", "operator": "CONTAINS", "toValue": null, "value": ["优先级值"] }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**组合多个条件**(放在同一个数组内为 AND 关系):
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"conditionGroups": [[
|
|
134
|
+
{ "className": "user", "fieldIdentifier": "assignedTo", "format": "list", "operator": "CONTAINS", "toValue": null, "value": ["用户ID"] },
|
|
135
|
+
{ "className": "statusStage", "fieldIdentifier": "statusStage", "format": "list", "operator": "CONTAINS", "toValue": null, "value": ["TODO", "DOING"] }
|
|
136
|
+
]]
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
> **注意**:`conditions` 传给 API 时必须是 **JSON 字符串**(`JSON.stringify(obj)`),不是对象。
|
|
141
|
+
|
|
142
|
+
### 3. 获取工作项详情
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
GET /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 4. 编辑工作项(标题/描述/状态/负责人)
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
PUT /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
**修改状态**:
|
|
155
|
+
```json
|
|
156
|
+
{ "status": "100011" }
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
常见状态 ID(从项目中的工作项动态获取,以下为参考值):
|
|
160
|
+
|
|
161
|
+
| 状态名 | 状态 ID |
|
|
162
|
+
|--------|---------|
|
|
163
|
+
| 待处理 | 100005 |
|
|
164
|
+
| 处理中 | 100010 |
|
|
165
|
+
| 开发完成 | 100011 |
|
|
166
|
+
| 已完成 | 100014 |
|
|
167
|
+
| 已取消 | 141230 |
|
|
168
|
+
|
|
169
|
+
> 不同项目的状态 ID 可能不同!先从该项目的工作项中收集已有状态,再使用对应 ID。
|
|
170
|
+
|
|
171
|
+
**修改描述**:
|
|
172
|
+
```json
|
|
173
|
+
{ "description": "新的描述内容" }
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
**修改标题**:
|
|
177
|
+
```json
|
|
178
|
+
{ "subject": "新标题" }
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**修改负责人**:
|
|
182
|
+
```json
|
|
183
|
+
{ "assignedTo": "用户ID" }
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
可组合多个字段一次更新。成功返回 **HTTP 204**(无 body)。
|
|
187
|
+
|
|
188
|
+
### 5. 添加评论
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
POST /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/comments
|
|
192
|
+
Body: { "content": "评论内容" }
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
返回 `{ id: "评论ID" }`。
|
|
196
|
+
|
|
197
|
+
**获取评论列表**:
|
|
198
|
+
```
|
|
199
|
+
GET /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/comments?page=1&perPage=20
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 6. 创建子任务
|
|
203
|
+
|
|
204
|
+
```
|
|
205
|
+
POST /oapi/v1/projex/organizations/{orgId}/workitems
|
|
206
|
+
Body: {
|
|
207
|
+
"spaceId": "项目ID",
|
|
208
|
+
"subject": "子任务标题",
|
|
209
|
+
"workitemTypeId": "任务类型ID",
|
|
210
|
+
"assignedTo": "用户ID", // 必需!
|
|
211
|
+
"parentId": "父工作项ID", // 关键:建立父子关系
|
|
212
|
+
"description": "描述(可选)"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
> `assignedTo` 虽然看似可选,但 API 强制要求,缺少会报错。
|
|
217
|
+
|
|
218
|
+
**获取任务类型 ID**:
|
|
219
|
+
```
|
|
220
|
+
GET /oapi/v1/projex/organizations/{orgId}/projects/{projectId}/workitemTypes?category=Task
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 7. 工时管理
|
|
224
|
+
|
|
225
|
+
#### 预估工时
|
|
226
|
+
|
|
227
|
+
**查看**:
|
|
228
|
+
```
|
|
229
|
+
GET /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/estimatedEfforts
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
**创建**(单位:分钟):
|
|
233
|
+
```
|
|
234
|
+
POST /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/estimatedEfforts
|
|
235
|
+
Body: {
|
|
236
|
+
"spentTime": 480, // 480分钟 = 8小时
|
|
237
|
+
"description": "预估工时描述",
|
|
238
|
+
"owner": "用户ID"
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
#### 实际工时
|
|
243
|
+
|
|
244
|
+
**查看**:
|
|
245
|
+
```
|
|
246
|
+
GET /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/effortRecords
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**创建**:
|
|
250
|
+
```
|
|
251
|
+
POST /oapi/v1/projex/organizations/{orgId}/workitems/{workitemId}/effortRecords
|
|
252
|
+
Body: {
|
|
253
|
+
"actualTime": 240, // 240分钟 = 4小时
|
|
254
|
+
"description": "实际工时描述",
|
|
255
|
+
"gmtStart": 1772800000000, // 开始时间(毫秒时间戳)
|
|
256
|
+
"gmtEnd": 1772803000000 // 结束时间(毫秒时间戳)
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### 8. 跨项目搜索用户任务(优化策略)
|
|
261
|
+
|
|
262
|
+
云效 API 的 `workitems:search` 必须指定 `spaceId`,不支持全局搜索。推荐优化流程:
|
|
263
|
+
|
|
264
|
+
**第一步:缩小项目范围** — 用 `members` conditions 只获取用户参与的项目:
|
|
265
|
+
```javascript
|
|
266
|
+
const memberConditions = JSON.stringify({
|
|
267
|
+
conditionGroups: [[{
|
|
268
|
+
className: "user", fieldIdentifier: "members",
|
|
269
|
+
format: "list", operator: "CONTAINS", toValue: null, value: [USER_ID]
|
|
270
|
+
}]]
|
|
271
|
+
});
|
|
272
|
+
// POST projects:search with body: { conditions: memberConditions }
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
**第二步:服务端过滤任务** — 用 `assignedTo` + `status NOT_CONTAINS` 只返回自己未完成的任务:
|
|
276
|
+
```javascript
|
|
277
|
+
const taskConditions = JSON.stringify({
|
|
278
|
+
conditionGroups: [[
|
|
279
|
+
{ className: "user", fieldIdentifier: "assignedTo", format: "list", operator: "CONTAINS", toValue: null, value: [USER_ID] },
|
|
280
|
+
{ className: "status", fieldIdentifier: "status", format: "list", operator: "NOT_CONTAINS", toValue: null, value: ["100014", "141230"] }
|
|
281
|
+
]]
|
|
282
|
+
});
|
|
283
|
+
// POST workitems:search with body: { spaceId, category: "Task", conditions: taskConditions, ... }
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**第三步:并发批处理**(15 个项目一批):
|
|
287
|
+
```javascript
|
|
288
|
+
const batchSize = 15;
|
|
289
|
+
for (let i = 0; i < projects.length; i += batchSize) {
|
|
290
|
+
const batch = projects.slice(i, i + batchSize);
|
|
291
|
+
const results = await Promise.all(batch.map(p => searchWorkitems(p.id, taskConditions)));
|
|
292
|
+
myTasks.push(...results.flat());
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
> 通过 assignedTo + status NOT_CONTAINS 服务端过滤,每个项目只返回该用户的未完成任务,大幅减少数据量。
|
|
297
|
+
> 注意:`members` 项目过滤在实测中未生效(仍返回全量项目),建议直接扫描所有项目但用 batchSize=20 并发处理。
|
|
298
|
+
|
|
299
|
+
## 不支持的操作
|
|
300
|
+
|
|
301
|
+
| 操作 | 原因 |
|
|
302
|
+
|------|------|
|
|
303
|
+
| 添加附件 | API 不支持(504 超时/415 不支持的类型),MCP 源码中无附件相关实现 |
|
|
304
|
+
| 获取用户信息 | `/oapi/v1/platform/user` 需要「用户信息(读取)」权限,项目 Token 不支持 |
|
|
305
|
+
|
|
306
|
+
## 开发工作流(核心流程)
|
|
307
|
+
|
|
308
|
+
用户日常开发工作流,会频繁使用。当用户说"读取任务"、"查看需求"、"创建提测单"、"提测"时触发。
|
|
309
|
+
|
|
310
|
+
### 流程概览
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
读取任务(Task) → 查找父需求(Req) → 读取需求详情 → 开发 → 创建提测单 → 编辑提测单 → 转给测试
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Step 1: 读取任务并找到父需求
|
|
317
|
+
|
|
318
|
+
通过 `serialNumber`(如 EZML-1878)找到任务,任务的 `parentId` 字段指向父需求。
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// 1. 搜索项目中的任务,找到目标任务
|
|
322
|
+
// 项目编号 = serialNumber 的前缀(如 EZML-1878 → EZML)
|
|
323
|
+
// 先搜索项目列表找到 customCode 匹配的项目
|
|
324
|
+
|
|
325
|
+
// 2. 从任务的 parentId 获取父需求
|
|
326
|
+
const task = items.find(i => i.serialNumber === "EZML-1878");
|
|
327
|
+
const parentDetail = await yunxiaoReq(`/oapi/v1/projex/organizations/${ORG}/workitems/${task.parentId}`);
|
|
328
|
+
// parentDetail.categoryId === "Req"
|
|
329
|
+
// parentDetail.serialNumber === "EZML-1877"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**关键字段**:
|
|
333
|
+
- `parentId`: 父工作项 ID(Task → Req 的关联)
|
|
334
|
+
- `categoryId`: 工作项类别(`Task` / `Req` / `Bug` / `Risk`)
|
|
335
|
+
- `workitemType`: `{ name, id }` — 如 `{ name: "产品类需求", id: "9uy29901re573f561d69jn40" }`
|
|
336
|
+
- `space`: `{ name, id }` — 所属项目
|
|
337
|
+
|
|
338
|
+
### Step 2: 读取父需求详情
|
|
339
|
+
|
|
340
|
+
```javascript
|
|
341
|
+
const parentDetail = await yunxiaoReq(`/oapi/v1/projex/organizations/${ORG}/workitems/${parentId}`);
|
|
342
|
+
// description 是 JSON 字符串: { "htmlValue": "<article>...</article>" }
|
|
343
|
+
let desc = parentDetail.description;
|
|
344
|
+
try { desc = JSON.parse(desc).htmlValue; } catch {}
|
|
345
|
+
// 去除 HTML 标签提取纯文本
|
|
346
|
+
const text = desc.replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
需求描述是 HTML 格式,包含:需求背景、原型链接、具体需求列表等。
|
|
350
|
+
|
|
351
|
+
### Step 3: 查看同级子任务
|
|
352
|
+
|
|
353
|
+
查看父需求下的所有子任务(了解分工):
|
|
354
|
+
|
|
355
|
+
```javascript
|
|
356
|
+
// 搜索该项目所有 Task,筛选 parentId 匹配的
|
|
357
|
+
const allTasks = await searchWorkitems(projectId, "Task");
|
|
358
|
+
const siblings = allTasks.filter(t => t.parentId === parentReqId);
|
|
359
|
+
// 展示: serialNumber, subject, status.name, assignedTo.name
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### Step 4: 创建提测单
|
|
363
|
+
|
|
364
|
+
提测单是父需求(Req)下的一个 **Task 子任务**,指定给测试人员。
|
|
365
|
+
|
|
366
|
+
```javascript
|
|
367
|
+
// 获取 Task 类型 ID
|
|
368
|
+
const types = await yunxiaoReq(`/oapi/v1/projex/organizations/${ORG}/projects/${projectId}/workitemTypes?category=Task`);
|
|
369
|
+
const taskTypeId = types[0].id; // 通常只有一个 Task 类型
|
|
370
|
+
|
|
371
|
+
// 创建提测单(本质是创建 Task,parentId 指向父需求)
|
|
372
|
+
await yunxiaoReq(`/oapi/v1/projex/organizations/${ORG}/workitems`, {
|
|
373
|
+
method: "POST",
|
|
374
|
+
body: {
|
|
375
|
+
spaceId: projectId,
|
|
376
|
+
subject: "提测-需求标题摘要",
|
|
377
|
+
workitemTypeId: taskTypeId,
|
|
378
|
+
assignedTo: "测试人员ID", // 必需!
|
|
379
|
+
parentId: parentReqId, // 关键:挂在父需求下
|
|
380
|
+
description: "提测单 HTML 内容" // 见下方模板
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Step 5: 提测单描述模板
|
|
386
|
+
|
|
387
|
+
根据团队实际使用的提测单格式(参考 SARW-117、SARW-28 等),提测单描述包含以下结构:
|
|
388
|
+
|
|
389
|
+
```html
|
|
390
|
+
<article class="4ever-article">
|
|
391
|
+
<p>项目名称:{项目名称}</p>
|
|
392
|
+
<p>项目版本:{版本号}</p>
|
|
393
|
+
<p>项目分支:后端分支:{后端分支} 前端分支:{前端分支} h5分支:{h5分支}</p>
|
|
394
|
+
<p>项目需求:</p>
|
|
395
|
+
<ol>
|
|
396
|
+
<li><div>{需求1描述} <span data-type="mention" data-id="{开发者ID}">@{开发者名}</span></div></li>
|
|
397
|
+
<li><div>{需求2描述} <span data-type="mention" data-id="{开发者ID}">@{开发者名}</span></div></li>
|
|
398
|
+
</ol>
|
|
399
|
+
<p>开发人员:</p>
|
|
400
|
+
<ul style="list-style-type:none">
|
|
401
|
+
<li><div><input type="checkbox" readonly=""> <span data-type="mention" data-id="{ID}">@{名字}</span></div></li>
|
|
402
|
+
</ul>
|
|
403
|
+
<p>自测:</p>
|
|
404
|
+
<p>{自测截图或说明}</p>
|
|
405
|
+
<p>AI扫描:</p>
|
|
406
|
+
<p>{AI扫描结果}</p>
|
|
407
|
+
<p>提测人&验收人</p>
|
|
408
|
+
<p><span data-type="mention" data-id="{提测人ID}">@{提测人}</span> <span data-type="mention" data-id="{验收人ID}">@{验收人}</span></p>
|
|
409
|
+
</article>
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**@提及语法**:`<span data-type="mention" data-id="{用户ID}" data-login="{用户ID}">@{用户名}</span>`
|
|
413
|
+
|
|
414
|
+
### Step 6: 转给测试
|
|
415
|
+
|
|
416
|
+
创建提测单后,将对应的开发任务状态改为"已提测"(如果项目有此状态)或"开发完成":
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
// 修改任务状态为开发完成
|
|
420
|
+
await yunxiaoReq(`/oapi/v1/projex/organizations/${ORG}/workitems/${taskId}`, {
|
|
421
|
+
method: "PUT",
|
|
422
|
+
body: { status: "100011" } // 开发完成
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### 常用工作项类型 ID(按项目动态获取)
|
|
427
|
+
|
|
428
|
+
| 类别 | 类型名 | 用途 |
|
|
429
|
+
|------|--------|------|
|
|
430
|
+
| Task | 任务 | 开发任务、提测单 |
|
|
431
|
+
| Req | 产品类需求 | 产品需求(父级) |
|
|
432
|
+
| Req | 业务类需求 | 业务需求 |
|
|
433
|
+
| Req | 技术类需求 | 技术需求 |
|
|
434
|
+
| Bug | 缺陷 | Bug 单 |
|
|
435
|
+
|
|
436
|
+
> 每个项目的类型 ID 不同,通过 `GET /workitemTypes?category={类别}` 获取。
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## 操作流程示例
|
|
441
|
+
|
|
442
|
+
### 批量修改状态
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
用户:把 SARW-87、SARW-74 改为开发完成
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
1. 搜索 SARW 项目中的工作项,找到 serialNumber 匹配的 ID
|
|
449
|
+
2. 从项目已有工作项中收集状态 → 找到"开发完成"的 ID
|
|
450
|
+
3. 对每个工作项调用 PUT 更新状态
|
|
451
|
+
4. 逐个 GET 验证更新结果
|
|
452
|
+
|
|
453
|
+
### 查询个人任务
|
|
454
|
+
|
|
455
|
+
```
|
|
456
|
+
用户:查看我所有待处理的任务
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
1. 用 `assignedTo` + `status NOT_CONTAINS` conditions 并发搜索每个项目(服务端过滤)
|
|
460
|
+
2. 并发批处理(batchSize=20)扫描所有项目
|
|
461
|
+
3. 汇总展示
|
|
462
|
+
|
|
463
|
+
### 读取任务需求并提测
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
用户:读取 EZML-1878,看看要做什么
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
1. 从 serialNumber 前缀找到项目(EZML → 搜索 customCode 匹配)
|
|
470
|
+
2. 搜索项目 Task,找到 EZML-1878
|
|
471
|
+
3. 通过 `parentId` 获取父需求(EZML-1877)详情
|
|
472
|
+
4. 解析父需求 HTML 描述,提取需求内容
|
|
473
|
+
5. 展示给用户
|
|
474
|
+
|
|
475
|
+
```
|
|
476
|
+
用户:创建提测单
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
1. 获取项目 Task 类型 ID
|
|
480
|
+
2. 生成提测单描述(使用模板)
|
|
481
|
+
3. 创建子任务(parentId 指向父需求,assignedTo 指向测试人员)
|
|
482
|
+
4. 修改开发任务状态为"开发完成"
|
|
483
|
+
|
|
484
|
+
## Token 权限要求
|
|
485
|
+
|
|
486
|
+
| 权限 | 读取操作 | 写入操作 |
|
|
487
|
+
|------|---------|---------|
|
|
488
|
+
| 项目协作(读取) | ✅ 查询项目、搜索工作项、获取详情 | - |
|
|
489
|
+
| 项目协作(读写) | ✅ 全部读取 | ✅ 修改状态、编辑、评论、创建、工时 |
|
|
@@ -291,7 +291,83 @@ GROUP BY d.wallet_id
|
|
|
291
291
|
|
|
292
292
|
---
|
|
293
293
|
|
|
294
|
-
##
|
|
294
|
+
## 九、MySQL only_full_group_by 规范(必须遵守)
|
|
295
|
+
|
|
296
|
+
> 生产环境 MySQL 开启了 `sql_mode=only_full_group_by`,所有报表 SQL 必须满足此规则,否则报 `BadSqlGrammarException`。
|
|
297
|
+
|
|
298
|
+
### 核心规则
|
|
299
|
+
|
|
300
|
+
**SELECT 中所有非聚合字段,必须出现在 GROUP BY 中,且表达式必须完全一致。**
|
|
301
|
+
|
|
302
|
+
### 常见错误:GROUP BY 表达式与 SELECT 不一致
|
|
303
|
+
|
|
304
|
+
```sql
|
|
305
|
+
-- ❌ 错误:SELECT 用 DATE_FORMAT,GROUP BY 用 DATE
|
|
306
|
+
SELECT
|
|
307
|
+
DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
|
|
308
|
+
SUM(atr.amount) AS totalAmount
|
|
309
|
+
FROM acc_trade atr
|
|
310
|
+
GROUP BY DATE(atr.trade_time) -- ❌ 表达式不同,触发 only_full_group_by 报错
|
|
311
|
+
ORDER BY DATE(atr.trade_time) ASC
|
|
312
|
+
|
|
313
|
+
-- ✅ 正确:GROUP BY 与 SELECT 使用完全相同的表达式
|
|
314
|
+
SELECT
|
|
315
|
+
DATE_FORMAT(atr.trade_time, '%Y-%m-%d') AS statisticDate,
|
|
316
|
+
SUM(atr.amount) AS totalAmount
|
|
317
|
+
FROM acc_trade atr
|
|
318
|
+
GROUP BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') -- ✅ 与 SELECT 一致
|
|
319
|
+
ORDER BY DATE_FORMAT(atr.trade_time, '%Y-%m-%d') ASC
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### 常见错误:SELECT 包含非聚合字段未加入 GROUP BY
|
|
323
|
+
|
|
324
|
+
```sql
|
|
325
|
+
-- ❌ 错误:canteen_name 未在 GROUP BY 中
|
|
326
|
+
SELECT
|
|
327
|
+
DATE(pay_time) AS orderDate,
|
|
328
|
+
canteen_name, -- ❌ 非聚合字段,未在 GROUP BY
|
|
329
|
+
SUM(real_amount) AS netAmount
|
|
330
|
+
FROM report_order_info
|
|
331
|
+
GROUP BY DATE(pay_time), canteen_id
|
|
332
|
+
|
|
333
|
+
-- ✅ 正确:所有非聚合字段都加入 GROUP BY
|
|
334
|
+
SELECT
|
|
335
|
+
DATE(pay_time) AS orderDate,
|
|
336
|
+
canteen_name,
|
|
337
|
+
SUM(real_amount) AS netAmount
|
|
338
|
+
FROM report_order_info
|
|
339
|
+
GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 包含 canteen_name
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### fix SQL 中的正确写法
|
|
343
|
+
|
|
344
|
+
```xml
|
|
345
|
+
<insert id="initFix">
|
|
346
|
+
INSERT INTO report_sum_xxx (id, statistic_date, canteen_id, canteen_name,
|
|
347
|
+
order_count, consume_amount, net_amount)
|
|
348
|
+
SELECT
|
|
349
|
+
#{id},
|
|
350
|
+
DATE(pay_time), -- SELECT 用 DATE(pay_time)
|
|
351
|
+
canteen_id,
|
|
352
|
+
canteen_name,
|
|
353
|
+
COUNT(*),
|
|
354
|
+
SUM(CASE WHEN consume_type = 1 THEN real_amount ELSE 0 END),
|
|
355
|
+
SUM(real_amount)
|
|
356
|
+
FROM report_order_info
|
|
357
|
+
WHERE pay_time BETWEEN #{startTime} AND #{endTime}
|
|
358
|
+
GROUP BY DATE(pay_time), canteen_id, canteen_name -- ✅ 与 SELECT 完全一致
|
|
359
|
+
</insert>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
### 开发检查项
|
|
363
|
+
|
|
364
|
+
- [ ] SELECT 每个非聚合字段,在 GROUP BY 中都有对应
|
|
365
|
+
- [ ] GROUP BY 的表达式与 SELECT 中的**完全一致**(`DATE_FORMAT(x, 'Y-m-d')` ≠ `DATE(x)`)
|
|
366
|
+
- [ ] ORDER BY 也使用相同表达式(保持一致)
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
## 十、开发检查清单
|
|
295
371
|
|
|
296
372
|
### 建表
|
|
297
373
|
- [ ] 分组维度字段 + 金额汇总字段 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
|
|
@@ -308,9 +384,13 @@ GROUP BY d.wallet_id
|
|
|
308
384
|
### 查询接口
|
|
309
385
|
- [ ] 分页 + 合计行(PageVO + TotalVO)+ 三并行 CompletableFuture
|
|
310
386
|
|
|
387
|
+
### SQL 合规(only_full_group_by)
|
|
388
|
+
- [ ] SELECT 非聚合字段全部在 GROUP BY 中
|
|
389
|
+
- [ ] GROUP BY 表达式与 SELECT 完全一致
|
|
390
|
+
|
|
311
391
|
---
|
|
312
392
|
|
|
313
|
-
##
|
|
393
|
+
## 十一、关键代码位置
|
|
314
394
|
|
|
315
395
|
| 类型 | 路径 |
|
|
316
396
|
|------|------|
|
|
@@ -286,7 +286,69 @@ public ReportBaseTotalVO<XxxVO> pageSummary(XxxParam param) {
|
|
|
286
286
|
|
|
287
287
|
---
|
|
288
288
|
|
|
289
|
-
##
|
|
289
|
+
## 九、MySQL only_full_group_by 规范(必须遵守)
|
|
290
|
+
|
|
291
|
+
> MySQL 默认开启 `sql_mode=ONLY_FULL_GROUP_BY`,SELECT 中所有非聚合列必须出现在 GROUP BY 中,且 GROUP BY 表达式必须与 SELECT 表达式**完全一致**。
|
|
292
|
+
|
|
293
|
+
### 核心规则
|
|
294
|
+
|
|
295
|
+
**SELECT 的表达式 == GROUP BY 的表达式**(字符级别完全一致)
|
|
296
|
+
|
|
297
|
+
### ❌ 错误示例(GROUP BY 与 SELECT 不一致)
|
|
298
|
+
|
|
299
|
+
```xml
|
|
300
|
+
<!-- 报错:Expression #1 of SELECT list is not in GROUP BY clause -->
|
|
301
|
+
SELECT
|
|
302
|
+
DATE_FORMAT(roi.consume_time, '%Y-%m-%d') AS statisticDate,
|
|
303
|
+
SUM(roi.real_amount) AS totalAmount
|
|
304
|
+
FROM report_order_info roi
|
|
305
|
+
GROUP BY DATE(roi.consume_time) <!-- ❌ DATE() ≠ DATE_FORMAT(..., '%Y-%m-%d') -->
|
|
306
|
+
ORDER BY DATE(roi.consume_time)
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### ✅ 正确示例(GROUP BY 与 SELECT 完全一致)
|
|
310
|
+
|
|
311
|
+
```xml
|
|
312
|
+
SELECT
|
|
313
|
+
DATE_FORMAT(roi.consume_time, '%Y-%m-%d') AS statisticDate,
|
|
314
|
+
SUM(roi.real_amount) AS totalAmount
|
|
315
|
+
FROM report_order_info roi
|
|
316
|
+
GROUP BY DATE_FORMAT(roi.consume_time, '%Y-%m-%d') <!-- ✅ 与 SELECT 完全一致 -->
|
|
317
|
+
ORDER BY DATE_FORMAT(roi.consume_time, '%Y-%m-%d') <!-- ✅ ORDER BY 也保持一致 -->
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### ❌ 错误示例(SELECT 含非聚合列未加入 GROUP BY)
|
|
321
|
+
|
|
322
|
+
```xml
|
|
323
|
+
SELECT
|
|
324
|
+
roi.canteen_id,
|
|
325
|
+
roi.canteen_name, <!-- ❌ 非聚合列未在 GROUP BY 中 -->
|
|
326
|
+
SUM(roi.real_amount) AS totalAmount
|
|
327
|
+
FROM report_order_info roi
|
|
328
|
+
GROUP BY roi.canteen_id
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### ✅ 正确示例(所有非聚合列都在 GROUP BY 中)
|
|
332
|
+
|
|
333
|
+
```xml
|
|
334
|
+
SELECT
|
|
335
|
+
roi.canteen_id,
|
|
336
|
+
roi.canteen_name,
|
|
337
|
+
SUM(roi.real_amount) AS totalAmount
|
|
338
|
+
FROM report_order_info roi
|
|
339
|
+
GROUP BY roi.canteen_id, roi.canteen_name <!-- ✅ 所有非聚合列都在 GROUP BY -->
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### 检查清单
|
|
343
|
+
|
|
344
|
+
- [ ] SELECT 中按日期分组时使用 `DATE_FORMAT(col, '%Y-%m-%d')`,**不要用 `DATE()`**
|
|
345
|
+
- [ ] GROUP BY 表达式与 SELECT 中对应列**逐字相同**(复制粘贴而非重写)
|
|
346
|
+
- [ ] ORDER BY 中同样使用与 GROUP BY 一致的表达式
|
|
347
|
+
- [ ] SELECT 中所有非聚合列(无 SUM/COUNT/AVG/MAX/MIN 包裹)都出现在 GROUP BY 中
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## 十、开发检查清单
|
|
290
352
|
|
|
291
353
|
### 建表
|
|
292
354
|
- [ ] 分组维度 + 金额汇总 + 审计字段(crby/crtime/upby/uptime/del_flag),无 tenant_id
|
|
@@ -302,10 +364,11 @@ public ReportBaseTotalVO<XxxVO> pageSummary(XxxParam param) {
|
|
|
302
364
|
|
|
303
365
|
### 查询
|
|
304
366
|
- [ ] ReportBaseTotalVO + CompletableFuture 并行 + MgrUserAuthPO 权限
|
|
367
|
+
- [ ] GROUP BY / ORDER BY 表达式与 SELECT 完全一致(only_full_group_by)
|
|
305
368
|
|
|
306
369
|
---
|
|
307
370
|
|
|
308
|
-
##
|
|
371
|
+
## 十一、关键代码位置
|
|
309
372
|
|
|
310
373
|
> 路径前缀均为 `core-report/.../statistics/`
|
|
311
374
|
|