Repository navigation
tests.yml and adopt-project.py: the Windows job installs git-filter-repo 2.47.0, so the adoption test files that skip without it run there, and a source that keeps its own root .gitattributes gets one marked block of /<copy> text eol=lf lines appended to it in the split, so a Windows clone writes the pinned copies at the root with the bytes the pin records; the verification counts that file as the source's bytes followed by exactly that block, and update-shape.py apply --add extends it (#471) - #511
Merged
Merged
Conversation
… test files that skip without it run there and an adoption is executed on Windows for the first time, and the comments and docstrings that said that job installs none now say why it does (#471) v1.0.0 says that every AGENTS.md procedure, adopt included, works end to end on Linux, macOS and Windows. `tests-windows` left `git-filter-repo` out on purpose, as a job for the entry point and the materializer alone, so every test that executes an adoption skipped there and the Windows half of that sentence was a claim no runner had checked. - `tests-windows` installs `git-filter-repo` at the ubuntu job's pin, 2.47.0, on ONE line: PowerShell is that runner's default shell, and the ubuntu job's `\` continuation is bash's. pip puts `git-filter-repo.exe` in Python's `Scripts` directory, which setup-python puts on PATH; probe run 38312924968 found it there and `git filter-repo --version` answered. - The suite still runs whole, as before. - The comment that explained leaving it out now says why it is installed; the job header no longer names it as a reason for a second job, and the macOS job's step no longer says "unlike the windows job". - Seven test docstrings said the Windows job installs none, as the reason their filter-repo-free tests run there. They now say a machine that has none runs them, which stays true. `tests/test_shape_doctor.py`'s `no_adoption_offered` said the missing-tool case was "the Windows CI job, by design"; no CI runner lacks the tool now, and its assertion holds either way. The 23 files are the 21 that spell `shutil.which("git-filter-repo")`, `test_adopt_filter_repo_failure.py` (`shutil.which(FILTER_REPO_TOOL)`) and `test_adopt_git_reads.py`, which imports `needs_filter_repo`. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…that keeps a `.gitattributes` of its own is a strict xfail on Windows, because the shape's `eol=lf` rule lands in `shape/` and a Windows checkout writes CRLF into every pinned copy at the root, until the fix that appends that rule to the source's own file lands (#471) Probe run 38312924968 ran the adoption suites on Windows for the first time: 1696 passed, 34 skipped, and this one case failed. The source tracks `* text=auto` at its root, so `execute` writes the shape's `* text=auto eol=lf` beside it at `shape/.gitattributes`, where it governs only `shape/`. The nine copies `contracts/shape-pin.yaml` pins at their own paths are then governed by the source's rule alone, and git's checkout on Windows (`core.eol=native`, or Git for Windows' default `core.autocrlf=true`) writes them with CRLF. `validate-pins.py` digests the bytes on disk (#51), so the first bootstrap reports shape-copy-drift on all nine. Each digest Windows reported is the sha256 of the CRLF form of the pinned LF bytes, and the same nine findings, byte for byte, come back on Linux under `core.eol=crlf` or `core.autocrlf=true`. Brett Heap ruled at 2026-10-12T22:48Z that the split appends a marked block of `/<copy> text eol=lf` lines to the source's own root `.gitattributes`. That fix is a separate pull request on #471. Until it lands this case is a STRICT xfail on Windows only, applied to this parameter alone through `request.applymarker`: the other test of the same fixture parameter passes there, and a mark on the fixture's param would make it a strict XPASS. The fix turns the xfail red, so it removes the mark. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ets one marked block of `/<copy> text eol=lf` lines appended to it in the split, so a Windows clone writes the pinned copies at the root with the LF bytes the pin records and the first bootstrap no longer reports shape-copy-drift on every one, the verification counts that file as the source's bytes followed by exactly that block, and `update-shape.py apply --add` extends it (#471) Adoption's first run on a Windows runner (probe run 38312924968) failed one ordinary case. The source tracked `* text=auto` in its own root `.gitattributes`, so `execute` wrote the shape's `* text=auto eol=lf` beside it at `shape/.gitattributes`. Git reads attributes for a path from the `.gitattributes` files of its own directory and those above it, so that copy governs `shape/` alone, and the nine copies the pin names at their own paths (`Makefile`, `AGENTS-shape.md`, five under `scripts/`, `contracts/repository-naming.yaml`, the workflow) answered to the source's rule: a Windows checkout wrote them with CRLF, and `validate-pins.py`, which digests the bytes on disk (#51), reported shape-copy-drift on each. Under Git for Windows' default `core.autocrlf=true` any root `.gitattributes` but the shape's does the same, `*.png binary` included. A root-level file takes attributes from the root `.gitattributes` alone, so no file beside the source's can fix it. Brett Heap ruled at 2026-10-12T22:48Z that the split appends a marked block to the source's own file, accepting that this overrides "never written over" for that one file, that the verification gets an exception for it, and that `update-shape --add` extends the block. - `scripts/shape_materialize.py` spells the block once: `EOL_BLOCK_HEAD`, a comment naming openRepoShape and #471, then `eol_line` for each pinned copy outside `shape/` (`eol_block_paths`), in the pin's order, anchored and literal; a path git would read as a pattern is refused (`materialize-attributes-path`). `with_eol_block` appends it after the source's bytes, adding only a final newline the file lacked. `materialize_assembly_root` appends it when the shape's `.gitattributes` collided, and records the copies in `Materialized.eol_block`. A `.gitattributes` that is a symlink is not written through. - The `.gitattributes` follow-up says what the block protects, that a Windows clone reports shape-copy-drift without it, that none of the source's lines changed, and that merging the rest of `shape/.gitattributes` is optional. - `adopt-project.py` prints the append, lists every line it added in the split commit's message under "APPENDED TO .gitattributes", and `_land_the_eol_block` counts the source's file as landed in the root when its blob there is exactly `with_eol_block(source bytes, copies outside shape/)`, recomputed from the split's own pin. Anything else, a source line changed or the block short of a copy or with one too many, is still `adopt-lost`. - `update-shape.py apply --add` names each added copy in the block, through the ledger so a refusal puts it back, and commits the file with the copies. A root with no block, or whose `.gitattributes` is a symlink, is left alone. The block is found in a CRLF checkout of the file too. - A source with no root `.gitattributes`, or one the plan sends to a leg, keeps today's behaviour: the shape's own file is the root's. Tests: `tests/test_adopt_attributes_eol_block.py` (new, no `git filter-repo`) holds the block's text, the materializer's append, the follow-up, the split message and the verification with six mutants of the file; `tests/test_adopt_ignored_shape_files.py` now asserts the source's bytes come first and the block after them, and clones the adopted assembly under `core.autocrlf=true` and under `core.eol=crlf`, with the source's own `README.md` checked out CRLF as the control, and bootstraps it; `tests/test_update_shape_add.py` extends, leaves alone and rolls back the block. The two `test_adopt_filter_repo_failure.py` tests whose stub `git-filter-repo` is a `#!` script lose `WINDOWS_SKIP`: git starts that stub, and Git for Windows runs a `#!` script with its own `sh`. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… still names a file it stripped reads the list entries and not every string in `scripts/shape_materialize.py`, because the materializer now spells `.gitattributes` once outside the lists, for the block an adoption appends, and that broke every test in the module (#471) `strip_the_real_additions` removes `.gitattributes` from the copy lists of a cloned upstream with `unname_everywhere`, which strips the whole line ` ".gitattributes",`, and then asserted that the text `".gitattributes"` appeared nowhere in the module, as a proxy for "no list names it". `ATTRIBUTES = ".gitattributes"` is not a list entry and no list reads it, but it failed that proxy, so the module fixture raised and every test that uses it errored. The check now looks for what was stripped: a list entry line. Found by the mutation run for this change, whose `no-extend` mutant met the fixture error instead of the test. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ds out of the notice with their `spelled` form, because the notice escapes each backslash of a Windows path, and the attributes-symlink test skips on Windows as the suites' other disk-symlink tests do, so both pass where #471's second Windows probe failed them (#471) Probe run 38321799510 (Windows, this change at b2c0924 with the workflow's `git-filter-repo` install) passed 1754 and failed two tests, both the tests' own: - `test_following_the_notice_after_a_failed_leg_ends_adoption_verified`, one of the two `git-filter-repo` stub tests this change lets run on Windows. The notice names the leg repositories `_spelled` (#212): between backticks, with a backslash escaped, so a Windows path reads `D:\\a\\...` there, as `conftest.spelled` documents. The test compared what it read with the raw `str()` of the path, which holds on POSIX alone. It now compares with `spelled()`, as its next assertion already did, and deletes the same two paths. The other stub test passed on Windows: Git for Windows does run the `#!` stub. - `test_an_attributes_symlink_is_not_written_through`. `os.symlink` made a link on the runner, but one whose target is spelled `config/attributes` leads nowhere on Windows, so the materializer found the path free and wrote through it. It now carries `needs_disk_symlinks`, as the nine other disk-symlink tests of the adoption suites do. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…urce keeps gets the split's line-ending block appended and is verified as the source's bytes plus that block, said where they promised that nothing is overwritten and that every source path verifies by blob sha (#471) Brett Heap ruled at 2026-10-13T11:30Z that each lane-4 fix carries its own doc lines; #483, which they had been handed to, closed through #505 before this change was on main. Each file keeps its line count (README.md 1422, AGENTS.md 490, both at their caps): - README.md, "Ambiguous is an answer, and no source line is overwritten" (was "nothing is overwritten"): the collision paragraph says a kept root `.gitattributes` gets a block of `/<copy> text eol=lf` lines appended, and the verification counts that file as the source's bytes plus the block. `.gitignore` leaves the example list and "after the split" the root tree, to keep the paragraph at seven lines. - README.md, "Why `.gitattributes` is one of the copies": in an adopted root that kept its own file, the block appended to it is what carries the rule. - AGENTS.md step 7 states the same exception for the verification table; the no-code variant's item 2 no longer restates "by blob sha" and defers to step 7, which it already names, so the file keeps 490 lines. - docs/handbook.html mirrors both README paragraphs, its footer names #471, and its README.md blob is recomputed. "The only change to its history is ONE split commit" is unchanged: the block is in that commit, so it still holds. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
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 3 days and 9 hours by commenting |
Reviewer's GuideThis PR fixes Windows bootstrap drift for adopted projects that keep a root Sequence diagram for adopting a project with a root .gitattributessequenceDiagram
participant Adopt as adopt-project.py
participant Materializer as shape_materialize.py
participant Source as Source root
participant Git as Git checkout
participant Verify as Adoption verification
Adopt->>Materializer: materialize_assembly_root()
Materializer->>Source: with_eol_block(existing, eol_block_paths)
Source-->>Materializer: Source bytes plus exact pinned-copy LF block
Materializer-->>Adopt: Materialized.eol_block
Adopt->>Verify: _land_the_eol_block()
Verify->>Verify: with_eol_block(source bytes, pinned paths)
Verify-->>Adopt: Count root .gitattributes as landed
Git-->>Source: Checkout pinned copies with LF bytes
Sequence diagram for extending the LF block with update-shape --addsequenceDiagram
participant User
participant Update as update-shape.py
participant Ledger as Rollback ledger
participant Attributes as Root .gitattributes
participant Pin as Shape pin
User->>Update: apply --add
Update->>Attributes: extend_eol_block(existing, added paths)
Attributes-->>Update: Extended block
Update->>Ledger: write(.gitattributes, extended block)
Update->>Pin: Repin added copies
Update->>Ledger: Commit paths including .gitattributes
Ledger-->>User: Refusal restores original file
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…ets no block even where `core.symlinks=false` checks it out as a plain file, its follow-up says nothing was appended and lists the lines a regular file needs, the verification finds the shape's pin through a root-kept `contracts` link, and `--add` extends the last block (#471) Review round 1 of #511 at 8f8cf6f (NOT MERGEABLE on one MUST): - MUST-1. `_append_eol_block` asked the disk whether `.gitattributes` was a link. Git for Windows' default `core.symlinks=false` checks a tracked link out as a plain file holding its target's path, so the block was appended into the link: the split kept mode 120000 over the old target followed by the block, `execute` said none of its own lines was changed, and it failed `adopt-lost: .gitattributes` after both legs were pushed, where main verifies the same source. `_tracked_as_link` now reads git's index (`ls-files --stage`, mode 120000) as well. Tested on every platform with a link in the index only, checked out under `core.symlinks=false`: in the materializer, and end to end with the setting in git's command scope for every git the run starts. - SHOULD-2. The `.gitattributes` follow-up described a block that is never appended to a link, and sent the person to add its lines where the link leads, which git never reads. `plan` now reads the link from the source tree's mode, and the follow-up for one says nothing was appended, that git reads no `.gitattributes` that is a symlink, that a regular file in its place is the remedy (or a clone's `.git/info/attributes`, for that clone alone), and lists the exact lines. - SHOULD-3. `_land_the_eol_block` looked for the shape's pin among the split's plain files, so a source keeping `contracts -> meta` in the root, whose pin the materializer writes through the link, ended `adopt-lost: .gitattributes` where main verifies it. It reads the pin through the split's links now (`_SplitTree`), as `_shape_file_findings` does, and the docstring says so. Tested on a split whose `contracts` link is in the tree only, and end to end with a disk link. - NIT-1. `extend_eol_block` extended the FIRST head line; a source that held a copy of it before its own later rules got `--add` lines that those rules overrode. It extends the last one, which is the block adoption appended. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…root `.gitattributes` block of #471, said where the README lists what `--add` writes, at an unchanged line count; the handbook's README blob is recomputed (#471) Review round 1 of #511, NIT-4: in an adopted root whose `.gitattributes` carries the block, `--add` also writes that file and commits it, and the README's list of what it writes was short by that act. The clause goes into the sentence that lists them, and lines 1078-1090 are re-wrapped to keep README.md at its cap of 1422. The handbook does not list `--add`'s acts, so only its `README.md blob` moves. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…attributes-lf-block, so the Windows job installs git-filter-repo and runs the adoption suites over #471's fix, with #497's strict Windows xfail removed by hand because the merge does not conflict (#471) #497 is folded into #511 (decision of lane openRepoShape-4's coordinator after #511's review round was verified at 4aba2bd), so all of #471 lands in one pull request and one CI cycle, and #511's own Windows job runs the adoption suites with the fix. The two branches merge CLEANLY, so git keeps #497's `WINDOWS_CRLF_COPIES = pytest.mark.xfail(sys.platform == "win32", strict=True, ...)` and its `request.applymarker` in `tests/test_adopt_ignored_shape_files.py`. Over this branch's fix that case passes on Windows, and a strict xfail that passes is a failure (`XPASS(strict)`). Resolved by hand, as #511's review gave it: - #497's module docstring change (lines 26-27) is kept; - the `#: KNOWN ON WINDOWS, AND STRICT` comment and the `WINDOWS_CRLF_COPIES` constant are deleted; - `test_the_assembly_bootstraps_where_it_failed_with_shape_copy_missing` is back to `(adopted, tmp_path)`, with no `request` and no `applymarker`. The file is this branch's version plus #497's docstring lines; #497 touches nothing else in it. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `git hash-object --stdin` the link target as bytes, not text, so no newline translation or locale codec can touch the blob on any platform (#471) #511's review, NIT-6: `index_only_link` in `tests/test_adopt_attributes_eol_block.py` and the source link in `test_a_linked_attributes_file_checked_out_as_a_plain_file_adopts` passed `input=` a str with `text=True`. The target is now encoded to UTF-8 bytes and the oid decoded from git's output. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.gitattributes gets one marked block of /<copy> text eol=lf lines appended to it in the split, so a Windows clone writes the pinned copies at the root with the bytes the pin records and its first bootstrap no longer reports shape-copy-drift on every one; the verification counts that file as the source's bytes followed by exactly that block, and update-shape.py apply --add extends it (#471).gitattributes gets one marked block of /<copy> text eol=lf lines appended to it in the split, so a Windows clone writes the pinned copies at the root with the bytes the pin records; the verification counts that file as the source's bytes followed by exactly that block, and update-shape.py apply --add extends it (#471)
This was referenced Oct 13, 2026
…471's PR carries main's #509 and #435 and its footer reads right (#471) One conflict, docs/handbook.html's footer, which every PR in the doc train rewrites. Resolved by the doc-train rule: main's text is kept whole, including #509's clause "a taken clone path refused up front, added (#498)", and this PR's own clause, "the line-ending block an adoption appends to a root .gitattributes the source keeps, and its verification as the source's bytes plus that block, added (#471)", follows it in the same "; ... added (#NNN)" form before " — a page cannot name the commit". Only the touched lines were re-wrapped, to the footer's 100-column width. The footer's `README.md blob` moves from c61ac37 (this branch before the merge; main's own was 43df8f4) to e6c41c9, `git hash-object README.md` of the merged README.md. README.md stays at 1422 lines and AGENTS.md at 490. Every other file merged without a conflict, including adopt-project.py, which both sides changed (main's #435 shape checks on the plan, this PR's block). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…itory with conftest's `rmtree`, which clears the read-only bit git gives its objects, so the Windows job, which refuses to unlink them, no longer ends 12 adoption-pin tests in an error at setup (#471) #471 folds in #497's `git-filter-repo` install, so the adoption suites ran on a Windows runner for the first time, and the run was green but for 12 errors, all at the module fixture `adopted` of tests/test_adopt_pins.py. The fixture copies a repository with `shutil.copytree` to give the scaffold a pin source, and deletes the copy with `shutil.rmtree` before `check` so the digest is proved to have been read from it. Git writes its loose objects and packs READ-ONLY, and Windows refuses to unlink a read-only file, so the delete raised `PermissionError: [WinError 5]` there and succeeded everywhere else. `tests/conftest.py`'s `rmtree` clears the read-only bit and retries the one call that failed, and is what every other test that deletes a tree holding a `.git` already uses. The fixture now calls it. Test-only: no tool changes. The other `shutil.rmtree` calls under tests/ were each read and none needs the same: test_cli_reference.py:391 and test_setup_sh.py:496 pass `ignore_errors=True` over a scratch directory of generated text and over a checkout the Windows job never runs, test_family.py:1580 removes a submodule's working directory, which holds a `.git` FILE and no objects, and test_update_shape_landing.py:1284 removes a plain directory the test made. Checked against main's f391e58 (#435), which asks the keys of a plan file their types before anything is read: nothing this PR adds reads `source:`, `legs:`, `paths:`, `allow_empty_legs:` or `pins:` of a plan, and its one plan-writing change, the `.gitattributes` follow-up, is a string in the same `follow_ups:` list, which the new checks accept (`plan ok`). A plan holding `source: 5`, `legs: []`, `paths: {}` and `pins: true` is named by key by `check` (exit 1) and refused by `execute` (exit 2) before a work directory is made. Nothing changed for that. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e doc-train footer round for #511 Main took three commits since this branch's merge base f391e58: d52b100 (lane 3's #462, `--reuse-empty-repo` for scaffold-project.py and family.py init), 8cd7dbe (lane 2's #286, update-shape.py refuses a pinned copy, pin or manifest kept as a symlink as `update-rewrite-symlink`) and d41cc29 (#402, the doctor's NEXT is only the command). One file conflicted. docs/handbook.html, the footer: main's text kept whole, including #462's `--reuse-empty-repo` clause (added (#462)), then this PR's own clause after it in the same `; ... added (#NNN)` form, with the lines it touched re-wrapped to the footer's 100 columns: "the line-ending block an adoption appends to a root .gitattributes the source keeps, and its verification as the source's bytes plus that block, added (#471)". `README.md blob` is set to the merged README.md's `git hash-object`: was e6c41c9 on this branch (8c658ef on main), now caeb53e. README.md and AGENTS.md merged without a conflict and keep both sides' statements; they stay at 1422 and 490 lines. Where main's commits meet this PR's files: #462 changes only the two ruleset hint constants in scripts/shape_materialize.py, which adoption does not print (ADOPT_RULESET_HINT is its own), and scaffold-project.py's call to materialize_assembly_root passes no collision_dir, so it never reaches the line-ending block. #286's symlink refusal does not meet `--add`'s extension of the block: _extend_the_eol_block leaves a `.gitattributes` that is a symlink alone before it calls the ledger, so Rollback.write's new check never sees it. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 13, 2026
…5 cites the 34 pull requests merged since #343, section 6 drops the 13 known-limit issues that closed and adds the 63 checklist lines the five parents gained, section 7 lists the 12 open milestone issues, and the preflight sentence offers only what the preflight offers (#472) Refresh round 1 of the draft, against main d41cc29 (#402, merged 2026-10-13T21:17:39Z). GitHub was read between 21:23 and 21:43 UTC: the merged pull requests, the v1.0.0 milestone open and closed, every open issue, and the five tracking parents with their sub-issues, bodies and comments. Every merge commit of the 217 merged pull requests is on main. Section 5 places each of the 34 pull requests merged after #343 (2026-10-12T22:31:20Z) in its area, from its own body, one clause each; all 217 merged pull requests are now cited, with #16, #53 and #57 still in two areas. Three earlier sentences are made true of main: `check` has more verdicts than the four it named, and #465's docs and #505's accuracy pass show that `validate-pins.py` may ask `gh api` and that `openRepoShape --preflight` offers only `gh`, its login and its credential helper (`setup-project.py --preflight` also offers `git`). Section 6 lists the 47 open, unmilestoned sub-issues (it listed 60). It drops #189, #293, #303, #328, #386, #286, #259, #275, #322, #324, #338, #339 and #359, which closed. #389 is reworded now that #305's refusal has landed. #420 and the `a=b` line are narrowed to a hand-edited `adopt_branch:`, because an offline probe on d41cc29 shows #493's `plan-tracking-branch-missing` now stops a bad `tracking_branch:` at `check`, with no leg made, while `adopt_branch: "a..b"` still passes `check` and fails after both legs exist. The family annotated-tag line says `bump --to` now refuses one (#466). The 63 new lines are the parents' checklist lines posted or added since the first build, each checked against the code at d41cc29. Left out, and said so in the refresh record: docstring, comment and wording nits (as L2-7 was), lines that describe a branch not on main (#504's, #507's, #511's, #515's and #517's), lines filed into the milestone (#485, #514, #516), and three with no effect a person meets. Section 7 lists the 12 open milestone issues; 24 of the 30 it listed have closed. Lane 4's #510, #511, #512 and #513 have not landed, so #471, #499, #500, #501 and #502 stay open here and the Windows qualifier stays. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The only conflict was the footer of docs/handbook.html. Resolved by the doc-train rule: main's text kept whole, including #507's "added (#487)" clause (the last one on main), this PR's own "added (#471)" clause put after it, only the touched lines re-wrapped to the footer's width, and the README.md blob set to `git hash-object README.md` of the merged README.md: caeb53e (this PR's earlier head cb5f5a7) becomes 43316b9 (main's was ab6ec9b). README.md stays at 1422 lines and AGENTS.md at 490. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ision_follow_up`'s docstring moves up beside the docstring's first paragraph, so the docstring's end, which #512 (#502) also extends, merges without a hand resolution (#471) This PR and #512 each appended a paragraph at the END of `collision_follow_up`'s docstring, so a merge of one into the other, once the first lands, conflicts in a .py file and needs a hand resolution. The paragraph about a `.gitattributes` that is a symlink now sits straight after the docstring's first paragraph, as its own paragraph with one blank line on each side, and the docstring ends, as it does on main, with "and says both merges come first (#493's review, F2)." Not one word changes and nothing else in any file: the sorted lines of the file are equal before and after. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Contributor
Author
…ck: footer-only refresh under ruling 27 Main brought #515, #507, #517, #510, #513 and #512 since the previous merge base 89c6a6c. The one conflict is the handbook footer in docs/handbook.html, which #512 rewrote so that its "(#502)" clause is now last. Resolution: main's footer text kept whole (#498, #462, #487, #500, #499, #502), this PR's clause (the line-ending block an adoption appends to a root .gitattributes the source keeps, and its verification as the source's bytes plus that block, added (#471)) added after #502's in the same "; ... added (#NNN)" form and before " — a page cannot name the commit", the touched lines re-wrapped to 100 columns, and "README.md blob" set from ae817c5 (main's) to f3b378d, the `git hash-object README.md` of the merged README.md (this branch's own was 43316b9). No other file conflicted and nothing else was hand-resolved; README.md stays at 1422 lines and AGENTS.md at 490. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…itattributes-lf-block: three hunks in adopt-project.py and scripts/shape_materialize.py are kept on both sides, and #504's check of root-kept symlinks refuses only the links where #511 alone wrote the shape's copy through or failed after both legs Main's tree after #504 holds lane 1's `fix/adopt-root-kept-symlinks` as it landed; this branch held main 8beb3d9 (#512) with #471's block. The merge conflicted in two files, and the footer of docs/handbook.html did not, because #504 does not touch the handbook: the branch's footer (main 8beb3d9's clauses, then #471's) stands as it was. CONFLICTS, AND HOW EACH WAS RESOLVED - adopt-project.py, the `from shape_materialize import (...)` list: the union. #504's lines as they are (`SYMLINK_HOPS`, `TOO_MANY_SYMLINKS`, `template_files`), with `ATTRIBUTES` added beside `SHAPE_REPOSITORY` and #511's `eol_block_paths, eol_line, with_eol_block` on a line of their own. - scripts/shape_materialize.py, `materialize_assembly_root`'s docstring: both paragraphs, #504's `spell` paragraph first, as main holds it, then #511's `.gitattributes` paragraph. - scripts/shape_materialize.py, its tail: `result = _materialize(...)` with #504's `spell=spell`, then #511's `_append_eol_block(target, result, collision_dir)` when there is a `collision_dir`, then `return result`. Every other hunk merged without a conflict, among them `_commit_the_split`, where #504's `spell=_spelled` and #511's `appended to .gitattributes` print and `appended=materialized.eol_block` sit side by side, and the three test files #504 adds or changes. HOW THE TWO MEET, asked of the code and of real adoptions through `plan`, `check` and `execute` (each read first on #511 alone, on #504 alone, and on the merge, in a prepared merge of #504's then-head, before main moved) (a) `materialize-attributes-path` needs no `spell`. Only `eol_line` raises it, and only over the shape's own copy-list names. `_append_eol_block` reads `Materialized.shape_files`, which `root_key` names as the copy lists do and never resolves through a link: a source that keeps `.github -> ci` still gets `.github/workflows/validate.yml` there. The split message and the verification name the pin rows written from the same lists, and `update-shape.py apply --add` names the standard's added copy paths (`add.path`), which become pin rows. `spell` exists for the names a source's symlinks lead to, and none of them reaches `eol_line`; `_append_eol_block` and `extend_eol_block` raise nothing else. (b) A root `.gitattributes` that is a symlink. Where `check` passes, the merge does what #511 alone does, to the blob: a link to a file in the source, or to one outside it that exists, is kept, gets no block, and has the shape's copy beside it, as #511's `attributes_link` follow-up says. Where #504's `check` now refuses, `plan-root-symlink`, before any leg, #511 alone did not do what that follow-up said. A dangling link had the shape's copy written THROUGH it, to a new root file, and `execute` said `adoption verified`. A link to an absolute path with nothing there had the copy written outside the assembly and failed `adopt-shape-file-missing` after both legs. A climb above the root ended `REFUSED committing the split` on a FileNotFoundError after both legs. A `.gitattributes` under a symlinked directory: `shape -> vendor` with `vendor/` free adopts with the block on both; with a `vendor/.gitattributes` of the source's there, #511 alone stopped at `materialize-copy-blocked` after both legs, and the merge refuses it as `plan-root-symlink` first; `conf -> real` holding `real/.gitattributes` adopts with the block on both. #504 refuses nothing the block or its verification needs, and the two PRs say the same of a link: git reads no `.gitattributes` that is one. The plan's follow-up for a link that `check` refuses is a prediction no `execute` acts on, as for every other follow-up of a plan `check` refuses. (c) The rest of #504's diff does not meet the block. `template_files` seeds legs. `_refuse_unwritable` runs inside `place`, before `_append_eol_block`, which writes the root `.gitattributes` itself, never through `place` and never through a link. `adopt-symlinks-off` is about a link ABOVE a shape path, and a link AT `.gitattributes` is a plain file in such a clone, which #511's `_tracked_as_link` already skips. `_SplitTree`'s new wording, "follows too many symlinks", changes no lookup `_pin_through_links` makes. One defect is on #511 alone as on this merge, and is left for a decision rather than fixed here: a pinned copy written through a directory link the root keeps (`.github -> ci`) is named in the block by its pinned path, `/.github/workflows/validate.yml`, which git never matches against the file it tracks, `ci/workflows/validate.yml`. A source whose own attributes say `eol=crlf` then checks that copy out with CRLF, and `validate-pins.py` reports shape-copy-drift on it. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s` says what happens to the pin row of its copy, as #512 (#502) has every other pinned copy's say, so a person who merges the rest of `shape/.gitattributes` into the root file and removes it is told that `update-shape.py check` reports the row `merged-away` and `apply` drops it (#471) #512 (#502) ended the follow-up of every pinned collision copy with "Then remove shape/X: `update-shape.py check` reports its pin row `merged-away`, and `apply` drops the row". The `.gitattributes` branch of `collision_follow_up` returns before that sentence. Its follow-up said to merge the rest of `shape/.gitattributes` into the root file if the whole project should check out with LF, and said nothing of the row that removing the copy leaves, on which the project's own `validate-pins.py` is red until `apply` drops it. The sentence is added in #512's words, under the condition the merge already carries: "Merge the rest of shape/.gitattributes into .gitattributes only if the whole project should check out with LF, and only then remove shape/.gitattributes: ...". A copy that is kept, which is what the follow-up leaves by default, keeps its row, and that row stays `unchanged`. The follow-up for a `.gitattributes` that is a symlink does not get it. That follow-up sends nobody to merge or remove the copy, only to replace the link with a regular file holding the source's lines and the block's. The copy stays, pinned, so a row sentence there would describe a step nobody was given. tests/test_adopt_attributes_eol_block.py holds both follow-ups to that, and the regular one's new ending exactly. tests/test_update_shape_merged_away.py turns its fixture's body into one helper and adopts #502's own source a second time, with a root `.gitattributes` of its own. It then does the follow-up as a person does it: the copy is merged into the root file and removed, `check` calls its row `merged-away`, and its NEXT, pasted, drops that row and nothing else. The root file keeps the merge's bytes, block and all, and `validate-pins.py` and the doctor are green. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…`, `eol_line` and `with_eol_block`, are a `from shape_materialize import` statement of their own, so a pull request that adds names to the shared list above it merges without a hand resolution (#471) The first `from shape_materialize import (...)` statement is the list every change to this file's imports edits: #504 added `SYMLINK_HOPS`, `TOO_MANY_SYMLINKS` and `template_files` to it, and #518 (#181) adds `PLACEHOLDER_RE`, `TEMPLATED` and `render`. Git treats two changes to adjacent lines of one block as one conflict, so #471's names on lines of that same list made #518 conflict there a second time, over names it never touched, and whoever merged it had to rewrite the list by hand. The four names now sit in a statement of their own, after the one that imports #172's shape half, with that statement and a comment between them and the shared list. The shared list reads as main holds it. Nothing else changes: the set of names `adopt-project.py` takes from `shape_materialize` is the same 48, parsed with `ast` before and after, and every other statement of the module is the same. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ck, a refresh round under ruling 27 for #511 (#471) The merge is CLEAN: git merged adopt-project.py automatically (#494 touches only `_failed_loudly` there, #511 the eol-block and ignored-shape-file code), and scripts/family.py, scripts/repo_shape.py, shape-doctor.py, templates/assembly-root/scripts/validate-pins.py and tests/test_refusal_default_remediation.py came in whole from main. No file conflicted and nothing was resolved by hand. #494 touches neither README.md nor docs/handbook.html, so the handbook footer keeps its clause "(#471)" after #502's and the line `README.md blob f3b378d` is unchanged: the old and the new blob are the same, equal to `git hash-object README.md` of the merged README.md. README.md stays at 1422 lines and AGENTS.md at 490. The merged tree is d58ea4b, the tree `git merge-tree --write-tree 9f28d90 3121945` prints, so the merge adds nothing main did not already hold. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
|
This was referenced Oct 14, 2026
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…ook marker must sit at the top of its section, the footer's "added (#N)" clauses are frozen as a tuple wherever a new one is put, and a horizontal rule under a list item's or a block quote's paragraph, or under an indented line, is not taken for a heading (#519) The three nits from the re-review of 2f26ce3 on PR #551. A. "At the top of its section" was not enforced: a marker moved below a paragraph, blank lines and all, passed, and a branch deleting that paragraph then conflicted with one moving the markers. A marker now has nothing but blank lines and other markers between it and its part's opening tag (`handbook_parts` gives where that tag ends). There is a new fixture case for it. B. A new "added (#N)" clause put before "That list is history, and it ends there." passed, because the check looked only after the sentence. HANDBOOK_HISTORY_CLAUSES freezes the footer's clause numbers in order. Any `added (#N)` in the footer that is not in it, and any reordering, is refused, and a clause after the closing sentence is still refused by name. The fixture that let a merged-in clause through is replaced by one showing a new clause refused, plus one for order. The tuple ends at (#502) here; the merge of main that follows adds #511's (#471). C. The setext check refused valid horizontal rules. README_NOT_PARAGRAPH treats a line indented by a tab or four spaces as no paragraph text, and readme_uncut_h2s tracks whether the paragraph being read is inside a list item or a block quote (a lazy line, or one indented under an open list, continues it), so a `-` run under such a paragraph is a rule. A setext refusal now adds "or, if you meant a horizontal rule, put a blank line above it". The review's three cases (list continuation, lazy quote line, tab- or four-space-indented line) and a second paragraph of a list item are fixtures that pass, and a paragraph after a closed list still fails. Each fix bites: switching off the top-of-section rule, the clause tuple, the container tracking or the tab rule in a scratch copy fails at least one test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…s footer clause joins the frozen history, its README blob line goes, and the handbook's markers for the three README sections #511 changed are moved after a re-read (#519) #511 landed in v1.0.0 with README and handbook edits made under the old footer rule, and conflicted with this branch in the footer only. Footer: "added (#471)" is kept as the last clause of the frozen history, before "That list is history, and it ends there."; #511's `README.md blob f3b378d...` sentence is dropped. HANDBOOK_HISTORY_CLAUSES gains 471 as its last entry, the one change this merge makes to the test. .github/workflows/tests.yml merged cleanly: #511's install step is byte for byte main's, and this branch's three comments stand. The README sections #511 changed (git diff 3121945 5ed3e5d -- README.md), re-read against the page: - § Adopting an existing repository (read by `adopt`): the `###` heading "... no source line is overwritten", the block appended to a kept root `.gitattributes`, and its verification as the source's bytes plus the block are all in #511's own page edits. The rest is compression to keep the line count (`.gitignore` left out of the example list, "root tree" for "root tree after the split", "as `drop`"); the page's longer wording stays true. Marker moved. - § Bootstrap is COPIED (read by `copied`): the appended block as the other answer is in #511's page edit; "who just clones" is compression. Marker moved. - § Keeping a project's shape current (read by `current` and `doctor`): `apply --add` now names each file it adds in an adopted root's own `.gitattributes` block. #511's page edits did not cover this, so `current`'s upstream-added item now says it. The doctor subsection is untouched by #511. Both markers moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…-yaml-keywords: clean, and its tree is 88c22f0, the tree of the scratch merge a9439715 every Phase A run was made on Main 5ed3e5d is #511 (lane 4) for #471, landed at 16:08:28Z, merged here by sha. The split appends one marked block of `/<copy> text eol=lf` lines to a source's own root `.gitattributes` and verifies that file as the source's bytes followed by exactly that block; `update-shape.py apply --add` extends it; the Windows job installs git-filter-repo 2.47.0. No conflict. The tree is 88c22f0, which is `git merge-tree --write-tree 42362de 5ed3e5d`, and the tree of a9439715bfb50186565526cdddc828f57b14409f, the scratch commit made before #511 landed from 42362de and #511's predicted squash bd78e56e (tree d58ea4b, which 5ed3e5d's tree is). So every Phase A result at a9439715 holds here: - one unit changed on both sides, `cmd_plan`, keeping this branch's validator-first comment and #511's `attributes_link`; every other unit is the one side's that changed it; no name in adopt-project.py is bound twice; - #511 adds no `check` finding and no refusal before any leg exists, and changes no template, no `render` and no line `execute` writes into the project's YAML; - the targeted sets, both modes, exit 0: 1928/1833 in 14 files, 3869/3686 in 65 more, 963/953 in 11, 364 in 10, 248/223 in five, and 37 in #511's tests/test_adopt_attributes_eol_block.py; the quoting file is 524; - the Windows newline emulation over this branch's four test files, test_adopt_root_symlinks and test_adopt_attributes_eol_block: 1280 passed, and 1207 passed with 73 skipped, 0 failed; - byte identity for the three flag sets and for a source with its own `.gitattributes`; the class fuzz (3146 rows, #181 names none, no false or double clause); the why, pin-file and residual drivers unchanged; - the composition probes with #504, #513 and #511. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…5 cites #504 and #494, section 6 drops #178 and #183, which #504 closed, adds the four limits #504 disclosed for #457 (J1, J2, J3, reftable) now that they describe main, and narrows two lines #504 and #494 made partly untrue, and section 7 lists the 2 open milestone issues (#472) Refresh round 3a, against main 3121945 (#494, merged 2026-10-14T13:21:57Z). Two pull requests merged after round 2's 8beb3d9, and both merge commits are on main, as are all 225: - #504 (b0826e0, lane 1, closes #178 and #183) under adopt-project.py, as a new bullet, "Symlinks the root keeps": `plan-root-symlink` and `adopt-symlinks-off`, both before any leg. - #494 (3121945, lane 3, closes #370) under the shared helpers, where #370's parent #459 puts it: a refusal with no remedy of its own prints its `REFUSED` line alone, `validate-pins.py` keeps the lockstep remedy, and `family.py add` refuses `gitmodules-unreadable`. Lane 3 asked on #473 (13:22Z) that #370 be placed in the notes. Each line was written from the PR body and its squash message, and its central claim read in main's code: `_entry_findings` and `_root_symlink_findings` (adopt-project.py:4701, :5717), `_refuse_symlinks_checked_out_as_files` (:5824) before `_create_leg_remotes` (:7542, :7580); `Refusal`'s empty default (scripts/repo_shape.py:166-174), `validate-pins.py`'s handler (templates/assembly-root/scripts/validate-pins.py:1181-1184), family.py's `gitmodules-unreadable` (:1912-1926), and the doctor rows that take `exc.remediation or None` (shape-doctor.py:4546). Section 6: - Dropped #178 and #183, closed by #504. - Added L1-J1, L1-J2, L1-J3 (#457, 2026-10-12T23:36Z) and L1-K8 (#457, 2026-10-14T01:17Z). Round 2 left them out because they described #504's branch. #504's body lists all four as its known limits. J2's `check` half (the refusal and its false sentence), J1's `check` half (`plan ok`) and J3's `check` halves were run at 3121945 in an offline probe. J1's and J3's `execute` halves rest on lane 1's measurement and the code (`_has` and `CLONE_GIT_TREES` at :5427-5438; `adopt-symlinks-off` at :5824-5866). K8 is read from the code only (`CLONE_GIT`, :5201-5206), and its line says it is untested. - Narrowed L1-K1's line. A link at `contracts/shape-pin.yaml` that leads nowhere is now a `plan-root-symlink` finding, since the walk follows `SHAPE_PIN` (shape_materialize.py's `assembly_root_paths`). The probe ran it, and a real file there still passes `check`. The line now says a file, or a link there to a declared pin's file, as K1 measured. - Narrowed #408's line. The issue's own repro, a policy file that does not parse, carried `repo_shape`'s default lockstep remedy, which #494 removed, so the `placement` row now has no NEXT. Only a policy file that is not UTF-8 still gets prose there (`load_yaml`'s own remedy, which holds no backtick). An in-process probe of `PathPolicy.load` with main's and 8beb3d9's repo_shape.py shows both. - Kept L1-K2, K9 and K10, which lane 1 measured as the same on #504's branch. The five parents have no new comment and an unchanged body since round 2's read (03:11Z to 03:18Z). Their sub-issues still list every open, unmilestoned one: 45 (#457 10, #458 6, #459 17, #460 9, #461 3). Section 7: the milestone is 45 closed and 2 open (#471, #472). #370 left the list. Of the three issues section 7 named as outside the milestone, only #181 is open. No claim is made about an unlanded pull request (#511, #518), and nothing is said about a tag or a date. 7,354 words before, 7,628 after. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…5 cites #511 and drops the Windows qualifier now that the Windows job runs the adoption suites, section 6 cites the v1.1.0 issue of each line the triage scheduled, drops the five lines closed on their parents and #408, and adds #511's line-ending limits, and section 7 lists #472 alone (#472) Refresh round 3b, against main 5ed3e5d (#511, merged 2026-10-14T16:08:28Z, closing #471). All 226 merge commits are on main. Section 5. #511 is cited twice: under adopt-project.py, as a new bullet on a source's own `.gitattributes`, and under the test suite and CI, in the "Three operating systems" bullet. "Every source path is verified by blob sha" gains the exception AGENTS.md step 7 now states. The claims were read in main's code: - `.github/workflows/tests.yml`:171-182, where `tests-windows` installs `git-filter-repo==2.47.0`; :209, where it runs the whole suite; and :118-125, where it runs on pull requests, not on a push to main; - `scripts/shape_materialize.py`: `EOL_BLOCK_HEAD` (:701), `eol_line` (:712-721), `eol_block_paths` (:724-729), `with_eol_block` (:738-744), and `_append_eol_block` (:813-831), which writes through no symlink, as called from `materialize_assembly_root` (:2256-2257); - `adopt-project.py`: `_land_the_eol_block` (:8081-8124, called at :8063) and the split message's `APPENDED TO` (:7953); - `update-shape.py`: `_extend_the_eol_block` (:3061-3089). The qualifier. Main's workflow now does what the unqualified sentences say, so both `<!-- drop the Windows qualifier once #471 lands as proposed -->` comments and the two sentences they marked are gone. Section 6, re-read after lane 3's v1.1.0 triage (#473, 16:03:48Z, on Brett Heap's approval at 14:31:28Z): - 74 lines marked `→ #N` or "rides with #N" on their parent now end with that number, as in (#524). For each parent, the set of numbers cited equals the set its marks name. One sentence in the section's opening says what the number is. - Dropped, each closed on its parent: `--project ATLAS` and a fork's `origin` (#457, ruled behaviour); the scaffold's three working trees (#459, fixed by #505 and kept documented); and the `shape currency` row's uncommitted-copy line (#460, obsolete since #402 and #512). Also #408, closed as fixed by #494. - Added, under #457: the symlinked root `.gitattributes`, the nested one (#530), and a root adopted before #511 (lane 4, 2026-10-12T23:25Z); then #511's three posted at 16:09:31Z: a copy written through a symlinked directory, a CRLF `.gitattributes` under a rule other than `text=auto`, and a `filter=` or `working-tree-encoding=` rule. Its fourth, a test helper that feeds text on stdin, is left out: no person meets it. These rest on lane 4's measurement (V511c at 1ce4f45, whose tree is main's); `eol_line` writing only `text eol=lf` and the symlink skip were read in code. - Reworded: - AT-scaffold N9 (#458), with lane 4's 16:10:03Z note. `cmd_apply` always stages the pin and the manifest (`update-shape.py`:3280-3281), and `_land` prints them all (:3224). - The annotated-tag line (#459). It is now ruled that `bump --to` peel it (#541), so "One rule is still to be chosen" is gone. - The Windows symlink line (#457), narrowed to its parent's own words. #511's end-to-end test of a source that tracks a symlinked `.gitattributes` under `core.symlinks=false` carries only `needs_filter_repo` (tests/test_adopt_ignored_shape_files.py:862-863), so it now runs on Windows. - Left out, new: the three CI lines new on #461 (the merge queue and two CI costs). They are this repository's CI, not a limit of the shape. - Lane 1 has posted no #504 batch, so D13 stays out. Section 7: v1.0.0 is 46 closed and #472 open. #181 is still outside the milestone. No claim is made about an unlanded pull request (#518, #551), and nothing is said about a tag or a date. 7,628 words before, 7,906 after. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 14, 2026
brettheap
added a commit
that referenced
this pull request
Oct 14, 2026
… keyword, so `--spec-path null`, `--id null` and `--tracking-branch true` adopt as typed, and `plan`, `check` and `execute` refuse, before anything exists, a value the project's own loader would read back as something else from the line `execute` writes (#518) `adopt-project.py`'s plan writer, `y()`, now writes a string bare only where the plan's own YAML loader (`scripts/repo_shape.py`) reads it back as that same string, after `key: ` and as a `- ` item. Anything else is quoted with `double_quoted`. So `null`, `true` and their other spellings are quoted. `yes`, `on` and `.inf` stay bare, because this loader reads them as text. On `main` the plan read such a value back as no value or a boolean: - `--spec-path null` mounted the leg at `spec`; - `--tracking-branch null` put both legs on `main`; - `--id null` made the id `northwind`; - `--tracking-branch true`, `--spec-path true`, `--id true`, `--pin null@<commit>` and `--elected-by null` were refused at `check` about a value nobody typed. Each of these now adopts and bootstraps as typed. An ordinary adoption is byte for byte `main`'s, for three sets of flags. The same rule guards what `execute` writes into the project's own YAML. `plan` (`adopt-manifest-misread`, before the plan is written), `check` (`plan-manifest-misread`) and `execute` (before any leg exists) refuse a value the project's own loader would read back as something else from the line `execute` writes. The gate builds that line with the materializer's own `render`, from the template or from `_template_table`, and asks the loader. It keeps no list of words. It asks the project's own `validate-manifest.py` first. It names a value only where that validator makes no finding about it and `execute` writes it in every mode, so the two never both name one value. On `main`'s shipped templates it names nothing, because #259 and #514 quote the lines it asks about; #516 quotes a pin file's own `product:`. It stays as a backstop, as #314's scaffold twin does: a line written bare again is refused with no edit to the rule. An explicit null leg path in a plan (`spec_path: null`, `~` or nothing) is now `plan-bad-leg-path` instead of the default `spec`. The leg-path not-text sentence asks for the path meant, not "so quote it". The gate composes with #513 and #504. Its finding comes after the mount paths, before #504's `plan-root-symlink`, and #513's `plan-not-the-source` follows both. A plan with all three causes, probed with the pins' list written bare, gets one finding each at `check`. `plan` and `execute` refuse #513's cause first. #511 adds no finding: on a source that keeps its own `.gitattributes`, #181's values adopt with #511's line-ending block appended, and a plan this gate refuses is refused before that file is touched. What this leaves to `main`: - A hand-edited plan value its loader reads as a number or a boolean (`id: 007`, `tracking_branch: true` beside a source branch `True`) is written as `str` of it, as on `main`. - The scaffold's twin of the gate (#314) is a second function. Making them one is a follow-up. tests/test_adopt_plan_yaml_quoting.py adds 524 tests, all through the real loader: - the round trip of every keyword spelling, number, date, indicator and escape candidate, every string over two small alphabets, and 4000 seeded strings; - 20 values read back as given through `plan`, `check` and `execute`; - the gate's refusals, asked in this process over copies of the templates and of the pins' list written bare again. The revert table, measured at 9efe95a, names the tests each change's revert fails. Two guards fail none alone and 15 together, which the PR discloses. At 7d6e13c the targeted sets pass with and without `git-filter-repo`, the rest skipped: 1928 and 1833 tests in 14 files, 3869 and 3686 in 65 more, 963 and 953 in 11, 364 in 10, 248 and 223 in five more of `main`'s test files that changed since 89c6a6c, and 37 in #511's new one. Reviews: - Lane 2's P2 is fixed by the gate. - review181's adversarial pass found 7 NITs. - rv181f's re-verifies found F2, F2b, G1 (a BLOCKER), G2 and H1, each fixed. - rv181g found B1 (a BLOCKER, fixed in 9efe95a), and M1, the `with_origin` test fix that #513's merge needed (6c9e585). - Codex found no major issue at 9efe95a, and SonarCloud's nine new smells there are fixed. `main` was merged in thirty-one times up to 7d6e13c (`main` 5ed3e5d), never rebased, each conflict resolved by keeping both sides. CI is green on Linux, macOS and Windows at 7d6e13c, which has `main` 5ed3e5d merged in. Closes #181 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 14, 2026
…ion records the hash of every README section it reads, and README.md's line cap is 1422 plus one file per raise in tests/readme-cap.d/, so two README pull requests no longer conflict on the footer's one blob or the cap's one line, and a stale page names exactly which sections to re-read (#519) (#551) Brett Heap's ruling 29 on #519 (2026-10-14T15:18:19Z, "Hash per section"). Every README pull request rewrote the handbook footer's one README blob and the README line cap's one line, so any two of them conflicted. - README.md is cut into segments: the preamble, then one per `## ` heading. A segment's fingerprint is the git blob sha1 of its text, computed in Python with CRLF read as LF. - Each part of docs/handbook.html records the segments it reads, one `<!-- reads README.md § <heading>: blob <sha> -->` marker each, and `HANDBOOK_READS` maps every part to its segments. - `test_the_handbook_follows_the_readmes_outline` checks: - every marker names a real segment; - every segment is read by some part; - every marker's blob is current, and a stale one names the segment and every part that reads it; - each part's markers are exactly its row. - The footer's single README blob and its per-PR "added (#N)" clauses retire. The clauses up to #511's are kept as frozen history. - README.md's line cap is `README_CAP_BASE` (1422) plus one file per raise in tests/readme-cap.d/ (`<n>.txt`: `+N`, a date, a reason). - tests/test_agents_md_codes.py and .github/workflows/tests.yml now name the per-section hashes. README.md itself does not change. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-4 (openRepoShape-4)
Claim: #471 (comment)
What changes, and why
Adoption's first run on a Windows runner (#471's probe run 38312924968) failed one ordinary case: a source that tracks its own root
.gitattributes.executewrites the shape's* text=auto eol=lfbeside it atshape/.gitattributes, and git reads attributes for a path only from the.gitattributesfiles of its own directory and those above it, so that copy governsshape/alone. The nine copiescontracts/shape-pin.yamlnames at their own paths (Makefile,AGENTS-shape.md, five underscripts/,contracts/repository-naming.yaml,.github/workflows/validate.yml) answered to the source's rule. A Windows checkout wrote them with CRLF, andvalidate-pins.py, which digests the bytes on disk (#51), reportedshape-copy-drifton each. Under Git for Windows' defaultcore.autocrlf=trueany root.gitattributesbut the shape's does it,*.png binaryincluded, so every adopted source that keeps one was exposed. A root-level file takes attributes from the root.gitattributesalone, so no file beside the source's can fix it.Brett Heap ruled at 2026-10-12T22:48Z (ruling A): the split appends a marked block of
/<copy> text eol=lflines to the source's own root.gitattributes. He accepted that this overrides "never written over" for that one file, that the verification gets an exception for it, and thatupdate-shape --addextends the block.The change, by tool
scripts/shape_materialize.pyspells the block once:EOL_BLOCK_HEAD, a comment line naming openRepoShape and tests-windows never executes an adoption: git-filter-repo is not installed there, so the eight adopt suites skip and v1.0.0's "adopt works on Windows" is unverified #471;eol_line(path),/<path> text eol=lf, for each pinned copy outsideshape/(eol_block_paths), in the pin's order. Each line is literal and anchored; a path git would read as a pattern is refused (materialize-attributes-path);with_eol_blockappends it after the source's bytes, adding only a final newline the file lacked;materialize_assembly_rootappends it when the shape's.gitattributescollided, and records the copies inMaterialized.eol_block. A.gitattributesthat is a symlink is not written through;.gitattributesfollow-up says what the block protects, that a Windows clone reports shape-copy-drift without it, that none of the source's lines changed, and that merging the rest ofshape/.gitattributesis optional. It ends with update-shape.py and shape-doctor.py: no NEXT line pre-fills--accept-localfor a locally edited shape copy, so theapplya row names, pasted, meetsupdate-local-driftand its question to the human instead of re-pinning the edit as the standard, and ashape/collision copy merged into its original and removed ismerged-awaytocheck, whoseapplydrops its pin row behind the same yes, so an adopted root that did its follow-up validates green again (#501, #502) #512's sentence (update-shape.py, validate-pins.py and shape-doctor.py: once an adopted root does its own follow-up "merge shape/.gitignore into .gitignore" and removes the merged copy, the copy's pin row iscopy-missing, so the project'sbootstrap.pyand itsvalidatecheck go red,applyrefuses with a remedy (--accept-local) that is refused in the same words, and no tool removes the row, which leaves only undoing the merge or the hand edit of the pin that AGENTS.md forbids #502) for every other pinned copy, under the same condition as the merge: only then removeshape/.gitattributes, after whichupdate-shape.py checkreports its pin rowmerged-awayandapplydrops the row. The follow-up for a.gitattributesthat is a symlink has no such sentence, because it never sends anyone to merge or remove the copy, which stays pinned.adopt-project.py:shape_materialize(ATTRIBUTES,eol_block_paths,eol_line,with_eol_block) in a statement of their own, so that none of them sits in the shared list adopt-project.py:y()quotes what the plan's loader would read as a keyword, so--spec-path null,--id nulland--tracking-branch trueadopt as typed, andplan,checkandexecuterefuse, before anything exists, a value the project's own loader would read back as something else from the lineexecutewrites #518 (open) also edits;APPENDED TO .gitattributes;_land_the_eol_blockcounts the source's.gitattributesas landed in the root only when its blob there is exactly the source's bytes followed by the block for the copies outsideshape/, recomputed from the split's own pin. Anything else is stilladopt-lost: a source line changed, the block short of a copy or holding one too many, or a line after it.update-shape.py apply --addnames each added copy in the block:.gitattributesis a symlink, is left alone;.gitattributes, or one the plan sends to a leg, keeps today's behaviour: the shape's own file is the root's.Documentation
The docs are corrected in this PR (Brett Heap, 2026-10-13T11:30Z). They had first been handed to lane 5's #483 (issuecomment-6134543847, on his word at 2026-10-12T23:24Z), but #483 closed through #505 (
af01068b) before this change was onmain.README.md(1422 lines) andAGENTS.md(490) are at their line caps, so each keeps its count and is reworded in place:README.md,### Ambiguous is an answer, and no source line is overwritten(was "nothing is overwritten"). The collision paragraph now says that a kept root.gitattributesgets a block of/<copy> text eol=lflines appended, and that the verification counts that file "as the source's bytes plus the block". To keep the paragraph at seven lines,.gitignoreleaves the example list and "after the split" leaves "root tree".README.md, "Why.gitattributesis one of the copies":* text=auto eol=lf"(or the block appended to an adopted root's own file, tests-windows never executes an adoption: git-filter-repo is not installed there, so the eight adopt suites skip and v1.0.0's "adopt works on Windows" is unverified #471)" puts the answer where the digests are.AGENTS.mdstep 7: the table accounts for every source path "by blob sha (a kept root.gitattributesas the source's bytes plus the line-ending block the split appends)". This is one line longer.AGENTS.md, the no-code variant's item 2: it no longer restates "and the table still accounts for every source path by blob sha". It already says "step 7 included", and dropping the clause saves the line step 7 gained.docs/handbook.html:<strong>heading, the collision paragraph and the verification, and the.gitattributesparagraph;copy-missing, so the project'sbootstrap.pyand itsvalidatecheck go red,applyrefuses with a remedy (--accept-local) that is refused in the same words, and no tool removes the row, which leaves only undoing the merge or the hand edit of the pin that AGENTS.md forbids #502's, in the footer's; ... added (#NNN)form. Each merge ofmainsince review round 1 that moved the footer (f391e58,d41cc29,89c6a6c,8beb3d9) conflicted there alone, and kept main's clauses whole;README.md blobis the hash of the merged README.md, recomputed at each of those merges (f3b378dc5704de79ee63972d0ea0be14dc693209at9f28d90, the valuetests/test_repo_hygiene.pychecks)."The only change to its history is ONE split commit" (
README.mdand the handbook) is unchanged. The block is in that commit, so it still holds.Tests
tests/test_adopt_attributes_eol_block.py(new, nogit filter-repo, so every CI job runs it):needs_disk_symlinks), as the adoption suites' other disk-symlink tests do: a link whose target is spelledconfig/attributesleads nowhere there;--accept-localfor a locally edited shape copy, so theapplya row names, pasted, meetsupdate-local-driftand its question to the human instead of re-pinning the edit as the standard, and ashape/collision copy merged into its original and removed ismerged-awaytocheck, whoseapplydrops its pin row behind the same yes, so an adopted root that did its follow-up validates green again (#501, #502) #512's row sentence, under the merge's own condition), the link variant's, which says nothing of a row, and the split message;adopt-lost.tests/test_adopt_ignored_shape_files.py:shape/;test_a_checkout_that_writes_crlf_still_bootstrapsclones the adopted assembly undercore.autocrlf=trueand undercore.eol=crlf, set in git's command scope so the Windows job's owncore.autocrlf=falsecannot outrank it. The source's ownREADME.md, checked out CRLF, is the control, and every pinned copy must be LF on disk and bootstrap green.tests/test_update_shape_add.py:--addextends the block on a branch commit, leaves a root without the block alone, and a refused apply puts the block back. The module fixture's check that no copy list still names a stripped file now reads list entries, not every string in the materializer (the newATTRIBUTESconstant is not a list entry).tests/test_adopt_filter_repo_failure.py: the two tests whose stubgit-filter-repois a#!script loseWINDOWS_SKIP, because git starts that stub, and Git for Windows runs a#!script with its ownsh.test_following_the_notice_after_a_failed_leg_ends_adoption_verifiednow compares the leg repositories it reads out of the notice with theirspelledform: the notice names a path between backticks with each backslash escaped (adopt-project.pycheckandexecuteprint a source path raw in a FINDING, so a\r,\n,\x85, U+2028 or ESC in a name overwrites, splits or escapes the line on a terminal, whereplan's questions spell the same path with_spelled#212), so a Windows path readsD:\\a\\...there, and the rawstr()the test compared with before holds on POSIX alone. The nine other#!-stub tests stubgh, which adopt starts throughsubprocess. On Windows CreateProcess finds onlygh.exethere, never a.cmd, and their arguments (a pull request's body) hold newlines a batch file cannot receive. They stay skipped.tests/test_adopt_pins.py: the module fixture deletes itsshutil.copytreecopy of a repository withrmtreefromtests/conftest.py, which clears the read-only bit git gives its objects and retries.shutil.rmtreeraisedPermissionError: [WinError 5]there, and the first Windows run of the adoption suites ended all 12 pin tests (four tests, three spellings) in an error at setup (see Runs).tests/test_update_shape_merged_away.py: the fixture's body is one helper,adopt_and_clone, whichadopted_hereand a newadopted_with_attributes(the same source, keeping a root.gitattributes) call.test_a_merged_away_attributes_copys_row_goes_and_the_root_file_staysfollows the follow-up's own sentence through a real adoption. It mergesshape/.gitattributesinto the root file and removes it, then pastescheck's NEXT: the row ismerged-away,applydrops it and nothing else in the pin moves, the root file stays as the merge left it, block and all,validate-pins.pyexits 0 and the doctor reads COMPLIANT.Mutation check: three mutants, each removing one half of the fix, are each caught by the tests that pin that half. They are: no append in the materializer, no verification exception, and no
--addextension.Runs
Run locally on Linux at
e2922e8(the change onmainatcc8f2da), withgit-filter-repo2.47.0 on PATH:test_shape_doctor.py, run below), plus the new module: 2046 passed;test_shape_doctor.py,test_scaffold_leg_path_collisions.py,test_repo_hygiene.pyandtest_agents_md_codes.py: 553 passed, 3 skipped (the PowerShell line, printed only on a Windows host);test_update_shape*.pyfiles: 341 passed, 3 skipped (junctions, Windows only).Then at
8f8cf6f, after mergingmainat47b1e06(20a188e, no conflicts) and the docs commit:test_shape_doctor.py,test_scaffold_leg_path_collisions.py,test_repo_hygiene.py,test_agents_md_codes.pyandtest_cli_reference.py(line caps, the handbook's README blob, AGENTS.md's codes): 575 passed, 3 skipped (the PowerShell line);Then at
4aba2bd, after review round 1:test_adopt_leg_path_collisions.py,test_adopt_e2e.pyandtest_adopt_root_manifest.py, the other callers of_predict_collisionsand the follow-ups: 148 passed.Then at
cf2cfaf, after folding in #497 (30922cd) and NIT-6:test_adopt_ignored_shape_files.py,test_adopt_attributes_eol_block.pyandtest_repo_hygiene.py: 126 passed, none skipped;test_shape_doctor.py -k "filter or adopt"(tests.yml: the Windows job installs git-filter-repo 2.47.0, so the 23 test files that skip without it run there and an adoption is executed on Windows for the first time, and the comments and docstrings that said that job installs none now say why it does (#471) #497 edited its docstrings): 7 passed, 345 deselected;actionlint1.7.12 on.github/workflows/tests.yml: no findings.Then at
d131876, after mergingmainatf391e58(c349ab4; its one conflict was the handbook's footer) and the fixture fix totests/test_adopt_pins.py(the verifier's run):test_adopt_pins.py,test_adopt_attributes_eol_block.py,test_adopt_ignored_shape_files.py,test_update_shape_add.pyandtest_repo_hygiene.py: 216 passed, none skipped;test_shape_doctor.py,test_scaffold_leg_path_collisions.py,test_agents_md_codes.pyandtest_cli_reference.py: 526 passed, 3 skipped (the PowerShell line).Then at
91ed4a5, after mergingmainatd41cc29(cb5f5a7) and at89c6a6c(c8cb46a), each with the handbook's footer as its one conflict, and moving the.gitattributes-symlink paragraph ofcollision_follow_up's docstring up beside the docstring's first paragraph, so that the docstring's end, which #512 (#502) also extends, merges without a hand resolution:test_adopt_pins.py,test_adopt_attributes_eol_block.py,test_adopt_ignored_shape_files.py,test_adopt_execute_refusals.py,test_adopt_filter_repo_failure.py,test_adopt_clone_template.py,test_adopt_entry_paths.py,test_adopt_mount_ignored.py,test_adopt_submodules.py,test_adopt_verify.py,test_update_shape_add.py,test_shape_doctor.pyandtest_scaffold_leg_path_collisions.py: 1264 passed, 3 skipped.Then at
9f28d90, after two more merges ofmain, the fold, and the import move:mainat8beb3d9(000b0b0: update-shape.py and shape-doctor.py: no NEXT line pre-fills--accept-localfor a locally edited shape copy, so theapplya row names, pasted, meetsupdate-local-driftand its question to the human instead of re-pinning the edit as the standard, and ashape/collision copy merged into its original and removed ismerged-awaytocheck, whoseapplydrops its pin row behind the same yes, so an adopted root that did its follow-up validates green again (#501, #502) #512, with adopt-project.py:--organd--projectmust name the repository the source names, compared without case, soplanrefuses--project BillingServiceforacme/billing-serviceasadopt-not-the-sourceinstead of writing a plan whoseexecuteacts onacme/BillingService,checkfinds andexecuterefuses a plan edited to name another asplan-not-the-source, and a source whose own name the naming policy refuses is told to rename it first, never to retype--project(#499) #513 and openRepoShape --doctor: the installed form keeps the clone of the standard it ran from and names it on the report's last line, so a NEXT line pasted after the command has exited runs instead of dying withcan't open file, and it hands--jsonand--placement-plan <file>on toshape-doctor.pyinstead of refusing them (#500) #510 before it) conflicted in the handbook's footer alone.mainatb0826e0(7924d75: adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504, lane 1'scheckof root-kept symlinks) conflicted in three hunks, inadopt-project.py'sfrom shape_materialize import (...)list and inmaterialize_assembly_root's docstring and tail, and each keeps both sides: adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504'sspellis passed through, and the.gitattributesblock is appended after it. adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504 does not touch the handbook;plan,checkandexecute(the symlinks are under Known limits).materialize-attributes-pathneeds nospell: onlyeol_lineraises it, over the shape's own copy-list names, which are never resolved through a link, andspellis for the names a source's symlinks lead to;8579d4f(the follow-up's row sentence and its tests, above). The import move is9f28d90: merging adopt-project.py:y()quotes what the plan's loader would read as a keyword, so--spec-path null,--id nulland--tracking-branch trueadopt as typed, andplan,checkandexecuterefuse, before anything exists, a value the project's own loader would read back as something else from the lineexecutewrites #518 (open) into this branch has the same conflict regions inadopt-project.pyas merging it intomainatb0826e0, compared region by region, so this change adds none;git-filter-repo2.47.0 on PATH: 1771 passed, 3 skipped, none failed. The skips are the PowerShell line (tests/test_shape_doctor.py:9119, printed only on a Windows host), and no adoption test skipped. The files are the three hygiene files;test_adopt_attributes_eol_block.py,test_adopt_pins.py,test_adopt_ignored_shape_files.py,test_adopt_execute_refusals.py,test_adopt_filter_repo_failure.py,test_adopt_clone_template.py,test_adopt_entry_paths.py,test_adopt_mount_ignored.py,test_adopt_submodules.py,test_adopt_verify.py,test_adopt_project_is_the_source.py,test_adopt_leg_path_collisions.py,test_adopt_root_symlinks.pyandtest_adopt_finding_spelling.py;test_update_shape_add.py,test_update_shape_merged_away.pyandtest_update_shape.py;test_shape_doctor.pyandtest_scaffold_leg_path_collisions.py.Then at
1ce4f45, the head, after mergingmainat3121945(#494, lane 3's change for #370: a refusal built without a remedy prints itsREFUSEDline alone):REFUSEDline alone, the assembly root'svalidate-pins.pystill adds the lockstep remedy to its own, andaddrefuses a.gitmodulesgit cannot rea (#370) #494 changes neither README.md nordocs/handbook.html(the README blob staysf3b378dc5704de79ee63972d0ea0be14dc693209). The two changes share onlyadopt-project.py, where repo_shape.py, validate-pins.py, family.py: a refusal built without a remedy prints itsREFUSEDline alone, the assembly root'svalidate-pins.pystill adds the lockstep remedy to its own, andaddrefuses a.gitmodulesgit cannot rea (#370) #494's one hunk is in_failed_loudly, which this change does not touch. The one refusal this change adds,materialize-attributes-path, names its own remedy, so it prints as it did;REFUSEDline alone, the assembly root'svalidate-pins.pystill adds the lockstep remedy to its own, andaddrefuses a.gitmodulesgit cannot rea (#370) #494'stest_refusal_default_remediation.py: 1791 passed, 3 skipped (the PowerShell line), none failed, and no adoption test skipped.The Windows probe was
ci/w471-probe-2(b09370d): this change atb2c0924, before its rebase ontocc8f2da, with #497'sgit-filter-repoinstall, run on Windows only over the files that skip without it and the two test files this change adds or extends, with no xfail mark. Run 38321799510: 1754 passed, 2 failed, 32 skipped.test_the_assembly_bootstraps_where_it_failed_with_shape_copy_missing[its-own-attributes], the case #471 found, passed there. The two failures were the tests' own and are fixed above: the notice-following test's expectation, and the symlink test's missing Windows skip. Neither fix changes a tool, so no third probe was run.This pull request's own
tests-windowsjob installsgit-filter-repotoo (#497 is folded in, below), so its CI was the first to run the adoption suites on Windows over this fix:cf2cfaf(run 38398207725) ended6517 passed, 561 skipped, 12 errorsin 1:21:06, and no test failed. All 12 errors were at the module fixture oftests/test_adopt_pins.py(four tests, three spellings):PermissionError: [WinError 5]fromshutil.rmtreeover the read-only objects of a copied repository.d131876is the fix (see Tests);d131876(run 38418701101) passed on all three OSes, and so did91ed4a5(run 38483318578), whose Windows job took 127.5 minutes;9f28d90(run 38569393760) passed on all three OSes and SonarCloud. Its Windows job finished at 15:27Z, 138 minutes after the run started. It is this pull request's H0 under ruling 27;1ce4f45, its clean merge ofmainat3121945, is the head (run 38591604388): Linuxtestspassed at 16:04Z and SonarCloud at 15:42Z. That is what ruling 27 asks of a head that adds only a clean merge ofmainto H0. Its macOS and Windows jobs were still running when this body was written.Review round 1 (#511 at
8f8cf6f, NOT MERGEABLE on one MUST)Fixed at
ea9c209(tools and tests) and4aba2bd(README.md); no amend, rebase or force..gitattributesundercore.symlinks=false:120000over the old target followed by the block.executethen said "none of its own lines was changed" and failedadopt-lost: .gitattributesafter both legs were pushed. Main verifies the same source._append_eol_blocknow also asks git's index (_tracked_as_link,ls-files --stage, mode120000).test_a_link_checked_out_as_a_plain_file_is_not_written_through, in the materializer, with the link checked out undercore.symlinks=false;test_a_linked_attributes_file_checked_out_as_a_plain_file_adopts, end to end, withcore.symlinks=falsein git's command scope for every git the run starts. The split's entry is the source's120000blob, unchanged, and the run verifies.plannow reads the link from the source tree's mode. Its follow-up says the split appended nothing, says git reads no.gitattributesthat is a symlink, and names the remedy: a regular file in its place, or a clone's.git/info/attributesfor that clone alone. It lists the exact lines.test_the_follow_up_for_a_linked_attributes_file_lists_the_lines_it_needschecks those lines against the ones the materializer gives a regular file. The end-to-end test above reads the follow-up in the plan and in the split message..gitattributesplus a root-keptcontracts -> meta:adopt-lost: .gitattributes. Main verifies it._pin_through_linksover_SplitTree), as_shape_file_findingsreads it. The docstring is corrected.test_the_pin_is_read_through_a_link_the_root_kept, with the link in the tree only, on every platform;test_own_attributes_and_a_linked_contracts_kept_in_the_root_adopt, end to end, with a disk link (needs_disk_symlinks).--addnow extends the LAST head line, which is the block adoption appended (test_the_last_block_is_extended_when_the_source_held_the_head_already).--addsentence now says that it names the file in a root.gitattributesblock of tests-windows never executes an adoption: git-filter-repo is not installed there, so the eight adopt suites skip and v1.0.0's "adopt works on Windows" is unverified #471. Lines 1078-1088 are re-wrapped, and README.md stays at 1422 lines. The handbook's README blob is recomputed at every footer round, so no value is kept here (see Documentation). The handbook does not list--add's acts.Mutants, each undoing one fix, were run against the new tests in a scratch worktree at
4aba2bd: the unmutated tree passed all 6 new tests, and each mutant was caught:disk-only-link-check(MUST-1) failed both link tests;flat-pin-lookup(SHOULD-3) failed bothcontracts -> metatests;no-link-follow-up(SHOULD-2) failed the follow-up test and the end-to-end one;first-head(NIT-1) failed the last-block test.Folded in #497
#497 (
ci/windows-adoption-suites) is superseded by this pull request. Its two reviewed commits are merged here at30922cd, the merge of its head6824181, which also carries #497's update-branch merge ofcc8f2da:a528006:tests.yml'stests-windowsjob installsgit-filter-repo2.47.0, the ubuntu job's pin, on one PowerShell line, and still runs the whole suite;At tests.yml: the Windows job installs git-filter-repo 2.47.0, so the 23 test files that skip without it run there and an adoption is executed on Windows for the first time, and the comments and docstrings that said that job installs none now say why it does (#471) #497's base, 23 test files skipped without the tool (the 21 that spell
shutil.which("git-filter-repo"),test_adopt_filter_repo_failure.pyandtest_adopt_git_reads.py), 178 of their 1731 items. The tool adds about 8 to 11 minutes to the Windows job. Its first run there was probe run 38312924968: 1696 passed, 34 skipped, and 1 failed, the case this change fixes.a8b5064: a strict Windows xfail (WINDOWS_CRLF_COPIES) ontest_the_assembly_bootstraps_where_it_failed_with_shape_copy_missing[its-own-attributes].The two branches merged with no conflict, so git kept that xfail. Over this fix the case passes on Windows (probe run 38321799510), and a strict xfail that passes fails as
XPASS(strict). The merge commit therefore removes it by hand, as #511's review gave it:#: KNOWN ON WINDOWS, AND STRICTcomment and theWINDOWS_CRLF_COPIESconstant;(adopted, tmp_path)signature, withoutrequestand without theapplymarkerlines.git diff 4aba2bd 30922cd -- tests/test_adopt_ignored_shape_files.pyis #497's two docstring lines and nothing else.actionlint1.7.12 on.github/workflows/tests.ymlreports nothing.Known limits, for #457's checklist
update-shape --addonly extends a block that is there..gitattributesthat is a symlink (on the disk, or only in git's index) gets no block. Git reads no.gitattributesthat is a link, so the pinned copies at the root still check out CRLF on Windows until a human replaces the link with a regular file. The plan's follow-up says so and lists the exact lines. Since adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504 (b0826e0),checkrefuses three such links asplan-root-symlink, andexecuterefuses them before any leg exists: a dangling one, one to an absolute path with nothing there, and one that climbs above the root to nothing. This change alone wrote the shape's copy through the first two (the absolute one wrote a file outside the assembly) and failed after both legs were pushed on the third. Every linkcheckpasses gives the same split as this change alone, blob for blob: the link kept, no block, the shape's copy beside it..gitattributesthat setseolor-textfor a pinned copy under it outranks the root block for that copy..github -> ci) is named by its link spelling, so its line in the block governs nothing (NIT-2). adopt-project.py:checkfollows each symlink the plan keeps where the shape writes its files, andexecuterefuses one the shape cannot write through, or one it writes beneath that git checks out as a file, before any leg exists, so such a link no longer breaks the run after both legs are pushed or has the shape's files written outside the assembly (#183, #178) #504 still accepts such a link. Measured at1ce4f45with a source rule* text=auto eol=crlf,validate-pins.pyreportsshape-copy-driftfor that one copy. This is not a regression: main at3121945, with no block, reports nine for the same source..gitattributescommitted with CRLF under a non-autotext/eolrule is renormalised whole when the split re-stages it. It fails closed, asadopt-lost: .gitattributes(NIT-3). A CRLF blob under* text=autois fine. Main at3121945behaves the same.filter=orworking-tree-encoding=rule still reaches a pinned copy. With*.py working-tree-encoding=UTF-16LE-BOM,executestops after both legs exist, atgit add -A. Anidentrule reaches none, because no pinned copy holds$Id$.Closes #471
🤖 Generated with Claude Code