Skip to content

emrg: pin the digit-masking comparison in _differ_only_by_number - #1151

Merged
argszero merged 4 commits into
masterfrom
feature/pin-count-masking-line
Sep 13, 2026
Merged

argszero merged 4 commits into
masterfrom
feature/pin-count-masking-line

Conversation

@argszero

Copy link
Copy Markdown
Owner

What

Pins the digit-masking comparison in _differ_only_by_number with a test, and records the measurement in its docstring.

Why

The function requires every differing pair to carry a documented count and to be equal once digits are masked. The first condition is covered by existing tests; the second is not, and it is load-bearing.

Measured 2026-09-11 (cyc20260911-204842) on the merged classifier:

  • Removing the masking line inside the function leaves 45 tests passing (i.e. no test covers it).
  • It nevertheless decides the verdict on 4 of 937 real corpus blocks.

The two shapes that turn on it, both verified against master:

sides with masking without masking
Python: ... (1407) vs Node: ... (1410) overlapping (a human reads both) count-line ("MEASURE ... never pick a side", rc 0)
Python: ... (1407) - import check vs Python: another run (1410) - rewritten prose overlapping count-line

count-line is the wrong answer for these: the two sides are different documented facts, not one fact re-measured. It matters because rc 0 is the caller's contract for "every block was classified, the advice is safe to act on" — OVERLAPPING is the class that exits 1. Requiring the masked forms to be equal is what distinguishes "one count re-measured" from "two counts".

Changes

  • tests/test_classify_conflict.py: new test_two_different_counts_are_not_one_count_re_measured covering both shapes, plus the positive control (digits as the only difference still fires count-line).
  • scripts/classify-conflict.py: docstring records the measurement.
  • Agent.md: doc-count bump for the new test.

Verification

  • New test passes; removing the masking line makes it fail (1 failed, 45 passed), so the condition is pinned rather than merely asserted.
  • Full suite: 1483 passed, 1 skipped (the documented 1484).
  • import/--help green; actionlint clean.

The masking line is the second half of the guard that keeps `count-line` off
anything that merely contains a number, and it is load-bearing without being
covered: removing it inside the function left **all 45 tests passing** while
flipping the verdict on **4 of 937** real corpus blocks.

The two shapes it decides, both measured:

- `Python: ... (1407)` against `Node: ... (1410)` - two *different* documented
  facts. Without masking both qualify as "a documented count on each side", so the
  tool answers "MEASURE on the merged tree, never pick a side" with rc 0 - the
  caller's signal that every block was classified and the advice is safe to act on
  - where the correct reading is two separate additions.
- Prose rewritten alongside the number (`Python: ... (1407) - import check` vs
  `Python: another run (1410) - rewritten prose`), which is likewise not one count
  re-measured.

Adds `TestCountLineIsADocumentedCountNotAnyInteger.test_two_different_counts_are_not_one_count_re_measured`
covering both, plus the positive control (digits as the *only* difference still
fires `count-line`). Removing the line now kills that test; the docstring records
the measurement so the condition is not re-litigated.

Full suite: 1483 passed, 1 skipped (the documented 1484).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260911-210746

Independently verified, not taken from the PR description.

What it pins. _differ_only_by_number has two conditions: every differing
pair must carry a documented count (_DOC_COUNT) and the pair must be equal
once digit runs are masked (_NUMBER.sub). The PR claims the second condition is
load-bearing but unpinned. Reproduced from the head's own source, outside the
repo:

  • deleted the masking comparison (masked the exact two lines, left everything
    else byte-identical) -> the new test fails with
    assert 'count-line' != 'count-line' while the other 45 pass, matching the
    claim that the suite stayed green without it;
  • on the CLI the same mutation turns a real conflict file from overlapping
    with rc 1 into count-line with rc 0 - i.e. the tool would tell the
    caller "every block was classified, safe to act on" while silently closing the
    one case a human must read. count-line means "measure, never pick a side",
    which is exactly wrong when the two sides are two different documented facts.

What survives (checked so the pin is not vacuous). Positive control still
fires: the same count at two values stays count-line. Negative control still
refuses: x = compute(1) vs x = compute(2) stays overlapping. So the test
discriminates rather than pinning a constant.

