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.
- package/CHANGELOG.md +14 -0
- package/README.md +9 -8
- package/app/core/bookmeta.py +10 -3
- package/app/core/bookmeta_catalog.py +16 -0
- package/app/core/covergen.py +176 -56
- package/app/core/jobs.py +23 -10
- package/app/core/modelhub.py +23 -3
- package/app/core/notify.py +11 -1
- package/app/core/paihang.py +5 -2
- package/app/core/pipeline.py +159 -10
- package/app/core/publish/browser.py +21 -7
- package/app/core/publish/fanqie.py +16 -2
- package/app/core/publish/flow.py +25 -1
- package/app/core/publish/flows-fanqie-calibrated.json +324 -37
- package/app/core/publish/manager.py +25 -9
- package/app/core/publish/qimao.py +18 -3
- package/app/core/runner.py +19 -4
- package/app/core/settings.py +3 -1
- package/app/core/store.py +13 -1
- package/app/core/zentao.py +26 -26
- package/app/main.py +20 -0
- package/app/ui/app.js +441 -60
- package/app/ui/i18n.js +243 -9
- package/app/ui/index.html +34 -52
- package/app/ui/style.css +116 -24
- package/package.json +1 -1
package/app/core/zentao.py
CHANGED
|
@@ -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 =
|
|
661
|
+
tid = _task_of(claim, side).get("task_id") or ""
|
|
661
662
|
if commit:
|
|
662
|
-
lines.append("
|
|
663
|
-
|
|
664
|
-
|
|
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("
|
|
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("
|
|
671
|
-
|
|
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,
|
|
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)" % (
|
|
688
|
+
lines.append("排查结论:%s问题(%s)" % (tri.get("side"),
|
|
689
689
|
tri.get("reason") or tri.get("by") or "按规则"))
|
|
690
|
-
|
|
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
|
|
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,
|
|
937
|
-
"""认领一个 bug:排查 →
|
|
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
|
-
|
|
955
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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":
|