@comate/zulu 1.3.3 → 1.3.4-beta.1
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/comate-engine/assets/skills/auto-commit-comate/SKILL.md +42 -62
- package/comate-engine/assets/skills/auto-commit-comate/references/data_structures.md +69 -75
- package/comate-engine/assets/skills/auto-commit-comate/references/interaction_instruction.md +220 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/build_git_commit_payload.py +195 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/build_icafe_cards_payload.py +80 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/cache_manager.py +69 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/git_diff_cli.py +5 -0
- package/comate-engine/assets/skills/auto-commit-comate/scripts/match_card_cli.py +5 -1
- package/comate-engine/assets/skills/auto-commit-comate/scripts/payload_validators.py +309 -0
- package/comate-engine/assets/skills/code-security-comate/SKILL.md +2 -1
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-go_sql_injection.md +627 -5
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-java_sql_injection.md +545 -21
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-php_sql_injection.md +596 -13
- package/comate-engine/assets/skills/code-security-comate/references/vul_repair-python_sql_injection.md +480 -82
- package/comate-engine/assets/skills/code-security-comate/scripts/http_client.py +10 -2
- package/comate-engine/assets/skills/code-security-comate/scripts/repair_vulnerability.py +12 -10
- package/comate-engine/assets/skills/code-security-comate/scripts/report_chat.py +1 -1
- package/comate-engine/assets/skills/comate-docs-comate/SKILL.md +70 -105
- package/comate-engine/assets/skills/comate-docs-comate/references/doc-map-extended.md +52 -7
- package/comate-engine/assets/skills/comate-docs-comate/references/models-and-billing.md +45 -26
- package/comate-engine/assets/skills/comate-docs-comate/references/product-overview.md +60 -14
- package/comate-engine/assets/skills/create-image-comate/SKILL.md +7 -7
- package/comate-engine/assets/skills/get-ugate-token-comate/SKILL.md +2 -0
- package/comate-engine/node_modules/@comate/plugin-engine/dist/index.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/index-7HvPXRep.js +1 -0
- package/comate-engine/node_modules/@comate/plugin-host/dist/index.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/main.js +1 -1
- package/comate-engine/node_modules/@comate/plugin-host/dist/user-DVjAG3wH.js +44 -0
- package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +8 -8
- package/comate-engine/server.js +378 -272
- package/dist/bundle/index.js +3 -3
- package/package.json +1 -1
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""共享 payload 验证和转换函数
|
|
2
|
+
|
|
3
|
+
为 build_icafe_cards_payload.py 和 build_git_commit_payload.py 提供验证和转换逻辑。
|
|
4
|
+
每个验证函数返回错误列表(list[str]),空列表表示验证通过。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _check_type(value, expected_type, path):
|
|
11
|
+
"""检查值的类型,返回错误列表。"""
|
|
12
|
+
if not isinstance(value, expected_type):
|
|
13
|
+
type_name = expected_type.__name__ if isinstance(expected_type, type) else str(expected_type)
|
|
14
|
+
actual_name = type(value).__name__
|
|
15
|
+
if expected_type is str and isinstance(value, (int, float)):
|
|
16
|
+
actual_name = "数字"
|
|
17
|
+
return [f"'{path}' 应为 {type_name},实际为 {actual_name}"]
|
|
18
|
+
return []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def convert_icafe_cards_payload(raw):
|
|
22
|
+
"""将模型传入的原始数据转换为 camelCase payload。
|
|
23
|
+
|
|
24
|
+
接受 snake_case 或 camelCase 的混合输入,完成转换和合并。
|
|
25
|
+
模型只需要把 match_card_cli.py 的输出原封传入,加上 viewMode 和 cards(排序后)。
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
raw: 模型传入的原始 dict
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
(converted_data, errors)
|
|
32
|
+
"""
|
|
33
|
+
errors = []
|
|
34
|
+
|
|
35
|
+
if not isinstance(raw, dict):
|
|
36
|
+
return None, ["输入必须是一个 JSON 对象"]
|
|
37
|
+
|
|
38
|
+
# 从 raw 中提取字段,兼容 snake_case 和 camelCase
|
|
39
|
+
cards = raw.get("cards", [])
|
|
40
|
+
space_prefix = raw.get("spacePrefix") or raw.get("space_prefix", "")
|
|
41
|
+
space_id = raw.get("spaceId") or raw.get("space_id", 0)
|
|
42
|
+
space_name = raw.get("spaceName") or raw.get("space_name", "")
|
|
43
|
+
available_spaces = raw.get("availableSpaces") or raw.get("available_spaces", [])
|
|
44
|
+
view_mode = raw.get("viewMode") or raw.get("view_mode", "")
|
|
45
|
+
defaults_raw = raw.get("defaults", {})
|
|
46
|
+
|
|
47
|
+
# 构建 defaults
|
|
48
|
+
if isinstance(defaults_raw, dict):
|
|
49
|
+
defaults = {
|
|
50
|
+
"title": defaults_raw.get("title", ""),
|
|
51
|
+
"typeId": defaults_raw.get("typeId") or defaults_raw.get("type_id", ""),
|
|
52
|
+
"typeName": defaults_raw.get("typeName") or defaults_raw.get("type_name", ""),
|
|
53
|
+
"spaceId": defaults_raw.get("spaceId") or defaults_raw.get("space_id", 0),
|
|
54
|
+
}
|
|
55
|
+
else:
|
|
56
|
+
defaults = {"title": "", "typeId": "", "typeName": "", "spaceId": 0}
|
|
57
|
+
|
|
58
|
+
# 如果模型没传 defaults 但 raw 里有顶层字段,从顶层取
|
|
59
|
+
if not defaults.get("typeId") and raw.get("available_types"):
|
|
60
|
+
defaults["typeId"] = str(raw["available_types"][0]["id"]) if raw["available_types"] else ""
|
|
61
|
+
defaults["typeName"] = raw["available_types"][0]["name"] if raw["available_types"] else ""
|
|
62
|
+
if not defaults.get("spaceId") and space_id:
|
|
63
|
+
defaults["spaceId"] = space_id
|
|
64
|
+
|
|
65
|
+
data = {
|
|
66
|
+
"cards": cards,
|
|
67
|
+
"spacePrefix": space_prefix,
|
|
68
|
+
"spaceId": space_id,
|
|
69
|
+
"spaceName": space_name,
|
|
70
|
+
"availableSpaces": available_spaces,
|
|
71
|
+
"viewMode": view_mode,
|
|
72
|
+
"defaults": defaults,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# 验证
|
|
76
|
+
errors.extend(_validate_icafe_cards(data))
|
|
77
|
+
|
|
78
|
+
return data, errors
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _validate_icafe_cards(data):
|
|
82
|
+
"""验证转换后的 icafe-cards payload。"""
|
|
83
|
+
errors = []
|
|
84
|
+
|
|
85
|
+
# cards
|
|
86
|
+
if not isinstance(data["cards"], list):
|
|
87
|
+
errors.append("'cards' 应为数组")
|
|
88
|
+
else:
|
|
89
|
+
for i, card in enumerate(data["cards"]):
|
|
90
|
+
if not isinstance(card, dict):
|
|
91
|
+
errors.append(f"'cards[{i}]' 应为对象")
|
|
92
|
+
continue
|
|
93
|
+
for key in ("sequence", "title", "type", "status"):
|
|
94
|
+
if key not in card:
|
|
95
|
+
errors.append(f"'cards[{i}].{key}' 缺失")
|
|
96
|
+
elif not isinstance(card[key], str):
|
|
97
|
+
# sequence 可能是数字,转成字符串
|
|
98
|
+
if key == "sequence":
|
|
99
|
+
card[key] = str(card[key])
|
|
100
|
+
else:
|
|
101
|
+
errors.append(f"'cards[{i}].{key}' 应为字符串")
|
|
102
|
+
|
|
103
|
+
# spacePrefix
|
|
104
|
+
if not isinstance(data["spacePrefix"], str):
|
|
105
|
+
errors.append("'spacePrefix' 应为字符串")
|
|
106
|
+
elif not data["spacePrefix"]:
|
|
107
|
+
errors.append("'spacePrefix' 不能为空字符串")
|
|
108
|
+
|
|
109
|
+
# spaceId
|
|
110
|
+
if not isinstance(data["spaceId"], (int, float)):
|
|
111
|
+
errors.append("'spaceId' 应为数字")
|
|
112
|
+
|
|
113
|
+
# spaceName
|
|
114
|
+
if not isinstance(data["spaceName"], str):
|
|
115
|
+
errors.append("'spaceName' 应为字符串")
|
|
116
|
+
|
|
117
|
+
# availableSpaces
|
|
118
|
+
if not isinstance(data["availableSpaces"], list):
|
|
119
|
+
errors.append("'availableSpaces' 应为数组")
|
|
120
|
+
else:
|
|
121
|
+
for i, space in enumerate(data["availableSpaces"]):
|
|
122
|
+
if not isinstance(space, dict):
|
|
123
|
+
errors.append(f"'availableSpaces[{i}]' 应为对象")
|
|
124
|
+
continue
|
|
125
|
+
for key in ("id", "prefix", "name", "types"):
|
|
126
|
+
if key not in space:
|
|
127
|
+
errors.append(f"'availableSpaces[{i}].{key}' 缺失")
|
|
128
|
+
# types 检查(但允许为空)
|
|
129
|
+
if "types" in space and isinstance(space["types"], list):
|
|
130
|
+
for j, t in enumerate(space["types"]):
|
|
131
|
+
if not isinstance(t, dict):
|
|
132
|
+
errors.append(f"'availableSpaces[{i}].types[{j}]' 应为对象")
|
|
133
|
+
continue
|
|
134
|
+
if "id" not in t:
|
|
135
|
+
errors.append(f"'availableSpaces[{i}].types[{j}].id' 缺失")
|
|
136
|
+
if "name" not in t:
|
|
137
|
+
errors.append(f"'availableSpaces[{i}].types[{j}].name' 缺失")
|
|
138
|
+
|
|
139
|
+
# viewMode
|
|
140
|
+
valid_modes = ("list", "create")
|
|
141
|
+
if not isinstance(data["viewMode"], str):
|
|
142
|
+
errors.append(f"'viewMode' 应为字符串,取值范围为 {valid_modes}")
|
|
143
|
+
elif data["viewMode"] not in valid_modes:
|
|
144
|
+
errors.append(f"'viewMode' 应为 {valid_modes} 之一,实际为 '{data['viewMode']}'")
|
|
145
|
+
|
|
146
|
+
# defaults
|
|
147
|
+
if not isinstance(data["defaults"], dict):
|
|
148
|
+
errors.append("'defaults' 应为对象")
|
|
149
|
+
else:
|
|
150
|
+
defaults = data["defaults"]
|
|
151
|
+
for key in ("title", "typeId", "typeName", "spaceId"):
|
|
152
|
+
if key not in defaults:
|
|
153
|
+
errors.append(f"'defaults.{key}' 缺失")
|
|
154
|
+
if "title" in defaults:
|
|
155
|
+
errors.extend(_check_type(defaults["title"], str, "defaults.title"))
|
|
156
|
+
if "typeId" in defaults:
|
|
157
|
+
errors.extend(_check_type(defaults["typeId"], str, "defaults.typeId"))
|
|
158
|
+
if "typeName" in defaults:
|
|
159
|
+
errors.extend(_check_type(defaults["typeName"], str, "defaults.typeName"))
|
|
160
|
+
if "spaceId" in defaults:
|
|
161
|
+
if not isinstance(defaults["spaceId"], (int, float)):
|
|
162
|
+
errors.append("'defaults.spaceId' 应为数字")
|
|
163
|
+
else:
|
|
164
|
+
defaults["spaceId"] = int(defaults["spaceId"])
|
|
165
|
+
|
|
166
|
+
# spaceId 转为 int
|
|
167
|
+
if isinstance(data["spaceId"], float):
|
|
168
|
+
data["spaceId"] = int(data["spaceId"])
|
|
169
|
+
|
|
170
|
+
return errors
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def convert_git_commit_payload(raw):
|
|
174
|
+
"""将模型传入的原始数据转换为并验证 git-commit payload。
|
|
175
|
+
|
|
176
|
+
模型需要传入 commitMessage、boundCard、workspaces。
|
|
177
|
+
workspaces 中的 diffSummary 保持 snake_case(与 git_diff_cli.py 输出一致)。
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
raw: 模型传入的原始 dict
|
|
181
|
+
|
|
182
|
+
Returns:
|
|
183
|
+
(converted_data, errors)
|
|
184
|
+
"""
|
|
185
|
+
errors = []
|
|
186
|
+
|
|
187
|
+
if not isinstance(raw, dict):
|
|
188
|
+
return None, ["输入必须是一个 JSON 对象"]
|
|
189
|
+
|
|
190
|
+
commit_message = raw.get("commitMessage") or raw.get("commit_message", "")
|
|
191
|
+
bound_card = raw.get("boundCard") or raw.get("boundCard") or raw.get("icafe_card")
|
|
192
|
+
workspaces = raw.get("workspaces", [])
|
|
193
|
+
|
|
194
|
+
data = {
|
|
195
|
+
"commitMessage": commit_message,
|
|
196
|
+
"boundCard": bound_card,
|
|
197
|
+
"workspaces": workspaces,
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
errors.extend(_validate_git_commit(data))
|
|
201
|
+
|
|
202
|
+
return data, errors
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _validate_git_commit(data):
|
|
206
|
+
"""验证转换后的 git-commit payload。"""
|
|
207
|
+
errors = []
|
|
208
|
+
|
|
209
|
+
# commitMessage
|
|
210
|
+
if not isinstance(data["commitMessage"], str):
|
|
211
|
+
errors.append("'commitMessage' 应为字符串")
|
|
212
|
+
elif not data["commitMessage"]:
|
|
213
|
+
errors.append("'commitMessage' 不能为空字符串")
|
|
214
|
+
|
|
215
|
+
# boundCard
|
|
216
|
+
bound_card = data["boundCard"]
|
|
217
|
+
if bound_card is not None and not isinstance(bound_card, dict):
|
|
218
|
+
errors.append("'boundCard' 应为对象或 null")
|
|
219
|
+
elif isinstance(bound_card, dict):
|
|
220
|
+
for key in ("sequence", "title", "type", "status", "spacePrefix"):
|
|
221
|
+
if key not in bound_card:
|
|
222
|
+
errors.append(f"'boundCard.{key}' 缺失")
|
|
223
|
+
for key in ("sequence", "title", "type", "status", "spacePrefix"):
|
|
224
|
+
if key in bound_card and not isinstance(bound_card[key], str):
|
|
225
|
+
# sequence 可能是数字
|
|
226
|
+
if key == "sequence":
|
|
227
|
+
bound_card[key] = str(bound_card[key])
|
|
228
|
+
else:
|
|
229
|
+
errors.append(f"'boundCard.{key}' 应为字符串")
|
|
230
|
+
|
|
231
|
+
# workspaces
|
|
232
|
+
if not isinstance(data["workspaces"], list):
|
|
233
|
+
if isinstance(data["workspaces"], str):
|
|
234
|
+
errors.append("'workspaces' 应为数组,不能是字符串")
|
|
235
|
+
else:
|
|
236
|
+
errors.append("'workspaces' 应为数组")
|
|
237
|
+
elif len(data["workspaces"]) == 0:
|
|
238
|
+
errors.append("'workspaces' 数组不能为空")
|
|
239
|
+
else:
|
|
240
|
+
for i, ws in enumerate(data["workspaces"]):
|
|
241
|
+
p = f"workspaces[{i}]"
|
|
242
|
+
if not isinstance(ws, dict):
|
|
243
|
+
errors.append(f"'{p}' 应为对象")
|
|
244
|
+
continue
|
|
245
|
+
|
|
246
|
+
ws_required = {"workspace", "repoName", "currentBranch",
|
|
247
|
+
"remoteBranches", "hasChanges", "diffSummary"}
|
|
248
|
+
for key in ws_required:
|
|
249
|
+
if key not in ws:
|
|
250
|
+
errors.append(f"'{p}.{key}' 缺失")
|
|
251
|
+
|
|
252
|
+
if "workspace" in ws:
|
|
253
|
+
errors.extend(_check_type(ws["workspace"], str, f"{p}.workspace"))
|
|
254
|
+
if "repoName" in ws:
|
|
255
|
+
errors.extend(_check_type(ws["repoName"], str, f"{p}.repoName"))
|
|
256
|
+
if "currentBranch" in ws:
|
|
257
|
+
errors.extend(_check_type(ws["currentBranch"], str, f"{p}.currentBranch"))
|
|
258
|
+
|
|
259
|
+
# remoteBranches
|
|
260
|
+
if "remoteBranches" in ws:
|
|
261
|
+
if not isinstance(ws["remoteBranches"], list):
|
|
262
|
+
errors.append(f"'{p}.remoteBranches' 应为数组")
|
|
263
|
+
else:
|
|
264
|
+
for j, branch in enumerate(ws["remoteBranches"]):
|
|
265
|
+
bp = f"{p}.remoteBranches[{j}]"
|
|
266
|
+
if not isinstance(branch, dict):
|
|
267
|
+
errors.append(f"'{bp}' 应为对象")
|
|
268
|
+
continue
|
|
269
|
+
if "name" not in branch:
|
|
270
|
+
errors.append(f"'{bp}.name' 缺失")
|
|
271
|
+
elif not isinstance(branch["name"], str):
|
|
272
|
+
errors.append(f"'{bp}.name' 应为字符串")
|
|
273
|
+
if "isCurrent" not in branch:
|
|
274
|
+
errors.append(f"'{bp}.isCurrent' 缺失")
|
|
275
|
+
elif not isinstance(branch["isCurrent"], bool):
|
|
276
|
+
errors.append(f"'{bp}.isCurrent' 应为布尔值")
|
|
277
|
+
|
|
278
|
+
# hasChanges + diffSummary 一致性
|
|
279
|
+
if "hasChanges" in ws:
|
|
280
|
+
if not isinstance(ws["hasChanges"], bool):
|
|
281
|
+
errors.append(f"'{p}.hasChanges' 应为布尔值")
|
|
282
|
+
else:
|
|
283
|
+
ds = ws.get("diffSummary")
|
|
284
|
+
if not ws["hasChanges"] and ds is not None:
|
|
285
|
+
errors.append(f"'{p}.hasChanges' 为 false 时,'{p}.diffSummary' 必须为 null")
|
|
286
|
+
elif ws["hasChanges"] and ds is None:
|
|
287
|
+
errors.append(f"'{p}.hasChanges' 为 true 时,'{p}.diffSummary' 不能为 null")
|
|
288
|
+
elif ws["hasChanges"] and isinstance(ds, dict):
|
|
289
|
+
if "changed_files" not in ds:
|
|
290
|
+
errors.append(f"'{p}.diffSummary.changed_files' 缺失")
|
|
291
|
+
elif not isinstance(ds["changed_files"], list):
|
|
292
|
+
errors.append(f"'{p}.diffSummary.changed_files' 应为数组")
|
|
293
|
+
else:
|
|
294
|
+
for k, cf in enumerate(ds["changed_files"]):
|
|
295
|
+
cfp = f"{p}.diffSummary.changed_files[{k}]"
|
|
296
|
+
if not isinstance(cf, dict):
|
|
297
|
+
errors.append(f"'{cfp}' 应为对象")
|
|
298
|
+
continue
|
|
299
|
+
for fkey, ftype in [("file", str), ("insertions", int), ("deletions", int)]:
|
|
300
|
+
if fkey not in cf:
|
|
301
|
+
errors.append(f"'{cfp}.{fkey}' 缺失")
|
|
302
|
+
elif not isinstance(cf[fkey], ftype):
|
|
303
|
+
errors.append(f"'{cfp}.{fkey}' 应为 {ftype.__name__}")
|
|
304
|
+
if "stat_summary" not in ds:
|
|
305
|
+
errors.append(f"'{p}.diffSummary.stat_summary' 缺失")
|
|
306
|
+
elif not isinstance(ds["stat_summary"], str):
|
|
307
|
+
errors.append(f"'{p}.diffSummary.stat_summary' 应为字符串")
|
|
308
|
+
|
|
309
|
+
return errors
|
|
@@ -4,6 +4,7 @@ description: 代码安全漏洞扫描与修复工具。当用户涉及以下任
|
|
|
4
4
|
metadata:
|
|
5
5
|
enableWhen:
|
|
6
6
|
- isInternal
|
|
7
|
+
version: V1.3.0
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Code Security - 代码安全漏洞扫描与修复
|
|
@@ -113,7 +114,7 @@ python3 scripts/parse_scan_result.py --scan-result <scan_result.json路径> --pr
|
|
|
113
114
|
直接传入解析结果文件执行修复:
|
|
114
115
|
|
|
115
116
|
```bash
|
|
116
|
-
python3 scripts/repair_vulnerability.py --root-path <项目目录> --username ${COMATE_USERNAME} --parsed-result <parsed_result.json路径>
|
|
117
|
+
python3 scripts/repair_vulnerability.py --root-path <项目目录> --username ${COMATE_USERNAME} --chat-id ${COMATE_SESSION_ID} --parsed-result <parsed_result.json路径>
|
|
117
118
|
```
|
|
118
119
|
|
|
119
120
|
脚本自动从 `parsed_result.json` 提取普通漏洞并按文件聚合,然后调用修复接口。
|