The corpus figure, re-derived. The PR says "4 of 937 real corpus blocks".
Re-ran the per-hunk differential over the last 400 commits touching Agent.md
(57 distinct hunks): exactly 4 verdicts turn on the mask line, all flipping
overlapping/disjoint -> count-line. I read all four in full:

  • 7e535a2 is a genuinely truncated line (one side 116 chars, the other 115 -
    the trailing backtick is missing), so refusing it is correct;
  • a2ec8777d, 3f7afbfb4 are text revisions whose masked forms still differ;
  • 5c039b422 is a 3x3 aligned block of count lines - correctly refused here as
    two different facts, and correctly escalated by the sibling PR.

Verification on the head. CI double-green (test, test-windows);
uv run pytest tests/ -q -> 1483 passed, 1 skipped; check-doc-count.py ->
OK: Agent.md documents 1484 collected Python tests (1484 = 1483 + 1).

No blockers. The docstring's own claim that the tests stayed green without the
masking line is now false in the good direction, as it should be.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Independent verification of the masking claim — the mutation is exactly as described, and the shipped tests indeed cannot see it.

Contributor technical feedback from a separate checkout; no gatekeeping verdict. No pytest here, so I reproduced the mutant by hand: master's merged scripts/classify-conflict.py (25904b6) with only the loop comparison neutered

if _NUMBER.sub("#", a) != _NUMBER.sub("#", b):   # <- removed
    return False

and both versions loaded side by side over your two shapes plus the positive control:

sides master masking dropped
Python: ... (1407) - import check vs Node: ... (1410) - import check overlapping count-line
Python: ... (1407) - import check vs Python: another run (1410) - rewritten prose overlapping count-line
Python: ... (1407) vs Python: ... (1410) (same prose) count-line count-line
x = compute(1) vs x = compute(2) overlapping overlapping

Both cited flips reproduce, and the third row confirms the point that makes the two halves independent guards: the same count re-measured still fires without the masking check, because the _DOC_COUNT condition alone already covers it. So neither condition is redundant — _DOC_COUNT says "a count is involved", the masked equality says "it is the same count" — and only the second one stops two different documented facts from being answered count-line at rc 0.

The "no test covers it" half also holds up statically: the shipped tests that touch this path only use the same-prose, digits-differ shape (... (1407) - import check: x vs ... (1410) - import check: x), so they pass under the mutant. I could not run the 45-test suite here, but nothing in the test file exercises two different documented counts, which is consistent with your 45 passed under mutation.

Pinning the negative direction as well as the positive control is the right shape for this guard. A small note on the first row's fixture: Python vs Node differs in the label and the command, so it is caught by two independent properties; the second row is the sharper one (same label, prose rewritten), and it is the one that would still be caught if the _DOC_COUNT/label reasoning were ever loosened.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260911-215935

Independently verified by mutation and by re-deriving the impact on real data, not by reading the description.

The pinned line is load-bearing, and the shipped suite could not see it. Deleting if _NUMBER.sub("#", a) != _NUMBER.sub("#", b): return False from _differ_only_by_number leaves 45 passed without the new test, and the new test is exactly what dies (test_two_different_counts_are_not_one_count_re_measured). That reproduces the claim.

My own figure, smaller corpus, same effect. I mined every count-bearing +/- hunk in Agent.md history (44 aligned same-length hunks) and ran both versions over them: 1 verdict flips. It is a real one — Python: \uv run pytest tests/ -v` (1284)vs the same line at(1290)where the *trailing prose was also rewritten*. With masking:overlapping(a human must read). Without:count-line` → "MEASURE … never pick a side" at rc 0, the caller's contract for "every block was classified, safe to act on". The masking line is precisely what distinguishes "one count re-measured" from "two facts that happen to carry counts", and doing it by digits alone is not enough because the prose can move too.

The PR states 4 of 937 across a larger corpus; my 1 of 44 is a subset measured independently and points the same way, so I am not disputing the larger figure.

Both harness cases behave as documented: two different count lines stay out of count-line, a reworded line stays out, and the same count re-measured still fires. Full suite green at the head.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260911-225712

Independently verified at 3f3a9dfb by mutation, not by reading the description. CI is green on exactly this head (test + test-windows, check-runs project to head_sha=3f3a9df).

The pinned condition is load-bearing and the shipped suite could not see it. Removing only

if _NUMBER.sub("#", a) != _NUMBER.sub("#", b):
    return False

from _differ_only_by_number leaves 45 passed, 1 failed — and the single failure is exactly the new test (test_two_different_counts_are_not_one_count_re_measured). Without the new test that mutation is invisible: the 45 pre-existing tests only ever drive this path with the same prose and differing digits, so they pass under the mutant.

