@comate/zulu 1.4.0-beta.4 → 1.4.0-beta.6

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 (53) hide show
  1. package/comate-engine/assets/skills/auto-commit/SKILL.md +2 -0
  2. package/comate-engine/assets/skills/auto-commit-sandbox-comate/SKILL.md +2 -2
  3. package/comate-engine/assets/skills/code-review/SKILL.md +6 -5
  4. package/comate-engine/assets/skills/code-review/agents/custom-reviewer.md +2 -2
  5. package/comate-engine/assets/skills/code-review/agents/meta-reviewer.md +2 -2
  6. package/comate-engine/assets/skills/code-review/agents/style-reviewer.md +72 -10
  7. package/comate-engine/assets/skills/code-review/references/dispatch-template.md +12 -12
  8. package/comate-engine/assets/skills/code-review/references/rules/Java/JAVA_STYLE_RULES.md +11 -5
  9. package/comate-engine/assets/skills/code-security/SKILL.md +110 -41
  10. package/comate-engine/assets/skills/code-security/references/credential_hosting.md +190 -28
  11. package/comate-engine/assets/skills/code-security/references/vul_analysis-go_sql_injection.md +149 -0
  12. package/comate-engine/assets/skills/code-security/references/vul_analysis-java_sql_injection.md +185 -0
  13. package/comate-engine/assets/skills/code-security/references/vul_analysis-php_sql_injection.md +147 -0
  14. package/comate-engine/assets/skills/code-security/references/vul_analysis-python_sql_injection.md +143 -0
  15. package/comate-engine/assets/skills/code-security/references/vul_repair-go_sql_injection.md +2 -2
  16. package/comate-engine/assets/skills/code-security/references/vul_repair-sca.md +225 -0
  17. package/comate-engine/assets/skills/code-security/scripts/credential_hosting.py +12 -10
  18. package/comate-engine/assets/skills/code-security/scripts/credential_open_page.py +125 -0
  19. package/comate-engine/assets/skills/code-security/scripts/credential_poll.py +12 -9
  20. package/comate-engine/assets/skills/code-security/scripts/credential_url.py +81 -0
  21. package/comate-engine/assets/skills/code-security/scripts/ducc/get_claude_session_id.sh +33 -0
  22. package/comate-engine/assets/skills/code-security/scripts/ducc/open_browser.py +191 -0
  23. package/comate-engine/assets/skills/code-security/scripts/parse_scan_result.py +99 -16
  24. package/comate-engine/assets/skills/code-security/scripts/repair_vulnerability.py +66 -13
  25. package/comate-engine/assets/skills/code-security/scripts/scan_vulnerability.py +44 -12
  26. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/SKILL.md +8 -8
  27. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/long_running_task.md +0 -15
  28. package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/testing_strategy.md +1 -1
  29. package/comate-engine/assets/skills/create-image/SKILL.md +197 -206
  30. package/comate-engine/assets/skills/create-image/scripts/generate-image.ps1 +213 -0
  31. package/comate-engine/assets/skills/create-image/scripts/generate-image.sh +322 -0
  32. package/comate-engine/assets/skills/create-subagent/SKILL.md +23 -5
  33. package/comate-engine/fallbackServer.js +1 -1
  34. package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +1 -1
  35. package/comate-engine/server.js +89 -66
  36. package/dist/bundle/index.js +3 -3
  37. package/package.json +1 -1
  38. package/scripts/postinstall.js +4 -3
  39. package/comate-engine/assets/skills/code-review/evals/SKILL.md +0 -334
  40. package/comate-engine/assets/skills/code-review/evals/agents/gt-generator.md +0 -76
  41. package/comate-engine/assets/skills/code-review/evals/agents/miner.md +0 -87
  42. package/comate-engine/assets/skills/code-review/evals/agents/score-judge.md +0 -168
  43. package/comate-engine/assets/skills/code-review/evals/references/cli-query-template.md +0 -114
  44. package/comate-engine/assets/skills/code-review/evals/references/gt-schema.md +0 -77
  45. package/comate-engine/assets/skills/code-review/references/custom-rules/RULE_TEMPLATE.md +0 -141
  46. /package/comate-engine/assets/commands/{code-review-comate.md → code-review.md} +0 -0
  47. /package/comate-engine/assets/commands/{debug-comate.md → debug.md} +0 -0
  48. /package/comate-engine/assets/commands/{unit-test-comate.md → unit-test.md} +0 -0
  49. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/backend_dev.md +0 -0
  50. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/env_setup.md +0 -0
  51. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/frontend_dev.md +0 -0
  52. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/references/git_operations.md +0 -0
  53. /package/comate-engine/assets/skills/{create-automation-tasks-comate → create-automation}/scripts/check_config.py +0 -0
@@ -35,6 +35,7 @@ parsed_result.json 格式:
35
35
  "startLine": 12,
