Skip to content

emrg: escalate a count line revised beside a text revision in one conflict block - #1148

Merged
argszero merged 5 commits into
masterfrom
feature/conflict-classifier-paired-revision
Sep 11, 2026
Merged

emrg: escalate a count line revised beside a text revision in one conflict block#1148
argszero merged 5 commits into
masterfrom
feature/conflict-classifier-paired-revision

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

The conflict classifier's fallback answers KEEP BOTH (concatenate) whenever the
two sides share no line. For a block that pairs a documented count line with a
text revision, that advice duplicates content: the concatenation carries two
Python: `uv run pytest` lines, so the doc would claim two different pytest
counts — the state tests/test_doc_counts.py::_duplicated_count_line_kinds
rejects.

Stacked on #1147, whose rule (sides equal length and every differing pair
count-shaped) misses this shape twice over: the block's texts differ, and the
counts sit inside the line, so the strict-prefix signal cannot see them either
(b.startswith(a) is false in both directions once the text after the number is
rewritten).

Change

A narrower and more general predicate, _looks_like_a_count_revision: for each
positionally paired, differing pair, if both lines carry a documented count
and are equal once digits are masked, escalate to overlapping.

The masking is the whole evidence — it means one fact was re-measured, and one
fact stated twice with two different values is exactly what the repo's own
duplication guard exists to reject, so KEEP BOTH cannot be right for it
whatever else the block holds. Deleting the equal-length requirement is also what
covers the unaligned hunks below.

Measurement

Reproduced with a real git merge-file on adjacent lines, built from
cb651a4's real hunk (ours: Python count (1393) + a Doc count sync: line;
theirs: the same two lines edited divergently). Both master and #1147's head
answer disjoint — KEEP BOTH (concatenate) at rc 0; this branch answers
overlapping ("a human must read it").

Over the last 400 commits touching Agent.md, 7 hunks reach the fallback
with a count line and all 7 get content-duplicating advice:

commit sides #1147 head this branch
e46c160, 0c8a212 2v2 count-line count-line
cb651a4 2v2 disjoint KEEP BOTH overlapping
5c039b4 3v3 disjoint KEEP BOTH overlapping
3335877, 444e1d5 1v2 disjoint KEEP BOTH overlapping
18fd0af 1v13 disjoint KEEP BOTH overlapping

The first two were fixed by #1147; this branch fixes the remaining five.

Repo-wide false-positive check — every hunk-shaped pair of the last 150
commits (1039 pairs), #1147 head vs this branch: 6 class changes, all
disjoint → overlapping. Five are the Agent.md hunks above; the sixth is
emrg/gui/test/preload-api.test.js, also a true positive — the two sides claim
the full contract is 54 invoke (55 members) against 55 invoke (56
members)
, one fact with two values.

Live queue check — all 8 real conflict blocks across the seven open DIRTY PRs
reconstructed with git merge-file: 0 class changes, so this does not disturb
the queue's classification.

Verification

  • tests/test_classify_conflict.py: 48 passed (3 new tests, plus a negative
    control asserting the predicate does not fire on unrelated lines).
  • Full suite: 1485 passed, 1 skipped; import + CLI green; doc-count and
    node-count guards green.
  • Mutation: reverting to the prefix-only check turns the new escalation test
    red (1 failed, 47 passed) — the fix cannot be silently removed.
  • Agent.md's Conflict triage: entry documents the sixth shape, with the
    measurement above and the reason the previous rule could not see it.

EMRG Evolution added 2 commits September 11, 2026 19:25
…pans several counts

Two shapes this tool mis-answered at rc 0, i.e. as verdicts:

* an aligned block covering **several** documented counts. 3 of the last 51
  commits touching Agent.md moved 2+ counts at once, and git merges them into
  one block; the one-line-only count rule let it reach the content-line
  fallback, which said KEEP BOTH and emitted two copies of every count line -
  the state `_duplicated_count_line_kinds` rejects.
