Skip to content

Commit efbe813

Browse files
authored
Merge branch 'main' into experiment/code-review/inline-knowledge
2 parents 67ec7fe + 84b23d1 commit efbe813

14 files changed

Lines changed: 4418 additions & 84 deletions

File tree

dataset/codereview.jsonl

Lines changed: 74 additions & 55 deletions
Large diffs are not rendered by default.

docs/_data/code-review.json

Lines changed: 4224 additions & 0 deletions
Large diffs are not rendered by default.

pyproject.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "bcbench"
7-
version = "0.6.1"
7+
version = "0.7.0"
88
description = "Benchmarking tool for Business Central (AL) ecosystem, inspired by SWE-Bench"
99
readme = "README.md"
1010
requires-python = ">=3.13"
@@ -87,6 +87,11 @@ ignore = [
8787
"TRY301", # raise-within-try: suggested "abstract to inner function" refactor adds indirection
8888
]
8989

90+
[tool.ruff.lint.flake8-tidy-imports.banned-api]
91+
# Force sandboxed rendering:
92+
"jinja2.Template".msg = "Use jinja2.sandbox.SandboxedEnvironment instead; the bare Template is not sandboxed."
93+
"jinja2.Environment".msg = "Use jinja2.sandbox.SandboxedEnvironment instead; the bare Environment is not sandboxed."
94+
9095
[tool.ruff.lint.per-file-ignores]
9196
"tests/**" = ["ANN"]
9297
"notebooks/**" = ["ANN", "T20"] # notebooks routinely print() for analysis output

src/bcbench/agent/bcal/bc_eval_capi_bridge.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
_CERT_TENANT_ENV = "CAPI_TENANT_ID"
1717
_CERT_CLIENT_ENV = "CAPI_CLIENT_ID"
1818

19+
# Reasoning effort (not all models support this parameter), different models might have different available values:
20+
# Set to None to omit the parameter entirely (lets the model/service use its own default).
21+
_DEFAULT_REASONING_EFFORT: str | None = None
22+
1923

2024
def _to_jsonable(value: object) -> dict[str, object]:
2125
model_dump = getattr(value, "model_dump", None)
@@ -148,6 +152,10 @@ def main() -> int:
148152
if request.get("tools"):
149153
kwargs["tools"] = request["tools"]
150154

155+
reasoning_effort = request.get("reasoning_effort", _DEFAULT_REASONING_EFFORT)
156+
if reasoning_effort is not None:
157+
kwargs["reasoning_effort"] = reasoning_effort
158+
151159
response = _create_with_retry(client, kwargs)
152160
json.dump(_to_jsonable(response), sys.stdout)
153161
return 0

src/bcbench/agent/shared/mcp.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from pathlib import Path
55
from typing import Any
66

7-
from jinja2 import Template
7+
from jinja2.sandbox import SandboxedEnvironment
88

99
from bcbench.agent.shared.altool_paths import build_assembly_probing_paths, compiler_symbol_folder_for_container
1010
from bcbench.dataset import BaseDatasetEntry
@@ -13,6 +13,8 @@
1313

1414
logger = get_logger(__name__)
1515

16+
_jinja = SandboxedEnvironment(autoescape=False)
17+
1618

