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,365 @@
1
+ #!/usr/bin/env python3
2
+ """Independent evaluation for dsh-eval-guard warehouse submissions.
3
+
4
+ Reads submissions/<name>/{src/*.cpp,run/,cost.json}, compiles with the fixed
5
+ g++ flags, compares every public workload byte-for-byte with the expected
6
+ output, measures CPU time against the baseline, and writes a leaderboard plus
7
+ cost/violation/static-scan reports.
8
+
9
+ The evaluator is independent of the DSH plugin: copy the run/ directory from
10
+ $DSH_HOME/eval-guard/runs/<run-id>/ next to src/.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import hashlib
16
+ import json
17
+ import math
18
+ import os
19
+ import re
20
+ import resource
21
+ import subprocess
22
+ import sys
23
+ import time
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+
27
+ ROOT = Path(__file__).resolve().parent
28
+ DEFAULT_KIT = ROOT.parent / "meow-ai-arena" / "warehouse" / "workspace"
29
+ WORKLOADS = ["pub_small", "pub_wide", "pub_crowded", "pub_dynamic", "pub_flood"]
30
+ TIERS = {"quick": ["pub_small"], "full": WORKLOADS, "formal": WORKLOADS}
31
+ REPEATS = {"quick": 1, "full": 2, "formal": 3}
32
+ BUILD_FLAGS = ["-O2", "-std=c++20"]
33
+ MEMORY_LIMIT = 1024 * 1024 * 1024
34
+
35
+ STATIC_PATTERNS = [
36
+ (re.compile(r"#\s*include\s*<(fstream|filesystem|unistd\.h|windows\.h|winsock2?\.h|sys/|netinet/|arpa/|dlfcn\.h)>"), "可疑头文件"),
37
+ (re.compile(r"\b(fopen|freopen|ifstream|ofstream|filesystem::)\b"), "文件 IO"),
38
+ (re.compile(r"\b(system|popen|execv?p?e?|fork|posix_spawn|CreateProcess)\b"), "启动进程"),
39
+ (re.compile(r"\b(socket|connect|bind|listen|accept|sendto|recvfrom|getaddrinfo)\b"), "网络调用"),
40
+ (re.compile(r"\b(std::thread|pthread_create|_beginthread)\b"), "线程"),
41
+ ]
42
+
43
+
44
+ @dataclass
45
+ class Participant:
46
+ name: str
47
+ source: Path
48
+ kind: str
49
+ submission: Path | None = None
50
+ run: dict | None = None
51
+ usage: dict | None = None
52
+ cost: dict | None = None
53
+ violations: list = field(default_factory=list)
54
+ compile_error: str = ""
55
+ outputs: dict = field(default_factory=dict)
56
+ cpus: dict = field(default_factory=dict)
57
+ speedups: dict = field(default_factory=dict)
58
+ scores: dict = field(default_factory=dict)
59
+
60
+
61
+ def load_json(path: Path):
62
+ try:
63
+ return json.loads(path.read_text(encoding="utf-8"))
64
+ except Exception:
65
+ return None
66
+
67
+
68
+ def normalized(data: bytes) -> bytes:
69
+ return data.replace(b"\r\n", b"\n")
70
+
71
+
72
+ def digest(data: bytes) -> str:
73
+ return hashlib.sha256(normalized(data)).hexdigest()
74
+
75
+
76
+ def load_submission(directory: Path) -> Participant:
77
+ src = directory / "src"
78
+ if not src.is_dir() or not list(src.glob("*.cpp")):
79
+ raise RuntimeError("missing src/*.cpp")
80
+ run_dir = directory / "run"
81
+ run = load_json(run_dir / "run.json") or {}
82
+ usage = load_json(run_dir / "usage.json") or {}
83
+ violations = []
84
+ try:
85
+ violations = [line for line in (run_dir / "violations.jsonl").read_text(encoding="utf-8").splitlines() if line.strip()]
86
+ except OSError:
87
+ pass
88
+ cost = load_json(directory / "cost.json") or load_json(run_dir / "cost.json")
89
+ return Participant(directory.name, src, "submission", directory, run, usage, cost, violations)
90
+
91
+
92
+ def compile_sources(name: str, source: Path, out: Path) -> str:
93
+ sources = sorted(str(p) for p in source.glob("*.cpp"))
94
+ if not sources:
95
+ return "no .cpp files"
96
+ out.parent.mkdir(parents=True, exist_ok=True)
97
+ cmd = ["g++", *BUILD_FLAGS, "-o", str(out), *sources]
98
+ completed = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
99
+ if completed.returncode != 0:
100
+ return ("compile failed:\n" + completed.stderr[-4000:]).strip()
101
+ return ""
102
+
103
+
104
+ def _limit_memory():
105
+ try:
106
+ resource.setrlimit(resource.RLIMIT_AS, (MEMORY_LIMIT, MEMORY_LIMIT))
107
+ except (ValueError, OSError):
108
+ pass
109
+
110
+
111
+ def run_once(exe: Path, input_path: Path, timeout: float) -> dict:
112
+ before = resource.getrusage(resource.RUSAGE_CHILDREN)
113
+ started = time.perf_counter()
114
+ timed_out = False
115
+ output = b""
116
+ code = None
117
+ try:
118
+ with input_path.open("rb") as fin:
119
+ completed = subprocess.run(
120
+ [str(exe)], stdin=fin, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL,
121
+ timeout=timeout, preexec_fn=_limit_memory,
122
+ )
123
+ output = completed.stdout
124
+ code = completed.returncode
125
+ except subprocess.TimeoutExpired as error:
126
+ timed_out = True
127
+ output = error.stdout or b""
128
+ code = None
129
+ wall = time.perf_counter() - started
130
+ after = resource.getrusage(resource.RUSAGE_CHILDREN)
131
+ cpu = (after.ru_utime - before.ru_utime) + (after.ru_stime - before.ru_stime)
132
+ return {"cpu": max(cpu, 1e-6), "wall": wall, "output": output, "code": code, "timed_out": timed_out}
133
+
134
+
135
+ def measure(exe: Path, input_path: Path, repeats: int, timeout: float) -> dict:
136
+ runs = [run_once(exe, input_path, timeout) for _ in range(repeats)]
137
+ runs.sort(key=lambda item: item["cpu"])
138
+ return runs[len(runs) // 2]
139
+
140
+
141
+ def static_scan(source: Path) -> list[dict]:
142
+ findings = []
143
+ for path in sorted(list(source.glob("*.cpp")) + list(source.glob("*.h")) + list(source.glob("*.hpp"))):
144
+ try:
145
+ lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
146
+ except OSError:
147
+ continue
148
+ for number, line in enumerate(lines, 1):
149
+ for pattern, label in STATIC_PATTERNS:
150
+ if pattern.search(line):
151
+ findings.append({"file": path.name, "line": number, "kind": label, "text": line.strip()[:160]})
152
+ return findings
153
+
154
+
155
+ def price_for(participant: Participant, prices: dict) -> tuple[float, str]:
156
+ usage = participant.usage or {}
157
+ if not usage:
158
+ return 0.0, "missing"
159
+ run = participant.run or {}
160
+ provider = str(run.get("provider") or "")
161
+ model = str(run.get("model") or "")
162
+ table = prices.get(provider + "/" + model) or prices.get(model) or {}
163
+ if not table:
164
+ return 0.0, "missing"
165
+ def rate(name: str) -> float:
166
+ return float(table.get(name, 0) or 0) / 1_000_000.0
167
+ amount = float(usage.get("inputTokens", 0) or 0) * rate("input")
168
+ amount += float(usage.get("outputTokens", 0) or 0) * rate("output")
169
+ amount += float(usage.get("cacheReadTokens", 0) or 0) * rate("cacheRead")
170
+ amount += float(usage.get("cacheWriteTokens", 0) or 0) * rate("cacheWrite")
171
+ return amount, "tokens"
172
+
173
+
174
+ def money(participant: Participant, prices: dict) -> tuple[float, str, str]:
175
+ if isinstance(participant.cost, dict):
176
+ try:
177
+ amount = float(participant.cost.get("total", 0) or 0)
178
+ except (TypeError, ValueError):
179
+ amount = 0.0
180
+ return amount, "manual", str(participant.cost.get("currency", "USD"))
181
+ amount, source = price_for(participant, prices)
182
+ currency = "USD"
183
+ return amount, source, currency
184
+
185
+
186
+ def geometric(values: list[float]) -> float:
187
+ good = [value for value in values if value > 0]
188
+ if not good:
189
+ return 0.0
190
+ return math.exp(sum(math.log(value) for value in good) / len(good))
191
+
192
+
193
+ def main() -> int:
194
+ parser = argparse.ArgumentParser(description="warehouse-eval")
195
+ parser.add_argument("--tier", choices=sorted(TIERS), default="quick")
196
+ parser.add_argument("--submissions", default=str(ROOT / "submissions"))
197
+ parser.add_argument("--out", default="")
198
+ parser.add_argument("--kit", default=str(DEFAULT_KIT))
199
+ parser.add_argument("--only", default="", help="comma-separated workload names")
200
+ parser.add_argument("--repeat", type=int, default=0, help="override runs per workload")
201
+ parser.add_argument("--timeout", type=float, default=120.0)
202
+ args = parser.parse_args()
203
+
204
+ kit = Path(args.kit).resolve()
205
+ baseline_src = kit / "baseline"
206
+ workloads_dir = kit / "workloads" / "public"
207
+ if not baseline_src.is_dir() or not workloads_dir.is_dir():
208
+ print("kit is missing baseline/ or workloads/public/:", kit, file=sys.stderr)
209
+ return 2
210
+
211
+ names = list(TIERS[args.tier])
212
+ if args.only:
213
+ wanted = {item.strip() for item in args.only.split(",") if item.strip()}
214
+ names = [name for name in names if name in wanted]
215
+ repeats = args.repeat or REPEATS[args.tier]
216
+ if not names:
217
+ print("no workloads selected", file=sys.stderr)
218
+ return 2
219
+
220
+ out_dir = Path(args.out).resolve() if args.out else ROOT / "results" / time.strftime("%Y%m%d-%H%M%S")
221
+ out_dir.mkdir(parents=True, exist_ok=True)
222
+ build_dir = out_dir / "build"
223
+ prices = load_json(ROOT / "prices.json") or {}
224
+
225
+ print("tier=%s workloads=%s repeats=%d" % (args.tier, ",".join(names), repeats))
226
+ baseline = Participant("baseline", baseline_src, "baseline")
227
+ baseline.compile_error = compile_sources("baseline", baseline_src, build_dir / "baseline")
228
+ if baseline.compile_error:
229
+ print(baseline.compile_error, file=sys.stderr)
230
+ return 2
231
+
232
+ baseline_cpu = {}
233
+ expected = {}
234
+ for name in names:
235
+ input_path = workloads_dir / (name + ".in")
236
+ output_path = workloads_dir / (name + ".out")
237
+ if not input_path.is_file() or not output_path.is_file():
238
+ print("missing workload:", name, file=sys.stderr)
239
+ return 2
240
+ expected[name] = normalized(output_path.read_bytes())
241
+ result = measure(build_dir / "baseline", input_path, repeats, args.timeout)
242
+ baseline_cpu[name] = result["cpu"]
243
+ ok = normalized(result["output"]) == expected[name] and result["code"] == 0 and not result["timed_out"]
244
+ print(" baseline %-14s %s %.3fs" % (name, "ok" if ok else "MISMATCH", result["cpu"]))
245
+ baseline.outputs[name] = ok
246
+
247
+ submissions_dir = Path(args.submissions).resolve()
248
+ participants: list[Participant] = []
249
+ if submissions_dir.is_dir():
250
+ for directory in sorted(path for path in submissions_dir.iterdir() if path.is_dir()):
251
+ try:
252
+ participant = load_submission(directory)
253
+ except Exception as error:
254
+ print("skip %s: %s" % (directory.name, error))
255
+ continue
256
+ participant.compile_error = compile_sources(participant.name, participant.source, build_dir / participant.name)
257
+ if participant.compile_error:
258
+ print(" %-14s compile failed" % participant.name)
259
+ participants.append(participant)
260
+ if not participants:
261
+ print("no submissions under", submissions_dir)
262
+ return 1
263
+
264
+ for participant in participants:
265
+ if participant.compile_error:
266
+ continue
267
+ exe = build_dir / participant.name
268
+ for name in names:
269
+ result = measure(exe, workloads_dir / (name + ".in"), repeats, args.timeout)
270
+ correct = (
271
+ not result["timed_out"] and result["code"] == 0
272
+ and normalized(result["output"]) == expected[name]
273
+ )
274
+ participant.outputs[name] = correct
275
+ participant.cpus[name] = result["cpu"]
276
+ participant.speedups[name] = (baseline_cpu[name] / result["cpu"]) if correct else 0.0
277
+ print(" %-14s %-14s %s %.3fs %.1fx" % (
278
+ participant.name, name, "ok" if correct else "WRONG",
279
+ result["cpu"], participant.speedups[name],
280
+ ))
281
+
282
+ for name in names:
283
+ best = max([1.0] + [participant.speedups.get(name, 0.0) for participant in participants])
284
+ for participant in participants:
285
+ speedup = participant.speedups.get(name, 0.0)
286
+ if participant.compile_error or speedup <= 0 or best <= 1.0:
287
+ participant.scores[name] = 0.0
288
+ else:
289
+ participant.scores[name] = 100.0 * math.log(speedup) / math.log(best)
290
+
291
+ def summary_row(participant: Participant) -> dict:
292
+ amount, cost_source, currency = money(participant, prices)
293
+ correct = sum(1 for name in names if participant.outputs.get(name))
294
+ speedup = geometric([participant.speedups.get(name, 0.0) for name in names])
295
+ score = sum(participant.scores.get(name, 0.0) for name in names) / len(names)
296
+ scan = static_scan(participant.source)
297
+ return {
298
+ "name": participant.name,
299
+ "kind": participant.kind,
300
+ "score": round(score, 2),
301
+ "correct": correct,
302
+ "total": len(names),
303
+ "geomeanSpeedup": round(speedup, 2),
304
+ "cost": round(amount, 4),
305
+ "costSource": cost_source,
306
+ "currency": currency,
307
+ "violations": len(participant.violations),
308
+ "static": scan,
309
+ "compileError": participant.compile_error,
310
+ }
311
+
312
+ rows = [summary_row(participant) for participant in participants]
313
+ rows.sort(key=lambda row: (-row["score"], -row["correct"], -row["geomeanSpeedup"], row["name"]))
314
+ payload = {
315
+ "generatedAt": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
316
+ "tier": args.tier,
317
+ "workloads": names,
318
+ "repeats": repeats,
319
+ "baseline": {"name": "baseline", "cpu": {name: round(value, 6) for name, value in baseline_cpu.items()}},
320
+ "results": rows,
321
+ }
322
+ (out_dir / "summary.json").write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
323
+
324
+ lines = [
325
+ "# warehouse-eval 结果",
326
+ "",
327
+ "- 时间:%s" % payload["generatedAt"],
328
+ "- 档位:%s(%d 次/负载,取中位数)" % (args.tier, repeats),
329
+ "- 负载:%s" % ", ".join(names),
330
+ "",
331
+ "## 排行榜",
332
+ "",
333
+ "| 名次 | 名称 | 得分 | 正确 | 几何平均加速 | 成本 | 价格来源 | 违规 | 静态扫描 |",
334
+ "|---:|---|---:|---:|---:|---:|---|---:|---|",
335
+ ]
336
+ for rank, row in enumerate(rows, 1):
337
+ scan = "未发现" if not row["static"] else "%d 项" % len(row["static"])
338
+ lines.append("| %d | %s | %.1f | %d/%d | %.2fx | %.4f %s | %s | %d | %s |" % (
339
+ rank, row["name"], row["score"], row["correct"], row["total"], row["geomeanSpeedup"],
340
+ row["cost"], row["currency"], row["costSource"], row["violations"], scan,
341
+ ))
342
+ lines += ["", "## 逐负载", "", "| 名称 | 负载 | 结果 | 加速比 | 得分 |", "|---|---|---:|---:|---:|"]
343
+ for participant in participants:
344
+ for name in names:
345
+ lines.append("| %s | %s | %s | %.2fx | %.1f |" % (
346
+ participant.name, name, "正确" if participant.outputs.get(name) else "错误",
347
+ participant.speedups.get(name, 0.0), participant.scores.get(name, 0.0),
348
+ ))
349
+ lines += ["", "## 说明", "", "- 基准始终参加计时;正确性以 workloads/public/*.out 的逐字节比较为准(CRLF 视为 LF)。", "- 只有正确且比基线快的提交能拿加速分;得分按每条负载所有提交中的最高加速比归一化,再取平均。", "- 静态扫描只报告,不取消资格。", "- 没有数据时成本为 0 并标记来源 missing。"]
350
+ (out_dir / "summary.md").write_text("\n".join(lines) + "\n", encoding="utf-8")
351
+
352
+ with (out_dir / "workloads.csv").open("w", encoding="utf-8") as handle:
353
+ handle.write("name,workload,correct,baselineCpu,candidateCpu,speedup,score\n")
354
+ for participant in participants:
355
+ for name in names:
356
+ handle.write("%s,%s,%s,%.6f,%.6f,%.3f,%.2f\n" % (
357
+ participant.name, name, int(bool(participant.outputs.get(name))),
358
+ baseline_cpu[name], participant.cpus.get(name, 0.0), participant.speedups.get(name, 0.0), participant.scores.get(name, 0.0),
359
+ ))
360
+ print("wrote", out_dir / "summary.md")
361
+ return 0
362
+
363
+
364
+ if __name__ == "__main__":
365
+ raise SystemExit(main())
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env python3
2
+ """Single-workspace SWE-Bench Pro evaluator wrapper."""
3
+ from __future__ import annotations
4
+ import os, tempfile
5
+ from pathlib import Path
6
+ from common import EVAL_ROOT, candidate_row, clean_copy, emit, fail, parse_workspace, run_legacy
7
+
8
+ workspace = parse_workspace()
9
+ with tempfile.TemporaryDirectory(prefix='swe-pro-eval-') as td:
10
+ temp = Path(td)
11
+ candidate = temp / 'candidate' / 'workspace'
12
+ candidate.parent.mkdir(parents=True)
13
+ clean_copy(workspace, candidate, ignore_venv=True)
14
+ venv = workspace / '.venv'
15
+ if venv.is_dir():
16
+ os.symlink(venv, candidate / '.venv')
17
+ result = run_legacy(EVAL_ROOT / 'legacy' / 'swe_bench_pro.py', ['--submissions', str(temp)], temp / 'out')
18
+ if not result.get('ok'):
19
+ raise SystemExit(fail({'task': 'swe-bench-pro', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
20
+ row = candidate_row(result['summary'])
21
+ passed = row.get('passed', 0)
22
+ failed = row.get('failed', 0)
23
+ errors = row.get('errors', 0)
24
+ resolved = failed == 0 and errors == 0 and passed > 0
25
+ emit({'task': 'swe-bench-pro', 'ok': True, 'score': 100.0 if resolved else 0.0, 'resolved': resolved, 'passRate': row.get('score', 0), 'passed': passed, 'failed': failed, 'errors': errors, 'status': row.get('status')})
package/eval/tank.py ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python3
2
+ """Single-workspace tank evaluator wrapper."""
3
+ from __future__ import annotations
4
+ import shutil, tempfile
5
+ from pathlib import Path
6
+ from common import EVAL_ROOT, candidate_row, emit, fail, parse_workspace, run_legacy
7
+
8
+ workspace = parse_workspace()
9
+ with tempfile.TemporaryDirectory(prefix='tank-eval-') as td:
10
+ temp = Path(td)
11
+ bot = workspace / 'submission' / 'bot.cpp'
12
+ if not bot.is_file():
13
+ raise SystemExit(fail({'task': 'tank', 'ok': False, 'error': 'missing submission/bot.cpp'}))
14
+ (temp / 'candidate').mkdir()
15
+ shutil.copy2(bot, temp / 'candidate' / 'bot.cpp')
16
+ result = run_legacy(EVAL_ROOT / 'legacy' / 'tank.py', ['--submissions', str(temp), '--arena', str(EVAL_ROOT / 'arena' / 'tank'), '--tier', 'quick'], temp / 'out')
17
+ if not result.get('ok'):
18
+ raise SystemExit(fail({'task': 'tank', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
19
+ row = candidate_row(result['summary'])
20
+ emit({'task': 'tank', 'ok': True, 'score': row.get('score', row.get('rating', row.get('theta', 0))), 'rating': row.get('rating', row.get('theta', 0)), 'wins': row.get('wins', 0), 'losses': row.get('losses', 0), 'draws': row.get('draws', 0)})
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env python3
2
+ """Single-workspace warehouse evaluator wrapper."""
3
+ from __future__ import annotations
4
+ import shutil, tempfile
5
+ from pathlib import Path
6
+ from common import EVAL_ROOT, candidate_row, emit, fail, parse_workspace, run_legacy
7
+
8
+ workspace = parse_workspace()
9
+ with tempfile.TemporaryDirectory(prefix='warehouse-eval-') as td:
10
+ temp = Path(td)
11
+ src = workspace / 'src'
12
+ if not src.is_dir():
13
+ raise SystemExit(fail({'task': 'warehouse', 'ok': False, 'error': 'missing src/ directory'}))
14
+ (temp / 'candidate').mkdir()
15
+ shutil.copytree(src, temp / 'candidate' / 'src')
16
+ result = run_legacy(EVAL_ROOT / 'legacy' / 'warehouse.py', ['--submissions', str(temp), '--kit', str(EVAL_ROOT / 'arena' / 'warehouse' / 'workspace'), '--tier', 'quick'], temp / 'out')
17
+ if not result.get('ok'):
18
+ raise SystemExit(fail({'task': 'warehouse', 'ok': False, 'error': result.get('error', 'evaluation failed')}))
19
+ row = candidate_row(result['summary'])
20
+ emit({'task': 'warehouse', 'ok': True, 'score': row.get('speedup', 0), 'speedup': row.get('speedup', 0), 'correct': row.get('correct'), 'status': row.get('status')})