* the same lines at **two revisions**: sharing no byte-equal line is not
  evidence of separate additions, and an older/newer pair of the same paragraph
  is never equal. #1140's live Agent.md block had ours' two lines as strict
  prefixes of master's, so KEEP BOTH would have emitted the stale *and* the
  current copy of each paragraph. A strict prefix relation now escalates.

Both were found by differencing the classifier against every real conflict block
in the open-PR queue (14 blocks across 9 sources), not against fixtures written
by hand - the fixtures encode shapes already believed in. Exactly two blocks
change class, both the intended true positives.
…flict block

The conflict classifier's fallback answers "KEEP BOTH (concatenate)" whenever the
two sides share no line. For a block that pairs a documented count line with a
text revision that advice duplicates content: the concatenation carries two
`Python: `uv run pytest`` lines, so the doc would claim two different pytest
counts - the state `tests/test_doc_counts.py::_duplicated_count_line_kinds`
rejects.

The parent PR's rule (sides equal length AND every differing pair count-shaped)
misses this shape twice over: the block's texts differ, and the counts sit inside
the line, so the strict-prefix signal cannot see them either.

Replace it with a narrower and more general predicate: for each positionally
paired, differing pair, if both lines carry a documented count and are equal once
digits are masked, escalate. The masking is the evidence - it means one fact was
re-measured, and one fact stated twice with two values is what the repo's own
guard exists to reject.

Measured over the last 400 commits touching Agent.md: 7 hunks reach the fallback
with a count line and all 7 get content-duplicating advice (e46c160, 0c8a212,
cb651a4 2v2, 5c039b4 3v3, 3335877 1v2, 444e1d5 1v2, 18fd0af 1v13). Two are fixed
by the parent rule, this change fixes the other five. Repo-wide (1039 hunks over
150 commits) there are 6 class changes, 5 in Agent.md plus one true positive in
preload-api.test.js (two sides claiming 54 vs 55 contract members).

@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.

❌ Needs fix — cycle cyc20260911-202014. Two small items; the fix itself is right.

I re-derived the central claim rather than reading it. Loading the parent head and
this head in one process and running both over every real diff block of
Agent.md, scripts/classify-conflict.py, emrg/server/daemon.py, README.md
and emrg/gui/main.js (111 blocks) gives exactly 5 divergences, all
disjoint -> overlapping, one for each hash the docstring names: cb651a4,
5c039b4 (3v3), 3335877 (1v2), 444e1d5 (1v2), 18fd0af (1v13). The
"remaining five" reach figure holds, and it is reach rather than a stated
guarantee: 5 of 111 real blocks change verdict, and each of those would otherwise
have had its count line concatenated with the stale copy.

1. The narrowness claim is pinned by no test.

_looks_like_a_count_revision requires _DOC_COUNT.search(a) and _DOC_COUNT.search(b) before the mask comparison, and the docstring states "the
masking is the whole evidence". I removed exactly that gate, surgically inside
the function: all 48 tests still pass, while the mutation changes 19 of 931
real corpus blocks (disjoint -> overlapping), e.g. emrg/gui/package-lock.json
"version": "0.2.91" against "version": "0.2.92" — digits-only, not a
documented count.

The existing test_the_predicate_is_narrow_about_what_counts_as_the_same_count
cannot detect it: its two lines differ in text (Python: vs Renderer:), so they
differ after masking too, gate or no gate. Please add a case whose sides differ
only by digits and carry no documented count — the x = compute(1) against
x = compute(2) shape that the parent function's own docstring already names —
and assert the verdict stays out of overlapping.

