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