36
36
  "endLine": 12,
37
37
  "hash": "abc123...",
38
+ "importPath": "com.example:foo:1.2.3",
38
39
  "is_sensitive": false,
39
40
  "aiAnalysisStatus": 0,
40
41
  "aiAnalysisStatusText": "无需分析",
@@ -43,9 +44,10 @@ parsed_result.json 格式:
43
44
 
44
45
  aiAnalysisStatus 说明:
45
46
  0 - 无需分析
46
- 1 - 分析中
47
+ 1 - 分析中(脚本会单独归类到 analyzing_vuls;后续工作流会通过本地兜底分析判定,
48
+ 被判定为真实漏洞的会重新合入修复列表)
47
49
  2 - 真实漏洞
48
- 3 - 误报(不进入修复流程)
50
+ 3 - 误报(始终不进入修复流程)
49
51
  """
50
52
 
51
53
  import argparse
@@ -83,10 +85,13 @@ def parse_scan_result(scan_result):
83
85
  # type: (dict) -> dict
84
86
  """解析扫描结果,返回结构化漏洞数据。
85
87
 
86
- aiAnalysisStatus=3 (误报) 和 aiAnalysisStatus=1 (分析中) 的漏洞会被单独归类,不进入修复流程。
88
+ aiAnalysisStatus=3(误报)和 aiAnalysisStatus=1(分析中)的漏洞会被单独归类。
89
+ 误报永远不进入修复流程;分析中漏洞需先经工作流的「本地兜底分析」复核,
90
+ 被判定为真实漏洞的会被合并回修复列表。
87
91
  """
88
- data = scan_result.get("data", {})
89
- runs = data.get("sarif", {}).get("runs", []) or data.get("runs", [])
92
+ data = scan_result.get("data", {}) or {}
93
+ sarif = data.get("sarif") or {}
94
+ runs = sarif.get("runs") or data.get("runs") or []
90
95
 
91
96
  # 提取 bundleHash(由 scan_vulnerability.py 写入顶层)
92
97
  bundle_hash = scan_result.get("bundleHash", "")
@@ -122,6 +127,8 @@ def parse_scan_result(scan_result):
122
127
  level = level_config.get("level", "NONE") if level_config else "NONE"
123
128
  level_cn = LEVEL_MAP.get(level, "低危")
124
129
  vul_hash = result.get("properties", {}).get("hash", "")
130
+ # SCA 类漏洞会带有非空 importPath(依赖引入路径),用于后续判定是否走 SCA 本地修复
131
+ import_path = result.get("properties", {}).get("importPath", "") or ""
125
132
 
126
133
  # AI 分析状态
127
134
  ai_status = result.get("properties", {}).get("aiAnalysisStatus", 0)
@@ -172,6 +179,7 @@ def parse_scan_result(scan_result):
172
179
  "startLine": start_line,
173
180
  "endLine": end_line,
174
181
  "hash": vul_hash,
182
+ "importPath": import_path,
175
183
  "is_sensitive": is_sensitive,
176
184
  "aiAnalysisStatus": ai_status,
177
185
  "aiAnalysisStatusText": ai_status_text,
@@ -197,6 +205,23 @@ def parse_scan_result(scan_result):
197
205
  false_positive_vuls.sort(key=sort_key)
198
206
  analyzing_vuls.sort(key=sort_key)
199
207
 
208
+ # 去重:仅当 hash 相同(即扫描端认定为同一漏洞)时才合并;
209
+ # 不能仅按 (file, startLine, endLine, is_sensitive) 去重——多条来源不同的漏洞
210
+ # 可能共享同一个 sink 点(例如多个数据流汇入同一行),它们的 hash 各不相同,
211
+ # 必须各自保留以便后续修复完整覆盖。
212
+ def deduplicate(vuls):
213
+ seen = set()
214
+ result = []
215
+ for v in vuls:
216
+ key = v.get("hash") or (v["file"], v["startLine"], v["endLine"], v["is_sensitive"])
217
+ if key not in seen:
218
+ seen.add(key)
219
+ result.append(v)
220
+ return result
221
+
222
+ common_vuls = deduplicate(common_vuls)
223
+ sensitive_vuls = deduplicate(sensitive_vuls)
224
+
200
225
  return {
201
226
  "total": len(common_vuls) + len(sensitive_vuls) + len(false_positive_vuls) + len(analyzing_vuls),
202
227
  "common_count": len(common_vuls),
@@ -263,6 +288,11 @@ def format_vul_report(vuls, title="漏洞报告", project_dir=""):
263
288
  loc_link = _make_file_link(vul["file"], vul["startLine"], project_dir)
264
289
  lines.append(" - {}".format(loc_link))
265
290
 
291
+ # SCA 漏洞展示依赖引入路径(importPath),便于定位需要升级的依赖链
292
+ import_path = vul.get("importPath", "")
293
+ if import_path:
294
+ lines.append(" - **依赖引入路径**:`{}`".format(import_path))
295
+
266
296
  # 数据流折叠展示
267
297
  if vul["codeFlows"]:
268
298
  lines.append(" <details><summary>数据流</summary>\n")
@@ -303,30 +333,32 @@ def format_full_report(parsed, project_dir=""):
303
333
  if false_positive_count > 0:
304
334
  summary_parts.append("**{}** 个误报(已由 AI 分析确认,无需修复)".format(false_positive_count))
305
335
 
336
+ if analyzing_count > 0:
337
+ summary_parts.append("**{}** 个漏洞正在 AI 分析中(暂未判定)".format(analyzing_count))
338
+
306
339
  if summary_parts:
307
340
  parts.append("扫描发现 {}。\n".format(",".join(summary_parts)))
308
341
  else:
309
- if analyzing_count > 0:
310
- parts.append("扫描完成,**{}** 个漏洞正在 AI 分析中,暂未发现已确认的漏洞。\n".format(analyzing_count))
311
- else:
312
- parts.append("扫描完成,未发现漏洞。\n")
342
+ parts.append("扫描完成,未发现漏洞。\n")
313
343
  return "\n".join(parts)
314
344
 
315
- if analyzing_count > 0:
316
- parts.append("另有 **{}** 个漏洞正在 AI 分析中,分析完成后可能会被判定为误报而排除,分析期间暂不处理。\n".format(analyzing_count))
317
-
318
- # 只有误报没有真实漏洞的情况
319
- if real_vul_count == 0 and false_positive_count > 0:
345
+ # 只有误报没有真实漏洞、且没有分析中的情况(才能断言“全部为误报”)
346
+ if real_vul_count == 0 and false_positive_count > 0 and analyzing_count == 0:
320
347
  parts.append("所有检测到的漏洞均已被 AI 分析确认为误报,无需进行修复。\n")
321
348
  if parsed.get("false_positive_vuls"):
322
349
  parts.append(format_vul_report(parsed["false_positive_vuls"], "误报漏洞列表(仅供参考)", project_dir))
323
350
  return "\n".join(parts)
324
351
 
352
+ # 仅剩分析中的情况(无真实漏洞、无误报)
353
+ if real_vul_count == 0 and false_positive_count == 0 and analyzing_count > 0:
354
+ parts.append("当前暂无已确认的漏洞,需等待 AI 分析完成后再评估。\n")
355
+ return "\n".join(parts)
356
+
325
357
  if parsed["common_vuls"]:
326
358
  parts.append(format_vul_report(parsed["common_vuls"], "普通漏洞报告", project_dir))
327
359
 
328
360
  if parsed["sensitive_vuls"]:
329
- parts.append(format_vul_report(parsed["sensitive_vuls"], "硬编码漏洞报告", project_dir))
361
+ parts.append(format_sensitive_table(parsed["sensitive_vuls"], 10, project_dir))
330
362
 
331
363
  # 误报漏洞折叠展示
332
364
  if false_positive_count > 0:
@@ -346,6 +378,42 @@ def format_full_report(parsed, project_dir=""):
346
378
 
347
379
  return "\n".join(parts)
348
380
 
381
+ def format_sensitive_table(vuls, collapse_threshold=5, project_dir=""):
382
+ # type: (list, int, str) -> str
383
+ """将硬编码漏洞列表格式化为 Markdown 表格。
384
+
385
+ 当漏洞数量超过 collapse_threshold 时,使用 <details> 标签折叠展示,
386
+ 点击可展开或收起;否则直接展示完整表格。
387
+
388
+ Args:
389
+ vuls: 漏洞列表
390
+ collapse_threshold: 超过该行数时触发折叠(默认 5)
391
+ project_dir: 项目根目录,用于生成可点击的绝对路径链接。若为空,则相对路径链接。
392
+
393
+ Returns:
394
+ Markdown
395
+ """
396
+ if not vuls:
397
+ return "未发现硬编码漏洞。\n"
398
+
399
+ total = len(vuls)
400
+ header = "| 序号 | 漏洞名称 | 漏洞位置 | 漏洞等级 |\n| --- | --- | --- | --- |"
401
+ rows = []
402
+ for idx, vul in enumerate(vuls, 1):
403
+ loc_link = _make_file_link(vul["file"], vul["startLine"], project_dir)
404
+ rows.append("| {} | {} | {} | {} |".format(idx, vul["name"], loc_link, vul["level_cn"]))
405
+
406
+ if total <= collapse_threshold:
407
+ return "{}\n{}\n".format(header, "\n".join(rows))
408
+
409
+ # 前 collapse_threshold 条直接展示,超出部分用 <details> 折叠
410
+ visible = "{}\n{}".format(header, "\n".join(rows[:collapse_threshold]))
411
+ hidden_rows = rows[collapse_threshold:]
412
+ hidden_table = "{}\n{}".format(header, "\n".join(hidden_rows))
413
+ collapsed = "<details>\n<summary>展开查看剩余 {} 条漏洞</summary>\n\n{}\n\n</details>".format(
414
+ len(hidden_rows), hidden_table
415
+ )
416
+ return "{}\n\n{}\n".format(visible, collapsed)
349
417
 
350
418
  def main():
351
419
  """
@@ -374,6 +442,21 @@ def main():
374
442
  # 解析
375
443
  parsed = parse_scan_result(scan_result)
376
444
 
445
+ # 紧凑统计行(必须优先打印,保证即使后续长报告被截断,计数仍可被模型读取)
446
+ stats_line = (
447
+ "STATS: total={total} common={common} sensitive={sensitive}"
448
+ " false_positive={fp} analyzing={analyzing}"
449
+ ).format(
450
+ total=parsed["total"],
451
+ common=parsed["common_count"],
452
+ sensitive=parsed["sensitive_count"],
453
+ fp=parsed["false_positive_count"],
454
+ analyzing=parsed["analyzing_count"],
455
+ )
456
+ print(stats_line)
457
+ print(stats_line, file=sys.stderr)
458
+ print("")
459
+
377
460
  # 输出 Markdown 报告到标准输出
378
461
  report = format_full_report(parsed, args.project_dir)
379
462
  print(report)
@@ -389,4 +472,4 @@ def main():
389
472
 
390
473
 
391
474
  if __name__ == "__main__":
392
- main()
475
+ main()
@@ -30,8 +30,8 @@ import utils
30
30
 
31
31
  logger = logging.getLogger("repair")
32
32
 
33
- # 修复轮询最大次数(每次间隔 3 秒,约 10 分钟)
34
- MAX_REPAIR_POLLS = 200
33
+ # 修复轮询最大次数(每次间隔 3 秒,约 5 分钟)
34
+ MAX_REPAIR_POLLS = 100
35
35
  REPAIR_POLL_INTERVAL = 3
