codebee 0.1.15 → 0.1.17

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.
@@ -222,10 +222,13 @@ def load(force=False):
222
222
  except Exception:
223
223
  data = {}
224
224
  cfg = data.get("config") if isinstance(data, dict) else None
225
+ # 老扁平配置先迁移成产品档案(老键 products/workdir/... 不在新 defaults 里,
226
+ # 必须在按 _CFG_DEFAULTS 过滤之前完成迁移)
227
+ if isinstance(cfg, dict) and not cfg.get("product_profiles"):
228
+ cfg = _migrate_legacy(dict(cfg))
225
229
  merged = dict(_CFG_DEFAULTS)
226
230
  if isinstance(cfg, dict):
227
231
  merged.update({k: v for k, v in cfg.items() if k in _CFG_DEFAULTS})
228
- merged = _migrate_legacy(merged)
229
232
  claims = data.get("claims") if isinstance(data, dict) else None
230
233
  _STATE["config"] = merged
231
234
  _STATE["claims"] = {str(k): _normalize_claim(v)
@@ -651,24 +654,21 @@ def _diffstat(workdir, task_id):
651
654
 
652
655
  def _fix_summary(claim, run, profile, side):
653
656
  """我方某一端的修复摘要(转派评论与 resolve 报告共用的事实部分)。"""
654
- task = store.get_task((claim.get("tasks") or [{}])[0].get("task_id") if not side
655
- else _task_of(claim, side).get("task_id"))
656
657
  lines = []
657
658
  git = (run or {}).get("git") or {}
658
659
  commit = str(git.get("commit") or "").strip()
659
660
  from_branch = str(git.get("from_branch") or "").strip()
660
- tid = (_task_of(claim, side).get("task_id") if side else "") or ""
661
+ tid = _task_of(claim, side).get("task_id") or ""
661
662
  if commit:
662
- lines.append("%s修复提交 %s%s" % ("【%s】" % SIDE_CN[side] if side else "",
663
- commit,
664
- "(已合并回 %s)" % from_branch if from_branch else ""))
665
- stat = _diffstat(_repo_of(profile, side).get("workdir") if profile else "", tid)
663
+ lines.append("【%s】修复提交 %s%s" % (SIDE_CN.get(side, side), commit,
664
+ "(已合并回 %s)" % from_branch if from_branch else ""))
665
+ stat = _diffstat(str(_repo_of(profile, side).get("workdir") or ""), tid)
666
666
  if stat:
667
- lines.append("改动统计:%s" % stat)
667
+ lines.append("【%s】改动统计:%s" % (SIDE_CN.get(side, side), stat))
668
668
  v = (run or {}).get("verdict") or {}
669
669
  if v:
670
- lines.append("验证结论:%s" % ("通过(评审达标)" if v.get("pass") or v.get("publishable")
671
- else "完成"))
670
+ lines.append("【%s】验证结论:%s" % (SIDE_CN.get(side, side),
671
+ "通过(评审达标)" if v.get("pass") or v.get("publishable") else "完成"))
672
672
  return lines
673
673
 
674
674
 
@@ -679,15 +679,16 @@ def _task_of(claim, side):
679
679
  return {}
680
680
 
681
681
 
682
- def _report_text(claim, run, profile, cfg):
683
- """resolve 评论:确定性事实。"""
682
+ def _report_text(claim, runs, profile, cfg):
683
+ """resolve 评论:确定性事实(逐端汇总)。"""
684
684
  tri = claim.get("triage") or {}
685
685
  lines = ["【CodeBee 自动修复报告】",
686
686
  "Bug:#%s %s" % (claim.get("bug_id"), claim.get("title") or "")]
687
687
  if tri.get("side"):
688
- lines.append("排查结论:%s问题(%s)" % (SIDE_CN.get(tri["side"], tri["side"]),
688
+ lines.append("排查结论:%s问题(%s)" % (tri.get("side"),
689
689
  tri.get("reason") or tri.get("by") or "按规则"))
690
- lines.extend(_fix_summary(claim, run, profile, None))
690
+ for t in claim.get("tasks") or []:
691
+ lines.extend(_fix_summary(claim, runs.get(t.get("run_id")), profile, t.get("side")))
691
692
  lines.append("(本条由 CodeBee 禅道集成自动回写)")
692
693
  return "\n".join(lines)
693
694
 
@@ -851,7 +852,7 @@ def _finish_ok(claim, cfg):
851
852
  _set_claim(bid, state="done_manual",
852
853
  note="修复完成;auto_resolve 已关,请人工确认后到禅道解决 bug")
853
854
  return
854
- report = _report_text(claim, runs.get(tasks[0].get("run_id")), profile, cfg)
855
+ report = _report_text(claim, runs, profile, cfg)
855
856
  opened = _bug_opened_by(cfg, bid)
856
857
  try:
857
858
  _ensure_resolved(cfg, bid, report, opened)
@@ -933,8 +934,9 @@ def _sides_to_fix(profile, tri):
933
934
  return [s for s in verdict_sides if s in (profile.get("our_sides") or [])]
934
935
 
935
936
 
936
- def _route_one(bug, profile, cfg, seen):
937
- """认领一个 bug:排查 → 分流(建任务/转派/留人工)。返回动作文案或 None。"""
937
+ def _route_one(bug, profile, cfg, notify=True):
938
+ """认领一个 bug:排查 → 分流(建任务/转派/留人工)。notify=False 用于
939
+ need_manual 存量的重排查(仍无解时不重复群通知)。返回动作文案或 None。"""
938
940
  bid = str(bug.get("id") or "")
939
941
  tri = _triage(bug, profile, cfg)
940
942
  base_claim = {
@@ -951,8 +953,9 @@ def _route_one(bug, profile, cfg, seen):
951
953
  with _LOCK:
952
954
  _STATE["claims"][bid] = base_claim
953
955
  _save_locked()
954
- _notify("🐛❓ 禅道 Bug #%s 排查不出归属端(%s),留人工\n%s"
955
- % (bid, tri.get("reason") or "", bug.get("title") or ""))
956
+ if notify:
957
+ _notify("🐛❓ 禅道 Bug #%s 排查不出归属端(%s),留人工\n%s"
958
+ % (bid, tri.get("reason") or "", bug.get("title") or ""))
956
959
  return "need_manual"
957
960
 
958
961
  if tri["side"] == "not_ours":
@@ -1070,14 +1073,14 @@ def _scan(cfg):
1070
1073
  bid = str(bug.get("id") or "")
1071
1074
  if not bid or bid in seen or not _claimable(bug, profile):
1072
1075
  continue
1073
- _route_one(bug, profile, cfg, seen)
1076
+ _route_one(bug, profile, cfg)
1074
1077
  claimed += 1
1075
1078
  # need_manual 重排查(bug 仍激活才出现在列表里)
1076
1079
  for bid in retry_ids:
1077
1080
  with _LOCK:
1078
1081
  if _STATE["claims"].get(bid, {}).get("state") != "need_manual":
1079
1082
  continue
1080
- _route_one(by_id[bid], profile, cfg, set())
1083
+ _route_one(by_id[bid], profile, cfg, notify=False)
1081
1084
  return claimed
1082
1085
 
1083
1086
 
@@ -1184,10 +1187,7 @@ def _norm_profile(p, idx):
1184
1187
  repos[side] = {"workdir": wd,
1185
1188
  "git_rev": str(r.get("git_rev") or "").strip(),
1186
1189
  "verify_command": str(r.get("verify_command") or "").strip()}
1187
- # 我方端必须配仓库(our_sides 可为空=纯路由;但配了端没配目录到扫描时才暴露,
1188
- # 这里只拦「配置了 our_sides 却两个仓库目录都没有」的明显失误)
1189
- if our and not any(repos[s]["workdir"] for s in our):
1190
- raise ValueError("产品 %d:我方端已勾选但没有配置任何仓库工作目录" % pid)
1190
+ # 工作目录留空合法:扫描时回落「默认保存路径」(_launch_fix 同口径),不在此拦截
1191
1191
  prof["repos"] = repos
1192
1192
  hints = {}
1193
1193
  raw_hints = p.get("repo_hints") if isinstance(p.get("repo_hints"), dict) else {}
package/app/main.py CHANGED
@@ -304,6 +304,17 @@ class Handler(BaseHTTPRequestHandler):
304
304
  return self._send(200, "(报告尚未生成)", "text/markdown; charset=utf-8")
305
305
  return self._send(200, p.read_text(encoding="utf-8", errors="replace"),
306
306
  "text/markdown; charset=utf-8")
307
+ m = re.match(r"^/api/runs/([^/]+)/cover$", path)
308
+ if m:
309
+ # 封面图在运行目录而非任务工作目录,「成果」扫描够不着它,
310
+ # 前端封面卡内嵌缩略图走这条专用通道(文件名固定无穿越面)
311
+ run = store.get_run(m.group(1))
312
+ if not run:
313
+ return self._json(404, {"error": "not found"})
314
+ p = paths.RUNS_DIR / m.group(1) / "cover.png"
315
+ if not p.is_file():
316
+ return self._json(404, {"error": "封面尚未生成"})
317
+ return self._send(200, p.read_bytes(), "image/png")
307
318
  m = re.match(r"^/api/runs/([^/]+)/share$", path)
308
319
  if m:
309
320
  from core import share_page
@@ -738,6 +749,10 @@ class Handler(BaseHTTPRequestHandler):
738
749
  if path == "/api/models/refresh":
739
750
  from core import modelhub
740
751
  n, err = modelhub.refresh_models(self._body().get("id") or "")
752
+ if modelhub.is_no_list_note(err):
753
+ # 网关无 /models 列表接口(zcode-plan 等):预期情况按成功回报,
754
+ # 前端给中性提示而非红色「获取失败」
755
+ return self._json(200, {"ok": True, "count": 0, "note": err})
741
756
  return self._json(200, {"ok": bool(n), "count": n, "message": err})
742
757
  if path == "/api/models/add":
743
758
  # 手工添加模型:厂商列表接口调不通时直接填模型名进列表
@@ -988,6 +1003,11 @@ class Handler(BaseHTTPRequestHandler):
988
1003
  from core import zentao
989
1004
  res = zentao.scan_now() # 网络操作同步做(ThreadingHTTPServer 不堵别的请求)
990
1005
  return self._json(200, dict(res, ok=bool(res.get("ok"))))
1006
+ if path == "/api/zentao/modules":
1007
+ from core import zentao
1008
+ pid = (self._body() or {}).get("product")
1009
+ res = zentao.fetch_modules(pid)
1010
+ return self._json(200, dict(res, ok=bool(res.get("ok"))))
991
1011
  # 外部目录路由必须在通配的 market/<id>/(install|remove) 之前——
992
1012
  # 否则 "remote" 会被当成包名吞掉
993
1013
  if path == "/api/market/remote/refresh":