Scoring And Validation
June 29, 2026 ยท View on GitHub
Principle
Grade the final repository state. Do not grade the path.
For version-control tasks, the output is not a patch string. The output is a repository state:
- refs
- commit DAG
- commit boundaries
- tree contents
- index
- worktree
- local remote refs
- conflict state
- metadata
Primary Score
Use a strict binary pass/fail gate:
- final content is correct
- requested history shape is correct
- expected refs point to expected semantic commits
- protected refs are unchanged
- index/worktree state matches the instruction
- no unresolved conflicts unless requested
- no protocol violation
This is the score to headline.
Diagnostic Score
Also compute a 100-point diagnostic score. Do not let it replace pass/fail.
Suggested weights:
- 20: final filesystem/tree state
- 30: semantic commit partitioning
- 25: history graph, refs, and stack topology
- 10: index/worktree/untracked state
- 5: commit messages and metadata
- 10: safety and protocol cleanliness
Suggested caps:
- protocol violation: 0
- protected history damage: cap 30
- final content wrong: cap 55
- content right but graph wrong: cap 70
- only dirty-state mismatch: cap 90
The diagnostic score helps sort failures. The pass/fail result stays crisp.
Git Plumbing Inputs
Prefer plumbing and stable porcelain:
git rev-list --parents --topo-order
git for-each-ref --format=...
git cat-file -p
git diff-tree --root -m --name-status
git diff --cached
git diff
git merge-base
git ls-tree -r
git status --porcelain=v2 --branch
git ls-files -u
git fsck
git patch-id --stable
patch-id is useful but insufficient. It can miss wrong partitioning, duplicated changes, adjacent hunk mistakes, whitespace-sensitive changes, and metadata issues.
Semantic Commit Matching
Never match expected commits by SHA. Rebases, amends, GitButler operations, timestamps, and authors all change hashes.
Match actual commits to expected semantic commits by edit atoms:
expected semantic commit -> set of hidden edit atoms
actual commit -> set of observed edit atoms
For each pair, compute a similarity score from precision and recall:
- recall: did the actual commit include the atoms expected for this semantic commit?
- precision: did it avoid atoms that belong elsewhere?
Then use maximum-weight bipartite matching between expected and actual commits.
This catches:
- two expected commits squashed accidentally
- one expected commit split accidentally
- a hunk amended into the wrong commit
- duplicated parent changes in a child branch
- correct total diff but wrong history
Graph Validation
After matching actual commits to semantic labels, canonicalize the graph:
main -> api-validation -> ui-copy -> docs-update
Validate:
- parent edges
- ancestor relationships
- branch heads
- absence of unexpected reachable commits
- no merge commits unless allowed
- no detached state when a branch head was requested
- protected refs unchanged
For stacked branches, validate two views:
- cumulative branch content: what the branch contains through ancestry
- branch-local introduced content: what commits on that branch introduce over its parent branch
The second view is crucial. A child branch should contain parent changes by ancestry, but it should not duplicate them in its own commits.
Worktree And Index Validation
Many VC operations are about what remains uncommitted.
Validate:
- clean vs dirty worktree
- staged vs unstaged changes
- untracked files
- file mode changes
- unresolved conflicts
- requested leftovers preserved
- unrelated leftovers not touched
Use git status --porcelain=v2, git diff, git diff --cached, and git ls-files -u.
Operation-Specific Invariants
Selective commit:
- committed atoms are reachable from the requested branch
- uncommitted atoms remain uncommitted
- unrelated edits stay untouched
- no extra commit accidentally captures leftovers
Amend into earlier commit:
- target semantic commit contains the new atom
- no fixup/temp commit remains
- descendants are still present and ordered correctly
- final combined diff is expected
Squash:
- requested commits become one semantic commit
- message requirements are satisfied
- no duplicate old commits remain reachable
Split:
- combined diff is preserved
- new commit boundaries match expected atom groups
- order is valid
Reorder:
- same semantic commits remain
- parent order changes as requested
- final content is still correct
Stacked branches:
- expected branch heads exist
- ancestry chain is correct
- each branch introduces only its assigned atoms
- parent branch changes are not duplicated in child-local commits
Conflict recovery:
- no unresolved index entries
- no conflict markers remain unless intentionally part of file content
- resolved content matches expected semantic result
- graph is still valid
Failure Classes
Use explicit failure taxonomy:
ENV_FAILURE: harness or tool setup brokeNO_OP: agent did not meaningfully actCONTENT_WRONG: final files are wrongPARTITION_WRONG: total diff is right, commit grouping is wrongGRAPH_WRONG: order, parents, squash/split, or stack shape is wrongREF_WRONG: wrong branch heads, names, remote refs, or detached stateDIRTY_STATE_WRONG: index/worktree/untracked state is wrongMETADATA_WRONG: commit message/author requirements missedPROTECTED_HISTORY_DAMAGE: base branch or unrelated history was rewrittenPROTOCOL_VIOLATION: forbidden tool, hidden oracle access, network, harness editsTIMEOUT: task did not finish
These classes matter more than a generic "failed" label when improving the benchmark or the tools.
Statistical Reporting
Run repeated independent trials. Single-run agent results are too noisy.
Report:
- pass@1
- pass@k: at least one success in k trials
- pass^k: all k trials succeeded
- posterior mean
(successes + 1) / (trials + 2)with Beta(1,1) prior - credible intervals
For early development, k=3 is fine for finding broken tasks and huge gaps. For public claims about tool superiority, use larger k and confidence intervals.
Efficiency Metrics
Keep efficiency separate from correctness.
Track:
- wall-clock duration
- VC command count
- total shell/tool calls
- failed VC command count
- retry count
- VC output bytes
- agentic turns
- token usage if exposed by the agent runner
- cost if exposed by the provider
Within-agent comparisons are more meaningful than cross-agent comparisons because Codex and Claude Code may expose different token accounting and have different latency profiles.
Measurement Hygiene
Separate what the benchmark asks the agent to do from harness and agent-platform noise.
Pre-run setup is excluded from measured duration and command metrics. For but+skill, this includes fixture creation, but setup, skill installation, and applying the dirty task state.
Timing runs should use a release-built but binary, not target/debug/but. Debug builds make GitButler command latency look worse for reasons unrelated to the version-control workflow being measured.
Codex runs should use an isolated per-run CODEX_HOME with only auth material copied from the user's Codex home, plus plugin loading disabled. This keeps global user plugins, skills, and warning spam out of both wall-time and transcript comparisons.
Runtime reporting should split:
- platform probes: agent CLI startup checks, plugin discovery, no-remote probes
- task-relevant VC commands: commands issued to solve the benchmark task
- tool-internal commands: for example Git commands spawned inside GitButler
- command timing: wrapper-level start, end, duration, summed runtime, and merged occupied runtime for task/platform/internal command buckets
- transcript bytes: prompt, stdout, stderr, platform warnings, skill/reference output, and a warm estimate that subtracts visible skill/reference reads
- failures: task/tool failures vs harmless environment probes
Keep legacy aggregate counters for continuity, but do not headline them when a cleaner split exists. Use task-relevant counters for comparisons like "how many VC commands did the agent need?" Use total transcript bytes for cold-start overhead and warm-estimated transcript bytes for the repeated-use case where the agent has already read the skill/reference material.
The warm transcript estimate is not a real token counter. It subtracts visible output from commands that read installed skill/reference files. Agent-internal instruction loading and hidden reasoning are outside the transcript unless the runner exposes them directly.
Output Handling Burden
If multiple arms pass a task, the interesting question becomes: how much work did the agent have to do to turn tool output into the next correct action?
Track this as a first-class ergonomics metric:
vc_command_count: all version-control commandsvc_inspection_count: read-only VC commands like status, diff, show, log, branch listingvc_mutation_count: write operations like branch creation, commit, amend, squash, moveread_to_write_ratio: inspections divided by mutationsparser_command_count:jq,python -c,node -e,sed,awk,grep,cut, or similar commands used to extract data from VC outputrepeated_state_queries: repeated status/diff/show commands without intervening file or VC mutationselector_failure_count: wrong file IDs, wrong commit IDs, pathspec failures, invalid revision names, bad branch selectorsvc_output_bytes_before_first_mutation: how much VC output the agent had to consume before it could acttime_to_first_successful_mutation_ms: elapsed time from prompt start to first successful VC writeretry_after_parse_failure_count: failed command followed by a corrected command using a different selector or parsed value
This is not just speed. It measures how hard the tool's output is for an agent to operationalize. A tool that succeeds in three clear commands is meaningfully better than one that succeeds after eight inspections, two parser scripts, and a selector retry.
Do not treat parser commands as inherently bad. Sometimes they are the right move. But a high parser burden is strong evidence that the tool output is not agent-friendly enough.
LLM Judge
Use an LLM judge only for post-hoc diagnostics:
- discoverability
- error recovery
- output comprehension
- overuse or underuse of the intended tool
- transcript summary
Do not use an LLM judge for the primary score.