agentbridge-bench 0.1.0__py3-none-any.whl

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 (94) hide show
  1. agentbridge_bench/__init__.py +5 -0
  2. agentbridge_bench/cli.py +118 -0
  3. agentbridge_bench/data/configurations.yaml +65 -0
  4. agentbridge_bench/data/suites.yaml +19 -0
  5. agentbridge_bench/data/tasks/analysis-001/hidden_tests/test_hidden_analysis.py +33 -0
  6. agentbridge_bench/data/tasks/analysis-001/repo/pytest.ini +3 -0
  7. agentbridge_bench/data/tasks/analysis-001/repo/shop/__init__.py +1 -0
  8. agentbridge_bench/data/tasks/analysis-001/repo/shop/cart.py +15 -0
  9. agentbridge_bench/data/tasks/analysis-001/repo/shop/cli.py +18 -0
  10. agentbridge_bench/data/tasks/analysis-001/repo/shop/pricing.py +9 -0
  11. agentbridge_bench/data/tasks/analysis-001/repo/shop/tax.py +7 -0
  12. agentbridge_bench/data/tasks/analysis-001/repo/tests/test_cart.py +5 -0
  13. agentbridge_bench/data/tasks/analysis-001/solution/ANALYSIS.md +20 -0
  14. agentbridge_bench/data/tasks/analysis-001/task.yaml +30 -0
  15. agentbridge_bench/data/tasks/bugfix-001/hidden_tests/test_hidden_slugify.py +17 -0
  16. agentbridge_bench/data/tasks/bugfix-001/repo/pytest.ini +3 -0
  17. agentbridge_bench/data/tasks/bugfix-001/repo/tests/test_textutils.py +9 -0
  18. agentbridge_bench/data/tasks/bugfix-001/repo/textutils.py +9 -0
  19. agentbridge_bench/data/tasks/bugfix-001/solution/textutils.py +8 -0
  20. agentbridge_bench/data/tasks/bugfix-001/task.yaml +30 -0
  21. agentbridge_bench/data/tasks/bugfix-002/hidden_tests/test_hidden_median.py +24 -0
  22. agentbridge_bench/data/tasks/bugfix-002/repo/pytest.ini +3 -0
  23. agentbridge_bench/data/tasks/bugfix-002/repo/stats.py +15 -0
  24. agentbridge_bench/data/tasks/bugfix-002/repo/tests/test_stats.py +9 -0
  25. agentbridge_bench/data/tasks/bugfix-002/solution/stats.py +20 -0
  26. agentbridge_bench/data/tasks/bugfix-002/task.yaml +30 -0
  27. agentbridge_bench/data/tasks/bugfix-003/hidden_tests/test_hidden_pagination.py +27 -0
  28. agentbridge_bench/data/tasks/bugfix-003/repo/pagination.py +12 -0
  29. agentbridge_bench/data/tasks/bugfix-003/repo/pytest.ini +3 -0
  30. agentbridge_bench/data/tasks/bugfix-003/repo/tests/test_pagination.py +5 -0
  31. agentbridge_bench/data/tasks/bugfix-003/solution/pagination.py +14 -0
  32. agentbridge_bench/data/tasks/bugfix-003/task.yaml +31 -0
  33. agentbridge_bench/data/tasks/deps-001/hidden_tests/test_hidden_deps.py +31 -0
  34. agentbridge_bench/data/tasks/deps-001/repo/CHANGELOG.md +7 -0
  35. agentbridge_bench/data/tasks/deps-001/repo/pytest.ini +3 -0
  36. agentbridge_bench/data/tasks/deps-001/repo/requirements.txt +3 -0
  37. agentbridge_bench/data/tasks/deps-001/solution/CHANGELOG.md +9 -0
  38. agentbridge_bench/data/tasks/deps-001/solution/requirements.txt +3 -0
  39. agentbridge_bench/data/tasks/deps-001/task.yaml +26 -0
  40. agentbridge_bench/data/tasks/docs-001/hidden_tests/test_hidden_docs.py +36 -0
  41. agentbridge_bench/data/tasks/docs-001/repo/README.md +3 -0
  42. agentbridge_bench/data/tasks/docs-001/repo/mathlib.py +18 -0
  43. agentbridge_bench/data/tasks/docs-001/repo/pytest.ini +3 -0
  44. agentbridge_bench/data/tasks/docs-001/repo/tests/test_mathlib.py +13 -0
  45. agentbridge_bench/data/tasks/docs-001/solution/README.md +13 -0
  46. agentbridge_bench/data/tasks/docs-001/solution/mathlib.py +26 -0
  47. agentbridge_bench/data/tasks/docs-001/task.yaml +31 -0
  48. agentbridge_bench/data/tasks/feature-001/hidden_tests/test_hidden_email.py +24 -0
  49. agentbridge_bench/data/tasks/feature-001/repo/pytest.ini +3 -0
  50. agentbridge_bench/data/tasks/feature-001/repo/tests/test_validators.py +9 -0
  51. agentbridge_bench/data/tasks/feature-001/repo/validators.py +10 -0
  52. agentbridge_bench/data/tasks/feature-001/solution/tests/test_email.py +9 -0
  53. agentbridge_bench/data/tasks/feature-001/solution/validators.py +26 -0
  54. agentbridge_bench/data/tasks/feature-001/task.yaml +32 -0
  55. agentbridge_bench/data/tasks/feature-002/hidden_tests/test_hidden_lru.py +43 -0
  56. agentbridge_bench/data/tasks/feature-002/repo/cache.py +17 -0
  57. agentbridge_bench/data/tasks/feature-002/repo/pytest.ini +3 -0
  58. agentbridge_bench/data/tasks/feature-002/solution/cache.py +28 -0
  59. agentbridge_bench/data/tasks/feature-002/task.yaml +27 -0
  60. agentbridge_bench/data/tasks/feature-003/hidden_tests/test_hidden_durations.py +21 -0
  61. agentbridge_bench/data/tasks/feature-003/repo/durations.py +14 -0
  62. agentbridge_bench/data/tasks/feature-003/repo/pytest.ini +3 -0
  63. agentbridge_bench/data/tasks/feature-003/repo/tests/test_durations.py +6 -0
  64. agentbridge_bench/data/tasks/feature-003/solution/durations.py +22 -0
  65. agentbridge_bench/data/tasks/feature-003/task.yaml +31 -0
  66. agentbridge_bench/data/tasks/refactor-001/hidden_tests/test_hidden_refactor.py +25 -0
  67. agentbridge_bench/data/tasks/refactor-001/repo/geometry.py +22 -0
  68. agentbridge_bench/data/tasks/refactor-001/repo/pytest.ini +3 -0
  69. agentbridge_bench/data/tasks/refactor-001/repo/report.py +17 -0
  70. agentbridge_bench/data/tasks/refactor-001/repo/tests/test_report.py +7 -0
  71. agentbridge_bench/data/tasks/refactor-001/solution/geometry.py +13 -0
  72. agentbridge_bench/data/tasks/refactor-001/solution/report.py +11 -0
  73. agentbridge_bench/data/tasks/refactor-001/task.yaml +32 -0
  74. agentbridge_bench/data/tasks/review-001/hidden_tests/test_hidden_review.py +28 -0
  75. agentbridge_bench/data/tasks/review-001/repo/accounts.py +16 -0
  76. agentbridge_bench/data/tasks/review-001/repo/pytest.ini +3 -0
  77. agentbridge_bench/data/tasks/review-001/solution/REVIEW.md +7 -0
  78. agentbridge_bench/data/tasks/review-001/task.yaml +25 -0
  79. agentbridge_bench/data/tasks/tests-001/hidden_tests/test_hidden_generated_tests.py +49 -0
  80. agentbridge_bench/data/tasks/tests-001/repo/calculator.py +19 -0
  81. agentbridge_bench/data/tasks/tests-001/repo/pytest.ini +3 -0
  82. agentbridge_bench/data/tasks/tests-001/repo/tests/.gitkeep +0 -0
  83. agentbridge_bench/data/tasks/tests-001/solution/tests/test_calculator.py +24 -0
  84. agentbridge_bench/data/tasks/tests-001/task.yaml +25 -0
  85. agentbridge_bench/dataset.py +158 -0
  86. agentbridge_bench/metrics.py +90 -0
  87. agentbridge_bench/models.py +93 -0
  88. agentbridge_bench/reports.py +217 -0
  89. agentbridge_bench/runner.py +386 -0
  90. agentbridge_bench-0.1.0.dist-info/METADATA +50 -0
  91. agentbridge_bench-0.1.0.dist-info/RECORD +94 -0
  92. agentbridge_bench-0.1.0.dist-info/WHEEL +4 -0
  93. agentbridge_bench-0.1.0.dist-info/entry_points.txt +2 -0
  94. agentbridge_bench-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,5 @@
1
+ """AgentBench: reproducible evaluation of AI coding agents (package ``agentbridge-bench``)."""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,118 @@
1
+ """AgentBench command line: ``agentbridge-bench ...`` and ``agentbridge benchmark ...``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+ from typing import Annotated
7
+
8
+ import typer
9
+ from rich.markup import escape
10
+ from rich.table import Table
11
+
12
+ from agentbridge.cli.context import handle_errors
13
+ from agentbridge.cli.output import console
14
+ from agentbridge.errors import ConfigError, NotFoundError
15
+ from agentbridge_bench.dataset import (
16
+ default_benchmarks_dir,
17
+ load_configurations,
18
+ load_suites,
19
+ load_tasks,
20
+ )
21
+ from agentbridge_bench.reports import generate_reports
22
+ from agentbridge_bench.runner import run_benchmark
23
+
24
+ bench_app = typer.Typer(help="AgentBench: reproducible agent evaluation.", no_args_is_help=True)
25
+
26
+ DirOption = Annotated[
27
+ Path | None,
28
+ typer.Option("--benchmarks-dir", help="Directory with tasks/ and configurations.yaml"),
29
+ ]
30
+
31
+
32
+ @bench_app.command("list")
33
+ @handle_errors
34
+ def bench_list(benchmarks_dir: DirOption = None) -> None:
35
+ """List benchmark tasks, suites and configurations."""
36
+ root = benchmarks_dir or default_benchmarks_dir()
37
+ tasks = load_tasks(root)
38
+ table = Table(title=f"AgentBench tasks ({root})")
39
+ for column in ("Task", "Category", "Difficulty", "Timeout (s)"):
40
+ table.add_column(column)
41
+ for task in tasks.values():
42
+ table.add_row(task.task_id, task.task_category.value, task.difficulty, str(task.timeout))
43
+ console.print(table)
44
+ for name, ids in load_suites(root, tasks).items():
45
+ console.print(f"suite [bold]{name}[/bold]: {len(ids)} task(s)")
46
+ for name, config in load_configurations(root).items():
47
+ console.print(
48
+ f"configuration [bold]{name}[/bold] ({config.kind}): {escape(config.description)}"
49
+ )
50
+
51
+
52
+ @bench_app.command("run")
53
+ @handle_errors
54
+ def bench_run(
55
+ suite_name: Annotated[str, typer.Argument(help="Suite name, or a single task id.")],
56
+ config: Annotated[
57
+ list[str] | None,
58
+ typer.Option("--config", "-c", help="Configuration(s) to run (repeatable)."),
59
+ ] = None,
60
+ repetitions: Annotated[int, typer.Option("--repetitions", "-n", min=1, max=20)] = 1,
61
+ output: Annotated[Path | None, typer.Option("--output", help="Experiments directory.")] = None,
62
+ benchmarks_dir: DirOption = None,
63
+ ) -> None:
64
+ """Run a benchmark suite under one or more configurations."""
65
+ root = benchmarks_dir or default_benchmarks_dir()
66
+ tasks = load_tasks(root)
67
+ suites = load_suites(root, tasks)
68
+ if suite_name in suites:
69
+ selected = [tasks[i] for i in suites[suite_name]]
70
+ elif suite_name in tasks:
71
+ selected = [tasks[suite_name]]
72
+ else:
73
+ raise NotFoundError(
74
+ f"Unknown suite or task: {suite_name}", hint=f"Suites: {', '.join(suites)}"
75
+ )
76
+ configurations = load_configurations(root)
77
+ names = config or ["mock-oracle"]
78
+ unknown = [n for n in names if n not in configurations]
79
+ if unknown:
80
+ raise ConfigError(
81
+ f"Unknown configuration(s): {', '.join(unknown)}",
82
+ hint=f"Available: {', '.join(configurations)}",
83
+ )
84
+ exp_dir = run_benchmark(
85
+ root,
86
+ output or (root / "experiments"),
87
+ suite_name,
88
+ selected,
89
+ [configurations[n] for n in names],
90
+ repetitions,
91
+ progress=lambda msg: console.print(escape(msg)),
92
+ )
93
+ console.print(f"[green]Done.[/green] Report: {exp_dir / 'report.md'}")
94
+
95
+
96
+ @bench_app.command("report")
97
+ @handle_errors
98
+ def bench_report(
99
+ experiment_dir: Annotated[Path, typer.Argument(help="Experiment directory.")],
100
+ ) -> None:
101
+ """Regenerate CSV/Markdown/SVG reports from saved experiment records."""
102
+ if not (experiment_dir / "records").is_dir():
103
+ raise NotFoundError(f"No records/ directory in {experiment_dir}")
104
+ summary = generate_reports(experiment_dir)
105
+ for name, metrics in summary.items():
106
+ console.print(
107
+ f"{name}: success={metrics['task_success_rate']} executed={metrics['runs_executed']}"
108
+ )
109
+ console.print(f"Report: {experiment_dir / 'report.md'}")
110
+
111
+
112
+ def main() -> None:
113
+ """Entry point of the ``agentbridge-bench`` command."""
114
+ bench_app(prog_name="agentbridge-bench")
115
+
116
+
117
+ if __name__ == "__main__":
118
+ main()
@@ -0,0 +1,65 @@
1
+ # AgentBench configurations.
2
+ #
3
+ # A configuration is only executed when every provider it uses is available and
4
+ # can run without a human. Others are recorded as "not_executable" (never as
5
+ # failures and never as fabricated results).
6
+
7
+ configurations:
8
+ mock-oracle:
9
+ description: >-
10
+ Harness control. The mock provider writes the task's reference solution, so
11
+ every task should pass. Validates fixtures and hidden tests; says nothing
12
+ about AI agents.
13
+ kind: control
14
+ planner_mode: single
15
+ oracle: true
16
+ providers:
17
+ mock: {type: mock}
18
+ routing: {planner: mock, default_worker: mock}
19
+
20
+ mock-noop:
21
+ description: >-
22
+ Negative control. The mock provider writes an unrelated file, so tasks
23
+ should fail. Confirms the hidden tests cannot be passed without a change.
24
+ kind: control
25
+ planner_mode: single
26
+ providers:
27
+ mock: {type: mock}
28
+ routing: {planner: mock, default_worker: mock}
29
+
30
+ claude-single:
31
+ description: "Configuration A: single Claude Code agent (Task -> Claude Code -> Result)."
32
+ kind: single_agent
33
+ planner_mode: single
34
+ providers:
35
+ claude: {type: claude_code, command: claude, permission_mode: acceptEdits}
36
+ routing: {planner: claude, default_worker: claude}
37
+
38
+ claude-multi:
39
+ description: >-
40
+ Multi-agent workflow using Claude Code in three roles: analyst -> developer ->
41
+ reviewer (template plan), with an additional agent review of the developer diff.
42
+ kind: multi_agent
43
+ planner_mode: standard
44
+ providers:
45
+ claude: {type: claude_code, command: claude, permission_mode: acceptEdits}
46
+ routing: {planner: claude, default_worker: claude, reviewer: claude}
47
+
48
+ antigravity-remote:
49
+ description: "Configuration B: single Antigravity remote agent (manual handoff)."
50
+ kind: remote
51
+ planner_mode: single
52
+ providers:
53
+ antigravity: {type: antigravity_remote, command: agy}
54
+ routing: {planner: antigravity, default_worker: antigravity}
55
+
56
+ claude-antigravity:
57
+ description: >-
58
+ Configuration C: Claude planner -> Antigravity remote worker -> independent
59
+ reviewer. Requires a manual remote handoff.
60
+ kind: remote
61
+ planner_mode: provider
62
+ providers:
63
+ claude: {type: claude_code, command: claude, permission_mode: acceptEdits}
64
+ antigravity: {type: antigravity_remote, command: agy}
65
+ routing: {planner: claude, default_worker: antigravity, reviewer: claude}
@@ -0,0 +1,19 @@
1
+ # Named task suites. "all" (every task) is always available.
2
+ suites:
3
+ core:
4
+ - analysis-001
5
+ - bugfix-001
6
+ - bugfix-002
7
+ - bugfix-003
8
+ - deps-001
9
+ - docs-001
10
+ - feature-001
11
+ - feature-002
12
+ - feature-003
13
+ - refactor-001
14
+ - review-001
15
+ - tests-001
16
+ smoke:
17
+ - bugfix-001
18
+ - feature-001
19
+ - tests-001
@@ -0,0 +1,33 @@
1
+ from pathlib import Path
2
+
3
+ ROOT = Path(__file__).resolve().parents[1]
4
+
5
+
6
+ def text():
7
+ path = ROOT / "ANALYSIS.md"
8
+ assert path.exists(), "ANALYSIS.md was not written"
9
+ return path.read_text(encoding="utf-8")
10
+
11
+
12
+ def test_modules_listed():
13
+ content = text()
14
+ for module in ("cart", "pricing", "tax", "cli"):
15
+ assert module in content
16
+
17
+
18
+ def test_functions_listed():
19
+ content = text()
20
+ for name in ("add_item", "remove_item", "item_count", "apply_discount", "round_price",
21
+ "tax_for"):
22
+ assert name in content
23
+
24
+
25
+ def test_entry_point():
26
+ assert "main" in text()
27
+
28
+
29
+ def test_untested_modules():
30
+ lines = [line.lower() for line in text().splitlines() if "untested" in line.lower()]
31
+ for module in ("pricing", "tax", "cli"):
32
+ assert any(module in line for line in lines), module
33
+ assert not any("cart" in line for line in lines)
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ addopts = -p no:cacheprovider
3
+ testpaths = tests
@@ -0,0 +1 @@
1
+ """A tiny shop."""
@@ -0,0 +1,15 @@
1
+ """Shopping cart."""
2
+
3
+
4
+ def add_item(cart, sku, quantity=1):
5
+ cart[sku] = cart.get(sku, 0) + quantity
6
+ return cart
7
+
8
+
9
+ def remove_item(cart, sku):
10
+ cart.pop(sku, None)
11
+ return cart
12
+
13
+
14
+ def item_count(cart):
15
+ return sum(cart.values())
@@ -0,0 +1,18 @@
1
+ """Command line interface."""
2
+
3
+ import sys
4
+
5
+ from shop.cart import add_item, item_count
6
+
7
+
8
+ def main(argv=None):
9
+ args = sys.argv[1:] if argv is None else argv
10
+ cart = {}
11
+ for sku in args:
12
+ add_item(cart, sku)
13
+ print(item_count(cart))
14
+ return 0
15
+
16
+
17
+ if __name__ == "__main__":
18
+ raise SystemExit(main())
@@ -0,0 +1,9 @@
1
+ """Prices and discounts."""
2
+
3
+
4
+ def apply_discount(price, percent):
5
+ return price * (100 - percent) / 100
6
+
7
+
8
+ def round_price(price):
9
+ return round(price, 2)
@@ -0,0 +1,7 @@
1
+ """Sales tax."""
2
+
3
+ TAX_RATES = {"standard": 0.2, "reduced": 0.05}
4
+
5
+
6
+ def tax_for(amount, category="standard"):
7
+ return amount * TAX_RATES[category]
@@ -0,0 +1,5 @@
1
+ from shop.cart import add_item, item_count
2
+
3
+
4
+ def test_add():
5
+ assert item_count(add_item({}, "a", 2)) == 2
@@ -0,0 +1,20 @@
1
+ # Repository analysis
2
+
3
+ ## Modules in `shop`
4
+
5
+ - `shop.cart`: add_item, remove_item, item_count
6
+ - `shop.pricing`: apply_discount, round_price
7
+ - `shop.tax`: tax_for (plus the TAX_RATES table)
8
+ - `shop.cli`: main
9
+
10
+ ## Entry point
11
+
12
+ The command-line entry point is `shop.cli.main` (run `python -m shop.cli`).
13
+
14
+ ## Test coverage
15
+
16
+ Only `shop.cart` has tests (tests/test_cart.py).
17
+
18
+ - pricing: untested
19
+ - tax: untested
20
+ - cli: untested
@@ -0,0 +1,30 @@
1
+ # base_commit is null for fixture tasks: the base commit is synthesised
2
+ # deterministically from repo/ (see docs/benchmark-methodology.md).
3
+ task_id: analysis-001
4
+ repository: fixture
5
+ base_commit: null
6
+ task_category: repository_analysis
7
+ difficulty: easy
8
+ timeout: 900
9
+ issue_description: |
10
+ Write ANALYSIS.md describing this repository: list every module in the shop package together with its public functions, identify the command-line entry point, and state which modules have no tests (one line per untested module, containing the module name and the word "untested"). Do not modify source files.
11
+ acceptance_criteria:
12
+ - ANALYSIS.md lists all shop modules and their public functions
13
+ - ANALYSIS.md names the CLI entry point
14
+ - ANALYSIS.md lists the untested modules
15
+ expected_files:
16
+ - ANALYSIS.md
17
+ setup_commands: []
18
+ test_commands:
19
+ - - '{python}'
20
+ - -m
21
+ - pytest
22
+ - -q
23
+ - agentbench_hidden
24
+ regression_test_command:
25
+ - '{python}'
26
+ - -m
27
+ - pytest
28
+ - -q
29
+ - tests
30
+ license: MIT (authored for AgentBench)
@@ -0,0 +1,17 @@
1
+ from textutils import slugify
2
+
3
+
4
+ def test_collapses_separators():
5
+ assert slugify("Hello, World!") == "hello-world"
6
+
7
+
8
+ def test_strips_edges():
9
+ assert slugify(" --Python 3.12-- ") == "python-3-12"
10
+
11
+
12
+ def test_empty():
13
+ assert slugify("") == ""
14
+
15
+
16
+ def test_plain():
17
+ assert slugify("abc") == "abc"
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ addopts = -p no:cacheprovider
3
+ testpaths = tests
@@ -0,0 +1,9 @@
1
+ from textutils import slugify
2
+
3
+
4
+ def test_simple_word():
5
+ assert slugify("hello") == "hello"
6
+
7
+
8
+ def test_lowercases():
9
+ assert slugify("Hello") == "hello"
@@ -0,0 +1,9 @@
1
+ """Small text helpers."""
2
+
3
+ import re
4
+
5
+
6
+ def slugify(text: str) -> str:
7
+ """Convert text to a URL slug: lowercase words joined by single dashes."""
8
+ text = text.lower()
9
+ return re.sub(r"[^a-z0-9]", "-", text)
@@ -0,0 +1,8 @@
1
+ """Small text helpers."""
2
+
3
+ import re
4
+
5
+
6
+ def slugify(text: str) -> str:
7
+ """Convert text to a URL slug: lowercase words joined by single dashes."""
8
+ return re.sub(r"[^a-z0-9]+", "-", text.lower()).strip("-")
@@ -0,0 +1,30 @@
1
+ # base_commit is null for fixture tasks: the base commit is synthesised
2
+ # deterministically from repo/ (see docs/benchmark-methodology.md).
3
+ task_id: bugfix-001
4
+ repository: fixture
5
+ base_commit: null
6
+ task_category: bug_fixing
7
+ difficulty: easy
8
+ timeout: 900
9
+ issue_description: |
10
+ textutils.slugify() produces malformed slugs. slugify("Hello, World!") returns "hello---world-" but should return "hello-world". Runs of non-alphanumeric characters must collapse into a single dash, and the slug must not start or end with a dash.
11
+ acceptance_criteria:
12
+ - slugify collapses runs of separators into one dash
13
+ - slugify strips leading and trailing dashes
14
+ - Existing tests keep passing
15
+ expected_files:
16
+ - textutils.py
17
+ setup_commands: []
18
+ test_commands:
19
+ - - '{python}'
20
+ - -m
21
+ - pytest
22
+ - -q
23
+ - agentbench_hidden
24
+ regression_test_command:
25
+ - '{python}'
26
+ - -m
27
+ - pytest
28
+ - -q
29
+ - tests
30
+ license: MIT (authored for AgentBench)
@@ -0,0 +1,24 @@
1
+ import pytest
2
+
3
+ from stats import mean, median
4
+
5
+
6
+ def test_even():
7
+ assert median([1, 2, 3, 4]) == 2.5
8
+
9
+
10
+ def test_even_unsorted():
11
+ assert median([4, 1, 3, 2]) == 2.5
12
+
13
+
14
+ def test_single():
15
+ assert median([5]) == 5
16
+
17
+
18
+ def test_empty_raises_value_error():
19
+ with pytest.raises(ValueError):
20
+ median([])
21
+
22
+
23
+ def test_mean_unchanged():
24
+ assert mean([2, 4]) == 3
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ addopts = -p no:cacheprovider
3
+ testpaths = tests
@@ -0,0 +1,15 @@
1
+ """Basic statistics."""
2
+
3
+
4
+ def mean(values):
5
+ """Arithmetic mean of a non-empty sequence."""
6
+ values = list(values)
7
+ if not values:
8
+ raise ValueError("mean() of empty sequence")
9
+ return sum(values) / len(values)
10
+
11
+
12
+ def median(values):
13
+ """Median of a non-empty sequence of numbers."""
14
+ ordered = sorted(values)
15
+ return ordered[len(ordered) // 2]
@@ -0,0 +1,9 @@
1
+ from stats import mean, median
2
+
3
+
4
+ def test_mean():
5
+ assert mean([1, 2, 3]) == 2
6
+
7
+
8
+ def test_median_odd():
9
+ assert median([3, 1, 2]) == 2
@@ -0,0 +1,20 @@
1
+ """Basic statistics."""
2
+
3
+
4
+ def mean(values):
5
+ """Arithmetic mean of a non-empty sequence."""
6
+ values = list(values)
7
+ if not values:
8
+ raise ValueError("mean() of empty sequence")
9
+ return sum(values) / len(values)
10
+
11
+
12
+ def median(values):
13
+ """Median of a non-empty sequence of numbers."""
14
+ ordered = sorted(values)
15
+ if not ordered:
16
+ raise ValueError("median() of empty sequence")
17
+ middle = len(ordered) // 2
18
+ if len(ordered) % 2:
19
+ return ordered[middle]
20
+ return (ordered[middle - 1] + ordered[middle]) / 2
@@ -0,0 +1,30 @@
1
+ # base_commit is null for fixture tasks: the base commit is synthesised
2
+ # deterministically from repo/ (see docs/benchmark-methodology.md).
3
+ task_id: bugfix-002
4
+ repository: fixture
5
+ base_commit: null
6
+ task_category: bug_fixing
7
+ difficulty: easy
8
+ timeout: 900
9
+ issue_description: |
10
+ stats.median() returns the wrong value for sequences with an even number of elements: median([1, 2, 3, 4]) returns 3 instead of 2.5. It should return the mean of the two middle values. median([]) currently raises IndexError; it must raise ValueError instead.
11
+ acceptance_criteria:
12
+ - median returns the mean of the two middle values for even-length input
13
+ - median([]) raises ValueError
14
+ - Existing tests keep passing
15
+ expected_files:
16
+ - stats.py
17
+ setup_commands: []
18
+ test_commands:
19
+ - - '{python}'
20
+ - -m
21
+ - pytest
22
+ - -q
23
+ - agentbench_hidden
24
+ regression_test_command:
25
+ - '{python}'
26
+ - -m
27
+ - pytest
28
+ - -q
29
+ - tests
30
+ license: MIT (authored for AgentBench)
@@ -0,0 +1,27 @@
1
+ import pytest
2
+
3
+ from pagination import page_count, paginate
4
+
5
+ ITEMS = list(range(25))
6
+
7
+
8
+ def test_first_page():
9
+ assert paginate(ITEMS, 1, 10) == list(range(10))
10
+
11
+
12
+ def test_last_partial_page():
13
+ assert paginate(ITEMS, 3, 10) == [20, 21, 22, 23, 24]
14
+
15
+
16
+ def test_page_count_rounds_up():
17
+ assert page_count(25, 10) == 3
18
+
19
+
20
+ def test_page_count_exact_and_zero():
21
+ assert page_count(20, 10) == 2
22
+ assert page_count(0, 10) == 0
23
+
24
+
25
+ def test_invalid_page():
26
+ with pytest.raises(ValueError):
27
+ paginate(ITEMS, 0)
@@ -0,0 +1,12 @@
1
+ """List pagination helpers."""
2
+
3
+
4
+ def paginate(items, page, per_page=10):
5
+ """Return the items on a 1-indexed page."""
6
+ start = page * per_page
7
+ return items[start : start + per_page]
8
+
9
+
10
+ def page_count(total, per_page=10):
11
+ """Number of pages needed for ``total`` items."""
12
+ return total // per_page
@@ -0,0 +1,3 @@
1
+ [pytest]
2
+ addopts = -p no:cacheprovider
3
+ testpaths = tests
@@ -0,0 +1,5 @@
1
+ from pagination import page_count
2
+
3
+
4
+ def test_exact_pages():
5
+ assert page_count(20, 10) == 2
@@ -0,0 +1,14 @@
1
+ """List pagination helpers."""
2
+
3
+
4
+ def paginate(items, page, per_page=10):
5
+ """Return the items on a 1-indexed page."""
6
+ if page < 1:
7
+ raise ValueError("page numbers start at 1")
8
+ start = (page - 1) * per_page
9
+ return items[start : start + per_page]
10
+
11
+
12
+ def page_count(total, per_page=10):
13
+ """Number of pages needed for ``total`` items."""
14
+ return -(-total // per_page)
@@ -0,0 +1,31 @@
1
+ # base_commit is null for fixture tasks: the base commit is synthesised
2
+ # deterministically from repo/ (see docs/benchmark-methodology.md).
3
+ task_id: bugfix-003
4
+ repository: fixture
5
+ base_commit: null
6
+ task_category: bug_fixing
7
+ difficulty: medium
8
+ timeout: 900
9
+ issue_description: |
10
+ pagination.paginate() is documented as 1-indexed but paginate(items, 1) returns the second page. page_count(25, 10) returns 2 but there are 3 pages. Fix both, and make paginate() raise ValueError for page numbers below 1.
11
+ acceptance_criteria:
12
+ - paginate(items, 1) returns the first page
13
+ - page_count rounds up partial pages
14
+ - paginate raises ValueError for page < 1
15
+ - Existing tests keep passing
16
+ expected_files:
17
+ - pagination.py
18
+ setup_commands: []
19
+ test_commands:
20
+ - - '{python}'
21
+ - -m
22
+ - pytest
23
+ - -q
24
+ - agentbench_hidden
25
+ regression_test_command:
26
+ - '{python}'
27
+ - -m
28
+ - pytest
29
+ - -q
30
+ - tests
31
+ license: MIT (authored for AgentBench)