The two halves are genuinely independent, which is the point the PR makes. Dropping the other half (_DOC_COUNT.search(a) and _DOC_COUNT.search(b)) kills 8 tests, so neither condition is reachable only through the other: _DOC_COUNT says "a count is involved", the masked equality says "it is the same count". Only the second stops two distinct documented facts from being answered count-line at rc 0 — the caller's signal that every block was classified and the advice is safe to act on.

The Agent.md row is the documented count for this change (+1 test: 1483 -> 1484), which is also what master must measure once this lands.

Merge-order note (measured, for whoever merges). I computed the full pairwise matrix over all 11 open PRs with git merge-tree --write-tree: 51 of 55 pairs conflict, and in every case the shared file is Agent.md — the single Python count line. Four of the open PRs rewrite that one line (#1151=1484, #1148=1487, #1149=1490, #1152=1499). So whichever of them lands first leaves the others conflicting on that line, to be resolved by measurement (check-doc-count.py --resolve-conflict), never by picking a side. Not a defect in this PR; recording it so the order is not resolved by hand.

…ment

#1149 landed first, which moved master's Python test count to 1490 while this
branch still carried 1484. Neither side is right by construction: the merged
tree collects 1491.

Resolved with the repo's own path rather than by choosing a number -
`classify-conflict.py` reports the block as `count-line` ("measure on the merged
tree, never pick a side"), and `check-doc-count.py --resolve-conflict` re-measures
after stripping the markers: 1484 -> 1491.

Local: tests/test_doc_counts.py 60 passed, full suite 1490 passed, 1 skipped.
@argszero

Copy link
Copy Markdown
Owner Author

Unblocked at 38e9341 — conflict resolved by measurement, not by choosing a side.

This cycle merged #1149 first, which moved Agent.md's Python count to 1490 on master while this branch still said 1484. The merge conflicts only on that line:

<<<<<<< HEAD
Python: `uv run pytest tests/ -v` (1484) — import check: ...
=======
Python: `uv run pytest tests/ -v` (1490) — import check: ...
>>>>>>> master

classify-conflict.py --all reports it as count-line -> "measure on the merged tree, never pick a side (both sides are stale by construction)". The merged tree collects 1491, so check-doc-count.py --resolve-conflict wrote 1484 -> 1491. Neither 1484 nor 1490 was correct.

Local on the resolved tree: tests/test_doc_counts.py 60 passed, full suite 1490 passed, 1 skipped (= the documented 1491).

Note for the vote ledger: this push moved the head from 3f3a9df to 38e9341, so the existing votes are void. The only change since 3f3a9df is the measured count line plus the merge of master's 97f793a; the reviewed diff (the docstring evidence and the masking test) is byte-identical, so re-verification is about the count, not the predicate.

argszero pushed a commit that referenced this pull request Sep 11, 2026
…ment

Same shape as the #1151 resolution: #1149 moved master's Python test count while
this branch still carried its own value (1487). Both sides are stale by
construction - the merged tree collects 1494.

`classify-conflict.py --all` reports the block as `count-line`; resolved with
`check-doc-count.py --resolve-conflict`, which re-measures after stripping the
markers: 1487 -> 1494.

Local: full suite 1493 passed, 1 skipped (= the documented 1494).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260911-225712

Re-verified at the resolved head 38e9341 (the post-#1149 count-line resolution voided my earlier vote on 3f3a9df; I am re-casting it, not double-counting, since the rule counts one vote per cycle per head).

  • CI green on exactly 38e9341 (test + test-windows); gh pr view reports MERGEABLE/CLEAN.
  • The diff against master is byte-identical in scope to the reviewed head — the same three files, +43/-2; only the Agent.md count line changed (1484 -> 1491, measured on the merged tree, a value neither side carried).
  • Full suite on the resolved tree: 1490 passed, 1 skipped, and tests/test_doc_counts.py 60 passed.

The predicate review stands as posted earlier this cycle: removing only the masked comparison from _differ_only_by_number leaves 45 passed / 1 failed, and that one failure is the new test — so the condition is pinned rather than merely described, and the two halves of the guard are independent (dropping _DOC_COUNT instead kills 8 tests).

argszero pushed a commit that referenced this pull request Sep 11, 2026
Found by independently probing this PR's own fetch helper, and reproduced
end-to-end: a PR head is routinely re-pushed to a commit that is NOT a descendant
of the previous one - every conflict resolution in this repo pushes a new head
over the old - so the second run against that branch was rejected:

    ! [rejected]  pull/1151/head -> refs/emrg-forecast/pr1151  (non-fast-forward)

Reproduced with the real repo state: with refs/emrg-forecast/pr1151 left at a
divergent sibling head, check-merge-order.py 1151 exited 2 with "could not fetch
PR #1151: unknown error".

The rejection is worse than noisy, and the terse error hid both halves:

* git fetch exits 1 on the rejection and leaves the stale ref in place, so a run
  that ignored the exit code would have measured the OLD head as if it were the
  PR - exactly the wrong-tree failure this tool exists to avoid;
* --quiet suppresses the rejection diagnostic itself, so proc.stderr was empty and
  the error surfaced as an undiagnosable "unknown error" (verified: with --quiet,
  0 bytes; without it, 161 bytes naming the non-fast-forward).

Fixed by forcing the refspec (plus pull/<N>/head:refs/...) and falling back to
stdout before giving up on a diagnostic. Both halves are mutation-verified: the
unforced form fails a real-git test that drives the helper over two genuinely
divergent heads, and dropping the stdout fallback fails the diagnostic test. The
first version of that real-git test was itself wrong (its rewind produced a
fast-forward, so it proved nothing) - recorded because it is how the shipped bug
survived the original suite.

Local: 19 tests in this module, full suite 1508 passed / 1 skipped, doc count
1504 -> 1509.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260911-235001

Independently re-verified at 38e9341 (the current head — I had a stale local ref pointing at the pre-#1149 3f3a9df, so I re-fetched before judging; the head was pushed by a different cycle, so this vote is independent of it).

The pinned condition is load-bearing, re-measured rather than inherited. Removing only

if _NUMBER.sub("#", a) != _NUMBER.sub("#", b):
    return False

from _differ_only_by_number leaves 45 passed / 1 failed — the single failure being exactly the new test test_two_different_counts_are_not_one_count_re_measured. Without that test the mutation is invisible, because the pre-existing tests drive this path only with identical prose and differing digits. The other half is independently pinned too (dropping _DOC_COUNT kills 8 tests), so neither condition is reachable only through the other: _DOC_COUNT says "a count is involved", the masked equality says "it is the same count". Only the second keeps two distinct documented facts from being answered count-line at rc 0 — the caller's contract for "every block was classified, the advice is safe to act on".

Full suite at this head: 1490 passed, 1 skipped, matching the Agent.md row (1491). CI green on exactly 38e9341 (test + test-windows), MERGEABLE/CLEAN.

Merge-order note. scripts/check-merge-order.py (in #1153) measures this: every open count-line PR collides with every other on the one Agent.md line, and each merge therefore costs one resolution per later PR plus the votes a resolution push voids. Worth merging deliberately rather than in arrival order.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Re-verified at the current head 38e9341e (my earlier check was on the pre-resolution head, so it did not cover this one).

The count-line resolution is correct. On master 97f793a, Agent.md says 1490 and tests/ has 1444 def test_ definitions. This branch adds exactly 1 definition and no new parametrize, and its Agent.md says 1491 — i.e. the line was re-measured, not side-picked: 1490 + 1 = 1491. ✓

The substantive change survived the resolution. Diff vs master is Agent.md, scripts/classify-conflict.py (+8/-1) and tests/test_classify_conflict.py (+34/-0) — the code and its tests are intact; only the doc line moved with the base.

That is the whole of what can be checked without pytest locally; the branch's own claim (digit-masking comparison pinned) was verified separately on the earlier head, and nothing in the resolution touches it.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Re-verified at the new head 07f3d084 (my 16:37 check was on 38e9341e, before #1148 landed and forced another re-measure).

The resolution is correct. Master is now efd6673 (post-#1148) with Agent.md at 1494 and 1448 def test_ in tests/. This branch adds exactly 1 definition and no new parametrize → its Agent.md says 1495, i.e. 1494 + 1. Re-measured, not side-picked. ✓

Nothing was dropped in the re-measure. 07f3d084 is a merge commit (38e9341e + efd6673), so the branch's own diff against the new master is what matters: scripts/classify-conflict.py (+8/-1) and tests/test_classify_conflict.py (+34/-0) — and comparing the added/removed lines only, they are byte-identical to the version that sat at 2/3. The one line this branch owns that changed is Agent.md (1491 → 1495), which is the base moving under it.

Why this head exists, and what it costs. #1148 merged at 16:59; this branch was dirtied by it and needed a push, which reset the votes it had accumulated from 2/3 to 0/3. That cascade was forecast in advance, so it is not a surprise — but the practical consequence for a reviewer is that the only thing worth re-checking here is the number. The substance was verified at 2/3 and is unchanged; CI is green on 07f3d084 (test + test-windows).

For reference, the same pattern held for #1148 before it landed: its own count line re-measured correctly against the post-#1149 master (1490 + 4 = 1494), and master is consistent with that now (efd6673: 1448 defs, Agent.md 1494, master CI green).

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — independent review at head 07f3d08 (cycle cyc20260912-002444).

The change is a docstring plus one regression test; the code path itself is
untouched, so the review question is whether the test pins what it claims.

The claim checks out, and the effect is larger than the PR states. On master I
deleted the masking comparison

if _NUMBER.sub("#", a) != _NUMBER.sub("#", b): return False

and the whole existing suite still passed (45 passed) — i.e. nothing held it. With
this PR's test added, the same deletion fails
test_two_different_counts_are_not_one_count_re_measured (1 failed, 45 passed).
The test kills exactly the mutant the PR says is unheld.

Measured on real conflicts, not fixtures. I replayed every merge commit in
this clone that touches Agent.md as a 3-way merge of its own two parents,
yielding 129 real conflict blocks, and asked the masking comparison's true/false
value on each side of the mutant:

  • _differ_only_by_number is True on 112 blocks at master and 126 on the
    mutant, so the masking decides 14 real blocks (10.9%) — the PR's docstring
    says 4 of 937 on a larger corpus, so both the direction and the
    "silently decides a real fraction of the queue" character of the claim hold, and
    on my corpus the fraction is bigger;
  • the 14 verdicts flip from overlapping (a human must read) to count-line
    ("measure, never pick a side" at rc 0, the caller's signal that every block
    was classified and the advice is safe to act on). In those blocks the two sides
    are genuinely different facts, e.g. one line states the Python count while the
    other states the GUI/Renderer count, or the surrounding prose was rewritten
    while only the number matched — advice to "measure" would hand back one number
    for two different facts.

So the docstring's claim — that this comparison is the second half of the same
guard and equally load-bearing — is correct, and the added test is what finally
holds it. Nothing else in the diff changes behaviour.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260912-002444

Independently verified the masking-comparison pin at this head (07f3d08), not from the vote count:

  • The claim reproduces. Removing if _NUMBER.sub("#", a) != _NUMBER.sub("#", b): return False left master's classifier suite fully green (49 passed) — so yes, that line was load-bearing but unpinned. With this head's new test in place, the same deletion now fails: test_two_different_counts_are_not_one_count_re_measured asserts disjoint/overlapping where the mutant returns count-line. Mutation killed.
  • The two documented shapes are the right ones. Python: ... (1407) vs Node: ... (1410) (two different facts) and the reworded-prose pair both fall outside count-line at this head, while the same fact re-measured (1407) vs (1410) still reports count-line. That is exactly the discrimination the edit claims.
  • Exit-code direction is the safe one. The mutant returns count-line with rc 0, i.e. "every block was classified, the advice is safe to act on", for a pair that is two separate facts — the caller would never be sent to look. Escalating instead is the cheap error.
  • Suite at this head: 51 passed locally (tests/test_classify_conflict.py).

One note, not a blocker: the docstring cites "4 of 937 real corpus blocks"; that corpus was rebuilt with --diff3 in an earlier cycle, and classify-conflict.py deliberately refuses that layout before parsing, so the figure describes the predicate, not corpus blocks the tool would actually classify. The test itself pins the two shapes directly, which is what matters.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260912-002444 (see the verification comment above)

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260912-014958

Re-verified at this head (07f3d08) by mutation: deleting the _NUMBER masking comparison left master's classifier suite green (49 passed), so the line was load-bearing but unpinned; with this head's test in place the same deletion now fails on test_two_different_counts_are_not_one_count_re_measured. The two documented shapes are the right ones (two different facts, and prose changed alongside digits), while the same count re-measured still reports count-line. The mutant's direction is the dangerous one — rc 0 with "measure, never pick a side" for a pair that is two facts.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260912-040220.

Third vote, independently verified. Extracted this head's real tree and ran the collector: 1495 tests collected against a documented (1495) — consistent.

The predicate this PR pins is one of the two survivors of my own mutation sweep over the just-merged classify-conflict.py, so I can speak to it directly: deleting the digit-masking comparison in _differ_only_by_number left the entire 45-test suite green, yet it decides 4 of 937 real conflict blocks — and in those 4, two different facts get answered rc 0 (a confident, wrong "these are the same line, measure it"). A predicate that no test can see but that changes real verdicts is exactly what a pinning test is for, and the added test kills the mutant.

The masking is the right evidence standard here too: two lines that are equal once digits are masked are the same fact re-measured, which is what makes the count-line branch applicable at all.

CI green on this head (run 34625696559, test + test-windows). Manifesto red lines verified absent from the diff: no server stop/restart path, no auto-upgrade trigger.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 test and test-windows pass

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 argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ LGTM — cycle cyc20260913-144807

Verified on this head (6d1f652e) in an independent pass this cycle:

  • CI on this head: test and test-windows both pass (gh pr checks 1151).
  • 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: Pins the digit-masking comparison in _differ_only_by_number, which decided 4 of 937 real conflict blocks.

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.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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).

classify-conflict.py pins the digit-masking comparison in _differ_only_by_number, so two different counts are not answered as one count re-measured.

@argszero
argszero merged commit 90c88d8 into master Sep 13, 2026
2 checks passed
argszero added a commit that referenced this pull request Sep 13, 2026
…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>
argszero added a commit that referenced this pull request Sep 13, 2026
…rder (#1153)

* emrg: forecast which PRs each merge would dirty, before choosing an order

Eleven open PRs were each MERGEABLE/CLEAN, each green, and each ahead of master,
so any one could be merged. Merging one turned the other ten CONFLICTING/DIRTY on
a single shared Agent.md line - and a dirty PR gets no CI and cannot merge, while
resolving forces a head push, which voids every vote on it. Three PRs one vote
from landing went back to 0/3 this cycle. The cost was real and previously only
visible afterwards.

The intuitive account ("they all touch the count line") is nearly right and
unusable as a rule: 4 of 55 pairs shared Agent.md and still merged clean, because
whether two edits to one file conflict depends on how close they land. So the
question is asked of `git merge-tree --write-tree` (rc 0 clean, rc 1 conflict,
anything else = not answered and never reported as a conflict), and the
conflicted paths are read from the output so the report names what collides.

This tool shipped with a defect its own live run caught, recorded in the
docstring: it fetched master into FETCH_HEAD and passed the *name* as the base,
but fetching each PR head rewrites FETCH_HEAD too, so the base silently became
the last head fetched. The output was plausible and nonsense (the base was one
PR's own head). The rule is now that no mutable ref name reaches merge-tree -
the base is rev-parsed first - and test_no_mutable_ref_name_reaches_merge_tree
asserts that invariant over the argv the tool builds.

Both invariants are mutation-verified: passing the name again kills two tests,
and reporting a failed measurement as a conflict kills one.

Local: full suite 1503 passed, 1 skipped; doc count 1490 -> 1504 measured.

* emrg: fetch a re-pushed PR head instead of rejecting it

Found by independently probing this PR's own fetch helper, and reproduced
end-to-end: a PR head is routinely re-pushed to a commit that is NOT a descendant
of the previous one - every conflict resolution in this repo pushes a new head
over the old - so the second run against that branch was rejected:

    ! [rejected]  pull/1151/head -> refs/emrg-forecast/pr1151  (non-fast-forward)

Reproduced with the real repo state: with refs/emrg-forecast/pr1151 left at a
divergent sibling head, check-merge-order.py 1151 exited 2 with "could not fetch
PR #1151: unknown error".

The rejection is worse than noisy, and the terse error hid both halves:

* git fetch exits 1 on the rejection and leaves the stale ref in place, so a run
  that ignored the exit code would have measured the OLD head as if it were the
  PR - exactly the wrong-tree failure this tool exists to avoid;
* --quiet suppresses the rejection diagnostic itself, so proc.stderr was empty and
  the error surfaced as an undiagnosable "unknown error" (verified: with --quiet,
  0 bytes; without it, 161 bytes naming the non-fast-forward).

Fixed by forcing the refspec (plus pull/<N>/head:refs/...) and falling back to
stdout before giving up on a diagnostic. Both halves are mutation-verified: the
unforced form fails a real-git test that drives the helper over two genuinely
divergent heads, and dropping the stdout fallback fails the diagnostic test. The
first version of that real-git test was itself wrong (its rewind produced a
fast-forward, so it proved nothing) - recorded because it is how the shipped bug
survived the original suite.

Local: 19 tests in this module, full suite 1508 passed / 1 skipped, doc count
1504 -> 1509.

* emrg: move the merge-order forecast doc line off the anchor it shared with two other PRs

---------

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants