dsh-eval-guard 0.2.3 → 0.3.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.
Files changed (221) hide show
  1. package/README.md +91 -13
  2. package/assets/commit0/instance.json +30 -0
  3. package/assets/commit0/workspace.zip +0 -0
  4. package/assets/featurebench/hidden/test_regression.py +690 -0
  5. package/assets/featurebench/instance.json +16 -0
  6. package/assets/featurebench/workspace.zip +0 -0
  7. package/assets/swe-bench-pro/hidden/test.patch +36 -0
  8. package/assets/swe-bench-pro/instance.json +10 -0
  9. package/assets/swe-bench-pro/workspace.zip +0 -0
  10. package/eval/README.md +18 -0
  11. package/eval/arena/arena/README.md +21 -0
  12. package/eval/arena/arena/bt.py +104 -0
  13. package/eval/arena/arena/clock.py +15 -0
  14. package/eval/arena/arena/core.py +265 -0
  15. package/eval/arena/arena/run.cmd +24 -0
  16. package/eval/arena/arena/run.sh +11 -0
  17. package/eval/arena/arena/schedule.py +79 -0
  18. package/eval/arena/arena/tournament.py +183 -0
  19. package/eval/arena/bomberman/bots/README.md +16 -0
  20. package/eval/arena/bomberman/bots/deepseek-v4.1-flash.cpp +447 -0
  21. package/eval/arena/bomberman/bots/doubao-seed-2.1.cpp +542 -0
  22. package/eval/arena/bomberman/bots/glm-5.3.cpp +406 -0
  23. package/eval/arena/bomberman/bots/gpt-6-astra.cpp +768 -0
  24. package/eval/arena/bomberman/bots/gpt-6-sol.cpp +444 -0
  25. package/eval/arena/bomberman/bots/kimi-k3.cpp +432 -0
  26. package/eval/arena/bomberman/bots/mimo-v2.6-pro.cpp +529 -0
  27. package/eval/arena/bomberman/bots/opus-5.5.cpp +548 -0
  28. package/eval/arena/bomberman/bots/qwen-3.8-max.cpp +836 -0
  29. package/eval/arena/bomberman/tournament/README.md +11 -0
  30. package/eval/arena/bomberman/tournament/bt.py +56 -0
  31. package/eval/arena/bomberman/tournament/evaluate.py +199 -0
  32. package/eval/arena/bomberman/tournament/run.py +79 -0
  33. package/eval/arena/bomberman/tournament/schedule-2to10.json.gz +0 -0
  34. package/eval/arena/bomberman/workspace/MANIFEST.json +13 -0
  35. package/eval/arena/bomberman/workspace/README.md +29 -0
  36. package/eval/arena/bomberman/workspace/RULES.md +100 -0
  37. package/eval/arena/bomberman/workspace/baseline.cpp +240 -0
  38. package/eval/arena/bomberman/workspace/clock.py +25 -0
  39. package/eval/arena/bomberman/workspace/engine/arena.py +198 -0
  40. package/eval/arena/bomberman/workspace/engine/build.py +35 -0
  41. package/eval/arena/bomberman/workspace/engine/engine.py +256 -0
  42. package/eval/arena/bomberman/workspace/engine/verify_replay.py +24 -0
  43. package/eval/arena/bomberman/workspace/run.cmd +28 -0
  44. package/eval/arena/bomberman/workspace/run.sh +16 -0
  45. package/eval/arena/bomberman/workspace/viewer/index.html +42 -0
  46. package/eval/arena/ctf/bots/README.md +18 -0
  47. package/eval/arena/ctf/bots/deepseek-v4.1-flash.cpp +601 -0
  48. package/eval/arena/ctf/bots/doubao-seed-2.1-turbo.cpp +320 -0
  49. package/eval/arena/ctf/bots/fable-5.1.cpp +634 -0
  50. package/eval/arena/ctf/bots/glm-5.3.cpp +689 -0
  51. package/eval/arena/ctf/bots/gpt-6-astra.cpp +657 -0
  52. package/eval/arena/ctf/bots/gpt-6.1-sol.cpp +725 -0
  53. package/eval/arena/ctf/bots/kimi-k3.cpp +667 -0
  54. package/eval/arena/ctf/bots/mimo-v2.6-pro.cpp +533 -0
  55. package/eval/arena/ctf/bots/opus-5.5.cpp +645 -0
  56. package/eval/arena/ctf/bots/qwen-3.8-max.cpp +726 -0
  57. package/eval/arena/ctf/bots/sonnet-5.5.cpp +569 -0
  58. package/eval/arena/ctf/tournament/README.md +15 -0
  59. package/eval/arena/ctf/tournament/formal-2to8.json +31 -0
  60. package/eval/arena/ctf/tournament/formal-9to15.json +31 -0
  61. package/eval/arena/ctf/workspace/README.md +41 -0
  62. package/eval/arena/ctf/workspace/RULES.md +119 -0
  63. package/eval/arena/ctf/workspace/arena.py +186 -0
  64. package/eval/arena/ctf/workspace/baseline.cpp +257 -0
  65. package/eval/arena/ctf/workspace/clock.py +15 -0
  66. package/eval/arena/ctf/workspace/core.py +265 -0
  67. package/eval/arena/ctf/workspace/engine.py +389 -0
  68. package/eval/arena/ctf/workspace/run.cmd +24 -0
  69. package/eval/arena/ctf/workspace/run.sh +11 -0
  70. package/eval/arena/ctf/workspace/viewer.html +71 -0
  71. package/eval/arena/land/bots/README.md +17 -0
  72. package/eval/arena/land/bots/deepseek-4.1f@workbuddy#1/bot.cpp +608 -0
  73. package/eval/arena/land/bots/glm-5.3@workbuddy#1/bot.cpp +521 -0
  74. package/eval/arena/land/bots/gpt-6-astra@codex#1/bot.cpp +373 -0
  75. package/eval/arena/land/bots/gpt-6-sol@codex#1/bot.cpp +341 -0
  76. package/eval/arena/land/bots/kimi-k3@workbuddy#1/bot.cpp +492 -0
  77. package/eval/arena/land/bots/mimo-2.6-pro@zcode#1/bot.cpp +652 -0
  78. package/eval/arena/land/bots/mystery-model@zcode#1/bot.cpp +466 -0
  79. package/eval/arena/land/bots/opus-5.5@claude-code#1/bot.cpp +630 -0
  80. package/eval/arena/land/bots/sonnet-5@claude-code#1/bot.cpp +619 -0
  81. package/eval/arena/land/tournament/make_replay_page.py +17 -0
  82. package/eval/arena/land/tournament/rate.py +290 -0
  83. package/eval/arena/land/tournament/round_robin.py +172 -0
  84. package/eval/arena/land/tournament/sparring.cpp +356 -0
  85. package/eval/arena/land/workspace/RULES.md +114 -0
  86. package/eval/arena/land/workspace/TASK.md +46 -0
  87. package/eval/arena/land/workspace/anchors/.build/greedy_f2e6d2dea5ca +0 -0
  88. package/eval/arena/land/workspace/anchors/greedy.cpp +194 -0
  89. package/eval/arena/land/workspace/anchors/random.cpp +80 -0
  90. package/eval/arena/land/workspace/clock.py +64 -0
  91. package/eval/arena/land/workspace/engine/build.py +33 -0
  92. package/eval/arena/land/workspace/engine/game.py +273 -0
  93. package/eval/arena/land/workspace/engine/referee.py +308 -0
  94. package/eval/arena/land/workspace/starter/.build/bot_be32b45b4699 +0 -0
  95. package/eval/arena/land/workspace/starter/bot.cpp +82 -0
  96. package/eval/arena/land/workspace/viewer/viewer.html +210 -0
  97. package/eval/arena/tank/bots/README.md +24 -0
  98. package/eval/arena/tank/bots/half1/deepseek-v4.1-flash.cpp +367 -0
  99. package/eval/arena/tank/bots/half1/doubao-seed-evolving.cpp +402 -0
  100. package/eval/arena/tank/bots/half1/gemini-4-argon.cpp +1132 -0
  101. package/eval/arena/tank/bots/half1/glm-5.3.cpp +551 -0
  102. package/eval/arena/tank/bots/half1/gpt-6-astra.cpp +233 -0
  103. package/eval/arena/tank/bots/half1/gpt-6.1-sol-ultrafast.cpp +350 -0
  104. package/eval/arena/tank/bots/half1/gpt-6.1-sol.cpp +78 -0
  105. package/eval/arena/tank/bots/half1/grok-4.7.cpp +1099 -0
  106. package/eval/arena/tank/bots/half1/haiku-5.5.cpp +210 -0
  107. package/eval/arena/tank/bots/half1/kimi-k3.cpp +353 -0
  108. package/eval/arena/tank/bots/half1/mimo-v2.6-pro.cpp +336 -0
  109. package/eval/arena/tank/bots/half1/muse-wonder-full.cpp +560 -0
  110. package/eval/arena/tank/bots/half1/opus-5.5.cpp +435 -0
  111. package/eval/arena/tank/bots/half1/qwen-3.8-max.cpp +537 -0
  112. package/eval/arena/tank/bots/half2/deepseek-v4.1-flash.cpp +425 -0
  113. package/eval/arena/tank/bots/half2/doubao-seed-evolving.cpp +308 -0
  114. package/eval/arena/tank/bots/half2/gemini-4-argon.cpp +1390 -0
  115. package/eval/arena/tank/bots/half2/glm-5.3.cpp +651 -0
  116. package/eval/arena/tank/bots/half2/gpt-6-astra.cpp +241 -0
  117. package/eval/arena/tank/bots/half2/gpt-6.1-sol-ultrafast.cpp +356 -0
  118. package/eval/arena/tank/bots/half2/gpt-6.1-sol.cpp +87 -0
  119. package/eval/arena/tank/bots/half2/grok-4.7.cpp +1205 -0
  120. package/eval/arena/tank/bots/half2/haiku-5.5.cpp +210 -0
  121. package/eval/arena/tank/bots/half2/kimi-k3.cpp +487 -0
  122. package/eval/arena/tank/bots/half2/mimo-v2.6-pro.cpp +336 -0
  123. package/eval/arena/tank/bots/half2/muse-wonder-full.cpp +560 -0
  124. package/eval/arena/tank/bots/half2/opus-5.5.cpp +476 -0
  125. package/eval/arena/tank/bots/half2/qwen-3.8-max.cpp +392 -0
  126. package/eval/arena/tank/tournament/README.md +24 -0
  127. package/eval/arena/tank/tournament/bt.py +218 -0
  128. package/eval/arena/tank/tournament/final.json +202 -0
  129. package/eval/arena/tank/tournament/halftime.json +146 -0
  130. package/eval/arena/tank/tournament/progress.json +911 -0
  131. package/eval/arena/tank/tournament/schedule.py +100 -0
  132. package/eval/arena/tank/tournament/sparring.cpp +928 -0
  133. package/eval/arena/tank/tournament/tournament.py +1426 -0
  134. package/eval/arena/tank/workspace/README.md +169 -0
  135. package/eval/arena/tank/workspace/RULES.md +443 -0
  136. package/eval/arena/tank/workspace/arena.py +862 -0
  137. package/eval/arena/tank/workspace/baseline.cpp +259 -0
  138. package/eval/arena/tank/workspace/clock.py +103 -0
  139. package/eval/arena/tank/workspace/core.py +712 -0
  140. package/eval/arena/tank/workspace/engine.py +573 -0
  141. package/eval/arena/tank/workspace/history/.lock +0 -0
  142. package/eval/arena/tank/workspace/history/20261017-193610-08715287f91f-bot.cpp +109 -0
  143. package/eval/arena/tank/workspace/history/20261017-193619-f00b2d5635fa-gpt-6-astra.cpp +241 -0
  144. package/eval/arena/tank/workspace/history/20261017-193643-63319e0a2b1a-gpt-6_1-sol-ultrafast.cpp +356 -0
  145. package/eval/arena/tank/workspace/history/20261017-193647-41c1341d0a8a-gemini-4-argon.cpp +1390 -0
  146. package/eval/arena/tank/workspace/history/20261017-193652-e34c0f45539a-gpt-6_1-sol.cpp +87 -0
  147. package/eval/arena/tank/workspace/history/20261017-193654-c026348ce96e-opus-5_5.cpp +476 -0
  148. package/eval/arena/tank/workspace/history/20261017-193655-4a21366e94d1-muse-wonder-full.cpp +560 -0
  149. package/eval/arena/tank/workspace/history/20261017-193657-dbc4e9969c52-mimo-v2_6-pro.cpp +336 -0
  150. package/eval/arena/tank/workspace/history/20261017-193703-723cd036eb31-grok-4_7.cpp +1205 -0
  151. package/eval/arena/tank/workspace/history/20261017-193707-1ebbb227be68-deepseek-v4_1-flash.cpp +425 -0
  152. package/eval/arena/tank/workspace/history/20261017-193709-aeb0e7202ad5-doubao-seed-evolving.cpp +308 -0
  153. package/eval/arena/tank/workspace/history/20261017-193710-b21ebeb9bc76-kimi-k3.cpp +487 -0
  154. package/eval/arena/tank/workspace/history/20261017-193712-54cd86531905-haiku-5_5.cpp +210 -0
  155. package/eval/arena/tank/workspace/history/20261017-193714-efdb8e35c0bf-qwen-3_8-max.cpp +392 -0
  156. package/eval/arena/tank/workspace/history/20261017-193715-681423c23fdb-glm-5_3.cpp +651 -0
  157. package/eval/arena/tank/workspace/history/20261017-193717-1a8b3c75181d-sparring.cpp +928 -0
  158. package/eval/arena/tank/workspace/history/20261017-193720-1e0803a541e8-baseline.cpp +259 -0
  159. package/eval/arena/tank/workspace/history/log.jsonl +103 -0
  160. package/eval/arena/tank/workspace/judge/cycles.hpp +53 -0
  161. package/eval/arena/tank/workspace/judge/fast.cpp +337 -0
  162. package/eval/arena/tank/workspace/judge/referee.cpp +125 -0
  163. package/eval/arena/tank/workspace/judge/speed.cpp +99 -0
  164. package/eval/arena/tank/workspace/run.cmd +31 -0
  165. package/eval/arena/tank/workspace/run.sh +23 -0
  166. package/eval/arena/tank/workspace/tank.hpp +1762 -0
  167. package/eval/arena/tank/workspace/tank_main.cpp +215 -0
  168. package/eval/arena/tank/workspace/viewer.html +1150 -0
  169. package/eval/arena/warehouse/workspace/SPEC.md +130 -0
  170. package/eval/arena/warehouse/workspace/TASK.md +71 -0
  171. package/eval/arena/warehouse/workspace/baseline/common.h +88 -0
  172. package/eval/arena/warehouse/workspace/baseline/main.cpp +27 -0
  173. package/eval/arena/warehouse/workspace/baseline/old_stuff.cpp +87 -0
  174. package/eval/arena/warehouse/workspace/baseline/sim.cpp +427 -0
  175. package/eval/arena/warehouse/workspace/baseline/util.cpp +220 -0
  176. package/eval/arena/warehouse/workspace/clock.py +42 -0
  177. package/eval/arena/warehouse/workspace/src/common.h +88 -0
  178. package/eval/arena/warehouse/workspace/src/main.cpp +27 -0
  179. package/eval/arena/warehouse/workspace/src/old_stuff.cpp +87 -0
  180. package/eval/arena/warehouse/workspace/src/sim.cpp +427 -0
  181. package/eval/arena/warehouse/workspace/src/util.cpp +220 -0
  182. package/eval/arena/warehouse/workspace/tools/bench.py +119 -0
  183. package/eval/arena/warehouse/workspace/tools/check_input.py +161 -0
  184. package/eval/arena/warehouse/workspace/tools/gen.py +132 -0
  185. package/eval/arena/warehouse/workspace/tools/measure.py +107 -0
  186. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.in +1420 -0
  187. package/eval/arena/warehouse/workspace/workloads/public/pub_crowded.out +9162 -0
  188. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.in +1465 -0
  189. package/eval/arena/warehouse/workspace/workloads/public/pub_dynamic.out +3439 -0
  190. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.in +2757 -0
  191. package/eval/arena/warehouse/workspace/workloads/public/pub_flood.out +3126 -0
  192. package/eval/arena/warehouse/workspace/workloads/public/pub_small.in +731 -0
  193. package/eval/arena/warehouse/workspace/workloads/public/pub_small.out +2578 -0
  194. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.in +528 -0
  195. package/eval/arena/warehouse/workspace/workloads/public/pub_wide.out +2026 -0
  196. package/eval/bomberman.py +20 -0
  197. package/eval/commit0.py +21 -0
  198. package/eval/common.py +61 -0
  199. package/eval/ctf.py +20 -0
  200. package/eval/featurebench.py +25 -0
  201. package/eval/land.py +20 -0
  202. package/eval/legacy/bomberman-prices.json +4 -0
  203. package/eval/legacy/bomberman.py +161 -0
  204. package/eval/legacy/commit0.py +91 -0
  205. package/eval/legacy/ctf-prices.json +4 -0
  206. package/eval/legacy/ctf.py +178 -0
  207. package/eval/legacy/featurebench.py +103 -0
  208. package/eval/legacy/land-prices.json +4 -0
  209. package/eval/legacy/land.py +177 -0
  210. package/eval/legacy/swe_bench_pro.py +116 -0
  211. package/eval/legacy/tank-prices.json +3 -0
  212. package/eval/legacy/tank.py +408 -0
  213. package/eval/legacy/warehouse-prices.json +4 -0
  214. package/eval/legacy/warehouse.py +365 -0
  215. package/eval/swe-bench-pro.py +25 -0
  216. package/eval/tank.py +20 -0
  217. package/eval/warehouse.py +20 -0
  218. package/lib/client/index.js +376 -63
  219. package/lib/client/index.js.map +1 -1
  220. package/lib/index.js +684 -27
  221. package/package.json +2 -1
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ """Evaluate a FeatureBench single-feature submission.
3
+
4
+ Submission layout:
5
+ submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
6
+
7
+ The hidden feature test file is copied from the plugin asset; scoring runs the
8
+ instance's fail-to-pass and pass-to-pass files. Missing run/ statistics are skipped.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import re
15
+ import shutil
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ from pathlib import Path
20
+
21
+ ROOT = Path(__file__).resolve().parent
22
+ ASSET = ROOT.parents[1] / "assets" / "featurebench"
23
+ PASSED = re.compile(r"(\d+) passed")
24
+ FAILED = re.compile(r"(\d+) failed")
25
+ ERRORS = re.compile(r"(\d+) error")
26
+
27
+
28
+ def load_json(path: Path):
29
+ try:
30
+ return json.loads(path.read_text(encoding="utf-8"))
31
+ except Exception:
32
+ return None
33
+
34
+
35
+ def find_workspace(directory: Path) -> Path | None:
36
+ for candidate in (directory / "workspace", directory):
37
+ if (candidate / "seaborn").is_dir() and (candidate / "tests").is_dir():
38
+ return candidate
39
+ return None
40
+
41
+
42
+ def install_hidden_test(workspace: Path) -> None:
43
+ source = ASSET / "hidden" / "test_regression.py"
44
+ if source.is_file():
45
+ shutil.copy2(source, workspace / "tests" / "test_regression.py")
46
+
47
+
48
+ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
49
+ venv = workspace / ".venv" / "bin" / "python"
50
+ python = str(venv) if venv.is_file() else "python3"
51
+ tests = list(instance.get("FAIL_TO_PASS", [])) + list(instance.get("PASS_TO_PASS", []))
52
+ env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
53
+ completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
54
+ capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
55
+ output = (completed.stdout or "") + "\n" + (completed.stderr or "")
56
+ passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
57
+ failed = int(FAILED.search(output).group(1)) if FAILED.search(output) else 0
58
+ errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
59
+ if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
60
+ errors = 1
61
+ return passed, failed, errors, output[-3000:]
62
+
63
+
64
+ def main() -> int:
65
+ parser = argparse.ArgumentParser(description="featurebench-eval")
66
+ parser.add_argument("--submissions", default=str(ROOT / "submissions"))
67
+ parser.add_argument("--out", default="")
68
+ args = parser.parse_args()
69
+ instance = load_json(ASSET / "instance.json") or {}
70
+ root = Path(args.submissions).resolve()
71
+ directories = [p for p in sorted(root.iterdir()) if p.is_dir()] if root.is_dir() else []
72
+ if not directories:
73
+ print("no submissions under", root, file=sys.stderr)
74
+ return 2
75
+ out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
76
+ out.mkdir(parents=True, exist_ok=True)
77
+ rows = []
78
+ for directory in directories:
79
+ workspace = find_workspace(directory)
80
+ if workspace is None:
81
+ rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
82
+ continue
83
+ try:
84
+ install_hidden_test(workspace)
85
+ passed, failed, errors, detail = run_tests(workspace, instance)
86
+ total = passed + failed + errors
87
+ score = 100.0 * passed / total if total else 0.0
88
+ rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
89
+ except Exception as error:
90
+ rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
91
+ rows.sort(key=lambda row: (-row["score"], row["name"]))
92
+ payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "featurebench", "instance": instance.get("instance_id"), "results": rows}
93
+ (out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
94
+ lines = ["# FeatureBench · 单 feature 排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
95
+ for rank, row in enumerate(rows, 1):
96
+ lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
97
+ (out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
98
+ print("wrote", out / "summary.md")
99
+ return 0
100
+
101
+
102
+ if __name__ == "__main__":
103
+ raise SystemExit(main())
@@ -0,0 +1,4 @@
1
+ {
2
+ "USD": {},
3
+ "CNY": {}
4
+ }
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env python3
2
+ """Independent evaluation for land (territory) bot submissions.
3
+
4
+ Reads submissions/<name>/{bot.cpp,run/,cost.json}, then runs the official
5
+ round_robin.py + rate.py over the submissions and archive players. A missing
6
+ run/ directory is fine: only the bot is evaluated.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import json
12
+ import re
13
+ import shutil
14
+ import subprocess
15
+ import sys
16
+ import time
17
+ from pathlib import Path
18
+
19
+ ROOT = Path(__file__).resolve().parent
20
+ DEFAULT_TASK = ROOT.parent / "meow-ai-arena" / "land"
21
+ TIERS = {"quick": (2, 0), "full": (6, 10), "formal": (12, 20)}
22
+ STATIC_PATTERNS = [
23
+ (re.compile(r"#\s*include\s*<(fstream|filesystem|unistd\.h|windows\.h|winsock2?\.h|sys/|netinet/|arpa/|dlfcn\.h)>"), "可疑头文件"),
24
+ (re.compile(r"\b(fopen|freopen|ifstream|ofstream|filesystem::)\b"), "文件 IO"),
25
+ (re.compile(r"\b(system|popen|execv?p?e?|fork|posix_spawn|CreateProcess)\b"), "启动进程"),
26
+ (re.compile(r"\b(socket|connect|bind|listen|accept|sendto|recvfrom|getaddrinfo)\b"), "网络调用"),
27
+ (re.compile(r"\b(std::thread|pthread_create|_beginthread)\b"), "线程"),
28
+ ]
29
+
30
+
31
+ def load_json(path: Path):
32
+ try:
33
+ return json.loads(path.read_text(encoding="utf-8"))
34
+ except Exception:
35
+ return None
36
+
37
+
38
+ def static_scan(source: Path) -> list:
39
+ findings = []
40
+ for number, line in enumerate(source.read_text(encoding="utf-8", errors="replace").splitlines(), 1):
41
+ for pattern, label in STATIC_PATTERNS:
42
+ if pattern.search(line):
43
+ findings.append({"line": number, "kind": label, "text": line.strip()[:160]})
44
+ return findings
45
+
46
+
47
+ def money(directory: Path, run: dict, usage: dict, cost: dict, prices: dict):
48
+ if isinstance(cost, dict):
49
+ try:
50
+ return float(cost.get("total", 0) or 0), "manual", str(cost.get("currency", "USD"))
51
+ except (TypeError, ValueError):
52
+ pass
53
+ if not usage:
54
+ return 0.0, "missing", "USD"
55
+ provider = str(run.get("provider") or "")
56
+ model = str(run.get("model") or "")
57
+ table = prices.get(provider + "/" + model) or prices.get(model) or {}
58
+ if not table:
59
+ return 0.0, "missing", "USD"
60
+ def rate(name: str) -> float:
61
+ return float(table.get(name, 0) or 0) / 1_000_000.0
62
+ total = float(usage.get("inputTokens", 0) or 0) * rate("input")
63
+ total += float(usage.get("outputTokens", 0) or 0) * rate("output")
64
+ total += float(usage.get("cacheReadTokens", 0) or 0) * rate("cacheRead")
65
+ total += float(usage.get("cacheWriteTokens", 0) or 0) * rate("cacheWrite")
66
+ return total, "tokens", "USD"
67
+
68
+
69
+ def main() -> int:
70
+ parser = argparse.ArgumentParser(description="land-eval")
71
+ parser.add_argument("--tier", choices=sorted(TIERS), default="quick")
72
+ parser.add_argument("--submissions", default=str(ROOT / "submissions"))
73
+ parser.add_argument("--task", default=str(DEFAULT_TASK))
74
+ parser.add_argument("--with-history", action="store_true")
75
+ parser.add_argument("--out", default="")
76
+ parser.add_argument("--jobs", type=int, default=2)
77
+ args = parser.parse_args()
78
+
79
+ task = Path(args.task).resolve()
80
+ workspace = task / "workspace"
81
+ if not (workspace / "engine" / "referee.py").is_file():
82
+ print("land task not found:", task, file=sys.stderr)
83
+ return 2
84
+ submissions = Path(args.submissions).resolve()
85
+ dirs = [p for p in sorted(submissions.iterdir()) if p.is_dir() and (p / "bot.cpp").is_file()] if submissions.is_dir() else []
86
+ if not dirs:
87
+ print("no submissions found under", submissions, file=sys.stderr)
88
+ return 2
89
+
90
+ out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
91
+ out.mkdir(parents=True, exist_ok=True)
92
+ entries = out / "entries"
93
+ entries.mkdir(exist_ok=True)
94
+ names = {}
95
+ for directory in dirs:
96
+ name = re.sub(r"[^A-Za-z0-9_.-]+", "-", directory.name).strip("-") or "submission"
97
+ target = entries / name
98
+ target.mkdir(exist_ok=True)
99
+ shutil.copy2(directory / "bot.cpp", target / "bot.cpp")
100
+ names[name] = directory
101
+ if args.with_history:
102
+ for bot in sorted((task / "bots").glob("*/bot.cpp")):
103
+ target = entries / ("history-" + bot.parent.name.replace("/", "-"))
104
+ target.mkdir(exist_ok=True)
105
+ shutil.copy2(bot, target / "bot.cpp")
106
+
107
+ maps, melee = TIERS[args.tier]
108
+ results_jsonl = out / "results.jsonl"
109
+ cmd = [sys.executable, "-X", "utf8", str(task / "tournament" / "round_robin.py"),
110
+ "--entries", str(entries), "--out", str(results_jsonl), "--maps", str(maps),
111
+ "--melee-rounds", str(melee), "--jobs", str(max(1, args.jobs))]
112
+ if melee == 0:
113
+ cmd += ["--only", "duel"]
114
+ print("tier=%s maps=%d melee=%d entries=%d" % (args.tier, maps, melee, len(list(entries.iterdir()))), flush=True)
115
+ completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
116
+ if completed.returncode != 0:
117
+ print(completed.stdout[-4000:], completed.stderr[-4000:], file=sys.stderr)
118
+ return 2
119
+ ratings_prefix = out / "ratings"
120
+ rate = subprocess.run([sys.executable, "-X", "utf8", str(task / "tournament" / "rate.py"), str(results_jsonl),
121
+ "--entries", str(entries), "--out", str(ratings_prefix)], capture_output=True, text=True,
122
+ encoding="utf-8", errors="replace")
123
+ if rate.returncode != 0:
124
+ print(rate.stdout[-4000:], rate.stderr[-4000:], file=sys.stderr)
125
+ return 2
126
+ ratings = load_json(Path(str(ratings_prefix) + ".json")) or {}
127
+ models = ratings.get("models", {})
128
+ prices = load_json(ROOT / "prices.json") or {}
129
+
130
+ rows = []
131
+ all_names = list(names) + [name for name in models if name not in names]
132
+ for name in all_names:
133
+ directory = names.get(name)
134
+ if directory is not None:
135
+ run_dir = directory / "run"
136
+ run = load_json(run_dir / "run.json") or {}
137
+ usage = load_json(run_dir / "usage.json") or {}
138
+ cost = load_json(directory / "cost.json") or load_json(run_dir / "cost.json")
139
+ amount, source, currency = money(directory, run, usage, cost, prices)
140
+ violations = 0
141
+ if (run_dir / "violations.jsonl").is_file():
142
+ violations = len([line for line in (run_dir / "violations.jsonl").read_text(encoding="utf-8").splitlines() if line.strip()])
143
+ scan = static_scan(directory / "bot.cpp")
144
+ kind = "submission"
145
+ else:
146
+ run = {}
147
+ amount, source, currency = 0.0, "missing", "USD"
148
+ violations = 0
149
+ candidate = entries / name / "bot.cpp"
150
+ scan = static_scan(candidate) if candidate.is_file() else []
151
+ kind = "baseline" if name.startswith("anchor-") else "history"
152
+ model = models.get(name, {})
153
+ rows.append({
154
+ "name": name,
155
+ "kind": kind,
156
+ "score": round(float(model.get("score", 0) or 0), 2),
157
+ "theta": round(float(model.get("modes", {}).get("duel", {}).get("theta", 0) or 0), 3),
158
+ "cost": round(amount, 4), "costSource": source, "currency": currency,
159
+ "violations": violations,
160
+ "static": scan,
161
+ "run": run,
162
+ })
163
+ rows.sort(key=lambda row: (-row["score"], -row["theta"], row["name"]))
164
+ payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "tier": args.tier, "maps": maps, "melee": melee, "results": rows}
165
+ (out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
166
+ lines = ["# land-eval 结果", "", "- 时间:%s" % payload["generatedAt"], "- 档位:%s(单挑 %d 张图,混战 %d 轮)" % (args.tier, maps, melee), "", "## 排行榜", "", "| 名次 | 名称 | 总分 | 单挑 theta | 成本 | 价格来源 | 违规 | 静态扫描 |", "|---:|---|---:|---:|---:|---|---:|---|"]
167
+ for rank, row in enumerate(rows, 1):
168
+ scan = "未发现" if not row["static"] else "%d 项" % len(row["static"])
169
+ lines.append("| %d | %s | %.1f | %.2f | %.4f %s | %s | %d | %s |" % (rank, row["name"], row["score"], row["theta"], row["cost"], row["currency"], row["costSource"], row["violations"], scan))
170
+ lines += ["", "## 说明", "", "- 使用圈地官方 round_robin.py + rate.py;锚点 greedy/random/sparring 自动加入。", "- 没有 run/ 时仍评测 bot,只是成本/用量显示 missing。", "- 静态扫描只报告,不取消资格。"]
171
+ (out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
172
+ print("wrote", out / "summary.md")
173
+ return 0
174
+
175
+
176
+ if __name__ == "__main__":
177
+ raise SystemExit(main())
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env python3
2
+ """Evaluate a SWE-Bench Pro single-task submission.
3
+
4
+ Submission layout:
5
+ submissions/<name>/workspace/ (copy of the agent workspace, including .venv)
6
+
7
+ The hidden test patch is applied from the plugin asset; scoring runs the
8
+ instance's fail-to-pass and pass-to-pass tests. Missing run/ statistics are skipped.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import re
15
+ import subprocess
16
+ import sys
17
+ import time
18
+ from pathlib import Path
19
+
20
+ ROOT = Path(__file__).resolve().parent
21
+ ASSET = ROOT.parents[1] / "assets" / "swe-bench-pro"
22
+ PASSED = re.compile(r"(\d+) passed")
23
+ FAILED = re.compile(r"(\d+) failed")
24
+ ERRORS = re.compile(r"(\d+) error")
25
+
26
+
27
+ def load_json(path: Path):
28
+ try:
29
+ return json.loads(path.read_text(encoding="utf-8"))
30
+ except Exception:
31
+ return None
32
+
33
+
34
+ def as_list(value) -> list:
35
+ if isinstance(value, str):
36
+ try:
37
+ parsed = json.loads(value)
38
+ except Exception:
39
+ return [value]
40
+ return parsed if isinstance(parsed, list) else [value]
41
+ return list(value or [])
42
+
43
+
44
+ def find_workspace(directory: Path) -> Path | None:
45
+ for candidate in (directory / "workspace", directory):
46
+ if (candidate / "test").is_dir() and (candidate / "lib" / "ansible").is_dir():
47
+ return candidate
48
+ return None
49
+
50
+
51
+ def apply_hidden_tests(workspace: Path) -> None:
52
+ patch = ASSET / "hidden" / "test.patch"
53
+ if not patch.is_file():
54
+ return
55
+ subprocess.run(["patch", "-p1", "--forward", "-i", str(patch)], cwd=workspace,
56
+ capture_output=True, text=True, encoding="utf-8", errors="replace")
57
+
58
+
59
+ def run_tests(workspace: Path, instance: dict) -> tuple[int, int, int, str]:
60
+ venv = workspace / ".venv" / "bin" / "python"
61
+ python = str(venv) if venv.is_file() else "python3"
62
+ tests = as_list(instance.get("fail_to_pass")) + as_list(instance.get("pass_to_pass"))
63
+ if not tests:
64
+ tests = as_list(instance.get("selected_test_files_to_run"))
65
+ env = {k: v for k, v in __import__("os").environ.items() if k not in {"PYTHONPATH", "PYTHONHOME"}}
66
+ completed = subprocess.run([python, "-m", "pytest", *tests, "-q", "--tb=no"], cwd=workspace,
67
+ capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=1800, env=env)
68
+ output = (completed.stdout or "") + "\n" + (completed.stderr or "")
69
+ passed = int(PASSED.search(output).group(1)) if PASSED.search(output) else 0
70
+ failed = int(FAILED.search(output).group(1)) if FAILED.search(output) else 0
71
+ errors = int(ERRORS.search(output).group(1)) if ERRORS.search(output) else 0
72
+ if completed.returncode != 0 and passed == 0 and failed == 0 and errors == 0:
73
+ errors = 1
74
+ return passed, failed, errors, output[-3000:]
75
+
76
+
77
+ def main() -> int:
78
+ parser = argparse.ArgumentParser(description="swe-bench-pro-eval")
79
+ parser.add_argument("--submissions", default=str(ROOT / "submissions"))
80
+ parser.add_argument("--out", default="")
81
+ args = parser.parse_args()
82
+ instance = load_json(ASSET / "instance.json") or {}
83
+ root = Path(args.submissions).resolve()
84
+ directories = [p for p in sorted(root.iterdir()) if p.is_dir()] if root.is_dir() else []
85
+ if not directories:
86
+ print("no submissions under", root, file=sys.stderr)
87
+ return 2
88
+ out = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
89
+ out.mkdir(parents=True, exist_ok=True)
90
+ rows = []
91
+ for directory in directories:
92
+ workspace = find_workspace(directory)
93
+ if workspace is None:
94
+ rows.append({"name": directory.name, "status": "missing-workspace", "score": 0.0, "passed": 0, "failed": 0, "errors": 0})
95
+ continue
96
+ try:
97
+ apply_hidden_tests(workspace)
98
+ passed, failed, errors, detail = run_tests(workspace, instance)
99
+ total = passed + failed + errors
100
+ score = 100.0 * passed / total if total else 0.0
101
+ rows.append({"name": directory.name, "status": "ok", "score": round(score, 2), "passed": passed, "failed": failed, "errors": errors, "detail": detail})
102
+ except Exception as error:
103
+ rows.append({"name": directory.name, "status": "error", "score": 0.0, "passed": 0, "failed": 0, "errors": 1, "detail": str(error)})
104
+ rows.sort(key=lambda row: (-row["score"], row["name"]))
105
+ payload = {"generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "benchmark": "swe-bench-pro", "instance": instance.get("instance_id"), "results": rows}
106
+ (out / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
107
+ lines = ["# SWE-Bench Pro · 单任务排行", "", "- 实例:%s" % instance.get("instance_id", ""), "- 指标:隐藏 fail-to-pass + pass-to-pass 测试通过率", "", "| 名次 | 名称 | 得分 | 通过 | 失败 | 错误 | 状态 |", "|---:|---|---:|---:|---:|---:|---|"]
108
+ for rank, row in enumerate(rows, 1):
109
+ lines.append("| %d | %s | %.1f | %d | %d | %d | %s |" % (rank, row["name"], row["score"], row["passed"], row["failed"], row["errors"], row["status"]))
110
+ (out / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
111
+ print("wrote", out / "summary.md")
112
+ return 0
113
+
114
+
115
+ if __name__ == "__main__":
116
+ raise SystemExit(main())
@@ -0,0 +1,3 @@
1
+ {
2
+ "USD": {}
3
+ }