Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
7ed58aa
parser.py: mark reward.txt as outdated; training uses formulacode/rew…
ArjunS07 Sep 29, 2026
eaa1250
Merge remote-tracking branch 'origin/formulacode/canonical-task-templ…
ArjunS07 Sep 29, 2026
48c4266
task template: a patch that changes only compiled sources runs the te…
ArjunS07 Oct 6, 2026
9e2cbe8
task template: import sqlite3 before pandas so pytest-cov loads
ArjunS07 Oct 6, 2026
988640c
task template: compiled_fallback continues past a test file that fail…
ArjunS07 Oct 6, 2026
12e97d6
Merge remote-tracking branch 'origin/fix/sqlite-before-pandas' into s…
ArjunS07 Oct 6, 2026
d95d8b6
task template: pytest_runner puts the repo first on sys.path, so a co…
ArjunS07 Oct 7, 2026
cb41070
task template: remove a second installed copy of the project, and ski…
ArjunS07 Oct 7, 2026
94a2b47
Merge fix/runner-installed-package-conftest into survey/phase1-templa…
ArjunS07 Oct 7, 2026
6dafbf1
template: test and benchmark folders are not the project's package
ArjunS07 Oct 7, 2026
04c879f
Merge fix/template-project-shadowed (6dafbf1) into survey/phase1-temp…
ArjunS07 Oct 7, 2026
11ecad6
template: reinstall a copy built from the repo as editable instead of…
ArjunS07 Oct 7, 2026
98ca4e7
Merge fix/template-project-shadowed (11ecad6) into survey/phase1-temp…
ArjunS07 Oct 7, 2026
5c3b08e
Merge fix/baseline-commit-no-auto-gc (datasmith#44) into survey/phase…
ArjunS07 Oct 7, 2026
b2f1922
task template: paired timing rebuilds the base copy and the patched t…
ArjunS07 Oct 8, 2026
06748c7
task template: base_copy copies the repo twice instead of moving it, …
ArjunS07 Oct 8, 2026
45c1b2c
task template: LSV_REF 9d1b667 (lsv#9: the coverage pass traces child…
ArjunS07 Oct 8, 2026
03289ba
task image: remove git history after HEAD so the upstream fix is not …
ArjunS07 Oct 8, 2026
c6be027
tests: ruff format test_scrub_git.py
ArjunS07 Oct 8, 2026
ac86cde
Merge pull request #50 from formula-code/fix/prune-git-history-20261008
ArjunS07 Oct 8, 2026
f95eb40
task image: when benchmark discovery fails on a module the env lacks,…
ArjunS07 Oct 8, 2026
cc1cf7f
task template: put files a stored build script edited back to the bas…
ArjunS07 Oct 8, 2026
81df1c8
base_restore.json: scikit-image 6981 (its edit is next to an oracle h…
ArjunS07 Oct 8, 2026
55f95ba
Merge pull request #52 from formula-code/fix/base-edited-clean-base
ArjunS07 Oct 8, 2026
2ff4722
Merge pull request #53 from formula-code/fix/build-missing-deps
ArjunS07 Oct 8, 2026
ce6f012
template: confirmation pass with more paired rounds on unsure benchmarks
ArjunS07 Oct 8, 2026
4eb2b2a
tests: ruff format test_lsv_confirm_pass.py
ArjunS07 Oct 8, 2026
fd286c6
Merge pull request #54 from formula-code/feat/lsv-confirm-pass
ArjunS07 Oct 8, 2026
9a31599
template: FC_SKIP_PYTEST and FC_SKIP_SNAPSHOT for timing-only replay …
ArjunS07 Oct 8, 2026
0976765
template: read the skip flags before sourcing profile.d
ArjunS07 Oct 8, 2026
1e2909a
Merge pull request #55 from formula-code/feat/skip-pytest-snapshot
ArjunS07 Oct 8, 2026
cd98446
lsv_measure: --only times only the listed parameter cases
ArjunS07 Oct 9, 2026
90ceb67
Merge pull request #56 from formula-code/fix/confirm-only-cases
ArjunS07 Oct 9, 2026
3166ffb
pytest_runner: base run uses the args that produced the agent results…
ArjunS07 Oct 9, 2026
9331b25
prompt: tell the agent to leave the benchmark files and measurement c…
ArjunS07 Oct 9, 2026
9e9a559
test.sh: discard agent edits to the measurement setup before timing a…
ArjunS07 Oct 9, 2026
8f1a390
prompt: say measurement setup edits are thrown away (datasmith#59)
ArjunS07 Oct 9, 2026
9d624d9
pytest runner: leave the agent's new test files out of the base run
ArjunS07 Oct 10, 2026
7ebec8b
pytest runner: split untracked files by line so names with spaces sta…
ArjunS07 Oct 10, 2026
a46453a
pytest runner: use a container-local --basetemp so tmp_path works
ArjunS07 Oct 10, 2026
d6ed699
pytest runner: compare committed agent changes against the base commit
ArjunS07 Oct 10, 2026
783da81
task template: LSV_REF to lsv#10 (copy sys.modules before the extensi…
ArjunS07 Oct 13, 2026
5c70ad5
Merge remote-tracking branch 'origin/main' into release/template-v1
ArjunS07 Oct 19, 2026
9621a67
Merge remote-tracking branch 'origin/fix/base-run-committed-changes' …
ArjunS07 Oct 19, 2026
7246c48
Merge remote-tracking branch 'origin/feat/prompt-leave-measurement-se…
ArjunS07 Oct 19, 2026
3bbad40
Merge remote-tracking branch 'origin/feat/restore-measurement-setup' …
ArjunS07 Oct 19, 2026
2e86fdb
Merge remote-tracking branch 'origin/fix/lsv-ref-modules-snapshot' in…
ArjunS07 Oct 19, 2026
2518f34
Merge remote-tracking branch 'origin/formulacode/paired-measure' into…
ArjunS07 Oct 19, 2026
20b1b73
env image build: pin lsv and snapshot-tester to the template's commits
ArjunS07 Oct 19, 2026
038fc6a
task template: log the gate lease id with each acquire, so a stopped …
ArjunS07 Oct 19, 2026
0e244db
task template: shorter comments in parser.py, lsv_measure.py and reco…
ArjunS07 Oct 19, 2026
3f4bc00
task template: LSV_REF to lsv 909ec2f (lsv#7: a failing teardown does…
ArjunS07 Oct 19, 2026
d0b2d2d
task template: measure gate is a host lock folder of slot files (floc…
ArjunS07 Oct 19, 2026
698630b
task template: FC_SKIP_LSV skips the LSV measure and confirmation; re…
ArjunS07 Oct 19, 2026
2b7431d
image build check: add the precheck fields by_deps, always_affected, …
ArjunS07 Oct 19, 2026
4bf55df
task template: LSV_REF to lsv 6858286 (lsv#12: a changed binary file …
ArjunS07 Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/guide/verification.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,16 @@ the build *and* test stages have both passed, so a doomed attempt never pays it.
| `DATASMITH_VERIFY_MEASURE_ROUNDS` | `2` | LSV timing rounds; matches stage 7 |
| `DATASMITH_VERIFY_MEASURE_GEOMEAN_MIN` | `1.0` | Threshold for the `speedup_direction` warning |

## Correctness tests in harbor tasks

A harbor task's `tests/test.sh` runs `tests/pytest_runner.py`, which selects tests from the files the patch changes. Tests or snapshots must check correctness, and a trial where neither ran is graded `unverified`.

- Tests that map from the changed files by name run first.
- When none map and the patch changes Python sources, the runner runs the nearest tests folder above them (`strategy: regression-fallback:package-tests`).
- When none map and the patch changes compiled sources (C, C++, Cython, Fortran, CUDA, their `.in`, `.src` or `.tp` templates, or build files), the runner runs up to 12 test files from the nearest tests folder above them, best name matches first (`strategy: compiled_fallback`). `test.sh` rebuilds the extensions for the same file types first.
- The runner then stashes the patch, rebuilds, and runs the same tests at the base commit. A test that passes at base and fails with the patch is a regression.
- Oracle runs and agent attempts use the same selection.

## Preflight check

Before running verification, confirm your environment is properly configured:
Expand Down
66 changes: 65 additions & 1 deletion src/datasmith/docker/templates/apply_oracle_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,19 @@

Always exits 0. Whether the patch applied is a fact for the invariants to
judge, not a reason to abort the measure step.

Precheck fields (same as datasmith.verify.in_image.coverage_check):
by_deps = benchmarks LSV selects through a dependency on a file the patch changes (always_affected ones excluded).
edited_before = tracked files the image already has edited, before the patch.
edited_overlap = those files whose edited lines touch an oracle hunk (its changed or context lines).
"""

from __future__ import annotations

import argparse
import json
import os
import re
import subprocess # noqa: S404
import sys
import tempfile
Expand Down Expand Up @@ -88,24 +94,66 @@ def filter_patch(text: str, excluded_prefixes: list[str]) -> tuple[str, int]:
return "".join(kept), dropped


def _git(repo: str, *args: str) -> subprocess.CompletedProcess:
def _git(repo: str, *args: str, env: dict | None = None) -> subprocess.CompletedProcess:
return subprocess.run( # noqa: S603
["git", "-C", repo, *args], # noqa: S607
capture_output=True,
text=True,
errors="replace",
env=env,
)


# unidiff is not available in the image (Python 3.8, no network), so hunk headers are parsed here.
HUNK = re.compile(r"^@@ -(\d+)(?:,(\d+))? ")


def old_ranges(diff):
"""{file: [(first, last)]} old-side line ranges of each hunk; a pure insertion covers the line it follows."""
out, cur = {}, None
for line in diff.splitlines():
if line.startswith("+++ "):
cur = line[6:] if line.startswith("+++ b/") else None
if cur is not None:
out.setdefault(cur, [])
continue
m = HUNK.match(line)
if m and cur is not None:
start, n = int(m.group(1)), int(m.group(2) or 1)
out[cur].append((start, start + max(n, 1) - 1) if n else (start, start + 1))
return out


def overlapping(image_diff, patch_text):
"""Files whose image edits touch a hunk of the oracle patch."""
edits, patch = old_ranges(image_diff), old_ranges(patch_text)
return sorted(f for f, rs in edits.items() if any(a <= d and c <= b for a, b in rs for c, d in patch.get(f, [])))


def deps_counts(db_path: str, changed: list[str]) -> tuple[int, int]:
"""(by_deps, always_affected) from the deps DB lsv_init wrote; ``changed`` are absolute paths."""
from asv.contrib.lightspeed.deps_db import LightspeedDB
from asv.contrib.lightspeed.fingerprint import changed_files_with_fingerprints

db = LightspeedDB(db_path)
# shortcut: LightspeedDB has no public query for always_affected, so this reads db._con; upgrade when LSV adds one.
always = {r[0] for r in db._con.execute("select benchmark_id from benchmark_meta where always_affected = 1")}
affected = {str(b) for b in db.get_affected_benchmark_ids(changed_files_with_fingerprints(changed, db.get_stored_fshas()))}
return len(affected - always), len(always)


def main(argv: list[str] | None = None) -> int:
ap = argparse.ArgumentParser(description="Filter and apply the oracle patch")
ap.add_argument("--patch", required=True)
ap.add_argument("--repo", default="/workspace/repo")
ap.add_argument("--benchmark-dir", default="")
ap.add_argument("--base-sha", default="")
ap.add_argument("--out", default="/opt/formulacode/patch_info.json")
ap.add_argument("--deps-db", default="")
args = ap.parse_args(argv)

info = {"present": False, "applied": False, "files_changed": 0, "paths_excluded": 0}
info.update(dict.fromkeys(("by_deps", "always_affected", "edited_before", "edited_overlap")))

try:
with open(args.patch, encoding="utf-8", errors="replace") as fh:
Expand All @@ -128,6 +176,14 @@ def main(argv: list[str] | None = None) -> int:
filtered, dropped = filter_patch(text, excluded)
info["paths_excluded"] = dropped

info["edited_before"] = _git(args.repo, "diff", "--name-only", "HEAD").stdout.split()
info["edited_overlap"] = overlapping(_git(args.repo, "diff", "-U0", "HEAD").stdout, text)
# A copy of the starting tree in a separate index, so the files the patch changes exclude the image's own edits.
start_dir = tempfile.TemporaryDirectory()
start_env = dict(os.environ, GIT_INDEX_FILE=os.path.join(start_dir.name, "index"))
_git(args.repo, "read-tree", "HEAD", env=start_env)
_git(args.repo, "add", "-A", env=start_env)

if filtered.strip():
fd, tmp = tempfile.mkstemp(suffix=".patch")
try:
Expand All @@ -150,6 +206,14 @@ def main(argv: list[str] | None = None) -> int:
except OSError:
pass

patch_changed = [os.path.join(args.repo, p) for p in _git(args.repo, "diff", "--name-only", env=start_env).stdout.split()]
start_dir.cleanup()
if args.deps_db and os.path.exists(args.deps_db):
try:
info["by_deps"], info["always_affected"] = deps_counts(args.deps_db, patch_changed)
except Exception as exc: # noqa: BLE001 — a missing LSV leaves the fields None
print(f"[apply_oracle_patch] deps count failed: {exc}", file=sys.stderr)

if args.base_sha:
diff = _git(args.repo, "diff", "--name-only", args.base_sha)
if diff.returncode == 0:
Expand Down
8 changes: 3 additions & 5 deletions src/datasmith/docker/templates/docker_build_final.sh
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,9 @@ eval "$(micromamba shell hook --shell=bash)"
micromamba activate "$ENV_NAME" || true
set -u

micromamba run -n "$ENV_NAME" uv pip install git+https://github.com/formula-code/snapshot-tester.git || true
# LSV powers measure.sh's impacted-benchmark selection at container-run time.
# Installed here (per-build, NOT in the cached base image) so the same
# selector runs at stage 6 as at stage 7, where harbor's setup.sh installs it.
micromamba run -n "$ENV_NAME" uv pip install --no-cache git+https://github.com/formula-code/lsv.git || true
# Same commits as LSV_REF and SNAPSHOT_TESTER_REF in harbor_adapter/template/environment/Dockerfile.
micromamba run -n "$ENV_NAME" uv pip install git+https://github.com/formula-code/snapshot-tester.git@f08bb0cd51a03c891e65060e25e69f8700055ba2 || true
micromamba run -n "$ENV_NAME" uv pip install --no-cache git+https://github.com/formula-code/lsv.git@68582867dc721f27e018e79151cecc821131d95c || true
micromamba run -n "$ENV_NAME" uv pip install -q --upgrade coverage || true

# ── Discover ASV benchmarks ──────────────────────────────────────────
Expand Down
21 changes: 9 additions & 12 deletions src/datasmith/docker/templates/docker_build_run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -106,18 +106,15 @@ lock_repo_to_current_commit() {
# DO NOT run: git gc --prune=now --aggressive
# DO NOT run: git prune --expire=now

# 8) Verification: ensure no ref points to a descendant of HEAD
if while IFS= read -r ref; do
[[ "$ref" == "refs/heads/$BR" ]] && continue
if git merge-base --is-ancestor "$HEAD_SHA" "$(git rev-parse "$ref")"; then
echo "$ref"
exit 0
fi
done < <(git for-each-ref --format='%(refname)') ; then
: # no output means OK
else
echo "Error: some refs still point ahead of HEAD. Aborting."
return 1
# 8) Verification: no ref may point at a descendant of HEAD
local ahead
ahead=$(git for-each-ref --format='%(refname)' | while IFS= read -r ref; do
[[ "$ref" == "refs/heads/$BR" ]] && continue
git merge-base --is-ancestor "$HEAD_SHA" "$(git rev-parse "$ref^{commit}")" 2>/dev/null && echo "$ref"
done || true)
if [[ -n "$ahead" ]]; then
echo "Error: refs ahead of HEAD: $ahead" >&2
return 1
fi

}
Expand Down
1 change: 1 addition & 0 deletions src/datasmith/docker/templates/emit_measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def measure_block(
"patch_applied": patch_info.get("applied"),
"patch_files_changed": patch_info.get("files_changed"),
"patch_paths_excluded": patch_info.get("paths_excluded"),
**{k: patch_info.get(k) for k in ("by_deps", "always_affected", "edited_before", "edited_overlap")},
"benchmarks_impactable_n": impactable_n,
"benchmarks_measured_n": measured_n,
"benchmarks_degenerate_n": degenerate_n,
Expand Down
1 change: 1 addition & 0 deletions src/datasmith/docker/templates/measure.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ echo "[measure] applying oracle patch from ${PATCH_FILE} ..."
--repo /workspace/repo \
--benchmark-dir "${BENCHMARK_DIR:-}" \
--base-sha "$BASE_SHA" \
--deps-db "${LSV_OUTPUT_DIR}/results/.lightspeed_deps.db" \
--out "$PATCH_INFO" || echo "[measure] WARNING: apply_oracle_patch exited non-zero"

# ── 4. Measure the impacted set after the patch ──────────────────────────
Expand Down
1 change: 1 addition & 0 deletions src/datasmith/github/templates/final.md.j2
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The micromamba environment includes **Pytest** for testing and **Airspeed Veloci
* **Target the hot paths** shown by profiling.
* **Evidence-driven**: justify changes with benchmark/profile data.
* **Correctness first**: never trade correctness for speed.
* **Leave the measurement setup alone**: the ASV benchmark files, `asv.conf*`, `conftest.py` and `.github/` are restored to their original state before your change is measured, so edits there are thrown away. Speed up the library code instead.
{% if repo_description %}

**Repository Description**
Expand Down
18 changes: 15 additions & 3 deletions src/datasmith/harbor_adapter/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ def task_dir_name(self) -> str:
"pytest_runner.py",
"jinja_patch_plugin_pandas.py",
"rebuild.sh",
"project_imports.py",
"measure_lock.sh",
)


Expand Down Expand Up @@ -81,24 +83,31 @@ def generate_task(
rounds: int | None = None,
verifier_env: dict[str, str] | None = None,
expected_n: int | None = None,
restore_paths: list[str] | None = None,
) -> Path:
"""Render the Harbor task dir for ``rec``.

``rounds`` (default ``DATASMITH_LSV_ROUNDS``) drives both the image baseline and the trial passes.
``expected_n`` (from ``formulacode_task_overrides``) becomes ``FORMULACODE_EXPECTED_N`` for the
dilution_ratio invariant; ``None`` emits no key, so the invariant skips.
``restore_paths`` are repo files the image puts back to the base commit (base_restore.json).
"""
rounds = DATASMITH_LSV_ROUNDS if rounds is None else rounds
out_dir = self.out_root / rec.task_dir_name
env, tests, solution = (out_dir / d for d in ("environment", "tests", "solution"))
for d in (env, tests, solution):
d.mkdir(parents=True, exist_ok=True)

copy2(self.template_dir / "environment" / "entrypoint.sh", env / "entrypoint.sh")
for name in ("entrypoint.sh", "scrub_git.sh"):
copy2(self.template_dir / "environment" / name, env / name)
for name in TEST_HELPERS:
copy2(self.template_dir / "tests" / name, tests / name)
# The Dockerfile's baseline measurement runs lsv_init.py at image build, before /tests exists.
copy2(self.template_dir / "tests" / "lsv_init.py", env / "lsv_init.py")
for name in ("lsv_init.py", "project_imports.py"):
copy2(self.template_dir / "tests" / name, env / name)
if restore_paths:
copy2(self.template_dir / "environment" / "restore_base.sh", env / "restore_base.sh")
copy2(self.template_dir / "tests" / "rebuild.sh", env / "rebuild.sh")

_write(out_dir / "instruction.md", render_instruction_md(rec.instructions))
if expected_n is not None:
Expand All @@ -120,7 +129,10 @@ def generate_task(
patch_roots = sorted({p.split("/")[0] for p in re.findall(r"diff --git a/([^ ]+)", rec.patch) if "/" in p})
image_config = {k: v for k, v in config.items() if k not in ("patch", "gt_hash")} | {"patch_roots": patch_roots}
base_image = rec.container_name or "python:3.11-slim"
_write(env / "Dockerfile", render_dockerfile(base_image, numba_threads=cpus, lsv_rounds=rounds))
_write(
env / "Dockerfile",
render_dockerfile(base_image, numba_threads=cpus, lsv_rounds=rounds, restore_paths=restore_paths or ()),
)
_write(env / "config.json", json.dumps(image_config, indent=2))

ids = {"task_id": rec.task_id, "owner": rec.owner, "repo": rec.repo, "issue_number": rec.issue_number}
Expand Down
Loading
Loading