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.
Files changed (108) hide show
  1. package/.claude/agents/code-reviewer.md +3 -130
  2. package/.claude/hooks/skill-forced-eval.js +2 -0
  3. package/.claude/hooks/stop.js +24 -1
  4. package/.claude/skills/codex-code-review/SKILL.md +327 -0
  5. package/.claude/skills/leniu-report-customization/SKILL.md +82 -2
  6. package/.claude/skills/leniu-report-standard-customization/SKILL.md +65 -2
  7. package/.claude/skills/loki-log-query/SKILL.md +400 -0
  8. package/.claude/skills/mysql-debug/SKILL.md +58 -22
  9. package/.claude/skills/skill-creator/LICENSE.txt +202 -0
  10. package/.claude/skills/skill-creator/SKILL.md +479 -0
  11. package/.claude/skills/skill-creator/agents/analyzer.md +274 -0
  12. package/.claude/skills/skill-creator/agents/comparator.md +202 -0
  13. package/.claude/skills/skill-creator/agents/grader.md +223 -0
  14. package/.claude/skills/skill-creator/assets/eval_review.html +146 -0
  15. package/.claude/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  16. package/.claude/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  17. package/.claude/skills/skill-creator/references/schemas.md +430 -0
  18. package/.claude/skills/skill-creator/scripts/__init__.py +0 -0
  19. package/.claude/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  20. package/.claude/skills/skill-creator/scripts/generate_report.py +326 -0
  21. package/.claude/skills/skill-creator/scripts/improve_description.py +248 -0
  22. package/.claude/skills/skill-creator/scripts/package_skill.py +136 -0
  23. package/.claude/skills/skill-creator/scripts/quick_validate.py +103 -0
  24. package/.claude/skills/skill-creator/scripts/run_eval.py +310 -0
  25. package/.claude/skills/skill-creator/scripts/run_loop.py +332 -0
  26. package/.claude/skills/skill-creator/scripts/utils.py +47 -0
  27. package/.claude/skills/sync-back-merge/SKILL.md +66 -0
  28. package/.claude/skills/yunxiao-task-management/SKILL.md +489 -0
  29. package/.codex/skills/leniu-report-customization/SKILL.md +82 -2
  30. package/.codex/skills/leniu-report-standard-customization/SKILL.md +65 -2
  31. package/.codex/skills/loki-log-query/SKILL.md +400 -0
  32. package/.codex/skills/loki-log-query/environments.json +45 -0
  33. package/.codex/skills/mysql-debug/SKILL.md +58 -22
  34. package/.codex/skills/skill-creator/LICENSE.txt +202 -0
  35. package/.codex/skills/skill-creator/SKILL.md +479 -0
  36. package/.codex/skills/skill-creator/agents/analyzer.md +274 -0
  37. package/.codex/skills/skill-creator/agents/comparator.md +202 -0
  38. package/.codex/skills/skill-creator/agents/grader.md +223 -0
  39. package/.codex/skills/skill-creator/assets/eval_review.html +146 -0
  40. package/.codex/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  41. package/.codex/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  42. package/.codex/skills/skill-creator/references/schemas.md +430 -0
  43. package/.codex/skills/skill-creator/scripts/__init__.py +0 -0
  44. package/.codex/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  45. package/.codex/skills/skill-creator/scripts/generate_report.py +326 -0
  46. package/.codex/skills/skill-creator/scripts/improve_description.py +248 -0
  47. package/.codex/skills/skill-creator/scripts/package_skill.py +136 -0
  48. package/.codex/skills/skill-creator/scripts/quick_validate.py +103 -0
  49. package/.codex/skills/skill-creator/scripts/run_eval.py +310 -0
  50. package/.codex/skills/skill-creator/scripts/run_loop.py +332 -0
  51. package/.codex/skills/skill-creator/scripts/utils.py +47 -0
  52. package/.codex/skills/sync-back-merge/SKILL.md +66 -0
  53. package/.codex/skills/yunxiao-task-management/SKILL.md +489 -0
  54. package/.cursor/hooks/stop.js +23 -1
  55. package/.cursor/skills/leniu-report-customization/SKILL.md +82 -2
  56. package/.cursor/skills/leniu-report-standard-customization/SKILL.md +65 -2
  57. package/.cursor/skills/loki-log-query/SKILL.md +400 -0
  58. package/.cursor/skills/loki-log-query/environments.json +45 -0
  59. package/.cursor/skills/mysql-debug/SKILL.md +58 -22
  60. package/.cursor/skills/skill-creator/LICENSE.txt +202 -0
  61. package/.cursor/skills/skill-creator/SKILL.md +479 -0
  62. package/.cursor/skills/skill-creator/agents/analyzer.md +274 -0
  63. package/.cursor/skills/skill-creator/agents/comparator.md +202 -0
  64. package/.cursor/skills/skill-creator/agents/grader.md +223 -0
  65. package/.cursor/skills/skill-creator/assets/eval_review.html +146 -0
  66. package/.cursor/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  67. package/.cursor/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  68. package/.cursor/skills/skill-creator/references/schemas.md +430 -0
  69. package/.cursor/skills/skill-creator/scripts/__init__.py +0 -0
  70. package/.cursor/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  71. package/.cursor/skills/skill-creator/scripts/generate_report.py +326 -0
  72. package/.cursor/skills/skill-creator/scripts/improve_description.py +248 -0
  73. package/.cursor/skills/skill-creator/scripts/package_skill.py +136 -0
  74. package/.cursor/skills/skill-creator/scripts/quick_validate.py +103 -0
  75. package/.cursor/skills/skill-creator/scripts/run_eval.py +310 -0
  76. package/.cursor/skills/skill-creator/scripts/run_loop.py +332 -0
  77. package/.cursor/skills/skill-creator/scripts/utils.py +47 -0
  78. package/.cursor/skills/sync-back-merge/SKILL.md +66 -0
  79. package/.cursor/skills/yunxiao-task-management/SKILL.md +489 -0
  80. package/bin/index.js +606 -24
  81. package/package.json +1 -1
  82. package/src/platform-map.json +4 -0
  83. package/src/skills/codex-code-review/SKILL.md +261 -69
  84. package/src/skills/leniu-report-customization/SKILL.md +82 -2
  85. package/src/skills/leniu-report-standard-customization/SKILL.md +65 -2
  86. package/src/skills/loki-log-query/SKILL.md +400 -0
  87. package/src/skills/loki-log-query/environments.json +45 -0
  88. package/src/skills/mysql-debug/SKILL.md +58 -22
  89. package/src/skills/skill-creator/LICENSE.txt +202 -0
  90. package/src/skills/skill-creator/SKILL.md +479 -0
  91. package/src/skills/skill-creator/agents/analyzer.md +274 -0
  92. package/src/skills/skill-creator/agents/comparator.md +202 -0
  93. package/src/skills/skill-creator/agents/grader.md +223 -0
  94. package/src/skills/skill-creator/assets/eval_review.html +146 -0
  95. package/src/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  96. package/src/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  97. package/src/skills/skill-creator/references/schemas.md +430 -0
  98. package/src/skills/skill-creator/scripts/__init__.py +0 -0
  99. package/src/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  100. package/src/skills/skill-creator/scripts/generate_report.py +326 -0
  101. package/src/skills/skill-creator/scripts/improve_description.py +248 -0
  102. package/src/skills/skill-creator/scripts/package_skill.py +136 -0
  103. package/src/skills/skill-creator/scripts/quick_validate.py +103 -0
  104. package/src/skills/skill-creator/scripts/run_eval.py +310 -0
  105. package/src/skills/skill-creator/scripts/run_loop.py +332 -0
  106. package/src/skills/skill-creator/scripts/utils.py +47 -0
  107. package/src/skills/sync-back-merge/SKILL.md +66 -0
  108. 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>项目分支:后端分支:{后端分支}&nbsp;&nbsp;前端分支:{前端分支}&nbsp;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="">&nbsp;<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>提测人&amp;验收人</p>
408
+ <p><span data-type="mention" data-id="{提测人ID}">@{提测人}</span>&nbsp;<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