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"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"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"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"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"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). "
|
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()
|
@mcp.tool()
|
||||||
def propose_patch(target: str, diff: str, hypothesis: str) -> str:
|
def propose_patch(target: str, diff: str, hypothesis: str) -> str:
|
||||||
"""Submit a unified git diff (a/ b/ prefixes, paths relative to the target workspace)
|
"""Submit a unified git diff (a/ b/ prefixes, paths relative to the target
|
||||||
plus the hypothesis it tests. The harness applies it only after a path-allowlist
|
workspace, MUST end with a trailing newline) plus the hypothesis it tests.
|
||||||
check. Returns a patch_id, or an error string if refused."""
|
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)
|
return harness.propose_patch(target, diff, hypothesis)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+52
-12
@@ -222,6 +222,13 @@ fn handle(root: &Path, line: &str) -> Result<Value> {
|
|||||||
let diff = field(&req, "diff")?;
|
let diff = field(&req, "diff")?;
|
||||||
let hypothesis = field(&req, "hypothesis")?;
|
let hypothesis = field(&req, "hypothesis")?;
|
||||||
check_target(t)?;
|
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
|
// Allowlist: every path named by the diff must be a safe
|
||||||
// relative path (the git -C below roots them in the target
|
// relative path (the git -C below roots them in the target
|
||||||
// workspace; nothing outside it is reachable).
|
// 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 ws = root.join(format!("targets/{t}/workspace"));
|
||||||
let mut check = Command::new("git")
|
// Every proposal stands alone: reset the workspace to HEAD
|
||||||
.args(["-C", ws.to_str().unwrap(), "apply", "--check", "-"])
|
// first so probe/iteration patches cannot accumulate into a
|
||||||
.stdin(std::process::Stdio::piped())
|
// tested tree the ledger's patch field doesn't describe
|
||||||
.spawn()?;
|
// (phase2-comrak-004 audit finding).
|
||||||
check.stdin.take().unwrap().write_all(diff.as_bytes())?;
|
ensure!(
|
||||||
ensure!(check.wait()?.success(), "diff does not apply cleanly");
|
Command::new("git")
|
||||||
let mut apply = Command::new("git")
|
.args(["-C", ws.to_str().unwrap(), "checkout", "--", "."])
|
||||||
.args(["-C", ws.to_str().unwrap(), "apply", "-"])
|
.status()?
|
||||||
.stdin(std::process::Stdio::piped())
|
.success(),
|
||||||
.spawn()?;
|
"workspace reset failed"
|
||||||
apply.stdin.take().unwrap().write_all(diff.as_bytes())?;
|
);
|
||||||
ensure!(apply.wait()?.success(), "git apply 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()?;
|
||||||
|
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 patch_id = format!("{:x}", Sha256::digest(diff.as_bytes()))[..12].to_string();
|
||||||
let pending = root.join("results/pending");
|
let pending = root.join("results/pending");
|
||||||
std::fs::create_dir_all(&pending)?;
|
std::fs::create_dir_all(&pending)?;
|
||||||
|
|||||||
Binary file not shown.
Reference in New Issue
Block a user