1719
def _build_server_entry(server: dict[str, Any], template_context: dict[str, Any]) -> tuple[str, dict[str, Any]]:
1820
server_type: str = server["type"]
@@ -26,7 +28,7 @@ def _build_server_entry(server: dict[str, Any], template_context: dict[str, Any]
2628
}
2729
case "stdio":
2830
args: list[str] = server["args"]
29-
rendered_args = [Template(arg).render(**template_context) for arg in args]
31+
rendered_args = [_jinja.from_string(arg).render(**template_context) for arg in args]
3032
command: str = shutil.which(server["command"]) or server["command"]
3133
entry: dict[str, Any] = {
3234
"type": server_type,

src/bcbench/agent/shared/prompt.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import re
22
from pathlib import Path
33

4-
from jinja2 import Template
4+
from jinja2.sandbox import SandboxedEnvironment
55

66
from bcbench.config import get_config
77
from bcbench.dataset import BaseDatasetEntry
88
from bcbench.types import EvaluationCategory
99

1010
_config = get_config()
1111

12+
_jinja = SandboxedEnvironment(autoescape=False)
13+
1214

1315
def _transform_image_paths(content: str) -> str:
1416
dest_dir = _config.file_patterns.problem_statement_dest_dir
@@ -26,8 +28,7 @@ def build_prompt(entry: BaseDatasetEntry, repo_path: Path, config: dict, categor
2628

2729
task = _transform_image_paths(entry.get_task())
2830

29-
template = Template(template_str)
30-
return template.render(
31+
return _jinja.from_string(template_str).render(
3132
repo_path=repo_path,
3233
task=task,
3334
project_paths=", ".join(entry.project_paths),

src/bcbench/cli_options.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# Note: Defaults are provided in function signatures, not here
1212
RepoPath = Annotated[Path, typer.Option(help="Path to repository")]
1313

14-
OutputDir = Annotated[Path, typer.Option(help="Directory to save evaluation results")]
14+
OutputDir = Annotated[Path, typer.Option(help="Directory to save evaluation results", file_okay=False, dir_okay=True)]
1515

1616
RunId = Annotated[str, typer.Option(envvar="GITHUB_RUN_ID", help="Unique identifier for this evaluation run")]
1717

src/bcbench/dataset/codereview.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
from enum import StrEnum
4+
from typing import Annotated
45

56
from pydantic import BaseModel, ConfigDict, Field, field_validator
67

@@ -46,11 +47,11 @@ def from_input(cls, value: str) -> Severity:
4647
class ReviewComment(BaseModel):
4748
model_config = ConfigDict(frozen=True)
4849

49-
file: str
50-
line_start: int
51-
line_end: int | None = None
50+
file: Annotated[str, Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9./_-]*\.(al|json)$")]
51+
line_start: Annotated[int, Field(ge=1)]
52+
line_end: Annotated[int, Field(ge=1)] | None = None
5253
domain: str | None = None
53-
body: str
54+
body: Annotated[str, Field(min_length=1)]
5455
severity: Severity | None = None
5556

5657
@field_validator("severity", mode="before")

src/bcbench/dataset/dataset_entry.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,15 @@
2020
class TestEntry(BaseModel):
2121
model_config = ConfigDict(frozen=True)
2222

23-
codeunitID: int
23+
codeunitID: Annotated[int, Field(gt=0)]
2424
functionName: Annotated[frozenset[str], Field(min_length=1)]
2525

2626

2727
class EntryMetadata(BaseModel):
2828
model_config = ConfigDict(frozen=True)
2929

3030
area: str | None = None
31-
image_count: int | None = None
31+
image_count: Annotated[int, Field(ge=0)] | None = None
3232
persona: str | None = None
3333

3434

@@ -44,8 +44,8 @@ class BaseDatasetEntry(BaseModel):
4444
base_commit: str = Field(pattern=r"^[a-fA-F0-9]{40}$")
4545
created_at: Annotated[str, Field(min_length=1)]
4646
environment_setup_version: str = Field(pattern=r"^[0-9]{2}\.[0-9]{1}$")
47-
project_paths: list[str] = []
48-
patch: Annotated[str, Field(min_length=1)]
47+
project_paths: list[Annotated[str, Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9 \\/-]*$")]] = []
48+
patch: Annotated[str, Field(min_length=1, pattern=r"^[^\x00]*$")]
4949

5050
@classmethod
5151
def load(cls, dataset_path: Path, entry_id: str | None = None, random: int | None = None) -> list[Self]:
@@ -112,7 +112,7 @@ class _BugFixTestGenBase(BaseDatasetEntry):
112112

113113
fail_to_pass: Annotated[list[TestEntry], Field(alias="FAIL_TO_PASS", min_length=1)]
114114
pass_to_pass: Annotated[list[TestEntry], Field(alias="PASS_TO_PASS")] = []
115-
test_patch: Annotated[str, Field(min_length=1)]
115+
test_patch: Annotated[str, Field(min_length=1, pattern=r"^[^\x00]*$")]
116116

117117
@property
118118
def problem_statement_dir(self) -> Path:
@@ -158,9 +158,9 @@ class NL2ALEntry(BaseDatasetEntry):
158158
"""Dataset entry for NL2AL category — generate AL code from natural language."""
159159

160160
base_commit: str | None = None
161-
nl_prompt: Annotated[str, Field(min_length=1)]
161+
nl_prompt: Annotated[str, Field(min_length=1, pattern=r"^[^\x00]*$")]
162162
expected: Annotated[list[ChecklistAssertion], Field(min_length=1)]
163-
page: str
163+
page: Annotated[str, Field(pattern=r"^[A-Za-z0-9][A-Za-z0-9 ./]*$")]
164164
audience: Literal["Business", "Technical", "Both"]
165165

166166
def get_task(self) -> str:

src/bcbench/operations/hooks_operations.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import json
3+
import shlex
34
from pathlib import Path
45

56
from bcbench.config import get_config
@@ -60,15 +61,16 @@ def _setup_claude_hooks(repo_path: Path, script_path: str, tool_log_path: Path)
6061
with contextlib.suppress(json.JSONDecodeError):
6162
existing_settings = json.loads(settings_file.read_text(encoding="utf-8"))
6263

63-
tool_log_resolved = str(tool_log_path.resolve())
64+
tool_log_quoted = shlex.quote(str(tool_log_path.resolve()))
65+
script_path_quoted = shlex.quote(script_path)
6466
existing_settings["hooks"] = {
6567
"PreToolUse": [
6668
{
6769
"matcher": "",
6870
"hooks": [
6971
{
7072
"type": "command",
71-
"command": f'BCBENCH_TOOL_LOG="{tool_log_resolved}" pwsh -ExecutionPolicy Bypass -File "{script_path}"',
73+
"command": f"BCBENCH_TOOL_LOG={tool_log_quoted} pwsh -ExecutionPolicy Bypass -File {script_path_quoted}",
7274
}
7375
],
7476
}

0 commit comments

Comments
 (0)