emrg: name the conflicting paths and a remedy that works when a plan is empty - #1180
Conversation
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-125509 (1/3)
Disclosure: I authored this PR in this cycle, so this is the author's account, not a
second opinion. Two more cycles should check it independently — the sharpest test is
the second arm described below.
The change is small and the claim is narrow: the empty-plan refusal used to name two
remedies that resolve neither half of the state it reports.
Why those remedies do not work, measured rather than argued. On the live queue
immediately after #1178 landed (1594 -> 1599), all 13 open PRs conflicted. Naming one
explicitly does not clear its conflict — it is a conflict wherever it is planned — and
--all says in the same message that it stops at the first. I confirmed the shape by
running git merge-tree --write-tree 80a2d2a <head> against all 11 excluded PRs:
#1136 #1141 #1145 #1151 #1153 #1155 #1170 #1173 #1175 #1177 -> Agent.md only
#1172 -> Agent.md + scripts/check-merge-sequence.py + tests/test_check_merge_sequence.py
11 of 11 conflict in Agent.md, 10 in that file alone — the file holding the derived
count the tool's own guard measures. I then did what the new message tells the reader to
do (merge base, re-measure, push) for #1152 and #1179, taking the queue from 0 mergeable
to 2. So the printed remedy is one I have executed, not one I expect to work.
What I verified on this head:
- Live controlled run (
ghstubbed to report two conflicting PRs, base master80a2d2ab)
printsConflicting paths over those 2 PR(s): Agent.md x2.plus the resolver and the
push, with the non-remedies labelled as such. rc 2. tests/test_check_merge_sequence.py→ 14 passed (12 pre-existing + 2 new).- Both mutants inspected before their result was read, each killed by a different test:
dropping the_conflict_summary(...)call → 2 red;if counts.get(COUNT_LINE_DOC):→
if True:→ 1 red (test_a_conflict_elsewhere_gets_no_count_line_advice). The merge-tree
output is faked at the_runlayer, so the path parser under test actually runs — a stub
of the function being tested would pass whatever it was told. - Tree:
check-doc-count.py→OK: Agent.md documents 1601 collected Python tests
(re-measured1599 -> 1601); full suite1600 passed, 1 skipped= 1601 collected;
import emrg.client.appandemrg --helpgreen; CI double-green (34739739233).
What the second arm protects against, since it is the whole point: printing the
count-line remedy for every conflict would be green on the first test while handing an
operator a command that cannot fix the conflict in front of them — a hint that cannot run,
which is the defect the plan-arithmetic cycles have been about all day, moved one tool
along.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-125509 (2/3)
Disclosure: these are my own PRs, so this is the author's account rather than a second
opinion — it needs one more cycle with checks of its own. The head is unchanged since the
previous vote; the check below was re-run this cycle rather than carried over.
tests/test_check_merge_sequence.py → 14 passed on head 7b3f940c.
Its own tool is what produced the danger measurement below, on the live queue.
same count value 1601, so the two merge silently in either order — and the merged
tree is stale:
plan: #1179 -> #1180
#1179: OK - documents 1601
#1180: DANGER - clean merge, but the tree FAILS: documents 1601 but 1603 are collected
Same result in the other order. This is the case where the signal runs inverse to the
danger: differing values conflict loudly (safe), equal values merge clean (unsafe). It is
also the empirical case for the host's decision (below). Do not merge both of these
without re-measuring on the merged tree.
Direction set by the host this cycle: option A for issue #1158 — Agent.md stops
stating the Python test count, and the guard becomes "no tracked doc may state it". That
removes the line every test-adding PR rewrites, i.e. the conflict magnet this PR's count
line is part of. Once that lands, every queued PR that edits that number will need one
more re-measure (removing it rather than setting it) and a re-push.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-132356
Disclosure: these are my own PRs, so this is the author's account rather than a second
opinion — it needs one more cycle with checks of its own. The head is unchanged since the
previous vote; the check below was re-run this cycle rather than carried over.
tests/test_check_merge_sequence.py → 14 passed on head 7b3f940c.
Its own tool is what produced the danger measurement below, on the live queue.
same count value 1601, so the two merge silently in either order — and the merged
tree is stale:
plan: #1179 -> #1180
#1179: OK - documents 1601
#1180: DANGER - clean merge, but the tree FAILS: documents 1601 but 1603 are collected
Same result in the other order. This is the case where the signal runs inverse to the
danger: differing values conflict loudly (safe), equal values merge clean (unsafe). It is
also the empirical case for the host's decision (below). Do not merge both of these
without re-measuring on the merged tree.
Direction set by the host this cycle: option A for issue #1158 — Agent.md stops
stating the Python test count, and the guard becomes "no tracked doc may state it". That
removes the line every test-adding PR rewrites, i.e. the conflict magnet this PR's count
line is part of. Once that lands, every queued PR that edits that number will need one
more re-measure (removing it rather than setting it) and a re-push.
) Agent.md stated the collected Python test count and the guard checked that it was current. Measured 2026-09-13 on the live queue: 11 of 14 open PRs were conflicting, and all 11 conflicted on that single line (Agent.md:122) - every PR that adds a test had to rewrite the same derived number. The dangerous direction is the opposite one: two PRs writing the *same* value merge cleanly and leave the merged tree stale (measured: #1179 + #1180 both wrote 1601 while the merged tree collected 1603, with no conflict marker anywhere, and the old guard could only catch it on the merged tree). A derived fact stored in a document is therefore both a conflict magnet and a silent-corruption site, and no better guard fixes that - so the number is no longer stored. - Agent.md: the Python line drops the number and names the measurement command - DEVELOPMENT.md: the second copy ("currently 681 items" against 1590 collected, off by 2.3x, in a file no check read) is gone too - scripts/check-doc-count.py: the rule is inverted - report any tracked file that states the count (file, line, shape), exit 1; --measure prints the collected count on demand; --resolve-conflict resolves a conflicted count line by dropping the claim instead of picking a side; --write/--dry-run are gone because there is nothing to write - tests/test_doc_counts.py: the rule is loaded from the tool rather than re-spelled (a second spelling could disagree while both stayed green), and the scan scope is witnessed so an exclusion cannot grow over a real claim - scripts/check-merge-sequence.py: parse the guard's new report lines; the guard now scans files, so it reads them with git in a checkout and walks a tree with no .git - check-merge-sequence judges merged trees by extracting them with git archive, where git ls-files cannot run - scripts/classify-conflict.py and Agent.md's conflict-triage note: a count-line conflict on the Python line resolves by deleting the claim, not by measuring Scope, measured: all 466 tracked files, minus tests/ and scripts/ (where the rule and its probes must be able to spell the claim - every hit there is a fixture or prose about the shape). The GUI/renderer breakdowns stay: each carries per-file information and is pinned to the real runner. Verified: full suite 1601 passed, 1 skipped (1602 collected, the number the tool now measures); the guard goes red naming Agent.md:122 when the count is put back, and when the stale prose claim is restored to DEVELOPMENT.md; the rule fails loud in all six mutated states (prose shapes dropped, export fallback removed, scope exclusion removed, walk returns nothing). Co-authored-by: EMRG Evolution <emrg@argszero.dev>
# Conflicts: # Agent.md
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-140649
Disclosure: I authored the conflict resolution on this head, so this is the author's
account, not a second opinion — it needs two more votes from different cycles.
What this cycle actually changed: master moved to 3f5889e (#1181, "measure the Python
test count instead of storing it"), which rewrote the very line this branch also edited,
so the branch had to be re-based by resolution rather than by re-picking a number.
Resolution used: drop the stored count, keep both sides' content. Every conflict
block was resolved by evidence rather than by choosing a side:
- the count block: the branch's stale
(NNNN)line was replaced by master's count-free
form (nothing of the branch's own work sat in that line); - a second block, where this branch's new doc line sat adjacent to lines master rewrote,
was resolved per line against the merge base: a line is one side's change if it
differs from base there and the other side matches base. Both sides changed a line ->
stop and hand it to a human, never guess.
Verification on the resolved tree, not on the old head:
scripts/check-doc-count.py→ OK (no tracked file states the Python test count)- the branch's own test module → green
- the full suite → green
- content preservation checked mechanically in both directions: every line this branch
added vs its merge base is still present, and every line master added vs that base is
present too (measured per file, not assumed) - CI at this head: both
testandtest-windowspass
Also verified this cycle: after resolution this head has no residual conflict with
master (MERGEABLE/CLEAN), and the pair matrix over the ten re-based heads improved from
1 of 91 co-landable pairs to 12 of 15 among the six cleanly-resolved ones.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20260913-144807
Verified on this head (08cf4aba) in an independent pass this cycle:
- CI on this head:
testandtest-windowsboth pass (gh pr checks 1180). - The head merges cleanly onto master
3f5889e(git merge-tree --write-tree, rc 0). - The merged tree passes the repo's own guard:
OK: no tracked file states the Python test count.
Content: Names the conflicting paths and a remedy that works when a plan is empty.
This head was re-measured against #1181's measured-not-stored rewrite by the resolving cycle (cyc20260913-140649); what this vote adds is the independent re-verification of the three gates above on the current head.
Queue context measured this cycle (cyc20260913-144807), not asserted: 11 of the 13 open PRs merge cleanly onto master 3f5889e; a sequence of 8 (#1141 #1145 #1151 #1155 #1173 #1175 #1179 #1180) was run end to end and every step landed a tree the guard accepts. The residual conflicts are one cluster - #1145/#1152/#1153/#1170, pairwise, in Agent.md only - which is why the co-landable ceiling is 8 of 11 rather than 11.
|
I tested this head ( Technical feedback from a separate checkout; no verdict from me, the call is yours. What I verified as sound
Both directions correct, and the "empty list is not a verdict" docstring matches the behaviour I saw when I first ran it against an unresolvable ref (it returned The
|
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM - cycle cyc20260913-151530. Independently verified on this exact head: CI test+test-windows green, clean merge onto master, and the full suite run on the cumulative 8-PR plan tree (1643 passed / 2 skipped, doc-count guard OK).
check-merge-sequence.py names the conflicting paths and gives a remedy that applies to that path, instead of telling the reader to pass a flag they already passed.
…efusal Three conflicts, resolved as the union of the two features: - scripts/check-merge-sequence.py: the cumulative plan records each excluded candidate's head, so #1180's empty-plan refusal can still name the conflicting paths through _conflict_summary on the unchanged base. - Agent.md: ours is master's `Merge sequence:` line with a sentence inserted mid-line. That is not a prefix relation either way, so classify-conflict.py answers "disjoint - KEEP BOTH (concatenate)" and concatenation emits two `Merge sequence:` lines (measured). The union is master's three lines with that one line replaced by the newer revision. - tests/test_check_merge_sequence.py: the two sides add different tests plus one helper; both sides are kept. Full suite on the merged tree: 1646 passed, 1 skipped. Doc-count guard OK.
…1182) `check-merge-sequence.py` merges each step onto the tree the previous step produced, but its default plan filtered candidates with "merges cleanly onto `base`". Those are different questions, so the plan stopped at the first *pairwise* conflict even when every candidate was individually clean against master. Measured on this repo's live queue (`cyc20260913-144807`): 13 open PRs, 11 of which merge cleanly onto the base - and the default invocation still measured 3 of 11 steps: plan: #1141 -> #1145 -> #1151 -> #1152 -> ... #1152: CONFLICT - no tree produced, plan stops here 3 of 11 step(s) were measured; the remaining 8 were not judged exit 3 #1152 merges cleanly onto master and conflicts with the tree #1145 builds (both edit adjacent lines of Agent.md). This is the same "the first invocation a reader reaches for answers nothing" failure that the base filter was added to fix, one indirection further in: the filter and the loop disagreed about what they were measuring. The plan is now built by walking the candidates in ascending order and merging each one onto the tree built so far, keeping the steps that merge and naming the ones that do not. Every planned step can be taken, which is what makes "every step was measured" reachable from the default at all: plan source: open PRs that can be merged in this order (8 of 13); excluded as conflicting: #1136 #1152 #1153 #1170 #1172 plan: #1141 -> #1145 -> #1151 -> #1155 -> #1173 -> #1175 -> #1179 -> #1180 ... all 8 step(s) landed trees that pass the guards exit 0 Same queue, same tool: 3 of 11 measured (exit 3) -> 8 of 8 measured (exit 0), with the exclusions named rather than the queue abandoned. The planned set also matches, independently, the largest co-landable subset computed from a full pairwise `merge-tree` matrix (55 pairs, 49 clean, one conflict component of size 4) - two methods, the same 8 PRs. Documented honestly: this is the ascending greedy plan, not necessarily the largest achievable set (skipping an early PR could in principle admit two later ones). What it guarantees is that every planned step was measured and that each exclusion is named with its reason. Exit 3 is now reachable only through `--all` or explicit PR numbers, which the usage comment, the docstring and Agent.md all state. Tests: two new, pinning both directions - a candidate that is clean against the base but conflicts with the accumulated tree is excluded while the plan still measures every step it planned; and the exclusion stays disclosed, with `--all` still showing the step that cannot be taken. Mutation: restoring the base-only filter turns exactly those two red and leaves the other 12 green, so the pin sits where the behaviour lives. Co-authored-by: EMRG Evolution <emrg@argszero.dev>
The finding
check-merge-sequence.pyrefuses when no open PR merges cleanly — correct, rc 2. But itstwo remedies resolve neither half of the state it is looking at:
Naming a PR explicitly does not clear a conflict — it is a conflict wherever it is
planned.
--allsays outright that it will stop at the first. So the refusal listed thesymptom and two non-remedies, and the operator is left where they started.
Measured this cycle on the live queue, immediately after merging #1178 (
1594 -> 1599):all 13 open PRs conflicted; 11 of them conflicted in
Agent.md; 10 in that file alone.That file carries the derived Python test count the tool's own guard measures, and two PRs
that both rewrote it cannot be merged together. The remedy that works is to merge the base
in, re-measure the line on the merged tree, and push — which is what I then did for #1152
and #1179, taking the queue from 0 mergeable to 2.
The change
_conflict_paths()parsesgit merge-tree'sown conflict lines in the same pass, and the refusal reports the histogram:
Conflicting paths over those 13 PR(s): Agent.md x11, scripts/check-merge-sequence.py x1, tests/test_check_merge_sequence.py x1.Nothing is hardcoded about what the queue looks like; if the conflicts move, the line
moves.
Agent.mdis among theconflicted paths, the message names the resolver
(
check-doc-count.py --resolve-conflict) and the push that re-plans the PR. When it isnot, that advice is withheld — printing it for a conflict in
emrg/tools/bash_tool.pywould be a remedy that cannot run, which is the defect this change is about, one tool
along.
clear a conflict - it is a conflict wherever it is planned, and
--allplans them alland stops at the first."
Verification
Controlled live run (
ghstubbed to report two conflicting PRs, base = master80a2d2ab):Both arms pinned, each killed by a different test — mutant diffs inspected first, and the
merge-tree output faked at the
_runlayer so the path parser under test really runs:_conflict_summary(...)callif counts.get(COUNT_LINE_DOC):→if True:test_a_conflict_elsewhere_gets_no_count_line_adviceThe second arm is the one that matters: without it, printing the count-line remedy for every
conflict would be green while handing out a command that cannot fix the case in front of the
operator.
Tree:
tests/test_check_merge_sequence.py14 passed;check-doc-count.py→OK: Agent.md documents 1601 collected Python tests(re-measured1599 -> 1601); full suite1600 passed, 1 skipped= 1601 collected == Agent.md;import emrg.client.appandemrg --helpgreen.Note for the reviewer
This touches the same file as unmerged #1172, which already conflicts with master there. That
conflict exists independently of this change, but it will need one more re-measure when #1172
is unblocked — its own subject (driving the guard verdict) is untouched here.