36
36
  MAX_UPLOAD_RETRIES = 10
37
37
 
@@ -40,8 +40,10 @@ def diff_file_content(file_path, new_content):
40
40
  # type: (str, str) -> str
41
41
  """比较原文件与修复后内容,使用 difflib 生成精确 diff,返回 diff 列表 JSON。
42
42
 
43
- 每个不连续的变更区域(hunk)生成独立的 {from_content, to_content} 对,
44
- 确保每个 from_content 都是原文件中的连续子串,可以被 str.replace 精确匹配。
43
+ 每个不连续的变更区域(hunk)生成独立的 {from_content, to_content} 对。
44
+ 携带 n=3 上下文行,确保:
45
+ 1. from_content 包含足够上下文在文件中唯一,str.replace 不会误匹配
46
+ 2. 纯插入 hunk(只有 + 行)也有上下文锚点,from_content 不会为空字符串
45
47
  """
46
48
  with open(file_path, "r", encoding="utf-8") as f:
47
49
  old_content = f.read()
@@ -49,8 +51,8 @@ def diff_file_content(file_path, new_content):
49
51
  old_lines = old_content.splitlines(keepends=True)
50
52
  new_lines = new_content.splitlines(keepends=True)
51
53
 
52
- # 使用 unified_diff 找出所有差异区域,n=0 不提供上下文行
53
- diff = list(difflib.unified_diff(old_lines, new_lines, n=0))
54
+ # n=3 携带上下文行,避免 from_content 为空或匹配不唯一
55
+ diff = list(difflib.unified_diff(old_lines, new_lines, n=3))
54
56
 
55
57
  diffs = [] # type: list
56
58
  current_from = [] # type: list
@@ -68,6 +70,16 @@ def diff_file_content(file_path, new_content):
68
70
  for line in diff:
69
71
  if line.startswith("---") or line.startswith("+++"):
70
72
  continue
73
+ elif line.startswith("\\"):
74
+ # 防御性处理:兼容 git/POSIX 风格 diff 的 "" 标记。
75
+ # Python 标准库 difflib 当前版本不会输出该标记,但 splitlines(keepends=True)
76
+ # 已经天然保留了原文件是否以换行结尾的状态——若出现该标记,说明上一行实际无尾随换行,
77
+ # 此处主动剥掉可能被误加入的 "\n",确保 from_content 能在原文件中精确匹配。
78
+ if current_from and current_from[-1].endswith("\n"):
79
+ current_from[-1] = current_from[-1][:-1]
80
+ if current_to and current_to[-1].endswith("\n"):
81
+ current_to[-1] = current_to[-1][:-1]
82
+ continue
71
83
  elif line.startswith("@@"):
