review classifier: commit shas and index lines stay out of the prompt
Every classification's shared context began with the PR's head sha, headed each commit with its sha, and carried each file's `index <hash>..<hash>` line in the diff. A push or rebase rewrites all of those, so the provider's prompt cache missed from the first line of the user message even when the commit messages and hunks were unchanged (#5245: "cache hits show as 0.1x"). The header now names only repo#pr, each commit is headed by its position, and the diff drops `index` lines before lockfiles collapse. The `diff --git` paths and every hunk stay. The TryOut's head_sha is untouched: the controller still posts the review against it, and the journal line still logs it. On the ACP runtime the cost sink also adds each classification's tokens to `hyperhive.review_classifier.token.usage`, typed input / output / cacheRead / cacheCreation like claude's own `claude_code.token.usage`, so the hit rate is a number in the metrics store on both runtimes.
This commit is contained in:
parent
57d6d67556
commit
74b63dd031
4 changed files with 190 additions and 35 deletions
|
|
@ -30,22 +30,26 @@ Answer with exactly one JSON object and nothing else, no code fence:
|
|||
|
||||
/// The shared context: the PR's commits with their full messages, then its
|
||||
/// diff with lockfiles collapsed. Built once per run.
|
||||
///
|
||||
/// No object hash goes in: not the head's, a commit's or a blob's. A rebase
|
||||
/// rewrites all of them, and the provider's cache only serves a prefix that
|
||||
/// is byte for byte the same as an earlier one.
|
||||
#[must_use]
|
||||
pub fn shared_context(run: &TryOut) -> String {
|
||||
let mut out = format!(
|
||||
"# Pull request {}#{} at {}\n\n## Commits, oldest first\n\n",
|
||||
run.repo, run.pr, run.head_sha
|
||||
"# Pull request {}#{}\n\n## Commits, oldest first\n\n",
|
||||
run.repo, run.pr
|
||||
);
|
||||
for commit in &run.commits {
|
||||
for (n, commit) in run.commits.iter().enumerate() {
|
||||
let _ = write!(
|
||||
out,
|
||||
"### {}\n\n{}\n\n",
|
||||
commit.sha,
|
||||
"### Commit {}\n\n{}\n\n",
|
||||
n + 1,
|
||||
commit.message.trim_end()
|
||||
);
|
||||
}
|
||||
out.push_str("## Diff\n\n```diff\n");
|
||||
out.push_str(&collapse_lockfiles(&run.diff));
|
||||
out.push_str(&collapse_lockfiles(&strip_index_lines(&run.diff)));
|
||||
out.push_str("```\n\n");
|
||||
out
|
||||
}
|
||||
|
|
@ -111,6 +115,18 @@ fn collapse_lockfiles(diff: &str) -> String {
|
|||
out
|
||||
}
|
||||
|
||||
/// `diff` without the `index <hash>..<hash>` line of each file's header.
|
||||
/// The `diff --git` line, the other header lines and every hunk stay: a hunk
|
||||
/// line starts with ` `, `+`, `-` or `\`, so none of them is matched.
|
||||
fn strip_index_lines(diff: &str) -> String {
|
||||
let mut out = String::with_capacity(diff.len());
|
||||
for line in diff.lines().filter(|line| !line.starts_with("index ")) {
|
||||
out.push_str(line);
|
||||
out.push('\n');
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// One lockfile section being collapsed.
|
||||
struct Collapsed {
|
||||
path: String,
|
||||
|
|
@ -264,6 +280,46 @@ diff --git a/src/main.rs b/src/main.rs
|
|||
assert!(shared.contains("+fn main() { run(); }"), "{shared}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_rebase_that_changes_only_hashes_leaves_the_shared_context_unchanged() {
|
||||
let first = run();
|
||||
let mut rebased = run();
|
||||
rebased.head_sha = "def456".to_owned();
|
||||
rebased.commits[0].sha = "def456".to_owned();
|
||||
rebased.diff = rebased
|
||||
.diff
|
||||
.replace("index 1111..2222", "index 3333..4444")
|
||||
.replace(
|
||||
"diff --git a/src/main.rs b/src/main.rs\n",
|
||||
"diff --git a/src/main.rs b/src/main.rs\nindex 5555..6666 100644\n",
|
||||
);
|
||||
assert_eq!(shared_context(&first), shared_context(&rebased));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_hash_reaches_the_shared_context() {
|
||||
let mut r = run();
|
||||
r.diff
|
||||
.push_str("diff --git a/b.rs b/b.rs\nindex 7777..8888 100644\n--- a/b.rs\n+++ b/b.rs\n@@ -1 +1 @@\n-a\n+b\n");
|
||||
let shared = shared_context(&r);
|
||||
assert!(
|
||||
shared.starts_with("# Pull request hyperhive/hyperhive#7\n\n"),
|
||||
"{shared}"
|
||||
);
|
||||
for hash in ["abc123", "7777", "8888"] {
|
||||
assert!(!shared.contains(hash), "{hash} in {shared}");
|
||||
}
|
||||
assert!(
|
||||
shared.contains("### Commit 1\n\nnix: add a thing"),
|
||||
"{shared}"
|
||||
);
|
||||
assert!(
|
||||
shared.contains("diff --git a/b.rs b/b.rs\n--- a/b.rs"),
|
||||
"{shared}"
|
||||
);
|
||||
assert!(shared.contains("-a\n+b\n"), "{shared}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_quoted_path_is_read_from_its_b_side() {
|
||||
assert_eq!(
|
||||
|
|
|
|||
Loading…
Reference in a new issue