Repository navigation
tests/test_bom_tool_io.py: the byte-order-mark rows hold each tool's own read and write and that only U+FEFF is cut, so a utf-8-sig read that drops the mark and a cut that also takes spaces no longer survive (#1176) - #1188
Conversation
…own read and write and that only U+FEFF is cut, so a `utf-8-sig` read that drops the mark and a cut that also takes spaces no longer survive (#1176) #1050's rows (tests/test_bom_rewrites.py) hold what the three line rewriters return. They do not hold the tools' own read and write, nor that the cut in front of line 1 takes U+FEFF and nothing else, so rv1050's five mutants (`utf-8-sig` at `_repin`'s two reads and at `_leg_pin_text`'s read, and an `lstrip` that also cuts spaces and line feeds in `_marks_off` and in `rewrite_pin`) each passed the whole file set that reads a pin. The new file runs update-shape.py's `_repin` and bump-leg.py's `_leg_pin_text`, `rewrite_pin` and `write_lf` over a file that starts with 0, 1 or 2 marks, and each tool end to end (`update-shape.py apply` against a local `--upstream`, and `bump-leg.py`). It asserts that the marks are written back exactly and that every byte after them is the rewrite, that another zero-width or format character as the second character stays (refused in front of the key, written back in front of a key of its own), that a space, a tab or a whole file indented two spaces is refused as it was before #1050, and that a mark that is the second character of a comment is text. No tool code changes: no row showed a fault. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideAdds 120 regression tests covering the complete BOM lifecycle in File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
… the spec leg on a branch of their own, so the full suite no longer pushes `bump-leg-advance` twice and tests/test_bump_leg.py's 47 setup errors are gone (#1176) CI's full suite on #1188 (tests.yml run 39861510476) gave 47 setup errors in tests/test_bump_leg.py. This file imported test_bump_leg.py's module-scoped fixture `advanced`, and a module-scoped fixture runs once for every module that names it. Both runs push HEAD to the branch `bump-leg-advance` of the session's one shared remote, so the second push was refused as non-fast-forward: this file sorts first, and test_bump_leg.py's own `advanced` was the one refused. The gates run one file at a time and could not see it. The file now has its own module-scoped `advanced`, which pushes to `bom-tool-io-advance`, as the other modules that advance the spec leg do (test_bump_leg_unreadable.py, test_bump_leg_unopenable.py and others). Nothing else the file imports writes shared state: `upstream_and_project` builds in its own tmp paths and `tools` only loads two modules. All 120 rows are kept and the tools are untouched. tests/test_bom_tool_io.py, tests/test_bump_leg.py and tests/test_update_shape.py in ONE pytest: 218 passed, 47 errors at aa0275b, 264 passed, 1 skipped with this change. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-23T07:48:07Z) Independent review (RV1188, opus, ruling 8) of #1188 at SHOULD-1,
NIT-1,
NIT-2, the body's Merges.
NIT-3, :445 and :462 retype "has 0 Observation, nothing for this PR:
Checked:
Lane: openreposhape-3 (openRepoShape-3) |
…used in each tool's words as one behind a mark is, the compared pair of files is named before it is asserted, and the retyped refusal words are the finders' own (#1176) RV1188's review of #1188, from the head 024084b: SHOULD-1 (SonarCloud python:S3415, "Unify assertion argument order in this file"): the one assert with a bracketed tuple on the left was `assert (pin.read_bytes(), manifest.read_bytes()) == before`, the shape #131 (d682127) fixed. The pair is now bound to `now` and compared as `assert now == before`. Not `after`, which is a function in this file. Every other `==` assert has the value read on the left; the `in` asserts keep the text looked for on the left, as `in` reads. NIT-1: the module docstring, the row's docstring and the PR body said a space or a tab in front of the key was held "before a mark or after one", and the rows held no mark and one mark in front of it. A space or a tab in FRONT of a mark is now a row for each of the three files: refused unchanged by each tool's own code (update-pin-unrecognised, update-manifest-no-shape, bump-leg-pin-unrecognised), as the reader refuses it. Six rows more (126 in all), and the places are named in LEAD_PLACES. NIT-3: the two end-to-end refusal rows retyped "has 0 `commit` line(s)"; they now use FINDERS["shape-pin-commit"].refused and FINDERS["leg-pin-commit"].refused, as the in-process rows do. The tools are untouched. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-3, 2026-10-23T08:30:48Z) Delta review (RV1188, opus) of #1188's second fix round: head Verdict: READY. All four findings from the review of
Lane: openreposhape-3 (openRepoShape-3) |
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 10 hours and 26 minutes by commenting |



What changed
A follow-up outside v1.2.0 (Brett Heap, 2026-10-21T10:32:04Z). It lands only after the v1.2.0 tag, on a later merge word.
One new file,
tests/test_bom_tool_io.py(126 rows), and no tool code. #1050's byte-order-mark rows (tests/test_bom_rewrites.py) hold what each line rewriter returns. They do not hold the tools' own read and write, nor that the cut in front of line 1 takes U+FEFF and nothing else, so rv1050's five mutants each passed every file that reads a pin (#1176).update-shape.py's_repin(the shape pin and the manifest, through theRollbackledger) andbump-leg.py's_leg_pin_text,rewrite_pinandwrite_lf(in the order itsmaincalls them) run over a file that starts with 0, 1 or 2 marks. The file written starts with exactly the marks it was read with, and every byte after them is the rewrite. The marks are bytes (codecs.BOM_UTF8), never typed.update-shape.py apply(a local--upstreamonly, never the default GitHub one) on a root whose pin and manifest are committed with the marks, andbump-leg.pyon a leg pin with the marks: both write the marks back, and the project's own gate is green after.Cf) character as the second character (ZWSP, ZWNJ, ZWJ, word joiner, soft hyphen, LRM) stays: in front of the key the file is refused unchanged in each tool's words, and in front of a key of its own it is written back where it was. A space or a tab in front of the key (with no mark, behind a mark and in front of one) and a file indented two spaces on every line are refused as before update-shape.py and scripts/bump-leg.py: the three line rewriters find a first-line key after a byte-order mark, as every reader does, and write the mark back as it was (#933) #1050. A mark that is the second character of a comment is text and is written back.Measured at main (
961640c6)tests/test_bom_rewrites.py+tests/test_bom_reads.py= 37 passed. rv1050's five mutants (utf-8-sigat_repin's two reads and at_leg_pin_text's read; anlstripthat also cuts spaces and line feeds in_marks_offand inrewrite_pin) each pass the six files of the issue (272 passed under each).tests/test_bom_tool_io.py= 126 passed in about 13 s. Each of the five fails 5 to 20 of its rows (table below), and every row passes without them.Mutants
Each is one exact-once edit, run against the 3 or 6 existing files (the BOM files, plus the files that read a pin for M1 to M5 and M19 to M22) and against the new file. The tool file is restored from a saved copy after each and checked with
cmp(22 of 22cmpexit 0;git diffof the tools against HEAD is empty). Runner: W1176's ownmutants.py, in the lane's scratch._repinreads the shape pin asutf-8-sig_repinreads the manifest asutf-8-sig_leg_pin_textreads the leg pin asutf-8-sig_marks_off:lstripalso cuts spaces and line feedsrewrite_pin:lstripalso cuts spaces and line feeds_marks_off: cut widened to everyCfcharacterrewrite_pin: cut widened to everyCfcharacter_marks_off: cut left outrewrite_pin: cut left out_marks_off: cut also applied to the second characterrewrite_pin: cut also applied to the second character_marks_off: cut takes one mark onlyrewrite_pin: cut takes one mark only_marks_off: cut takes every mark anywhererewrite_pin: cut takes every mark anywhere_repinwrites the shape pin asutf-8-sig_repinwrites the manifest asutf-8-sigwrite_lf(bump-leg.py's write) asutf-8-sig_marks_off: a space or a tab in front of the marks is cut first (lstrip(" \t"), then the marks)rewrite_pin: the same cut, in front of the marks, first_marks_off: a space or a tab is cut only where a mark follows itrewrite_pin: the same, only where a mark followsTests
Under a stub
gh(exit 97),GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,GIT_SSH_COMMAND=false, a scratchGIT_CONFIG_GLOBALandEMAIL=tests@openreposhape.invalid, one pytest at a time. The two lines below are the first two heads', with 120 rows; the third head's gates, with 126, are in Fix round 2:tests/test_bom_tool_io.py tests/test_bom_rewrites.py tests/test_bom_reads.py tests/test_pin_rewrite_layout.py tests/test_repo_hygiene.py tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py tests/test_agents_md_codes.py tests/test_cli_reference.py: 550 passed, 2 skipped in 87.26s.tests/test_update_shape.py tests/test_bump_leg.py tests/test_update_shape_apply_lines_and_headers.py tests/test_update_shape_header_comments.py tests/test_bump_leg_dry_run_truth.py(the helpers the new file imports, and the files the issue names): 264 passed, 1 skipped in 200.18s.tests/test_shape_doctor.pyis not run: noshape-doctor.pyor helper changes.Merges
Re-listed live on 2026-10-23 at about 07:52Z and again at 08:21Z (the same eleven PRs at the same heads), and run at
d65340e5b:git merge-tree --write-treeof this head is clean (exit 0) with origin/main (961640c6, frozen until the v1.2.0 tag) and with each of the eleven open PRs that touchupdate-shape.py,scripts/bump-leg.py,scripts/shape_materialize.pyorscripts/repo_shape.py:update-shape.py: update-shape.py:applyputs every byte back under anyOSErrorafter its first write, says what it put back on stderr, and raises the error as it was, so a file gone between itsstatand its read, a broken pipe or anEIOno longer leaves the tree half-written behind a traceback (#1173) #1187 (cc8a87ee93, theapplyrollback of update-shape.py:applyrolls back anOSErrorafter its first write only where the system refused a read, so a file gone between thestatand the read, or an error naming no path, ends in a traceback with the tree half-written #1173, this lane's sibling), validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 (2593952c95), update-shape.py:update-accept-local-missing-nameasks a copy an earlier apply kept, readunchanged, as a module a written copy imports from and as an importer of a written one, so a plain apply that stops either on its import is refused (#1082) #1133 (a7bfee1bdb);scripts/bump-leg.py: bump-leg.py: a tag a leg with nooriginholds without its commit, or through a tag it lacks, is told so, a tag of a tree or a blob it lacks is refused by name, and rows holdYAML_NOT_TEXT_RE's exponents of two digits (#1103) #1131 (55c30f20f0), shape_materialize.py and bump-leg.py: the fetch for a branch's answer leaves out a pattern likerefs/heads*that puts a branch under another name, its clauses move into_left_out, and the docstrings name the clashes left and the way back (#1100) #1157 (baa19972a2, alsoscripts/shape_materialize.py);scripts/shape_materialize.py: scripts/shape_materialize.py:adopt_makefile_block's docstring says the block lands whereplacewrites the shape'sMakefile, the root's orshape/Makefilewhere the source keeps one, not the root's alone (#1135) #1160 (0068f5af8a), adopt-project.py, shape_materialize.py and tests: a named bad link left out of the copy and pin findings takes no other path's finding with it, and the docstrings and the materializer's refusal name the #979 exception and what helps it (#1089) #1118 (2307036abe), shape_materialize.py and the pinned AGENTS-shape.md: where the root keeps aGNUmakefileor amakefile, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file saysmakereads the first of the three and the block reaches it only once that file holds it (#1048) #1105 (b28f40de09), shape_materialize.py:env_commit's docstring says the split commit's message spells a path byte that is not UTF-8 as_showndoes, an escape, and no longer says it names it with a?in its place, which #900 replaced (#1018) #1104 (de89aa81b1);scripts/repo_shape.py: adopt-project.py and repo_shape.py: a name that holds a default-ignorable character, which a terminal draws as nothing, is spelled by its code point inplan's lines and in theunsafe-valuerefusal, so a branchma<U+034F>inno longer prints asmain(#1128) #1171 (8162b874ec) and repo_shape.py and bootstrap.py:COMMIT_RE,SHA256_REandCOMMIT_40end in\Z, not$, so a pin'scommit:ortree_sha256:that ends in a line break is refused as not 40 or 64 hex by every validator andadopt-project.py(#1017) #1126 (4c1f80cf4d).#1178 no longer touches these files. No open PR touches this file or
tests/test_update_shape.py,tests/test_bump_leg.py,tests/test_bom_rewrites.py,tests/test_pin_rewrite_layout.pyortests/conftest.py, which it imports. The rows were not run on a merged tree: a clean merge is a clean merge of text only.Fix round
CI's full suite on the first head (
aa0275be, tests.yml run 39861510476) gave 47 setup errors, all intests/test_bump_leg.py.tests-git-bash,packageand SonarCloud passed.tests/test_bom_tool_io.pyimportedtests/test_bump_leg.py's module-scoped fixtureadvanced. A module-scoped fixture runs once for every module that names it, and both runs pushHEADtobump-leg-advanceon the session's one shared remote. The file sorts first, sotests/test_bump_leg.py's own push was refused as non-fast-forward. No gate before the push ran the new file in the same pytest astests/test_bump_leg.py, so none could see it.tests/test_bom_tool_io.pyhas its own module-scopedadvanced, which pushes tobom-tool-io-advance, as the other modules that advance the spec leg do (tests/test_bump_leg_unreadable.pyand others). Nothing else the file imports writes shared state:upstream_and_projectbuilds in its own tmp paths andtoolsloads two modules. All 120 rows are kept, and the three end-to-endbump-leg.pyrows still run against a real advance of the leg.gh,GIT_SSH_COMMAND=false,EMAIL=tests@openreposhape.invalid:tests/test_bom_tool_io.py tests/test_bump_leg.py tests/test_update_shape.pyataa0275be:218 passed, 47 errors in 64.96s, eachERRORa setup oftests/test_bump_leg.pywith! [rejected] HEAD -> bump-leg-advance (non-fast-forward). The same 47 as CI.264 passed, 1 skipped in 80.64s.mutants.pyre-run at024084b9, each to its end: 18 of 18new rows: KILLED(the kill counts of the first head), 18 of 18cmpexit 0,runner exit 0, andgit diffof the three tool files against HEAD empty.gh:tests/test_bom_tool_io.pywith those five files in ONE pytest, in CI's order and reversed: 384 passed, 1 skipped in 131.44s and 384 passed, 1 skipped in 134.05s.024084b9:git merge-tree --write-treeis clean (exit 0) with origin/main (961640c6) and with update-shape.py:applyputs every byte back under anyOSErrorafter its first write, says what it put back on stderr, and raises the error as it was, so a file gone between itsstatand its read, a broken pipe or anEIOno longer leaves the tree half-written behind a traceback (#1173) #1187, validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166, scripts/shape_materialize.py:adopt_makefile_block's docstring says the block lands whereplacewrites the shape'sMakefile, the root's orshape/Makefilewhere the source keeps one, not the root's alone (#1135) #1160, shape_materialize.py and bump-leg.py: the fetch for a branch's answer leaves out a pattern likerefs/heads*that puts a branch under another name, its clauses move into_left_out, and the docstrings name the clashes left and the way back (#1100) #1157, update-shape.py:update-accept-local-missing-nameasks a copy an earlier apply kept, readunchanged, as a module a written copy imports from and as an importer of a written one, so a plain apply that stops either on its import is refused (#1082) #1133, bump-leg.py: a tag a leg with nooriginholds without its commit, or through a tag it lacks, is told so, a tag of a tree or a blob it lacks is refused by name, and rows holdYAML_NOT_TEXT_RE's exponents of two digits (#1103) #1131, adopt-project.py, shape_materialize.py and tests: a named bad link left out of the copy and pin findings takes no other path's finding with it, and the docstrings and the materializer's refusal name the #979 exception and what helps it (#1089) #1118, shape_materialize.py and the pinned AGENTS-shape.md: where the root keeps aGNUmakefileor amakefile, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file saysmakereads the first of the three and the block reaches it only once that file holds it (#1048) #1105, shape_materialize.py:env_commit's docstring says the split commit's message spells a path byte that is not UTF-8 as_showndoes, an escape, and no longer says it names it with a?in its place, which #900 replaced (#1018) #1104 and adopt-project.py:plan's table pads each name by the columns a terminal gives it, not the code points it holds, so a wide or combining name'slegcolumn lines up underlegwith every other row's (#1034) #1178. No other open PR touchestests/test_bom_tool_io.py.Fix round 2
RV1188's review of
024084b9(issuecomment-6360127603): READY, one SHOULD and three NITs. One new commit,tests/test_bom_tool_io.pyonly; the three tool files are byte-identical to HEAD (cmpagainst saved copies, exit 0).python:S3415("Unify assertion argument order in this file"). The one==assert with a tuple on its left,assert (pin.read_bytes(), manifest.read_bytes()) == before, is nownow = pin.read_bytes(), manifest.read_bytes()andassert now == before, the shape tests/test_naming_policy.py: bind the compared tuple to a name before asserting it, so Sonar's assertion-order rule stops misreading the literal-looking display as the expected side (#130) #131 (d682127d6) fixed. The name isnow, notafter, which is a function in this file. A scan of every assert in the file by syntax tree found no other==with the expected value on the left: theinasserts keep the text looked for on the left, asinreads. The local analyzer does not reproduce the rule, so the result is read on SonarCloud once CI has run on this head.LEAD_PLACES: no mark, behind a mark, in front of one). Each tool refuses it unchanged by its own code (update-pin-unrecognised,update-manifest-no-shape,bump-leg-pin-unrecognised), as the reader does. Six rows more: 126 in all. The module docstring, the row's docstring and the third bullet above say what the rows hold.scripts/repo_shape.pyadded to the files watched.FINDERS["shape-pin-commit"].refusedandFINDERS["leg-pin-commit"].refusedin place of the retyped words.tests/test_bom_tool_io.py,tests/test_bump_leg.pyandtests/test_update_shape.pyin ONE pytest, stubgh,EMAIL=tests@openreposhape.invalid:270 passed, 1 skipped in 129.58sin CI's order and270 passed, 1 skipped in 137.65sreversed. With the three other files the issue names (tests/test_bump_leg_dry_run_truth.py,tests/test_update_shape_apply_lines_and_headers.py,tests/test_update_shape_header_comments.py), six files in ONE pytest:390 passed, 1 skipped in 218.42sand390 passed, 1 skipped in 195.71sreversed.tests/test_bom_rewrites.py,tests/test_bom_reads.pyandtests/test_pin_rewrite_layout.py: 278 passed in 21.85s. The hygiene files, each alone:tests/test_repo_hygiene.py101 passed,tests/test_path_guard_hygiene.py49 passed,tests/test_no_auto_maintenance.py58 passed and 2 skipped,tests/test_agents_md_codes.py47 passed,tests/test_cli_reference.py23 passed.mutants.pyre-run at this head, each to its end, and four more for the new rows (M19 to M22): 22 of 22new rows: KILLED, 22 of 22cmpexit 0,runner exit 0. M4 and M5 fail two and one rows more than before. M21 and M22 cut a space or a tab only where a mark follows it, so only the new rows in front of a mark kill them; the six existing files all pass under both.Left out of scope
docs/release-notes/v1.2.0.md(it records v1.2.0's state, known limits included, and is not edited). No milestone.Closes #1176
Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code