bigpowers 2.89.0 → 2.90.0

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 (35) hide show
  1. package/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
  2. package/.pi/package.json +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/package.json +1 -1
  5. package/scripts/ab-sandbox.sh +85 -0
  6. package/scripts/bp-churn-rank.sh +3 -2
  7. package/scripts/check-spec-version-gap.sh +2 -1
  8. package/scripts/land-branch.sh +7 -3
  9. package/scripts/lib/__pycache__/link_utils.cpython-310.pyc +0 -0
  10. package/scripts/lib/__pycache__/srp_engine_links.cpython-310.pyc +0 -0
  11. package/scripts/lib/__pycache__/srp_engine_okf.cpython-310.pyc +0 -0
  12. package/scripts/lib/ab-arm-bigpowers.sh +42 -0
  13. package/scripts/lib/ab-eval.py +285 -0
  14. package/scripts/lib/ab-sandbox/agent.Dockerfile +33 -0
  15. package/scripts/lib/ab-sandbox/filter +6 -0
  16. package/scripts/lib/ab-sandbox/proxy.Dockerfile +13 -0
  17. package/scripts/lib/ab-sandbox/tinyproxy.conf +19 -0
  18. package/scripts/lib/ab_metrics.py +123 -0
  19. package/scripts/lib/ab_progress.py +103 -0
  20. package/scripts/lib/ab_report.py +116 -0
  21. package/scripts/lib/golden-suite-gates.sh +2 -0
  22. package/scripts/lib/parallel-review-worktrees.sh +3 -1
  23. package/scripts/lib/plan-consistency-check.sh +4 -2
  24. package/scripts/lib/run-benchmark.py +30 -16
  25. package/scripts/run-ab-eval.sh +7 -0
  26. package/scripts/run-smoke.sh +3 -2
  27. package/scripts/run-story-verify.sh +7 -0
  28. package/scripts/test-pipefail-pipe-race.sh +68 -0
  29. package/scripts/validate-doctrine.sh +3 -3
  30. package/tests/test_ab_ablation.py +91 -0
  31. package/tests/test_ab_eval.py +309 -0
  32. package/tests/test_ab_pi.py +63 -0
  33. package/tests/test_ab_progress.py +81 -0
  34. package/tests/test_ab_sandbox.py +94 -0
  35. package/tests/test_ab_score.py +113 -0
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env python3
2
+ # story: e58s03
3
+ """tests/test_ab_score.py — partial-credit scoring and the hard tasks (ab-11, ab-13).
4
+
5
+ Run: python3 tests/test_ab_score.py
6
+ Split from test_ab_eval.py (300-line cap); reuses its Sandbox and module loaders.
7
+ """
8
+ import shutil
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+ import unittest
13
+ from pathlib import Path
14
+
15
+ import yaml
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
18
+ from test_ab_eval import REPO_ROOT, Sandbox, met, rep # noqa: E402
19
+
20
+ TASKS = REPO_ROOT / "specs" / "benchmarks" / "ab" / "tasks"
21
+ HARD_TASKS = ["ab-11-ledger-rounding", "ab-13-orders-split"]
22
+
23
+
24
+ class TestTapScore(unittest.TestCase):
25
+ def test_share_of_passing_tests(self):
26
+ self.assertEqual(met.tap_score("# tests 4\n# pass 3\n# fail 1\n"), 0.75)
27
+
28
+ def test_several_summaries_are_summed(self):
29
+ self.assertEqual(met.tap_score("# pass 2\n# fail 0\nnoise\n# pass 1\n# fail 1\n"), 0.75)
30
+
31
+ def test_no_summary_is_none(self):
32
+ self.assertIsNone(met.tap_score("SyntaxError: boom\n"))
33
+
34
+
35
+ class TestScoreInRuns(Sandbox):
36
+ def set_score(self, command):
37
+ task_yaml = self.tasks_dir / "t-01" / "task.yaml"
38
+ task = yaml.safe_load(task_yaml.read_text())
39
+ task["score"] = command
40
+ task_yaml.write_text(yaml.safe_dump(task))
41
+
42
+ def test_score_command_sets_score(self):
43
+ self.set_score("printf '# pass 3\\n# fail 1\\n'; exit 1")
44
+ self.run_main("--adapter-cmd", "echo 42 > answer.txt", "--arms", "vanilla")
45
+ self.assertEqual(self.records()[0]["score"], 0.75)
46
+
47
+ def test_no_score_key_means_null(self):
48
+ self.run_main("--adapter-cmd", "echo 42 > answer.txt")
49
+ self.assertIsNone(self.records()[0]["score"])
50
+ report = yaml.safe_load(next(self.out.glob("AB-*.yaml")).read_text())
51
+ self.assertIsNone(report["delta"].get("score"))
52
+
53
+
54
+ class TestScoreInReport(unittest.TestCase):
55
+ def rec(self, arm, task, score):
56
+ return {"arm": arm, "task": task, "pass": score == 1.0, "tampered": False, "agent_error": False,
57
+ "cost_usd": None, "input_tokens": None, "output_tokens": None, "turns": None,
58
+ "duration_s": 1.0, "diff_lines": 1, "score": score}
59
+
60
+ def test_mean_score_and_delta(self):
61
+ records = [self.rec("vanilla", "t", 0.5), self.rec("vanilla", "t", 0.7),
62
+ self.rec("bigpowers", "t", 0.9), self.rec("bigpowers", "t", 1.0)]
63
+ result = rep.aggregate(records, ["vanilla", "bigpowers"], ["t"])
64
+ self.assertEqual(result["arms_summary"]["vanilla"]["mean_score"], 0.6)
65
+ self.assertEqual(result["per_task_score"]["t"]["bigpowers"], 0.95)
66
+ self.assertEqual(result["delta"]["score"], 0.35)
67
+ lo, hi = result["delta"]["score_ci95"]
68
+ self.assertLessEqual(lo, 0.35)
69
+ self.assertGreaterEqual(hi, 0.35)
70
+
71
+
72
+ @unittest.skipUnless(shutil.which("node"), "node is needed for the hard tasks")
73
+ class TestHardTasks(unittest.TestCase):
74
+ """Each hard task must fail as given, pass with its solution, and give partial credit."""
75
+
76
+ def grade(self, name: str, with_solution: bool) -> tuple[bool, float | None]:
77
+ task_dir = TASKS / name
78
+ task = yaml.safe_load((task_dir / "task.yaml").read_text())
79
+ work = Path(tempfile.mkdtemp(prefix="ab-hard-"))
80
+ try:
81
+ shutil.copytree(task_dir / "fixture", work, dirs_exist_ok=True)
82
+ if with_solution:
83
+ shutil.copytree(task_dir / "solution", work, dirs_exist_ok=True)
84
+ shutil.copytree(task_dir / "hidden", work, dirs_exist_ok=True)
85
+ run = lambda cmd: subprocess.run(["bash", "-c", cmd], cwd=work, capture_output=True, text=True)
86
+ passed = run(task["grade"]).returncode == 0
87
+ return passed, met.tap_score(run(task["score"]).stdout)
88
+ finally:
89
+ shutil.rmtree(work, ignore_errors=True)
90
+
91
+ def test_fixture_fails_with_partial_score(self):
92
+ for name in HARD_TASKS:
93
+ with self.subTest(task=name):
94
+ passed, score = self.grade(name, with_solution=False)
95
+ self.assertFalse(passed)
96
+ assert score is not None
97
+ self.assertLess(score, 1.0)
98
+
99
+ def test_solution_passes_with_full_score(self):
100
+ for name in HARD_TASKS:
101
+ with self.subTest(task=name):
102
+ self.assertEqual(self.grade(name, with_solution=True), (True, 1.0))
103
+
104
+ def test_score_runs_hidden_tests_only(self):
105
+ for name in HARD_TASKS:
106
+ with self.subTest(task=name):
107
+ score_cmd = yaml.safe_load((TASKS / name / "task.yaml").read_text())["score"]
108
+ self.assertIn("test-hidden", score_cmd)
109
+ self.assertNotRegex(score_cmd, r"(^|\s)test/")
110
+
111
+
112
+ if __name__ == "__main__":
113
+ unittest.main(verbosity=1)