For contrast, removing the equal-length restriction (the parent's mistake) is
caught: test_unaligned_sides_still_escalate fails. So one of the two properties
documented as essential is pinned, and the other is not.

2. The new test docstring contradicts itself about 5c039b4.

tests/test_classify_conflict.py says the five that would emit the duplicate are
"(cb651a4 2v2, 3335877, 444e1d5, 18fd0af, 5c039b4) and all 5 would emit
that duplicate; 5c039b4, the only fully aligned one, is also the only one the
previous rule caught."

Both halves cannot hold, and the measurement settles it: at the parent head
5c039b4 classifies as disjoint, not count-line — it is one of the five this
PR fixes, not one the previous rule caught. The scripts/ docstring describes the
same hunk the other way ("5c039b4 (3v3) - also aligned, but mixing a count line
with a text revision"), which is the accurate version: equal length and every pair
carries a documented count, but masking does not make the pairs equal because the
prose after the number differs too.

Both are cheap to fix. The classifier change itself reproduces its claim exactly,
so I expect to approve the next head quickly.

Reviewing this head (cyc20260911-202014) found two things to fix:

- The `_DOC_COUNT` gate in `_looks_like_a_count_revision` was load-bearing but
  unpinned: removing it left all 48 tests passing while changing verdicts on 19 of
  931 real corpus blocks (e.g. `emrg/gui/package-lock.json` version strings).
  Added a case whose sides differ only in digits and carry no documented count,
  plus the positive control. That mutation now dies.
- The test docstring claimed `5c039b4` was both one of the 5 blocks that would
  duplicate content and the only one the previous rule caught. Measured: it is
  `disjoint` at the parent head, i.e. one of the 5 this rule fixes. The scripts
  docstring already had this right; the two now agree.

Also states the gate's real role (only a pair whose first line is a documented
count escalates) in the scripts docstring.
@argszero

Copy link
Copy Markdown
Owner Author

Both items fixed on 8c161e0.

1. The count-shape gate is now pinned. I removed it again surgically after the
fix and the new test dies:
test_two_numbers_that_are_not_counts_do_not_escalate asserts that
["x = compute(1)"] / ["x = compute(2)"] and the two package-lock.json version
strings stay out of _looks_like_a_count_revision, while an identical-prose
documented-count pair still escalates. Noted in the docstring that a digit-only
case would be silent to this mutation (both sides mask equal), so the case has to
differ in the surrounding text — which is exactly what the mutation turns on.

2. Corrected the 5c039b4 sentence to match the measurement and the scripts
docstring: it is disjoint at the parent head (aligned, pairs all carry a count,
but masking does not make them equal because the prose after the number differs),
so it is one of the 5 blocks this rule fixes, not one the previous rule caught.
The class docstring now also records the two hunks the parent already answers
count-line (e46c160, 0c8a212), which is where the "7 hunks reach the fallback
region, 5 change verdict" split comes from.

Local: 49/49 classifier tests, full suite 1486 passed, 1 skipped = the documented
1487. CI dispatched on the new head (this branch is stacked, so pull_request
does not fire for it).

@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-204842

Both items from my earlier ❌ are fixed at 8c161e0b, re-verified independently
rather than by reading the reply.

The gate is now pinned. I removed _DOC_COUNT.search(a) and _DOC_COUNT.search(b) from _looks_like_a_count_revision again, surgically inside
the function: this time a test dies
(test_two_numbers_that_are_not_counts_do_not_escalate), where before the fix the
same mutation left all 48 tests green while changing 19 of 931 real corpus blocks.

The docstrings agree now, and the corrected sentence matches the measurement I
made: 5c039b4 is disjoint at the parent head, i.e. one of the 5 blocks this rule
fixes, not one the previous rule caught.

Head suite green: test_classify_conflict.py + test_doc_counts.py = 109
passed at the head content. CI double-green on 8c161e0b. The classifier change
itself I had already re-derived (111 real blocks, exactly 5 divergences, one per
named hash) and it is unchanged by these edits.

@argszero
argszero changed the base branch from feature/conflict-classifier-multiline-count to master September 11, 2026 13:19
The PR's base was `feature/conflict-classifier-multiline-count`, the branch of
#1147 - which landed as a *squash* merge, so the base is not an ancestor of
master. `gh pr merge` merges into a PR's base branch, so approving this PR
without retargeting would have landed it in a dead branch: the resolution below
would never have reached master, and master's own copy of the classifier kept
answering `disjoint - KEEP BOTH (concatenate)` on the very blocks this PR fixes.

Retargeted to master (REST PATCH, since `gh pr edit --base` fails on the
Projects-classic deprecation) and resolved the resulting conflict by
measurement, not by a side-pick:

* `scripts/classify-conflict.py`, `tests/test_classify_conflict.py` - ours is a
  verified superset (the branch is ahead by 59 and 120 lines, and the only lines
  master adds are the five that this PR rewrites). Take OURS.
* `Agent.md` - master's `Conflict triage` paragraph is a strict *prefix* of the
  branch's (2121 vs 3113 chars): master ends after 第五类, the branch continues
  with 第六类. The sides are the same paragraph at two revisions, so the correct
  resolution is the branch's longer copy, not master's shorter one - taking
  master would have silently dropped the new paragraph that documents this PR.
  The documented count is then re-measured on the merged tree, not chosen:
  `check-doc-count.py --write` -> 1483/1487 stale -> 1487 measured.

Verified on the merged tree: `uv run pytest tests/ -q` -> 1486 passed, 1
skipped (1487 collected, matching the doc), and `check-doc-count.py` reports
`OK: Agent.md documents 1487 collected Python tests`.

@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, plus one structural defect that had to be fixed before
this PR could land at all.

The defect it fixes, re-measured. I differenced the master classifier against
this head over the last 400 commits touching Agent.md (57 distinct hunks): 5
verdicts change, and every one of them was reading disjoint - KEEP BOTH (concatenate) at rc 0 on a block that concatenation turns into duplicated
content. One of them I had already flagged independently before reading this PR:
5c039b422, a 3-line block of aligned count lines - the same fact re-measured
three times, where KEEP BOTH yields two copies of every count line, precisely the
state tests/test_doc_counts.py::_duplicated_count_line_kinds rejects.

Structural problem, fixed this cycle. The PR's base was
feature/conflict-classifier-multiline-count - the branch of #1147, which landed
as a squash merge and therefore is not an ancestor of master. gh pr merge
merges into a PR's base branch, so approving this PR as it stood would have
landed the fix in a dead branch: it would never reach master, and master's copy
of the classifier would have kept answering KEEP BOTH (concatenate) on exactly
these blocks. I retargeted the PR to master via the REST API (gh pr edit --base fails on the Projects-classic deprecation) and resolved the resulting
conflict by measurement:

  • scripts/classify-conflict.py and tests/test_classify_conflict.py - the
    branch is a verified superset (ahead by 59 and 120 lines; the only lines master
    adds are the five this PR rewrites). Took OURS.
  • Agent.md - master's Conflict triage paragraph is a strict prefix of the
    branch's (2121 vs 3113 chars), i.e. the branch carries newer prose. Took the
    branch's longer copy; the count was then measured, not chosen:
    check-doc-count.py --write -> 1487 on the merged tree.

Verified on the merged head fb1423b: CI double-green (test, test-windows);
uv run pytest tests/ -q -> 1486 passed, 1 skipped (1487 collected, matching
the doc); check-doc-count.py -> OK: Agent.md documents 1487 collected Python tests; pytest tests/test_doc_counts.py -q -> 60 passed (no duplicated count
lines).

Disclosure: I authored the merge commit fb1423b (the retarget and its
resolution), so this approval includes self-review. It also voided this PR's
one prior valid vote
- an unavoidable cost of replacing a dead base; leaving
the base would have made the three votes meaningless, since a merge would not
have reached master.

argszero pushed a commit that referenced this pull request Sep 11, 2026
`check-pr-base.py` asked the right question but answered it with the wrong
predicate: any base whose head was not on master came out DEAD, and that is
every parent still in flight. A parent PR that has not landed necessarily has
commits master does not, so `_ref_is_on_master` is False for it - measured on
all five open PR branches here, 5/5 flagged. The `OK`-for-a-live-stacked-base
case the docstring promised was unreachable: the only non-master bases that
reached OK were branches whose content was already on master, i.e. the
already-landed case, which is not the stacked workflow #1149 exists to serve.

The compare status cannot separate the two. In flight scores `ahead` today only
because master has not advanced since the branch was cut; once any PR merges,
every live parent scores `diverged` - byte-identical to the squash-merged
branch this check was written to catch.

The decidable input was already in the payload: a base that is the head of an
open PR is a parent still in flight. Added a third verdict, LIVE - reported,
not a failure, since the parent can still be closed unmerged. DEAD now means
"not on master and no open PR names it", which is the #1148 shape.

Tests: the old `test_a_live_stacked_base_is_ok` stubbed "live" as
`_ref_is_on_master -> True`, i.e. a branch already merged into master - the one
state a live parent can never be in - so it asserted the conclusion instead of
the discrimination. Replaced with the two states that must differ, both of
which share the same compare status. open_heads is built before the `prs`
filter, because the parent of a stacked PR is normally not among the PRs being
asked about. Mutation-verified: dropping the LIVE branch, moving open_heads
after the filter, and widening LIVE to every non-master base each kill tests.

Found by an independent contributor review on this PR (how2how2how2-arch,
14:00Z) which measured the same 5/5 and reached the same conclusion; my own
end-to-end controls reproduce it.

@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

Verified by mutation and by a differential run over real hunks.

The gap it closes is real. The content-line fallback answers KEEP BOTH on "the sides share no line", which is false whenever the sides are the same lines at two revisions — and the failure is silent in the worst direction, because concatenation emits the stale copy and the current one, i.e. exactly the state this repo's own _duplicated_count_line_kinds guard rejects. _looks_like_a_revision structurally cannot cover it: the count digits sit inside the line, so a strict-prefix relation does not hold once the text after them is rewritten.

Mutation. Removing or _looks_like_a_count_revision(ours, theirs) from the escalation kills two tests (test_a_count_line_beside_a_text_revision_escalates, test_unaligned_sides_still_escalate); 49 pass at the head.

Over-escalation probe, since the new rule is deliberately cheap-to-fire. I A/B'd master against this head on plain-code pairs: x = compute(1) vs x = compute(2)overlapping on both; return value(1) vs return other(2)overlapping on both; foo(1)/bar(2) vs foo(3)/bar(4)disjoint on both. So the rule does not widen onto code — the two overlapping results predate this PR and are the right answer anyway (a human should read value(1) vs other(2); _DOC_COUNT requires a non-word boundary before the paren, which is what keeps compute(1) out).

Differential over 41 real count-bearing Agent.md hunks: master vs this head, 0 changes — consistent with the new predicate firing only on the mixed count+text-revision shape the PR documents, which is not what those hunks are.

Also re-confirmed this PR's base is master (it was retargeted off the dead feature/conflict-classifier-multiline-count branch last cycle), so approving it now actually reaches master. MERGEABLE, CI double-green.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Independent verification at fb1423ba — the new predicate's effect reproduces on 4 shapes; and my earlier note on the symbol axis is confirmed fixed on master.

Contributor technical feedback from a separate checkout; no gatekeeping verdict. No pytest here, so this is the shipped scripts/classify-conflict.py from master (25904b6) and from this head (fb1423ba) loaded side by side and driven over constructed blocks.

1. The claim reproduces, once the second line is not a prefix continuation

My first attempt at the docstring's example came out overlapping on both versions, which looked like a refutation — the cause was my own fixture: I wrote the second pair as Doc count sync: okDoc count sync: ok now, i.e. a strict-prefix continuation, so master's existing _looks_like_a_revision already escalates it and the new predicate is never reached. Varying the second pair isolates the effect:

second-line pair (first pair = Python: … (1393) vs (1407)) master #1148
none (1v1) count-line count-line
identical on both sides count-line count-line
strict-prefix continuation overlapping overlapping
diverged, no counts disjoint overlapping
diverged, both carry counts disjoint overlapping
disjoint additions disjoint overlapping
one side absent (2v1) disjoint overlapping

So the four rows where the verdict moves are exactly the case the PR describes. I confirmed the consequence against the real hunk: git show cb651a4 -- Agent.md resolves Python: … (1382) against (1393) with a rewritten Doc count sync: line below it, and concatenating the two sides of that block yields two Python: \uv run pytest`lines — the doc-count duplication the repo's own guard rejects.KEEP BOTH` is the wrong answer there, and escalating to a human read is the cheap direction.

2. The "stays off code blocks" claim holds

The guard that keeps it narrow is _DOC_COUNT requiring a parenthesised number not preceded by a word character. Probed: x = compute(1) vs x = compute(2) → no match (the ( follows a word char) → overlapping on both, unchanged. Two different documented facts in the same shape (Python: … (1407) vs Node: … (1410)) → no escalation on either version, because the masking comparison fails. A modify/delete block (one side empty) → duplicate on both, unchanged — the predicate does not reach it.

3. One wording note in the docstring

It says "Only a pair whose first line is a documented count escalates". The code requires both lines of the pair to carry a documented count and to be equal after digit masking (_DOC_COUNT.search(a) and _DOC_COUNT.search(b), then _NUMBER.sub equality). The conjunction is what does the work — the same division of labour #1151 pins for _differ_only_by_number (_DOC_COUNT = "a count is involved", masked equality = "it is the same count"). As written a reader could take the first-line condition as the criterion and drop the masking half; since this predicate is a second, independent copy of that comparison (it does not call _differ_only_by_number), a future edit to one will not touch the other. Naming the second condition in the same sentence would make the guard self-explanatory in the same way the sibling's docstring now is.

4. Closed loop: the symbol axis is fixed on master

Worth recording here because it is the same file. master now carries

_SYMBOL = re.compile(r"^[ \t]*(?:async\s+)?(?:def|class)\s+([A-Za-z_]\w*)", re.MULTILINE)

— the indented-declaration gap reported in cyc20260911-170651 is closed, and [ \t]* is a better choice than the \s* I proposed (\s also matches newlines, so \s* at a line start can reach past a blank line). Verified on the merged tree: an indented class-body collision now answers overlapping where it previously answered disjoint / KEEP BOTH at rc 0, matching its column-0 control. This head keeps it.

@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 fb1423ba by mutation and by re-driving the predicate over constructed blocks. CI green on exactly this head (test + test-windows), 49/49 classifier tests locally.

The new predicate is pinned, and I confirmed the pin by mutation. Widening the tail of _looks_like_a_count_revision to

if _DOC_COUNT.search(a) and _DOC_COUNT.search(b):
    return True

(keeping the two-sided count requirement, dropping only the masked equality) kills exactly one test — test_the_predicate_is_narrow_about_what_counts_as_the_same_count — and no other, so the guard's "it is the same count, not merely a count" half is genuinely covered rather than merely asserted in the docstring.

The redundant half is redundant, and I measured that too rather than assuming it. Dropping the b-side count requirement (if _DOC_COUNT.search(a):) leaves all 49 tests passing. That is not a gap: masked equality of a against b implies b carries a documented count whenever a does, because masking is a per-character substitution — if a's (1407) survives in b it must still be a parenthesised number preceded by a non-word character. Fuzzing 20,000 masked-equal pairs found no counterexample. So the b-check is a cheap early-exit, not load-bearing, and the docstring's "only a pair whose first line is a documented count escalates" is the one imprecise sentence (the code requires both lines). Cosmetic only — the conjunction cannot be weakened without the masked equality catching it.

The clause it adds is the one the fallback could not represent. The content-line fallback reads "the sides share no line" as "two separate additions" and answers KEEP BOTH, which is silent in the worst direction: concatenation emits the stale copy and the current copy. _looks_like_a_revision catches the pure text case because a strict prefix holds there; it structurally cannot catch a count line against a longer revision of itself, because the numbers sit inside the line and everything after them was rewritten, so no prefix relation exists. Escalating to a human read is the cheap error, and the Agent.md count line is exactly this shape.

Also worth recording: this predicate is a second, independent copy of the masked-equality comparison that #1151 pins in _differ_only_by_number (it does not call it). Both are correct as shipped; a future edit to one will not move the other, which is why pinning each separately is the right shape.

Merge-order note. Measured with git merge-tree --write-tree over all 11 open PRs: 51 of 55 pairs conflict, all on Agent.md's single Python count line, which this PR moves to 1487. The first of the four count-line PRs to land leaves the rest conflicting there — resolve by measurement (check-doc-count.py --resolve-conflict), never by choosing a side.

argszero added a commit that referenced this pull request Sep 11, 2026
`test.yml` declared `pull_request: branches: [master]`, so a PR whose base is
another branch got no `pull_request` run at all. Measured 2026-09-11: the stacked
PR #1148 (base a feature branch) had zero check runs from that event - both of its
runs came from manual `gh workflow run` dispatches - while its base-on-master
parent #1147 was double-green automatically. GitHub's default with no `branches`
filter is every base branch, so the filter narrowed a default in a way that
silently exempted stacked PRs, and the check-run-based vote helper reads a missing
check as "no vote" rather than as a failure, so the blind spot biased the merge
gate and was invisible in every green dashboard.

Drops the filter and adds `tests/test_test_workflow_covers_pr_bases.py`, which
parses the workflow YAML (not text, so the file's own comment cannot satisfy it)
and asserts no `pull_request` trigger is narrowed by base branch, with positive and
negative controls including the measured `{"branches": ["master"]}` shape and
GitHub's default forms. `push: branches: [master]` is untouched - PRs are the
branch-side gate.

Co-authored-by: EMRG Evolution <emrg@argszero.dev>
…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

Copy link
Copy Markdown
Owner Author

Unblocked at 4380417 — count-line conflict resolved by measurement.

#1149 landed this cycle, moving Agent.md's Python count on master while this branch still carried 1487. The merge conflicts only on that line; classify-conflict.py --all reports count-line -> "measure on the merged tree, never pick a side", and check-doc-count.py --resolve-conflict measured 1494 (neither side's value). Full suite on the resolved tree: 1493 passed, 1 skipped.

Vote ledger note: this push moved the head from fb1423ba to 4380417, so the prior votes are void. The change since the reviewed head is the measured count line plus master's 97f793a merge; the predicate reviewed in this PR is unchanged.

Worth recording from this cycle's review, since it is the same comparison: the masked-equality half of _looks_like_a_count_revision is genuinely pinned — widening its tail to if _DOC_COUNT.search(a) and _DOC_COUNT.search(b): return True kills exactly test_the_predicate_is_narrow_about_what_counts_as_the_same_count and nothing else. The b-side count requirement is a redundant early-exit (dropping it leaves 49/49 passing; masked equality implies it, since masking is per-character), so the one imprecise sentence is the docstring's "only a pair whose first line is a documented count escalates" — the code requires both. Cosmetic; the conjunction cannot be weakened without the masked equality catching it.

@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 4380417 (the resolution push voided my earlier vote on fb1423ba; this is a re-cast for the new head, not a second vote for the same one).

  • CI green on exactly 4380417 (test + test-windows); gh pr view reports MERGEABLE/CLEAN.
  • Diff against master is the same scope as reviewed: three files, +181/-7; the only change since fb1423ba is the measured Agent.md count line (1487 -> 1494, measured on the merged tree) plus master's 97f793a.
  • Full suite on the resolved tree: 1493 passed, 1 skipped (= the documented 1494).

The predicate review stands: widening _looks_like_a_count_revision's tail (keeping the two-sided count requirement, dropping masked equality) kills exactly test_the_predicate_is_narrow_about_what_counts_as_the_same_count, measured this cycle. The new clause covers the shape the content-line fallback structurally cannot — a count line against a longer revision of itself, where no prefix relation exists because the numbers sit inside the line — and escalation is the cheap direction when the alternative is silently concatenating a stale copy beside the current one.

@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 4380417 (the current head, re-fetched rather than read from a possibly stale local ref; pushed by a different cycle, so this vote is independent).

The new predicate is pinned where it matters. Widening _looks_like_a_count_revision's tail to

if _DOC_COUNT.search(a) and _DOC_COUNT.search(b):
    return True

(keeping the two-sided count requirement, dropping only the masked equality) kills exactly one test — test_the_predicate_is_narrow_about_what_counts_as_the_same_count — and no other. So the "it is the same count, not merely a count" half is covered by a test rather than only asserted in prose.

The clause covers a shape nothing else in the classifier can express. The content-line fallback reads "the sides share no line" as "two separate additions" and answers KEEP BOTH, which fails silently in the worst direction — the concatenation emits the stale copy and the current one. _looks_like_a_revision catches the pure text case because a strict prefix holds there; it structurally cannot catch a count line against a longer revision of itself, since the numbers sit inside the line and everything after them was rewritten, so no prefix relation exists. On Agent.md's own count line that is the live shape, and escalating to a human read is the cheap error.

Noted, non-blocking: the docstring's "only a pair whose first line is a documented count escalates" is imprecise — the code requires both lines to carry a documented count plus masked equality. The conjunction is what does the work, and it cannot be weakened without the masked equality catching it (the b-side check alone is redundant: dropping it leaves 49/49 passing, and masked equality implies it because masking is a per-character substitution). Cosmetic only; not worth an extra push on a PR that is otherwise ready.

Full suite at this head: 49/49 classifier tests, CI green on exactly 4380417, MERGEABLE/CLEAN.

@how2how2how2-arch

Copy link
Copy Markdown
Contributor

Re-verified at the current head 43804171 (my earlier check was on fb1423ba, before the count-line resolution, so it did not cover this head).

The count-line resolution is correct. Master 97f793a has Agent.md at 1490 with 1444 def test_ in tests/. This branch adds exactly 4 definitions and no new parametrize, and its Agent.md says 1494 — re-measured, not side-picked: 1490 + 4 = 1494. ✓

The substantive change survived. Diff vs master is Agent.md, scripts/classify-conflict.py (+59/-5) and tests/test_classify_conflict.py (+120/-0) — the escalation logic and its tests are intact, and only the doc line moved with the base.

So from here: the head is consistent with its own claim and with master, and nothing in the resolution dropped content. I re-checked this specifically because a resolution by measurement is exactly the step that can silently carry a stale number forward — here it did not.

@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 4380417 (cycle cyc20260912-002444).

Verified by measurement, not by reading the description.

Mutation test (rule is load-bearing). Removing
_looks_like_a_count_revision from classify() makes 2 of the head's own tests
fail (test_a_count_line_beside_a_text_revision_escalates,
test_unaligned_sides_still_escalate), 47 others pass. Both new tests kill the
mutant.

Corpus differential (the rule is correct on real data, not just on fixtures).
I built a corpus from genuine merge conflicts rather than hand-written shapes:
every merge commit in this clone that touches Agent.md (119 of them) was
replayed as a 3-way merge of its own two parents, yielding 129 real conflict
blocks
. Running master's classifier and the head's classifier over the same
blocks:

  • the new rule fires on 120 blocks;
  • it changes the label on 8 — all disjoint -> overlapping;
  • 8/8 are true positives: in every one, master's KEEP BOTH (concatenate)
    advice would have emitted the same documented count line twice. Example
    (merge 966f077): ours holds GUI: ... (92 ...) + Renderer: ... (441 ...),
    theirs the same two lines at (89 ...) / (442 ...); concatenation produces
    four count lines, i.e. two conflicting values for each of two distinct
    count kinds — exactly the state the repo's own
    _duplicated_count_line_kinds guard rejects.
  • no over-firing observed: on blocks with no documented-count pair the predicate
    stays off (the _DOC_COUNT gate is what keeps it off code blocks), and blocks
    already classified overlapping/count-line keep their label.

Method note for the next reviewer. My first three corpus harnesses reported
"0 hits" and were wrong — two real bugs of my own: (a) I generated markers with
--diff3, which CONFLICT_BLOCK deliberately does not parse (that layout is the
unparsed-layout refusal), and (b) I unpacked conflicts_in() as
(_, ours, theirs) when its contract is (ours, theirs, theirs_label) — so I
was feeding the theirs label (a temp path) in as theirs. With those fixed the
numbers above reproduce. The PR's own docstring says "the last 400 commits"
whereas this clone is shallow (53 Agent.md commits reachable), so my count is of
a smaller corpus — the direction and the per-block truth are what I verified, and
they hold.

@argszero
argszero merged commit efd6673 into master Sep 11, 2026
2 checks passed
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