72
84
  # 新 hunk 开始,先保存上一组
73
85
  _flush()
@@ -75,6 +87,11 @@ def diff_file_content(file_path, new_content):
75
87
  current_from.append(line[1:])
76
88
  elif line.startswith("+"):
77
89
  current_to.append(line[1:])
90
+ else:
91
+ # 上下文行(空格前缀),同时加入 from 和 to 作为锚点
92
+ context_line = line[1:]
93
+ current_from.append(context_line)
94
+ current_to.append(context_line)
78
95
 
79
96
  # 保存最后一组
80
97
  _flush()
@@ -133,7 +150,7 @@ def repair_vulnerability(root_path, vulnerability_info, username, user_id, chat_
133
150
  if upload_retry_count > MAX_UPLOAD_RETRIES:
134
151
  print("错误: 修复上传重试 {} 次后仍有缺失文件".format(MAX_UPLOAD_RETRIES), file=sys.stderr)
135
152
  return {"status": -1, "message": "修复上传重试次数超限"}
136
- print("上传缺失文件: {} 个 (重试 {}/{})".format(len(missing),
153
+ print("上传缺失文件: {} 个 (重试 {}/{})".format(len(missing),
137
154
  upload_retry_count, MAX_UPLOAD_RETRIES), file=sys.stderr)
138
155
  upload_files_for_repair(root_path, missing, username, user_id, chat_id)
139
156
  time.sleep(REPAIR_POLL_INTERVAL)
@@ -155,21 +172,39 @@ def repair_vulnerability(root_path, vulnerability_info, username, user_id, chat_
155
172
  return result
156
173
 
157
174
  poll_count += 1
158
- print("修复中,等待结果... ({}/{})".format(poll_count, MAX_REPAIR_POLLS), file=sys.stderr)
175
+ progress = min(99, int(poll_count * 100 / MAX_REPAIR_POLLS))
176
+ print("修复中... {}%".format(progress), file=sys.stderr)
159
177
  time.sleep(REPAIR_POLL_INTERVAL)
160
178
 
161
- print("错误: 修复超时,已轮询 {} 次仍未完成".format(MAX_REPAIR_POLLS), file=sys.stderr)
179
+ print("错误: 修复超时,已等待超过预期时间", file=sys.stderr)
162
180
  return {"status": -1, "message": "修复超时"}
163
181
 
164
182
 
183
+ def is_sca_vuln(vul):
184
+ # type: (dict) -> bool
185
+ """判定是否为 SCA 类漏洞。
186
+
187
+ SCA 漏洞的修复方式是升级依赖版本,与代码改写无关,后端修复接口对其无能为力,
188
+ 因此直接在本地参考修复手册(references/vul_repair-sca.md)处理,不发起后端请求。
189
+
190
+ 判定规则:漏洞条目带有非空 `importPath` 字段即为 SCA 漏洞。
191
+ """
192
+ return bool(vul.get("importPath"))
193
+
194
+
165
195
  def build_vulnerability_info(parsed):
166
196
  # type: (dict) -> dict
167
- """从 parsed_result.json 构建修复接口所需的 vulnerability-info。"""
197
+ """从 parsed_result.json 构建修复接口所需的 vulnerability-info。
198
+
199
+ SCA 类漏洞会被过滤掉,不进入后端修复请求,由 agent 在本地参考手册自行修复。
200
+ """
168
201
  bundle_hash = parsed.get("bundle_hash", "")
169
202
  common_vuls = parsed.get("common_vuls", [])
170
203
 
171
204
  file_map = {} # type: dict
172
205
  for vul in common_vuls:
206
+ if is_sca_vuln(vul):
207
+ continue
173
208
  fname = vul.get("file", "")
174
209
  if not fname:
175
210
  continue
@@ -207,7 +242,7 @@ def main():
207
242
 
208
243
  user_id = utils.make_user_id(args.username)
209
244
 
210
- logger.info("repair_vulnerability start: username=%s, chat_id=%s, root_path=%s", args.username,
245
+ logger.info("repair_vulnerability start: username=%s, chat_id=%s, root_path=%s", args.username,
211
246
  args.chat_id, args.root_path)
212
247
 
213
248
  root_path = os.path.realpath(args.root_path)
@@ -228,6 +263,23 @@ def main():
228
263
  print("无普通漏洞需要修复", file=sys.stderr)
229
264
  sys.exit(0)
230
265
  vulnerability_info = build_vulnerability_info(parsed)
266
+ # 全部为 SCA 漏洞时,后端 files 为空,直接走本地兜底,由 agent 参考
267
+ # references/vul_repair-sca.md 修复
268
+ if not vulnerability_info.get("files"):
269
+ sca_count = sum(1 for v in parsed.get("common_vuls", []) if is_sca_vuln(v))
270
+ result = {
271
+ "status": -3,
272
+ "message": "all_vulns_are_sca_use_local_fallback",
273
+ "fallback": True,
274
+ "sca_only": True,
275
+ "sca_count": sca_count,
276
+ "data": {"files": []},
277
+ }
278
+ output_file = os.path.join(output_dir, "repair_result.json")
279
+ with open(output_file, "w", encoding="utf-8") as f:
280
+ json.dump(result, f, ensure_ascii=False, indent=2)
281
+ print(output_file)
282
+ return
231
283
  else:
232
284
  try:
233
285
  vulnerability_info = json.loads(args.vulnerability_info)
@@ -235,7 +287,8 @@ def main():
235
287
  print("错误: 漏洞信息 JSON 解析失败: {}".format(e), file=sys.stderr)
236
288
  sys.exit(1)
237
289
 
238
- result = repair_vulnerability(root_path, vulnerability_info, args.username, user_id, args.chat_id)
290
+ result = repair_vulnerability(
291
+ root_path, vulnerability_info, args.username, user_id, args.chat_id)
239
292
 
240
293
  output_file = os.path.join(output_dir, "repair_result.json")
241
294
  with open(output_file, "w", encoding="utf-8") as f:
@@ -244,4 +297,4 @@ def main():
244
297
 
245
298
 
246
299
  if __name__ == "__main__":
247
- main()
300
+ main()
@@ -244,16 +244,31 @@ def get_analyzing_count(result):
244
244
  return count
245
245
 
246
246
 
247
+ def _has_valid_runs(result):
248
+ # type: (dict) -> bool
249
+ """检查结果中是否包含有效的 runs 数据(非空列表)。"""
250
+ data = result.get("data") or {}
251
+ sarif = data.get("sarif") or {}
252
+ runs = sarif.get("runs") or data.get("runs")
253
+ return bool(runs)
254
+
255
+
247
256
  def _poll_ai_analysis(scan_info, chat_id, username, user_id, ai_analysis_timeout):
248
257
  # type: (dict, str, str, str, int) -> dict
249
- """轮询等待 AI 分析完成,返回最新结果。"""
258
+ """轮询等待 AI 分析完成,返回最新结果。
259
+
260
+ 维护 last_valid_result:仅当响应 status != 1 且 runs 非空时更新。
261
+ 超时或循环结束时返回 last_valid_result(而非最后一次可能为空的响应),
262
+ 避免服务端中间态导致漏洞数据丢失。
263
+ """
250
264
  start_time = time.time()
251
265
  poll_interval = 10
266
+ last_valid_result = None # type: dict | None
252
267
 
253
268
  while True:
254
269
  elapsed = time.time() - start_time
255
270
  if elapsed >= ai_analysis_timeout:
256
- print("\nAI 分析等待超时(已等待 {} 分钟),将使用当前结果继续".format(
271
+ print("\nAI 分析等待超时(已等待 {} 分钟),将使用最近有效结果继续".format(
257
272
  int(elapsed // 60)), file=sys.stderr)
258
273
  logger.warning("AI analysis timeout after %d seconds", int(elapsed))
259
274
  break
@@ -266,17 +281,29 @@ def _poll_ai_analysis(scan_info, chat_id, username, user_id, ai_analysis_timeout
266
281
  json_body=scan_info,
267
282
  )
268
283
 
284
+ # 记录有效结果:status != 1 且 runs 非空
285
+ if result.get("status") != 1 and _has_valid_runs(result):
286
+ last_valid_result = result
287
+
269
288
  analyzing_count = get_analyzing_count(result)
270
- if analyzing_count == 0:
289
+ # status=1 表示服务端扫描/分析仍在进行中,此时 runs 通常为 null,
290
+ # 不能仅凭 analyzing_count == 0 就判定分析完成,需要 status != 1 才算真正完成
291
+ if result.get("status") != 1 and analyzing_count == 0:
271
292
  print("\nAI 分析完成!", file=sys.stderr)
272
293
  logger.info("AI analysis completed after %d seconds", int(time.time() - start_time))
273
294
  return result
274
295
 
275
- elapsed_min = int(elapsed // 60)
276
- elapsed_sec = int(elapsed % 60)
277
- print("AI 分析中... 剩余 {} 个漏洞待分析(已等待 {}分{}秒)".format(
278
- analyzing_count, elapsed_min, elapsed_sec), file=sys.stderr)
296
+ elapsed_progress = min(99, int(elapsed * 100 / ai_analysis_timeout)) if ai_analysis_timeout > 0 else 0
297
+ if result.get("status") == 1:
298
+ print("AI 分析中... {}%(服务端仍在处理)".format(elapsed_progress), file=sys.stderr)
299
+ else:
300
+ print("AI 分析中... {}%(剩余 {} 个漏洞待分析)".format(
301
+ elapsed_progress, analyzing_count), file=sys.stderr)
279
302
 
303
+ # 超时:优先返回最近一次有效结果,避免返回 runs 为空的中间态
304
+ if last_valid_result is not None:
305
+ return last_valid_result
306
+ # 兜底:如果从未拿到过有效结果,返回最后一次响应(调用方会做写入前校验)
280
307
  return result
281
308
 
282
309
 
@@ -325,11 +352,12 @@ def scan_vulnerability(root_path, chat_id="", username="", user_id="", wait_ai=T
325
352
  if result.get("status") != 1:
326
353
  break
327
354
  poll_count += 1
328
- print("扫描中,等待结果... ({}/{})".format(poll_count, MAX_SCAN_POLLS), file=sys.stderr)
355
+ progress = min(99, int(poll_count * 100 / MAX_SCAN_POLLS))
356
+ print("扫描中... {}%".format(progress), file=sys.stderr)
329
357
  time.sleep(SCAN_POLL_INTERVAL)
330
358
 
331
359
  if result.get("status") == 1:
332
- print("错误: 扫描超时,已轮询 {} 次仍未完成".format(MAX_SCAN_POLLS), file=sys.stderr)
360
+ print("错误: 扫描超时,已等待超过预期时间", file=sys.stderr)
333
361
  return {"status": -1, "message": "扫描超时"}, bundle_hash
334
362
 
335
363
  # 第二阶段:等待 AI 分析完成(可选)
@@ -417,10 +445,14 @@ def main():
417
445
  root_path, args.scan_result, chat_id=args.chat_id,
418
446
  username=args.username, user_id=user_id,
419
447
  )
420
- # 原地更新 scan_result.json
448
+ # 原地更新 scan_result.json(仅当结果包含有效 runs 时才覆盖,防止超时中间态清空漏洞)
421
449
  output_file = os.path.realpath(args.scan_result)
422
- with open(output_file, "w", encoding="utf-8") as f:
423
- json.dump(result, f, ensure_ascii=False, indent=2)
450
+ if _has_valid_runs(result):
451
+ with open(output_file, "w", encoding="utf-8") as f:
452
+ json.dump(result, f, ensure_ascii=False, indent=2)
453
+ else:
454
+ print("警告: AI 分析结果中 runs 为空,保留原 scan_result.json 不覆盖", file=sys.stderr)
455
+ logger.warning("AI analysis result has empty runs, skip overwriting %s", output_file)
424
456
  print(output_file)
425
457
  else:
426
458
  # 正常扫描模式
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: create-automation-tasks
2
+ name: create-automation
3
3
  description: |
4
4
  帮助用户创建、配置和管理 Comate Automation 自动化任务。当用户表达以下意图时触发:
5
5
  - 创建/设置/新建自动化任务、定时任务、cron 任务
@@ -7,6 +7,10 @@ description: |
7
7
  - "每天/每周/每月自动帮我做..."
8
8
  - 配置 webhook 触发任务
9
9
  不适用于普通的一次性任务请求,仅适用于需要周期性或事件驱动执行的自动化配置。
10
+ metadata:
11
+ enableWhen:
12
+ - isComateIDE
13
+ disable-model-invocation: true
10
14
  ---
11
15
 
12
16
  # create_automation_tasks Skill
@@ -54,8 +58,6 @@ execution:
54
58
  ---
55
59
 
56
60
  这里写任务指令(query)。必须自包含,包含所有必要上下文。
57
-
58
- 完成后请将执行摘要写入 promise_done_summary.md。
59
61
  ````
60
62
 
61
63
  同一任务可配置多个 trigger(schedule 至多一个):
@@ -122,7 +124,6 @@ AUTOMATION.md 的 body(query)会被一个独立的 Comate Agent Session 执
122
124
  **Agent 已知的上下文**:
123
125
  - 通过 Unattended Prompt 注入,Agent 知道自己是**无人值守自动化 Session**,不会等待用户确认
124
126
  - Agent 知道触发类型、时间、workspace、Git 状态等元信息
125
- - Agent 知道完成任务后必须写入 `promise_done_summary.md` 文件作为成功信号
126
127
 
127
128
  **Agent 不具备的能力**:
128
129
  - 没有本次对话的历史上下文(每次 run 是全新 Session)
@@ -177,7 +178,7 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
177
178
  2. **按需读取相关模板**:只读取与用户任务相关的模板
178
179
  3. **提取策略融入 query 或直接引用**:
179
180
  - 如果模板中的策略需要针对用户项目做具体适配(如替换具体命令、路径),则从模板中提取要点写进 query
180
- - 如果模板整体适用且无需适配,可以在 query 中引用模板的**绝对路径**让执行 Agent 自行读取,例如:`关于 Git 操作细节,参考 /Users/xxx/.comate/skills/create-automation-tasks/references/git_operations.md`(注意:必须使用绝对路径,不能用 `{skill_dir}` 变量,因为执行 Agent 无法解析该变量)
181
+ - 如果模板整体适用且无需适配,可以在 query 中引用模板的**绝对路径**让执行 Agent 自行读取,例如:`关于 Git 操作细节,参考 ~/.comate/skills/.system/create-automation/references/git_operations.md`(注意:必须使用绝对路径,不能用 `{skill_dir}` 变量,因为执行 Agent 无法解析该变量)
181
182
 
182
183
  ---
183
184
 
@@ -211,7 +212,7 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
211
212
 
212
213
  在设计 query 之前,先判断任务复杂度,选择对应策略:
213
214
 
214
- - **一条命令能完成**(跑测试、lint、生成报告)→ 直接写命令 + 摘要要求,**不读取任何模板,不添加任何额外策略**。示例 query:`运行 npm test,将结果摘要写入 promise_done_summary.md。`
215
+ - **一条命令能完成**(跑测试、lint、生成报告)→ 直接写命令,**不读取任何模板,不添加任何额外策略**。示例 query:`运行 npm test。`
215
216
  - **2-3 个步骤,逻辑清晰**(依赖更新 + 测试验证)→ 按需读取 1-2 个模板,提取适用要点
216
217
  - **多步骤端到端流程**(拉代码 → 修改 → 测试 → push)→ 读取多个模板组合设计,参考 `long_running_task.md` 设计分步方案
217
218
 
@@ -219,7 +220,6 @@ Agent 已自动加载 `.comate/rules/` 下的所有规则,大多数情况下
219
220
  - **环境配置写在 query 最前面**(如果需要的话):Agent 在全新 Session 中启动,不会继承用户终端的环境。参考 `references/env_setup.md` 中的策略
220
221
  - **利用 Skills/Rules 引用减少 query 冗余**:不需要在 query 中重复写 Rules 中已有的规范,Agent 会自动加载。但可以提示 Agent 注意某个 Rule 或使用某个 Skill(见上方"引用 Skills 和 Rules"语法)
221
222
  - 所有必要信息写进 query(不能依赖对话历史或隐含假设)
222
- - 结尾加上:`完成后请将执行摘要写入 promise_done_summary.md。`
223
223
 
224
224
  **3. 展示 AUTOMATION.md 并确认**
225
225
 
@@ -237,7 +237,7 @@ python3 {skill_dir}/scripts/check_config.py ~/.comate/automations/{taskName}/AUT
237
237
 
238
238
  **5. 引导试跑**
239
239
 
240
- > "配置已就绪。建议先去 Automations Dashboard 找到该任务,点击**立即运行(Run Now)**跑一次,在 Session History 中查看结果是否符合预期。"
240
+ > "配置已就绪。建议先去 Automations Dashboard 找到该任务,点击**立即运行(Run Now)**跑一次,在 Session History 中查看结果是否符合预期"
241
241
 
242
242
  ### 管理已有任务
243
243
 
@@ -58,21 +58,6 @@ Automation 任务可能被重复执行(定时触发、补跑等),query 设
58
58
 
59
59
  关键原则:**区分"代码问题"和"环境问题"**。如果测试失败是因为环境配置(缺少依赖、端口占用、服务未启动),应尝试修复环境而非修改代码。至少尝试 3 种不同的修复方案后再判定为阻塞。
60
60
 
61
- ### 执行摘要规范
62
-
63
- Automation Agent 的唯一交付物是 `promise_done_summary.md`。对于复杂任务,建议在 query 中明确摘要格式:
64
-
65
- ```
66
- 完成后将执行摘要写入 promise_done_summary.md,包含:
67
- - 执行了哪些步骤,每步的结果(成功/失败/跳过)
68
- - 如果有代码修改,列出修改的文件和改动性质
69
- - 关键的终端输出、测试结果数据(作为验证证据)
70
- - 遇到的问题和处理方式
71
- - 需要人工关注的事项(如:测试覆盖不足、某个修复需要人工确认等)
72
- ```
73
-
74
- 这份摘要是用户判断任务执行质量的唯一依据,应当清晰、诚实、包含充分证据。如果任务未完全成功,不要写这个文件——让 Session 以 stopped 状态结束,摘要中记录原因即可。
75
-
76
61
  ## query 片段示例
77
62
 
78
63
  **场景:全量安全扫描 + 自动修复**
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## 核心原则
13
13
 
14
- **不能只靠代码静态推断来判断结果,必须依赖真实运行信息。** 日志、终端输出、测试结果都是重要证据。验证证据应写入 `promise_done_summary.md`。
14
+ **不能只靠代码静态推断来判断结果,必须依赖真实运行信息。** 日志、终端输出、测试结果都是重要证据。
15
15
 
16
16
  ## 策略要点
17
17