emrg: classify merge-conflict blocks by the resolution the evidence supports - #1143
emrg: classify merge-conflict blocks by the resolution the evidence supports#1143argszero wants to merge 2 commits into
Conversation
…fects) Adversarial probing of classify-conflict.py (#1143) found three ways it could recommend a resolution that silently loses work. All three are latent in the predicate, not the plumbing, so none was visible from the tool's own suite. 1. duplicate compared declared NAMES only. "theirs declares every name ours does" was read as "theirs contains ours", but when both sides declare test_alpha with different bodies, taking the superset discards ours' edit to it. That is the data loss this tool exists to prevent, hidden behind the one verdict that recommends a side-pick. Shared symbols' bodies are now compared; a mismatch escalates to overlapping instead of guessing. 2. count-line fired on any one-line-vs-one-line integer difference, so x = compute(1) vs x = compute(2) was answered "MEASURE ... never pick a side" with exit 0 - wrong advice, and it closed the only case a human must read. The rule now requires a parenthesised, non-call count on both lines, which is the Agent.md shape. 3. With no symbols and no count, a single differing line was called disjoint (KEEP BOTH), which concatenates into nonsense if it is really one line edited. Ambiguous now escalates. Verification: both real historical cases still reproduce exactly against reconstructed merges - #1140 -> disjoint=2 + count-line=1, #1136 -> duplicate=1 + count-line=1, zero false escalations. Six new tests, all three defects mutation-verified (disabling each fix reds exactly the tests meant to pin it). Full suite 1435 passed / 2 skipped.
|
Follow-up: three latent defects found by adversarial probing at Before this cycle's review I probed the classifier with shapes the cascade can produce but its own suite did not model. All three let it recommend a resolution that silently loses work — the exact failure it exists to prevent, and none was visible from the tests or from the tool's output on the cases it was built for. 1. 2. 3. With no symbols and no count, a single differing line was called Verification. Both real historical cases still reproduce exactly against reconstructed merges: #1140 → The common thread is worth stating, because it is the same one that produced the tool: a predicate that is right on the case in front of you can be wrong on the case one step away, and the only way to find out is to construct the neighbouring case. Defect 1 is the same family as the line-vs-symbol bug the original PR fixed — subset tests keep getting read as containment claims. |
Why
Cycle
cyc20260911-112155hit the same merge-conflict shape twice and the twocases needed opposite resolutions:
--theirsdoesA single
git checkout --theirson the second case destroys work with no signal,and the first case looks identical at the marker level. Nothing in the repo
classified these conflicts, so each was resolved by reading by hand, and the
resolution that is wrong is the one that looks clean.
What
scripts/classify-conflict.py— a classifier only; it never edits files.For every conflict block it prints the class, the evidence, and the resolution
the evidence supports:
count-linecheck-doc-count.py --resolve-conflictdisjointduplicateoverlappingidenticalExit code 1 if any block is
overlapping, so a resolution loop cannotsilently auto-resolve a real edit collision. Exit 0 otherwise (a
count-lineor
disjoint/duplicateverdict is machine-actionable). Output is ASCII in allmodes.
The predicate is declared symbols, not text lines
The first implementation compared content lines, and both real conflicts were
misclassified as
overlapping: the two sides share boilerplate lines such as"""and), which look like overlap under a line-based test. Theshipped predicate extracts declared symbols (
def/classnames) from eachside, which is what actually distinguishes "two independent additions" from "the
same symbol edited twice". Both real cases are pinned as ground-truth regression
tests that reconstruct the actual #1140 (
disjoint) and #1136 (duplicate)merge states.
Verification
uv run pytest tests/ -q→ 1430 passed, 1 skipped (21 new tests)tests/test_classify_conflict.py→ all green; reverting to the line-basedpredicate reds 4 tests, including both ground-truth ones
from emrg.client.app import run_clientOK;python -m emrg --helpOKactionlint .github/workflows/*.ymlcleanpython3; auto-covered bytests/test_script_output_ascii.py(globs
scripts/*.py)scripts/check-doc-count.py→OK: Agent.md documents 1431 collected Python tests(
disjoint,count-line,overlappingincl. exit 1)Scope
Documentation added to
Agent.mdunder the tool list; theAgent.mdPython testcount was re-measured on this tree (1410 → 1431).