Repository navigation
update-shape.py and shape-doctor.py: no NEXT line pre-fills --accept-local for a locally edited shape copy, so the apply a row names, pasted, meets update-local-drift and its question to the human instead of re-pinning the edit as the standard, and a shape/ collision copy merged into its original and removed is merged-away to check, whose apply drops its pin row behind the same yes, so an adopted root that did its follow-up validates green again (#501, #502) - #512
Merged
Merged
Conversation
…`shape currency` NEXT no longer pass `--accept-local` for a locally edited shape copy, so the command a row names, pasted as printed, meets `update-local-drift`, which asks the human whether the project keeps the edit, instead of re-pinning that edit as the standard (#501) `cmd_check` folded `--accept-local <path>` for every `locally-modified` row into the `apply` it printed, beside `--yes`, and the doctor's `shape currency` row did the same in the `apply` after its `check`. AGENTS.md tells an assistant to run the command a row names, and pasted, that line re-pinned the project's own bytes: exit 0, a green `validate-pins.py`, a COMPLIANT doctor, and `update-local-drift`, the refusal that exists to put the question to the human, never fired. Brett Heap ruled on 2026-10-13, "Stop pre-filling it". Neither NEXT names the flag for a `locally-modified` row now, so the `apply` meets that refusal, whose remediation names the three exits: revert the edit, carry it upstream, or `--accept-local` on the human's word. `check` still names the flag in the row's detail, as the human's to give, the way it already names it for `both` (`keep_local_exits` beside `accept_local_exit`). `apply_command` loses the parameter that carried the paths, and the `--yes` in both lines stays as it was, as ruled. The doctor's `apply` keeps interpolating `accept`, which is now always empty, so the lines that build that `apply` are left byte for byte as they were. AGENTS.md's updating step 3 says, in the same line count, that no NEXT line passes the flag and that `apply` refuses and asks. Tests: the doctor's drift test pinned the hand-over on purpose, reasoning that a reader would know not to paste it, and now asserts no line of the report carries the flag. P10b pastes the `shape currency` row's `apply` as printed, is refused `update-local-drift`, and ends COMPLIANT with the person's `--accept-local`. `check`'s NEXT for a root with a local edit carries no path, so the quoting test for an `--accept-local` path on that line goes with the parameter. A new test pastes `check`'s NEXT over a committed local edit: refused, the row's digest unchanged, the three exits named, and re-pinned only with the flag added. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pe/` collision copy merged into the file it collided with and removed is `merged-away` to `check`, and `apply` drops its pin row in the re-pin commit behind the same yes, so an adopted root that does its own follow-up gets its `bootstrap.py` and `validate` green again instead of a `copy-missing` row whose only remedy was refused in the same words (#502) An in-place adoption pins the shape's copy of a name the source already uses where it lands, under `shape/`, and its follow-up says to merge it into the original. Removing the merged copy left a row naming a file that is not there: `validate-pins.py`, and with it `bootstrap.py` and CI's `validate`, said `shape-copy-missing`; `update-shape.py` called the row `copy-missing` and refused `update-conflict`, whose remediation said to re-run with `--accept-local`, which `_refuse_conflicts` never honours for that state; the doctor named that refused step; and no tool removed the row, leaving undoing the merge or the hand edit of the pin AGENTS.md forbids. Brett Heap ruled on 2026-10-12, "for auU-03, the pin row goes", and on 2026-10-13, "check names, apply drops": - `classify` asks `merged_into` first: a row under `COLLISION_DIR`, with nothing at it (`lexists`), whose original path the root holds as a file, is `merged-away`. The name is the act the follow-up asks for and the phrase this code and AGENTS.md already used, spelled like the other states. It is not a conflict, and the upstream is not read for it. - `apply` leaves such rows out of the pin it rewrites (`_repin`), prints a `dropped` line, says so in the confirmation and the commit message, and needs no new flag: the yes that covers every other row covers this one. A dropped row is no longer a pinned path for the NEXT line, and `update-rewrite-symlink` does not ask about a path nothing is written at. - A pinned copy deleted anywhere else, or a `shape/` copy whose original is gone too, stays `copy-missing`. `update-conflict`'s remediation is now built per state (`conflict_remediation`): a `copy-missing` row is told to put the copy back from the project's history, with or without `--accept-local`; `both`, `upstream-removed` and `unmapped` keep the words they had. - The doctor counts `merged-away` as drift, names it in the verdict (`DRIFTED (2 merged-away)`), and its `pins` row names the `update-shape.py check` the `shape currency` row names, but only where every finding the gate reports is a merged-away row's `shape-copy-missing`, judged by the same `merged_into`; any other finding keeps the gate as the NEXT. - `collision_follow_up` tells a pinned copy's follow-up that its row goes once the copy is removed, and by which step; a rendered file such as `shape/README.md`, which has no row, is told nothing about one. README.md, AGENTS.md and docs/handbook.html say the new verdict and what happens to the row, in the same line counts; the handbook names the new README blob. Tests (tests/test_update_shape_merged_away.py, one real adoption out of the standard the doctor tests build): the issue's own flow end to end, red gates, `check` and the doctor naming the step, `check`'s NEXT pasted, the two rows gone and nothing else in the pin moved, then green gates, a COMPLIANT doctor and nothing to do; a `shape/` copy removed with its original stays `copy-missing` and its refusal, with or without `--accept-local`, names the copy's own remedy; a pinned copy deleted elsewhere stays refused beside a merged-away row, and the `pins` row keeps the gate; and the plan's follow-ups. 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 GuideThe PR makes generated remediation commands safer by leaving local-edit acceptance to the human, and introduces an end-to-end Sequence diagram for human-controlled local drift acceptancesequenceDiagram
participant Human
participant Check as update-shape.py check
participant Apply as update-shape.py apply
participant Drift as update-local-drift
participant Pin as shape-pin.yaml
Human->>Check: Run check on locally-modified row
Check-->>Human: NEXT apply --yes; detail names --accept-local
Human->>Apply: Paste NEXT command
Apply->>Drift: Refuse local drift
Drift-->>Human: Choose revert, carry upstream, or accept locally
Human->>Apply: Re-run with --accept-local path
Apply->>Pin: Re-pin accepted local bytes
Flow diagram for dropping a merged-away collision pin rowflowchart TD
A[shape/X pin row] --> B{shape/X is absent?}
B -- No --> C[Keep normal classification]
B -- Yes --> D{Root file X exists?}
D -- No --> E[copy-missing]
D -- Yes --> F[merged-away]
F --> G[apply --yes]
G --> H[Drop shape/X row from pin]
G --> I[Copy nothing]
H --> J[validate-pins.py green]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This was referenced Oct 13, 2026
…f `update-conflict` no longer says this command never records a deletion and names `git checkout HEAD -- <path>` for a deletion not yet committed beside the parent of the commit that removed one, and the doctor's `shape currency` reason counts a `merged-away` row, so an `rm` then `apply` is sent to a commit that exists and the row's counts sum to its rows (#502) The fold of rv512's NIT-1, NIT-2 and NIT-3 on a5eb55d (review of PR #512). NIT-1. "this command never records a deletion" stopped being true in the commit it was written in: `apply` drops a merged-away row, which is a deletion recorded in the pin. The sentence now says which deletion it never records, that of a copy the project still needs; what it tells the person to do is unchanged. NIT-2. "the parent of the commit that removed it holds it" is false where the deletion is not committed, and that is the common way to meet this refusal: `rm .gitattributes`, then `apply`. No commit removed the copy, and `git log -1 -- <path>` names the commit that ADDED it. The sentence gives both exits: a deletion not yet committed comes back with `git checkout HEAD -- <path>`, a committed one from the parent of the commit that removed it, which `git log -1 -- <path>` names. `HEAD` is named because `git checkout -- <path>` restores from the index, which a staged `git rm` has already emptied of the path, so it refuses with `pathspec did not match`; `checkout HEAD --` puts back both an `rm` and a `git rm`, and it is the doctor's own line for a lost `repo_shape.py`. The `<path>` placeholder stays a placeholder in backticks, as before. NIT-3. The `shape currency` reason counted the five original states alone, so one merged-away row read `10 unchanged / 0 upstream-changed / 0 locally-modified / 0 both / 0 upstream-added (...): shape/.gitignore`, eleven rows under counts that sum to ten and a path named under no count, in the reason of the very row whose `apply` drops it. `merged-away` is counted after the five when its count is not zero, so a root with no such row reads exactly as before. Tests, in `tests/test_update_shape_merged_away.py`: the copy-missing test pins NIT-1's sentence and the absence of the old one; a new test deletes `.gitattributes` without committing, is refused with both exits named and nothing written, runs the `git checkout HEAD --` the sentence names, and `check` has nothing to do; a new test merges `shape/.gitignore` away and reads `1 merged-away` at the end of the `shape currency` counts, which sum to the row's own `detail.counts`. All three fail on a5eb55d's source and pass here. No other test quoted a changed sentence (`git grep` over `tests/`). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed-copies, after #402 made every doctor NEXT only a command: `check_shape_currency` keeps #402's `(command, why)` row with #501's `apply` that pre-fills no `--accept-local`, P10b and #402's `_shape_currency_moved` assert no flag in the `why`, #496's `re_pinned_from_the_merge` follows the refusal both ways round, #496's `unmerged` sentence says what the row does under #501, and the handbook footer adds (#502) after main's (#462) (#501, #502) Merges origin/main at d41cc29: #402's squash of a841533, on lane 2's #286 (8cd7dbe) and lane 3's #462 (d52b100). The resolution is the one prepared against a841533 in scratch (5a63d84a, 800 passed there), carried onto main: #402's squash holds a841533's change byte for byte in every file it touched (same patch-id per file), and main's only other change to the resolved files is #286's, which `git merge-file` applied onto the prepared files without a conflict. Three files conflicted: - shape-doctor.py, `check_shape_currency`, the pin-only branch and the final return: #402's `(command, why)` form, the `apply` in the row's `why`, under #501's rule that it carries no `--accept-local`. #402's `accept = "".join(...)` is dropped (that is #501), and so is this PR's `accept = ""` with its `{accept}`, kept only so #402's lines stayed byte for byte (review NIT-5); #501's comment moves beside the `apply` it governs, and #402's comment there no longer says the `why` carries the flag. - tests/test_shape_doctor.py, P10b: `assert "--accept-local" not in currency["next"] + currency["why"]`, since under #402 the `apply` is in the `why`. - docs/handbook.html, footer: main's text, every clause through (#462), then this PR's "the <code>merged-away</code> verdict, whose row <code>apply</code> drops, added (#502)", the two lines touched re-wrapped. `README.md blob` 8c658ef (main's; the branch had 2977f24) becomes c4f819a, `git hash-object` of the merged README.md (1422 lines; AGENTS.md 490). Changed beyond the markers, because #501 makes main's words false on the merged tree: - tests/test_doctor_next_is_a_command.py, `_shape_currency_moved`: asserts no `--accept-local` in the row's `why`, where it asserted the flag. - #496's `unmerged` sentence in `pin_restore` (shape-doctor.py) said the `shape currency` row names `--accept-local` where `check` calls the copy locally-modified. Under #501 the row never names it, so the doctor would print a false sentence. It now reads "The shape currency row never names it. Where update-shape.py check calls the copy locally-modified, the apply the row names is refused as update-local-drift, and where check calls it both, as update-conflict, and either refusal asks for --accept-local on that path"; `pin_restore`'s docstring and the test's say the same. - #496's `re_pinned_from_the_merge` (tests/test_shape_doctor.py) asserts the row's `apply` names no `--accept-local` either way round, runs it as spelled, expects `update-local-drift` on the update side and `update-conflict` on the person's, with nothing moved, then adds the person's `--accept-local <copy>` and reaches COMPLIANT; `assert_the_pin_alone_is_taken` checks the new sentence. - Lane 3's #402 review NITs: the helpers' `then[:2]` assertions quote `currency["why"]`, and three docstrings say `why` for "note". Everything else auto-merged, #286's `update-rewrite-symlink` and `copy-is-link` in update-shape.py and shape-doctor.py included. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erged-away collision copy only where `check` can read the root, so a root that keeps its pin or its manifest as a symlink, which `check` now refuses as `update-rewrite-symlink` (#286), keeps the gate as that row's NEXT instead of a `check` that exits 2 beside a `shape currency` row naming the link (#502) `with_merged_away_rows` names `check` as the `pins` row's NEXT where every finding the project's own `validate-pins.py` reports is a merged-away `shape/` copy's `shape-copy-missing`, and says it is the step the `shape currency` row names too. Its rule is to do so only where that step clears every finding. Lane 2's #286, merged into this branch from main, made `read_pin` and `prepare` refuse a root that keeps `contracts/shape-pin.yaml`, the `contracts` directory above it or the manifest as a symlink, before anything is classified, and made the doctor's `shape currency` row name replacing the link (`linked_record_row`). `validate-pins.py` still reads through the link and still reports the copies missing. So on such a root the `pins` row named a `check` that refuses, exit 2, and said that `check` reports the rows `merged-away` and that the `shape currency` row names it, both false (measured on the merged tree: pin, `contracts` and manifest kept as committed links, verdict `INVALID (pins)`). The row now asks the same `linked_record` question `linked_record_row` asks, of the root's own kind, and keeps the gate as its NEXT while the answer is a link, as it does for any finding the step would not clear. The `shape currency` row names the remedy; with the link replaced and committed, the doctor run again names `check`, as before. Also measured on the merged tree, and left as they are: the file a copy was merged into kept as a link, and `shape/` itself kept as a directory link, are still `merged-away`, and `apply` drops the rows and writes nothing through either link; a `copy-is-link` row beside merged-away rows is refused `update-rewrite-symlink` before `update-conflict`, so `conflict_remediation` never reads one. `tests/test_update_shape_merged_away.py` holds the three links, each failing without this change at the `pins` row's `merged_away` detail. 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 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>
brettheap
pushed a commit
that referenced
this pull request
Oct 13, 2026
…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>
…and-merged-copies: the handbook footer keeps main's clauses and this PR's #502 clause follows #499's Ruling 27 refresh round. #513 landed on main as cf118ff and rewrote the footer's clause list and README.md blob in docs/handbook.html, so this branch conflicts there and nowhere else (git merge-tree --write-tree 6943280 cf118ff exits 1 with that one path). The one hand resolution, docs/handbook.html's footer: - main's text is kept whole: the #462 clause, then #487, #500 and #499; - this PR's own clause, "the merged-away verdict, whose row apply drops, added (#502)", is added after main's last one (#499), in the same "; ... added (#NNN)" form and before " — a page cannot name the commit that contains it"; - only the two lines touched are re-wrapped, to 100 columns; - README.md blob: this branch's c4f819a and main's 6bf94d5 give way to ae817c5, the git hash-object of the merged README.md (AGENTS.md 490 lines, README.md 1422). README.md, AGENTS.md and tests/test_shape_doctor.py auto-merged with no conflict; no other file conflicted. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
This was referenced Oct 14, 2026
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…s a sentence to a pinned copy's collision follow-up and #501 changes what update-shape.py and the doctor print, none of it in this branch's lines, so the copy `place()` writes beside a kept path is walked as before and its follow-up now says how its pin row goes Merged by sha 8beb3d9, the one commit of main past cf118ff. No conflict: the tree is `git merge-tree --write-tree 64b2cc0 8beb3d9`'s, 22a21a3. The commit that meets this branch's files: - 8beb3d9 (#501 and #502 via #512): in scripts/shape_materialize.py, `collision_follow_up` ends a pinned copy's follow-up (a file in `COPIED_VERBATIM` or `COPIED_FROM_SHAPE`) with ". Then remove <copy>: `update-shape.py check` reports its pin row `merged-away`, and `apply` drops the row", and its docstring says why. Its hunks are in `collision_follow_up` alone, away from this branch's `_refuse_unwritable`, `place()` and `template_files`. The rest is update-shape.py, shape-doctor.py, docs and their tests, which this branch does not change. How it composes with this branch: - The walk this branch adds asks where `place()` writes each shape path and, where that path is taken, its copy under `shape/`. #512 changes only the follow-up sentence for such a copy, not where it lands or which paths are written, so the walk asks the same paths and refuses the same links. - No test of this branch asserts a follow-up's last words: its three test files and every file of the targeted set were searched for the old ending ("NOTHING was overwritten"); the one that calls `collision_follow_up` (test_adopt_leg_path_collisions.py) compares it with `_predict_collisions`, which calls it too. The ` beside ... nothing was overwritten)` lines this branch's tests read are `_commit_the_split`'s, which #512 does not change. Checked on the merged tree: no top-level name bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py, update-shape.py or shape-doctor.py but main's own (`YamlError`/`parse_yaml` in the materializer, `sys` in the doctor); no name loaded and bound nowhere; the signatures main changed (`_land`, `apply_command`, `commit_on_branch` in update-shape.py) are called only by main's own lines; each side's change reappears line for line in the other side's diff to the merge, in every file. 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
…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>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…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>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…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>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
Merged by sha, 8beb3d9, fetched at 03:08Z, on top of 9efe95a. It brings three commits since 89c6a6c, oldest first: 173c00e (#510 for #500: `openRepoShape --doctor`), cf118ff (#513 for #499: `--org` and `--project` must name the repository the source names) and 8beb3d9 (#512 for #501 and #502: update-shape.py, shape-doctor.py and `collision_follow_up` in scripts/shape_materialize.py). One conflict, one hunk, in adopt-project.py `_checked_plan_inputs`, the only unit both sides changed. Main passes `source` to `_check_names` for #513's own-name rule; this branch follows that call with #181's comment and its two refusals (`_refuse_unholdable_values`, then `_refuse_misread_values`). Resolved as main's call with `source`, then this branch's comment and two calls, unchanged in order. Main's other change to the function, `_refuse_another_repository` before any pin's digest is read, merged cleanly above it. So #513 refuses a plan for another repository before #181 asks any value. The comment in the hunk is brought up to date here (rv181g's N-7): since #259 and #514 a value the loader would misread is only one on a line written bare, no longer a pin's product. No code line differs from rv181g's proposed resolution (d55b975, the same merge with cf118ff, whose adopt-project.py is byte for byte 8beb3d9's). Every other path is `git merge-tree --write-tree 9efe95a 8beb3d9`'s. scripts/shape_materialize.py merges cleanly: #512 changes `collision_follow_up` alone, a function this branch does not touch. No top-level name is bound twice in adopt-project.py, and no name is read and never bound; shape_materialize.py and family.py import `parse_yaml` and `YamlError` twice, as on main. With this merge alone five tests of this branch's quoting file fail: their source's `origin` is a path ending in `seed`, which #513 reads as the repository and refuses `plan --org testorg --project Northwind` as `adopt-not-the-source`. The next commit puts that `origin` under `testorg/Northwind`, as cf118ff did for tests/test_adopt_tracking_branch.py. Lane: openreposhape-1 (openRepoShape-1) 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
…epo 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) 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, 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` now installs it at the ubuntu job's pin, on one line because PowerShell is that runner's shell, and still runs the whole suite. At #497's base 23 files skipped without it (the 21 that spell `shutil.which("git-filter-repo")`, `test_adopt_filter_repo_failure.py` and `test_adopt_git_reads.py`), 178 of their 1731 items. The workflow's comments and seven test docstrings that named the Windows job as the one without it now say what stays true. The tool adds about 8 to 11 minutes to the Windows job. Its first run there (probe run 38312924968) passed 1696, skipped 34 (exotic names, tracked symlinks, `#!` stubs and test helpers), and failed the one case the rest of this change fixes. Adoption's first run on a Windows runner failed one ordinary case: a source that tracks its own root `.gitattributes`. The shape's `* text=auto eol=lf` went beside it at `shape/.gitattributes`, where it governs only `shape/`, so the nine copies the pin names at their own paths 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 it. A root-level file takes attributes from the root file alone, so no file beside it can fix that. Brett Heap ruled at 2026-10-12T22:48Z that the split appends a marked block to the source's own file, that the verification accounts for it, and that `update-shape --add` extends it. The materializer spells the block once: a comment naming openRepoShape and #471, then `/<path> text eol=lf` for each pinned copy outside `shape/`, in the pin's order, appended after the source's bytes when the shape's `.gitattributes` collided, and never through a symlink. `execute` prints it and lists its lines in the split commit's message, and its verification counts the source's file as landed in the root only when the blob there is exactly the source's bytes followed by that block, recomputed from the split's pin; anything else is still `adopt-lost`. `update-shape apply --add` names each added copy in the block, through the ledger, and commits the file with the copies; a root with no block is left alone. The follow-up says what the block protects. A source with no root `.gitattributes` keeps today's behaviour. The two `git-filter-repo` failure tests whose stub is a `#!` script now run on Windows, where git starts the stub with its own `sh`. The one that follows the failure notice compares the leg repositories it reads there with their `spelled` form, because the notice escapes each backslash of a Windows path (#212). The test that a `.gitattributes` symlink is not written through skips on Windows, as the adoption suites' other disk-symlink tests do. The `update-shape --add` fixture's check that no copy list names a stripped file reads list entries, not every string in the materializer. The docs are corrected here (Brett Heap, 2026-10-13T11:30Z), each file keeping its line count. README.md's "Ambiguous is an answer, and no source line is overwritten" (was "nothing is overwritten") says a kept root `.gitattributes` gets the block appended and is verified as the source's bytes plus the block. Its "Why `.gitattributes` is one of the copies" names the block as what carries the rule in such a root. AGENTS.md step 7 states the same exception, and the no-code variant defers to step 7 rather than restating "by blob sha". docs/handbook.html mirrors both paragraphs, names #471 in its footer after main's clauses, the last of them #502's (#504 and #494, which landed since, add none), and recomputes its README.md blob over the merged README.md. Review round 1. A `.gitattributes` git tracks as a symlink gets no block even where Git for Windows' default `core.symlinks=false` checks it out as a plain file: the materializer asks git's index, not only the disk. Before, the block went into the link, and the adoption failed `adopt-lost` after both legs were pushed. The plan reads the link from the source tree. Its follow-up says nothing was appended and git reads no `.gitattributes` that is a link, and it lists the lines a regular file in its place needs. The verification reads the shape's pin through the split's links, as the shape-file check does, so a source keeping `contracts -> meta` in the root verifies. `--add` extends the last block. README.md's `--add` sentence names the block, at an unchanged line count. Merged with main at d41cc29, at 89c6a6c and at 8beb3d9. Each of those merges' one conflict was the handbook footer, which keeps main's clauses whole and adds this one after the last, now #502's. Two of what main brought meet this change, #513 and #512. #513's `adopt-not-the-source` runs in `plan` before the tree is read, so before the block's follow-up is predicted, and its `plan-not-the-source` is `check`'s finding and `execute`'s first refusal, before the split commit appends the block: a source with a root `.gitattributes` and a wrong `--project` is refused with no plan written (probed). #512's `merged-away` takes the `.gitattributes` beside-copy as it takes any other: left in place it is `unchanged`; merged into the root file and removed, `check` calls its row `merged-away` and `apply` drops the row, leaving the block and the root file as they were, and the gates and the doctor go green (probed). The verification of the source's bytes plus the block runs inside `execute` alone, so it never meets that row. A `.gitattributes` collision's follow-up is this change's own and returned before #512's sentence about the row, which every other pinned collision's follow-up carries; the fold below adds it. The rest do not meet it. #402 rewrote the doctor's NEXT lines, in a file this change does not touch. #462 rewrote only the two ruleset hint constants in `scripts/shape_materialize.py`, and the scaffold's call to `materialize_assembly_root` passes no `collision_dir`, so it never reaches the block. #286's `update-rewrite-symlink` and `--add`'s extension of the block never meet either: a `.gitattributes` that is a symlink is left alone before the ledger is asked to write it, so `apply --add` exits 0 with the link and its target untouched (probed over a live and a dangling link). #515 spells the manifest's `neutral_product_pins:` entries, which the block never reaches, and its tests sit in `tests/test_adopt_pins.py` beside this change's fixture fix and merged clean. #507 is `family.py` and the quoting of the commands it prints, #517 is `scaffold-project.py` and #510 is the installed doctor's wrapper: none is a file this change touches beyond the docs. Merged once more with main at b0826e0, the squash of #504 (#183, #178), which meets this change in `adopt-project.py` and `scripts/shape_materialize.py`; the handbook footer did not conflict, because #504 does not touch it. Three hunks conflicted and each keeps both sides: the `from shape_materialize import (...)` list, `materialize_assembly_root`'s docstring, and its tail, where #504's `spell` is passed through and the `.gitattributes` block is appended after it. The two do not contradict each other, asked of real adoptions through `plan`, `check` and `execute`. (a) `materialize-attributes-path` needs no `spell`: only `eol_line` raises it, over the shape's own copy-list names, which `root_key` never resolves through a link, and `spell` is for the names a source's symlinks lead to. (b) Where #504's `check` passes, a root `.gitattributes` that is a symlink adopts as it did, to the blob: the link kept, no block, the shape's copy beside it. Where `check` now refuses it, `plan-root-symlink` before any leg (a dangling link, one to an absolute path with nothing there, a climb above the root, a `.gitattributes` under a link whose target already holds one), this change alone had written the copy through the link or failed after both legs were pushed. (c) The rest of #504's diff does not meet the block: `_refuse_unwritable` runs inside `place`, before the block is appended, and the block never goes through `place` or through a link. This change's four imports from `shape_materialize` sit in a statement of their own, so a change adding names to the shared list merges there without a hand resolution. Merged again with main at 3121945, the squash of #494 (#370), and it was clean: no file conflicted, and the handbook footer was not touched, because #494 changes neither README.md nor docs/handbook.html. It meets this change in `adopt-project.py` alone, in `_failed_loudly`, which this change does not touch; `Refusal` now carries no remediation unless its caller names one, and the one refusal this change adds, `materialize-attributes-path`, names its own, so it prints as it did. - The follow-up for a source's own `.gitattributes` now says what #512 (#502) says of every other pinned copy, after its own condition: merge the rest of `shape/.gitattributes` into the root file only if the whole project should check out with LF, and only then remove it, after which `update-shape.py check` reports its pin row `merged-away` and `apply` drops the row. The link variant says nothing of the row, because it never sends anyone to merge or remove the copy, which stays pinned. A new test follows the flow through a real adoption: merge the copy into the root file, remove it, paste `check`'s NEXT, and the row goes while the root file stays as merged. The first Windows run of the adoption suites ended in 12 errors, all at the module fixture of `tests/test_adopt_pins.py`: it deleted a `shutil.copytree` copy of a repository with `shutil.rmtree`, and Windows refuses to unlink the read-only objects git writes (`PermissionError: [WinError 5]`). It now deletes it with `rmtree` from `tests/conftest.py`, which clears the read-only bit and retries, as the other suites do. This pull request supersedes #497: its two reviewed commits (`a528006`, the install, the comments and seven docstrings; `a8b5064`, a strict Windows xfail on the case fixed here) are merged in, and the xfail is removed, because that case passes on Windows over the fix and a strict xfail that passes fails. Closes #471 Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 14, 2026
…5 cites the six pull requests merged since #402, section 6 drops the `--help` verdict line #510 fixed and AT-family N2, which #501's ruling decides, and adds 28 checklist lines from the parents, and section 7 lists the 3 open milestone issues (#472) Refresh round 2. GitHub was read from 2026-10-14T03:11:22Z to 03:18:32Z; main was 8beb3d9 (#512) throughout. Every merge commit of the 223 merged pull requests is an ancestor of it. Section 5 adds, from each squash message and PR body: - adopt-project.py: #515 (a pinned `007` listed as text) and #513 (a new "The source's own name" bullet: `adopt-not-the-source`, `plan-not-the-source`, rename first); - update-shape.py and bump-leg.py: #512 (a new bullet: no NEXT passes `--accept-local`; `merged-away` rows dropped by `apply`); - setup, scaffold, family and the shared helpers: #517 (a `--pin` product listed as text) and #507 (`family-landing-in-standard`, `family-on-tracking-branch`, a quoted NEXT that pushes the branch); - shape-doctor.py: #510 (the installed doctor keeps its clone, and passes `--json` and `--placement-plan` through). The bullet's "writes nothing into the repository" now adds "but a `--placement-plan` file you name": a relative plan file given from inside the repository is written there, as #460's new line says. Section 6 drops two lines and adds 28; no known-limit issue closed, so the 47 issues stay: - #457, 16 lines: L2-14, which #515 makes true of main; 10 of lane 4's 14 lines from #513 (00:06Z), two of them, a backslash `origin` and a trailing slash, in one bullet; and L1-K1, K2, K3, K7, K9 and K10 from lane 1 (01:17Z). - #458: AT-family N2 is dropped, as lane 4's status note there says (00:48Z); 2 lines added from #512. - #459, 3 lines that #507 and #517 make true of main: the validate-family remedies line (03:27Z), the README `--into` line (11:46Z) and L2-15. - #460: the `openRepoShape --help` verdicts line is dropped (#510 fixed it); the `shape currency` line is reworded, because since #402 the `apply --branch` is the row's WHY and since #512 it has no `--accept-local`, but under `--branch` it is still refused `update-branch-uncommitted` before `update-local-drift`; 7 of lane 4's 8 lines from #510 (23:34Z) are added. Left out, by round 1's rules: #513's `insteadOf` line and README sentence (wording), its comments line (comment wording), its extra `git config` read (no effect a person meets); L1-K4 (cannot arise today); L1-K5, K6 and K8, which describe #518's and #504's branches; and #510's `--help` synopsis column (cosmetic). #458's 03:26Z restatement is folded into its existing `repo_shape.py` line: #507 removed the instance it added. Section 7 lists #370, #471 and #472, the milestone's three open issues of 47, and says #178, #181 and #183 are not in the milestone. What was read at 8beb3d9, by `git show` or `git grep` of the code or text each line cites: - section 5: all six new sentences (adopt-project.py:6986-7030 and :7812-7960; update-shape.py:287 and :1939-1969; shape-doctor.py:3238-3243 and :3298-3314; scaffold-project.py:1293-1330 and the neutral pin template's line 30; scripts/family.py:620-720 and :925-960; openRepoShape:97-111, :489-490 and :540-558); - #457: L2-14, the backslash and trailing-slash line (`_remote_repository`, adopt-project.py:1164-1182), `check`'s missing rename-first remedy (:5137), L1-K3 (scripts/repo_shape.py:2048), L1-K9 (adopt-project.py:3316-3333); - #458: both new lines (shape-doctor.py:2005-2060 and :3222-3227), and N2's drop (update-shape.py:2915-2919 and `apply_command`); - #459: all three (validate-family.py:356-358 and :1066-1071; README.md:861- 862, docs/handbook.html:1247; scaffold-project.py:1313-1330 against adopt-project.py:7017-7030); - #460: the dropped and reworded lines (openRepoShape:97-100, docs/cli.md: 160-163; shape-doctor.py:3306-3314 and `cmd_apply`'s refusal order), the adoption NEXT with no `--org` (shape-doctor.py:6141-6143), the two `--placement-plan` line (openRepoShape:540-558, shape-doctor.py:6828), the "writes nothing" line (openRepoShape:55 and :605, README.md:1112, docs/handbook.html:1467) and the kept-clone line (openRepoShape:490); - kept lines whose code a landing touched: #458's "not hand-edited" commit and title-less `gh pr create` (update-shape.py:2349-2353, :3796-3797), AT-family N3, #460's `=word` line (`UNQUOTED_POSIX` admits `=`), and #459's unquoted setup `cd` and step (7) (setup-project.py:2023-2029, :2169). The rest rest on the posting lane's measurement, not re-read here: of #457's lines, lane 4's mirror path, `--source` after the clone, recorded `source:`, `ATLAS`, fork, `straße` and null-`org:` lines (measured at cf118ff; adopt-project.py is the same at 8beb3d9), and lane 1's L1-K1, K2, K7 and K10 (measured on 8beb3d9); of #460's, the TERM, interrupted-run and drifted-project-test lines (measured at 173c00e; `openRepoShape` is the same at 8beb3d9). The file is 7,354 words, from 6,611: section 5 is 2,291 (2,085), section 6 4,351 (3,607) and section 7 109 (316). No test names `docs/release-notes/` (`git grep -n release-notes origin/main -- tests/` finds nothing), but two tests in tests/test_repo_hygiene.py read every tracked `.md` through `git ls-files`: the host-absolute-path test (`HOST_ABSOLUTE_PATH`, :1496-1527) and the "<word> naming families" count (:1545-1601). Their two patterns, applied to this commit's file, match nothing. No test was run. 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 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: #501 (comment) and #502 (comment)
Two audit findings in
update-shape.pyand the doctor'sshape currencyandpinsrows, one commit each: 7b4024b (#501) and a5eb55d (#502). Both change which command a row names and whatapplydoes with a pin row. Four rounds followed: 9899f25 (the review's three nits), ddb2df8 (the merge of maind41cc294, #402's squash, which brings #347 and #496 with it), 6943280 (thepinsrow under #347's link refusal) and c29b9b4 (the merge of maincf118ff7, #513).#501 (audit auU-02): no NEXT line pre-fills
--accept-localWhat was wrong. For every
locally-modifiedrow,update-shape.py checkput--accept-local <path>in theapplyon its NEXT line, beside--yes. The doctor'sshape currencyrow did the same in theapplyafter itscheck. AGENTS.md tells an assistant to run the command a row names. Pasted, that line re-pinned the project's own edit as the standard: exit 0, a greenvalidate-pins.pyand a COMPLIANT doctor.update-local-driftexists to ask the human whether the project keeps the edit, and it never fired.What changes (commit 7b4024b). Brett Heap ruled on 2026-10-13: "Stop pre-filling it".
check's NEXT and the doctor'sshape currencyrow leave the flag out for alocally-modifiedrow. Since shape-doctor.py: a NEXT is only the command, and the sentence that ended twenty-five of them after#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402 that row's NEXT is thecheckalone and itswhyis theapply, which carries no flag. Pasted, theapplymeetsupdate-local-drift. Its remediation names the three exits: revert the edit, carry it upstream, or--accept-localon the human's word.checkstill names the flag in that row's detail, as the human's to give: "Whether the project keeps the edit is the human's decision: revert it, carry it upstream, or, on their word,apply --accept-local <path>". It already does this forboth.apply_commandloses the parameter that carried the paths.--yesstays in both lines, as ruled.check_shape_currencyloses itsacceptstring with the flag. Its pin-only branch is main's byte for byte, and theapplyin itswhykeeps shape-doctor.py: a NEXT is only the command, and the sentence that ended twenty-five of them after#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402's form, the lane's trailer included, minus the flag.#502 (audit auU-03): a merged-away collision copy's row goes
What was wrong. An in-place adoption pins the shape's copy of a name the source already uses under
shape/. The adoption's follow-up then says to merge that copy into the original. If the merged copy is then removed, its pin row names a file that is not there.validate-pins.pysaidshape-copy-missing, and with itbootstrap.pyand CI'svalidatewent red.update-shape.pycalled the rowcopy-missingand refusedupdate-conflict. That refusal's remediation said to re-run with--accept-local, which_refuse_conflictsnever honours for that state. The doctor named that refused step. No tool removed the row: the only ways out were undoing the merge or the hand edit of the pin that AGENTS.md forbids.What changes (commits a5eb55d, 9899f25 and 6943280). Brett Heap ruled "for auU-03, the pin row goes" (2026-10-12T23:16Z) and "check names, apply drops" (2026-10-13T11:02Z).
merged-away.classifyasksmerged_intofirst. A row ismerged-awaywhen three things hold:shape/;lexists, so a dangling link or a directory still counts as something);shape/X->X.The name is the act the follow-up asks for, spelled like the other states. It is not a conflict, and the upstream is not read for it.
applydrops the row from the pin it rewrites (_repin). It printsdropped shape/X (merged into X and removed; its pin row goes, and nothing is copied), and its confirmation and commit message say so too. It needs no new flag: the yes that covers every other row covers this one. A dropped row is no longer a pinned path on the NEXT line, andupdate-rewrite-symlinkno longer asks about a path nothing is written at.copy-missing. That covers a pinned copy deleted anywhere else, and ashape/copy whose original is gone too.update-conflict's remediation is now built per state (conflict_remediation). Acopy-missingrow is told to put the copy back from the project's own history, because--accept-localhas no bytes to keep, and that this command never records the deletion of a copy the project still needs (it does record a merged-away row's). It names both ways back:git checkout HEAD -- <path>for a deletion not yet committed, the parent of the removing commit (git log -1 -- <path>names it) for a committed one (9899f25).both,upstream-removedandunmappedkeep their old words (MERGE_BY_HAND).merged-awayas drift and names it in the verdict (DRIFTED (2 merged-away)). Itsshape currencyrow's reason counts amerged-awayrow where there is one (9899f25).pinsrow names the sameupdate-shape.py checktheshape currencyrow names, as a single command. It does so only when every finding the gate reports is a merged-away row'sshape-copy-missing, judged by the samemerged_into, and only wherecheckcan read the root. A root that keeps its pin, thecontractsdirectory above it or its manifest as a symlink is refused bycheckitself (update-shape.py: a pinned copy,contracts/shape-pin.yamlor the manifest kept as a symlink, or under one, is refused asupdate-rewrite-symlinkbefore anything is written, andcheckcalls such a copycopy-is-link, soapplyno longer writes the standard's bytes at the link's end and reports a commit that does not hold them (#286) #347'supdate-rewrite-symlink), so the row keeps the gate there and theshape currencyrow says to replace the link (6943280). Any other finding keeps the gate as the NEXT.collision_follow_upinscripts/shape_materialize.py) tells a pinned copy's follow-up what happens to its row: "Then remove shape/X:update-shape.py checkreports its pin rowmerged-away, andapplydrops the row." A rendered file such asshape/README.mdhas no row and is told nothing about one.Tests
New and changed tests:
tests/test_update_shape_merged_away.py(new) builds one real adoption out of the standard that the doctor tests build, and has seven tests, nine items:test_a_merged_away_copys_row_goes_and_the_project_is_green_againis the issue's own flow, end to end. The gates go red.checkand both doctor rows name the step.check's NEXT is pasted, and the two rows go with nothing else in the pin moved. Thenbootstrap.pyandvalidate-pins.pyare green, the doctor is COMPLIANT, and there is nothing left to do.test_a_shape_copy_removed_with_its_original_stays_copy_missing: the refusal, with or without--accept-local, names the copy's own remedy.test_a_pinned_copy_deleted_elsewhere_stays_copy_missing: next to a merged-away row, it is still refused, and thepinsrow keeps the gate.test_a_deletion_not_yet_committed_comes_back_from_head(9899f25): afterrmand a refusedapply, the remediation'sgit checkout HEAD -- <path>, run as named, puts the copy back,checkhas nothing to do, and the refusedapplywrote nothing.test_the_currency_rows_counts_name_a_merged_away_row(9899f25): theshape currencyrow's counts sum to its rows where a merged-away row is one of them.test_the_pins_row_keeps_the_gate_while_check_refuses_a_linked_record(6943280), with the pin, thecontractsdirectory and the manifest each kept as a committed link: both copies merged away,checkrefusedupdate-rewrite-symlink, thepinsrow keeps the gate and says nothing ofmerged-away, and theshape currencyrow names replacing the link. With the link replaced and committed, thepinsrow namescheck.test_the_plans_follow_up_says_what_happens_to_the_row.tests/test_update_shape.py::test_checks_next_leaves_a_local_edit_to_the_human(new) pastescheck's NEXT over a committed local edit. The paste is refused, the row's digest is unchanged, and the three exits are named. The row is re-pinned only once the person's flag is added.tests/test_shape_doctor.py:test_an_edited_shape_copy_is_drift_and_names_update_shapeused to require the flag on purpose. It now asserts that no line of the report carries it, and its docstring says why.test_a_conflicted_copys_next_agrees_with_shape_currency_and_agents_md) pastes theshape currencyapplyas printed. It is refusedupdate-local-drift, and it ends COMPLIANT once the person's--accept-localis added.--accept-localand then says COMPLIANT (#485) #496's helperre_pinned_from_the_mergefollows thatapply, as spelled, to its refusal before it adds the person's flag:update-local-driftwith the update side's pin taken,update-conflictwith the person's side's.tests/test_doctor_next_is_a_command.py(shape-doctor.py: a NEXT is only the command, and the sentence that ended twenty-five of them after#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402's):_shape_currency_movedasserts--accept-localis absent from the row'swhy.tests/test_update_shape_next_quoting.py: thelocal-editvariant expects no flag.test_a_path_check_offers_to_accept_is_one_argumentis deleted, because the path it quoted is no longer on that line.Targeted run at c29b9b4, with
git-filter-repoon PATH, overtests/test_repo_hygiene.py,test_agents_md_codes,test_cli_reference,test_update_shape_merged_away,test_update_shape,test_update_shape_rewrite_symlink,test_update_shape_next_quoting,test_shape_doctor,test_doctor_next_is_a_command,test_openreposhape_commandandtest_adopt_leg_path_collisions: 850 passed, 3 skipped, 0 failed. The three skips are the PowerShell tests attests/test_shape_doctor.py:9118, which run on a Windows host only. The head before the last merge, 6943280, ran green ontests,tests-macos,tests-windowsand SonarCloud. c29b9b4 is that head merged with maincf118ff7, and its one hand resolution is the handbook footer.Mutants. Each mutant ran in a scratch checkout, with the tests of the commit named and the tools reverted, or, for the last, one line of them changed:
check's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501 reverted (the tools at main47b1e06c, the tests at 7b4024b): 8 fail, of the 52 intest_update_shape_next_quoting.pyand the three tests named above. These are the newchecktest, the fivelocal-editquoting variants, the doctor drift test and P10b. The quoting variants fail on argv equality, because the reverted line still carries--accept-local <path>. The other three fail onassert '--accept-local' not in ....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 reverted (the tools at 7b4024b, the tests at a5eb55d): all 4 merged-away tests fail. The rows readcopy-missingwheremerged-awayis expected, the refusal has the old remediation, and the follow-up lacks the row sentence.update-shape.pyandshape-doctor.pyat a5eb55d, the tests at 9899f25): the three new tests fail, each on the old sentence or the old count.pinsrow's link question changed (one line ofwith_merged_away_rows, all else at c29b9b4): removed, all three link cases fail, each atassert 'merged_away' not in ...; asked of the pin alone, not the manifest, onlymanifest-at-a-linkfails.Docs
The docs are corrected in this PR, as Brett Heap ruled at 2026-10-13T11:30Z for each lane-4 fix. The branch holds main
cf118ff7(#513). README.md keeps its 1422 lines, its cap, and AGENTS.md its 490, its cap. docs/handbook.html has no cap and is 1943 lines against main's 1942, because its footer clause wraps onto one more.merged-away("ashape/copy merged into its own file and removed:applydrops its row"). The line about a file with no row ends at "none of your business."; its clause "an adopted project merged some of them away on purpose" gave the line room.NEXTline passes it:applyrefuses, and asks."merged-away(below)".checkcalls its rowmerged-awayandapplydrops the row. A file with no row is no shape copy; re-deriving the list would resurrect it."merged-awayverdict, whose rowapplydrops, added (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)", after adopt-project.pyplannever compares--project(or--org) with the repository it adopts in place, so a source namedbilling-serviceis refused with a remediation that steers to--project BillingService, which plans and checksplan okagainstacme/billing-service, andexecutethen acts onacme/BillingService: a rawgit ls-remotefailure when no such repository exists, and, when a different repository of that name does, both legs, the split branch, the pull request and the topic on that repository #499's clause, which is main's last.ae817c5fdb324ff60440b959a15f3ad3b2a18b43.--helptext changed, so docs/cli.md stands. Its--accept-localline describes the flag, not a NEXT line.templates/*/AGENTS-shape.mdis unchanged. It is a pinned copy, and nothing in it is false now. It does say "Never edit a file that has a row", next to a follow-up that says to removeshape/X. One sentence there would settle it, but that file is a copy every project pins.Shared files
#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402 (d41cc294, for shape-doctor.py: fifteen NEXT builders end the command with# <comment>, which a stock interactive zsh (macOS's default shell) does not read as a comment, so pasting the line hands the comment's words to the command as arguments, runs whatever follows a;, and runs themake bootstrapthelegsandmemberscomments put in backticks, then exits 127 #277), update-shape.py: a pinned copy,contracts/shape-pin.yamlor the manifest kept as a symlink, or under one, is refused asupdate-rewrite-symlinkbefore anything is written, andcheckcalls such a copycopy-is-link, soapplyno longer writes the standard's bytes at the link's end and reports a commit that does not hold them (#286) #347 (8cd7dbe5, for update-shape.pyapplyrewrites a pinned copy,contracts/shape-pin.yamlor the manifest THROUGH a symlink the root keeps at that path (Rollback.writeopens itr+b), so the standard's bytes land at the link's end, outside the root if that is where it points, with exit 0, and--branchreports a commit holding a file that commit does not contain #286) and shape-doctor.py: a conflicted shape pin's NEXT takes the pin alone and gives a copy conflicted beside it to a person to merge by hand, so pasting it no longer drops an edit kept with--accept-localand then says COMPLIANT (#485) #496 (fc488548, for shape-doctor.py: withcontracts/shape-pin.yamland a pinned copy both conflicted, the NEXT takes the taken side's copies whole, so pasting it drops a person's--accept-localedit to the copy and the doctor then says COMPLIANT #485) are on main, and ddb2df8 merged them in. Where each met this PR:#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402,check_shape_currency: its(command, why)return is kept, with update-shape.pycheck's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501's comment beside it and noacceptstring. The row's NEXT is thecheck, and itswhyis theapplywithout the flag.#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402,tests/test_shape_doctor.pyP10b: both PRs changed the same assertion line, and this PR had to, because that line asserted the flag update-shape.pycheck's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501 removes. It now asserts"--accept-local" not in currency["next"] + currency["why"].#is the row'swhy, on aWHYline of its own and"why"in--json, so a NEXT pasted into an interactive zsh -- which reads that#as a word -- no longer hands the comment to the command as arguments, runs what follows its;, or runs themake bootstrapin its backticks, and the template validator's line isenv PYTHONPATH=... python3 ...(#277) #402,tests/test_doctor_next_is_a_command.py: its casecheck_shape_currency-copies-movedasserted--accept-local <copy>in the row'swhy, which update-shape.pycheck's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501 removes. It asserts the flag's absence now. The file's guard against a#tail passes in full, 42 items: this PR'spinsrow NEXT is a single command.contracts/shape-pin.yamlor the manifest kept as a symlink, or under one, is refused asupdate-rewrite-symlinkbefore anything is written, andcheckcalls such a copycopy-is-link, soapplyno longer writes the standard's bytes at the link's end and reports a commit that does not hold them (#286) #347 refuses a pin, a manifest or a pinned copy kept as a symlink, or under one, asupdate-rewrite-symlink, andcheckcalls such a copycopy-is-link. The merge exposed the interplay 6943280 fixes, in thepinsrow above. A merged-away row is not a pathapplywrites through, so_refuse_writes_through_a_linkleaves it out.--accept-localand then says COMPLIANT (#485) #496's helperre_pinned_from_the_mergeread--accept-local <copy>out of theshape currencyrow'sapplywhen the update side's pin is taken, so two of its cases (test_a_copy_conflicted_with_the_shape_pin_is_left_to_a_person[the-update-side-ours]andtest_a_makefile_conflicted_with_the_shape_pin_keeps_the_projects_target[the-update-side-ours]) failed on the merged tree withValueError: '--accept-local' is not in list. After update-shape.pycheck's NEXT, and the doctor'sshape currencyrow that sends the reader to it, already carry--accept-local <path>for every locally edited shape copy, so pasting the command AGENTS.md says to run as named re-pins the project's edit as the standard: no refusal, no question to the human, a greenvalidate-pins.pyand a COMPLIANT doctor, though AGENTS.md says never to pass--accept-localand that keeping the edit is the human's decision #501 theapplyas spelled is refused,update-local-drifthere, naming--accept-local <copy>, and moves nothing, so the helper follows it to the refusal and then adds the person's flag. Its sentence in the unmerged restore'swhynow says theshape currencyrow never names the flag and that theapplyit names is refused, asking for it. The four parametrized cases pass.--reuse-empty-reporeuses a leg that is empty or whose one commit is the scaffold's own seed, and the ruleset hint offers that re-run, by an operator holding the bypass right, as its one exit, so a project a ruleset refused part-way is finished by one re-run, not refusedscaffold-remote-existsfor its own legs or sent to a pull request with no base (#462) #506 (for scaffold-project.py, family.py init: after a ruleset refuses the first push, neither exitRULESET_HINToffers can finish: the pull request has no base in the repositorygh repo createleft empty, and the scaffold's re-run is refusedscaffold-remote-existsfor its own two empty legs, so a ruleset-protected organisation cannot seed a new project;--reuse-empty-repois to reuse empty legs, and the hint is to offer only that re-run #462), family.py:initrefuses to land in the standard's checkout and names a runnablescripts/family.py, printed commands quote paths, andadd,bumpandremoverefuse the tracking branch and print their branch's push (#488, #486, #487) #507 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, which each conflicted with this branch at the handbook footer, have landed, and so have setup-project.py: the clone path<into>/<Project>is refused in step (3), before anything is created, when anything at all stands there, naming the path and telling a rehearsal's leftover clone apart as one to move or delete, and the plan names the clone path before the yes, so rehearsing and then running for real in the same folder - README's own order - no longer creates three repositories and then refuses with a hand clone that fails on the same directory (#498) #509, family Makefile:siblings,parkandresumerefuse by name whenscripts/siblings.pyis absent, and update-shape.py says the Makefile it copies calls it, so a holder updated without--addno longer fails oncan't open file(#489) #503, adopt-project.py:checknames, andexecuterefuses before the work directory is made, a plan whosesource:orlegs:is not a mapping or whoseallow_empty_legs:orpaths:is not a list of whatplanwrites, and asks the same first of apins:that is not a list, so a number or a boolean in any of the four no longer ends both in a traceback andallow_empty_legs: codeis no longer read as four legs (#386) #435 and scaffold-project.py: a leg path the project's own YAML reader would read back as another type (null,~,true,False,2026,1.5) is refused before anything is created, under--dry-runalike, through the one definition adopt-project.py asks, so--spec-path 2026no longer creates three repositories whose first bootstrap failsmanifest-leg-path, andadopt-project.py executeno longer saysadoption verifiedbefore that bootstrap fails (#225) #266, which merged cleanly. The footer is where the landings met this PR: c29b9b4's one hand resolution keeps main's clauses through adopt-project.pyplannever compares--project(or--org) with the repository it adopts in place, so a source namedbilling-serviceis refused with a remediation that steers to--project BillingService, which plans and checksplan okagainstacme/billing-service, andexecutethen acts onacme/BillingService: a rawgit ls-remotefailure when no such repository exists, and, when a different repository of that name does, both legs, the split branch, the pull request and the topic on that repository #499's, adds this PR's(#502)clause after them, and setsREADME.md blobto the merged README's..gitattributesgets one marked block of/<copy> text eol=lflines 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, andupdate-shape.py apply --addextends it (#471) #511 (open; tested at91ed4a57) conflicts with this branch indocs/handbook.html, and only at the footer, as it does with maincf118ff7: the fix log line and the README blob line, which every docs PR rewrites. Whichever lands second takes a footer round.REFUSEDline alone, the assembly root'svalidate-pins.pystill adds the lockstep remedy to its own, andaddrefuses a.gitmodulesgit cannot rea (#370) #494, 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 and docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472) #473 (a draft) merge cleanly with this branch (git merge-treeat1d9e8b2a,64b2cc05and67360ccd). 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 changesshape-doctor.pyand 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) #504scripts/shape_materialize.py, both in other places.Left as they were (for the v1.0.0 checklist)
MERGE_BY_HANDstill tellsupstream-removedandunmappedrows to re-run with--accept-local, which_refuse_conflictshonours onbothalone. This is the same defect 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 fixes forcopy-missing, but neither ruling covered those two states.shape/, the doctor'sshape currencyNEXT still namescheck, whoseapplyrefuses it.update-conflictnow names the remedy.pinsrow askslinked_recordabout the pin and the manifest alone, and the gate aboutshape-copy-missing. A pinned copy kept as a link beside merged-away rows (and, by the same reading, anupstream-removedorunmappedrow beside them) is green tovalidate-pins.py, so the row still namescheckand says theapplyit prints drops their rows.checkprintsREFUSAL AHEADfor the other row, and thatapplyis refused. Nothing is written. Keeping the gate whilecheck's conflicts are non-empty would settle it.shape currencyrow's counts namemerged-awayand the five original states alone, so acopy-is-link,copy-missing,upstream-removed,unmappedoralready-at-targetrow is still a path under no count, as it was on main. That is outside 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.Closes #501
Closes #502
🤖 Generated with Claude Code