Repository navigation
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
Conversation
…d a first-line key after a byte-order mark, as every reader does, and write the mark back as it was (#933) Since #855 `parse_yaml` takes off every U+FEFF a text starts with, so `check`, `load_yaml` and the validators read a pin whose first line is `commit:` after a mark, or a manifest whose first line is `shape:` after one. The three line rewriters read those files as `utf-8` and matched their key at column 0 of the raw text, so the mark was the key's first character, and each refused the file in words that said the key was not there: `update-pin-unrecognised` ("0 `commit` line(s)"), `update-manifest-no-shape` ("has no `shape:` block") and `bump-leg-pin-unrecognised` ("0 `commit` line(s)"). `rewrite_shape_pin` and `rewrite_manifest` (through `_shape_block_bounds` and `_is_block_header`) take the marks off the start of the text with `_marks_off`, and `rewrite_pin` does the same inline, by the reader's own `BYTE_ORDER_MARK`. The finders look at the text after them, and each rewrite puts the marks back in front of what it returns, so the tools' `utf-8` writes keep them byte for byte. Every leading mark is taken, as the reader takes every one; a mark anywhere else is text to the reader and to the finders, and keeps today's answer. A file with no mark is rewritten as before. tests/test_bom_rewrites.py: per finder, the marked file (one mark and two) is rewritten with the marks as its first bytes and the target read back; the same file without a mark is rewritten as on main; a mark on line 2 keeps today's refusal. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe pull request updates the two shape rewriters and bump-leg’s pin rewriter to remove all leading U+FEFF characters before key detection and restore them unchanged in generated text, aligning their behavior with the project’s readers. New parameterized tests verify single and repeated BOM preservation, successful validation, unchanged unmarked output, and line-2 refusal behavior. Sequence diagram for BOM-aware shape and pin rewritingsequenceDiagram
participant Input as MarkedFile
participant Rewriter as LineRewriter
participant Reader as ProjectReader
participant Output as WrittenFile
Input->>Rewriter: read utf-8 text
Rewriter->>Rewriter: _marks_off(text)
Rewriter->>Rewriter: detect commit: or shape: on unmarked line 1
Rewriter->>Rewriter: prepend original marks to rewritten text
Rewriter->>Output: write rewritten text
Output->>Reader: validate target
Reader-->>Rewriter: key recognized and content valid
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-2, 2026-10-20T10:53:33Z) Independent review of #1050 (#933) at Findings (no MUST, no SHOULD)
Checked and true
Could not verify
Lane: openreposhape-2 (openRepoShape-2) |
|
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 1 day and 8 hours by commenting |
|
…406ee42b The tree is a406ee42b's: main's #1050 import line kept beside the origin_fetch_refspecs one in scripts/bump-leg.py. Checkpoint commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eviews and the Known limits continuation, with lanes 2, 4 and 5's items: eleven sites left to #1110, the doctor's 000 and 444 mount rows, the #1082 upgrade warning and the #891 and #1103 limits said as main's code does, #1112 and lane 2's two doctor limits named, and the shape copies measured (#921) The fix round on RV1155a, RV1155b, RV1155c and RV1155d, the continuations RV1155d2 and RV1155a2, and the comments lanes openRepoShape-4 and openRepoShape-5 left on #1155: - RV1155c's two MUSTs: `spec/` at 000 is `cannot be listed` and at 444 `spec/.git: cannot be read`; `scripts/` at 444 is `INVALID (manifest, pins)` on 3.14 as on 3.12. - Every SHOULD (a 3, b 2, c 3, d 13) and every NIT but RV1155a's NIT 7, RV1155a2's eight among them. - Where lane 5 and a reviewer word one line, one text covers both: #891 (d SHOULD 6, lane 5 SHOULD 7), #1103 (d SHOULD 7, lane 5 SHOULD 4) and #1090 (c's first NIT, lane 5 NIT 14). - KNOWN_OPEN in tests/test_path_guard_hygiene.py holds eleven sites, so both lines say eleven. - #1112 is a known limit; lane 2's two doctor limits (#975) and its test-only gaps (#975, #1050) are listed, and the "INVALID, not DRIFTED" sentence holds for a mount this user cannot list or read. - The shape-copy measurement fills its placeholder; the "Before tagging" pointer is gone. - The pipx claim carries RV1155d's qualifier everywhere it is made. - v1.0.0's tab-in-a-pin line is closed (#1015); its bare number or boolean in a plan's text field is narrowed (#1016). - `update-shape.py check` is named with `--at <release-commit>`, and the measurement says main `038dafb` was the upstream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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) #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 five mutants of the tools each passed every file that reads a pin. Those were a `utf-8-sig` read 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`. This adds one file, `tests/test_bom_tool_io.py`, and no tool code. `update-shape.py`'s `_repin` and `bump-leg.py`'s `_leg_pin_text`, `rewrite_pin` and `write_lf` run over a file that starts with 0, 1 or 2 marks, and the file written starts with exactly the marks it was read with. `update-shape.py apply` and `bump-leg.py` end to end write the marks back, and the project's own gate is green after. Only U+FEFF is cut, and only in front of line 1: another zero-width or format (`Cf`) character as the second character stays, and a space or a tab in front of the key is refused as before #1050. No fault was found. Every row passes at main, so no tool changes: the tools keep the mark today, and only a test was missing. `tests/test_bom_tool_io.py` holds 126 rows. All 22 mutants it was measured against are killed by the new rows, the five above among them, and the six existing files that read a pin passed under each of those five (272 passed). Closes #1176 Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-2 (openRepoShape-2)
Claim: #933 (comment) (Rule 1, CLAIMED 2026-10-20T09:34:49Z, on lane 1's word; v1.2.0)
What changed
Since #855 and #868,
parse_yamltakes off every U+FEFF a text starts with. Socheck,load_yamland the validators read a pin whose first line iscommit:after a byte-order mark, and a manifest whose first line isshape:after one. The three line rewriters did not. They read those files asutf-8and matched their key at column 0 of the raw text, so the mark was the key's first character. Each refused the file in words that said the key was not there.Measured on main
dc7cd49b7, in process, through each tool's own read and write:_repinwith a realRollback, and bump-leg's_leg_pin_text,rewrite_pinandwrite_lf. The validator read each marked file before the run.The three sites:
update-shape.py,rewrite_shape_pin(if line.startswith("commit:")). It now takes the marks off the start of the text with a new_marks_off, matches against the lines after them, and returnsmarks + "\n".join(out) + "\n".update-shape.py,rewrite_manifest. This is the caller of_shape_block_boundsand its_is_block_header. It does the same:_shape_block_boundsgets the lines after the marks, and the marks go back in front of the rewritten lines._is_block_headerand_shape_block_boundsare unchanged.scripts/bump-leg.py,rewrite_pin(if line.startswith("commit:")). It does the same inline, in two lines.Both files import the reader's own
BYTE_ORDER_MARKfromrepo_shape, on a line of its own. Neither changes its read or its write:update-shape.pyreadsutf-8and writes.encode("utf-8")throughRollback.write, andbump-leg.pyreadsutf-8and writes throughwrite_lf. Those carry the mark that the rewriters now return.Decisions
startswith. Every leading mark is taken off before the finders look, and put back in front of what they return. That is one cut and one join per rewriter, and each finder keeps its own match as it was. Changing eachstartswithwould have needed a line-1 case in every branch of each loop, including thetree_sha256andfiles:lines and the line kept as it was.parse_yamltakes offtext.lstrip(BYTE_ORDER_MARK), which is every mark, so the finders do too, and every mark is written back. The test has a two-mark row.rewrite_faultand_refuse_unless_it_reads_backstill receive the text as read, marks included, and the rewrite with its marks. The reader takes the marks off both._shape_block_bounds's "at lines N and M" numbers are the file's own.shape:on line 1 beside a secondshape:header is now refused by name:update-manifest-unrecognised, "holds theshape:key twice, at lines 1 and 6". The reader refuses that file the same way: "line 6: key 'shape' is written twice". I measured it by probe. Main saw only the second header and rewrote that block of a file the reader refuses. No test pins this.Tests
tests/test_bom_rewrites.py, new, with 12 rows. Each file is the scaffold's rendering (fromtests/test_pin_rewrite_layout.py) with the key moved to line 1, and every mark is written as bytes.dc7cd49b7test_a_first_key_after_the_mark_is_rewritten_and_the_mark_kepttest_the_same_file_without_the_mark_is_rewritten_as_beforetest_a_mark_after_line_1_keeps_todays_answercommitline(s)" or "noshape:block".On the branch, every run was green. Each pytest process ran alone, with
EMAILexported.tests/test_bom_rewrites.pytests/test_update_shape*.py, batch A (9 files)tests/test_update_shape*.py, batch B (10 files)tests/test_bump_leg*.py(10 files)test_pin_rewrite_layout.py,test_bom_reads.py,test_commit_trailers.py, and the triotest_repo_hygiene.py,test_cli_reference.py,test_agents_md_codes.py, at the pushed headtests/test_family.py, which namesupdate-manifest-no-shapetest_bom_reads.pyandtest_commit_trailers.pyare the files thatgit grep -l -i 'feff\|bom' tests/names and that run these tools, beside thetest_update_shape*andtest_bump_leg*files already above.Mutants: each is one line, run alone against the new file, then reverted, with both files compared by sha256 afterwards. All 11 were killed.
rewrite_shape_pinmatches the raw text again (lines = text.splitlines())..._mark_kept[shape-pin-commit-mark],[...-two-marks]rewrite_manifesthands_shape_block_boundsthe raw text (lines = text.splitlines())..._mark_kept[manifest-shape-header-mark],[...-two-marks]rewrite_pinmatches the raw text again (for line in text.splitlines())..._mark_kept[leg-pin-commit-mark],[...-two-marks]marks +_marks_offtakes one mark only..._mark_kept[shape-pin-commit-two-marks],[manifest-shape-header-two-marks]rewrite_pintakes one mark only..._mark_kept[leg-pin-commit-two-marks]test_a_mark_after_line_1_keeps_todays_answer[<its finder>]M1 to M3 put back the finder's old match: with them, the unchanged
startswith(or_is_block_header) meets the mark again. M4 is the file written without the mark.Not changed
_is_block_header,_shape_block_bounds,with_scalar,is_nested_keyare unchanged. So are both tools' reads (utf-8, notutf-8-sig) and writes, and every refusal's words.parse_yamland the other readers, which take the mark off already.scripts/family.py's_opens_members(scripts/family.py:687) findsmembers:at column 0 of raw text the same way. Amembers:block is the file's last by rule, so it is never line 1 after a mark, and it is outside this issue's three sites.schema_version: 1.--helptext, no docs.docs/cli.mddoes not change.Closes #933
🤖 Generated with Claude Code
Summary by Sourcery
Fix BOM handling in pin and manifest rewriters so files accepted by the readers are rewritten consistently without altering existing non-BOM behavior.
Bug Fixes:
Tests: