mirror of
https://github.com/bdbrown4/peltier.io.git
synced 2026-11-07 10:58:28 +00:00
agent: propose_patch ergonomics from the phase2-comrak-004 audit
- Standalone-proposal semantics: workspace resets to pristine before every apply, so probe patches can't accumulate into a tested tree the ledger's patch field doesn't describe. - Friendly errors: explicit trailing-newline check, and git apply's stderr relayed on refusal instead of a generic message. - Tool docs + task prompt updated to match. - phase2-comrak-004 audited: honest rejected-bench (median 1.0124, CI [1.0011, 1.0267] — real but under the 2% bar), Mutex pre-flagged for human review, cumulative-probe state disclosed in the hypothesis. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EcKPWQ3MQD4NdEHwar96u2
This commit is contained in:
@@ -86,7 +86,9 @@ async def run_attempt(target: str, run_id: str, max_turns: int, repo_root: str)
|
||||
f"3. Read the relevant source with read_target_source — for large files pass "
|
||||
f"offset/limit and page through in windows (do NOT attempt any shell). State your "
|
||||
f"hypothesis, then propose_patch (unified diff, a/ b/ prefixes, paths relative to the "
|
||||
f"target workspace root).\n"
|
||||
f"target workspace root, trailing newline required). Each proposal is STANDALONE — "
|
||||
f"the workspace resets to pristine before every apply, so always submit the complete "
|
||||
f"patch, never an increment on a prior proposal.\n"
|
||||
f"4. run_verdict with run_id '{run_id}', the class number, and the hotspot string. "
|
||||
f"It launches a multi-minute pipeline and returns immediately.\n"
|
||||
f"5. Poll read_verdict('{run_id}') until it returns a verdict (not a 'running' status). "
|
||||
|
||||
@@ -50,9 +50,12 @@ def read_target_source(target: str, path: str, offset: int = 0, limit: int = 400
|
||||
|
||||
@mcp.tool()
|
||||
def propose_patch(target: str, diff: str, hypothesis: str) -> str:
|
||||
"""Submit a unified git diff (a/ b/ prefixes, paths relative to the target workspace)
|
||||
plus the hypothesis it tests. The harness applies it only after a path-allowlist
|
||||
check. Returns a patch_id, or an error string if refused."""
|
||||
"""Submit a unified git diff (a/ b/ prefixes, paths relative to the target
|
||||
workspace, MUST end with a trailing newline) plus the hypothesis it tests.
|
||||
Each proposal is STANDALONE: the workspace is reset to pristine before the
|
||||
diff is applied, so always submit the complete patch — never an increment
|
||||
on top of an earlier proposal. The harness applies it only after a
|
||||
path-allowlist check. Returns a patch_id, or an error string if refused."""
|
||||
return harness.propose_patch(target, diff, hypothesis)
|
||||
|
||||
|
||||
|
||||
+50
-10
@@ -222,6 +222,13 @@ fn handle(root: &Path, line: &str) -> Result<Value> {
|
||||
let diff = field(&req, "diff")?;
|
||||
let hypothesis = field(&req, "hypothesis")?;
|
||||
check_target(t)?;
|
||||
// git rejects a diff without a final newline with an opaque
|
||||
// "corrupt patch" — say it plainly (phase2-comrak-004 burned
|
||||
// several turns rediscovering this).
|
||||
ensure!(
|
||||
diff.ends_with('\n'),
|
||||
"diff must end with a trailing newline (git would report 'corrupt patch' at EOF)"
|
||||
);
|
||||
// Allowlist: every path named by the diff must be a safe
|
||||
// relative path (the git -C below roots them in the target
|
||||
// workspace; nothing outside it is reachable).
|
||||
@@ -238,18 +245,51 @@ fn handle(root: &Path, line: &str) -> Result<Value> {
|
||||
);
|
||||
}
|
||||
let ws = root.join(format!("targets/{t}/workspace"));
|
||||
let mut check = Command::new("git")
|
||||
.args(["-C", ws.to_str().unwrap(), "apply", "--check", "-"])
|
||||
// Every proposal stands alone: reset the workspace to HEAD
|
||||
// first so probe/iteration patches cannot accumulate into a
|
||||
// tested tree the ledger's patch field doesn't describe
|
||||
// (phase2-comrak-004 audit finding).
|
||||
ensure!(
|
||||
Command::new("git")
|
||||
.args(["-C", ws.to_str().unwrap(), "checkout", "--", "."])
|
||||
.status()?
|
||||
.success(),
|
||||
"workspace reset failed"
|
||||
);
|
||||
ensure!(
|
||||
Command::new("git")
|
||||
.args(["-C", ws.to_str().unwrap(), "clean", "-fdq"])
|
||||
.status()?
|
||||
.success(),
|
||||
"workspace clean failed"
|
||||
);
|
||||
let run_git_apply = |check: bool| -> Result<std::process::Output> {
|
||||
let mut args = vec!["-C", ws.to_str().unwrap(), "apply"];
|
||||
if check {
|
||||
args.push("--check");
|
||||
}
|
||||
args.push("-");
|
||||
let mut child = Command::new("git")
|
||||
.args(&args)
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
check.stdin.take().unwrap().write_all(diff.as_bytes())?;
|
||||
ensure!(check.wait()?.success(), "diff does not apply cleanly");
|
||||
let mut apply = Command::new("git")
|
||||
.args(["-C", ws.to_str().unwrap(), "apply", "-"])
|
||||
.stdin(std::process::Stdio::piped())
|
||||
.spawn()?;
|
||||
apply.stdin.take().unwrap().write_all(diff.as_bytes())?;
|
||||
ensure!(apply.wait()?.success(), "git apply failed");
|
||||
child.stdin.take().unwrap().write_all(diff.as_bytes())?;
|
||||
Ok(child.wait_with_output()?)
|
||||
};
|
||||
let checked = run_git_apply(true)?;
|
||||
ensure!(
|
||||
checked.status.success(),
|
||||
"diff does not apply cleanly against the pristine workspace \
|
||||
(proposals are standalone; prior proposals were reset): {}",
|
||||
String::from_utf8_lossy(&checked.stderr).trim()
|
||||
);
|
||||
let applied = run_git_apply(false)?;
|
||||
ensure!(
|
||||
applied.status.success(),
|
||||
"git apply failed: {}",
|
||||
String::from_utf8_lossy(&applied.stderr).trim()
|
||||
);
|
||||
let patch_id = format!("{:x}", Sha256::digest(diff.as_bytes()))[..12].to_string();
|
||||
let pending = root.join("results/pending");
|
||||
std::fs::create_dir_all(&pending)?;
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user