Repository navigation
README.md, the handbook, AGENTS.md and the family-root docs: init's --into sentence names the family-landing-in-standard refusal, make siblings is described as it stops before cloning, and members/ has its one-spelling rule (#695) - #738
Conversation
… `--into` sentence names the `family-landing-in-standard` refusal, `make siblings` is described as it stops before cloning, and `members/` has its one-spelling rule, so three sentences that fell behind #488, #578 and #574 are true of main (#695) README.md and docs/handbook.html § Families: - The `--into` sentence says the default (the directory you are standing in) is refused inside a checkout of this standard, and names the code. - The `make siblings` paragraph says what it does since #578: the holder's parent folder must be named EXACTLY after its repository, case included, on every disk; where it is not, and no link of that name beside it leads to it, it WARNS, prints the exact `mv` and clones nothing until it is run, so the next run clones each member once. Lane 3's two #460 sentences (L20, L21) ride in it: a holder at `~/projects/InkRouter` is moved to `~/InkRouter/InkRouter`, beside that folder, not inside it. The one-spelling rule, one sentence in the same words in AGENTS.md (family item 2), README.md § Families, the handbook, the holder's README and the holder's pinned AGENTS-shape.md: a holder keeps `members/` in ONE spelling on every disk, and `add` refuses `family-members-dir-variant` (`member-path-taken` for a variant of the member's own path) until a human renames what sits under another spelling. Neither cap is raised. README.md stays at its cap of 1459 lines and AGENTS.md at 492, by cutting wording that is stated elsewhere: README § Families lost the "same rule projects have" aside, the tool's rationale after "Nothing ever moves a checkout" (the handbook, the holder's README and the tool's own output keep it), "not merely a repository at the right commit", the private-legs reason in the `make bootstrap` paragraph and the policy history in the naming paragraph; AGENTS.md lost the converse in the family intro, the `bump --trailer` sentence (stated in "Advancing a leg" and in AGENTS-shape.md) and "on your own initiative" in "Updating" step 2. The handbook marker for README § Families is recomputed. templates/family-root/AGENTS-shape.md is pinned: every holder re-pins it (`upstream-changed`). tests/test_family_docs_one_spelling.py holds the sentences to the five places and the codes to family.py. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-5, 2026-10-16T17:35Z) @codex review |
Reviewer's GuideThis documentation-focused PR synchronizes family guidance with current File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Codex Review: Didn't find any major issues. What shall we delve into next? Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
…e makes "the family folder `~/InkRouter`, beside `~/projects`, and the holder `~/InkRouter/InkRouter`, not `~/projects/InkRouter/InkRouter`", so "beside that folder" no longer reads as the holder itself (#695) Review nit N1 on 73fa3ff. What sits beside `~/projects` is the family folder `~/InkRouter`; the holder is one level inside it. The sentence now says so in README § Families and the handbook, and STRAIGHT_IN_A_FOLDER in tests/test_family_docs_one_spelling.py holds the new words. The README stays under its cap (1458 of 1459): the one added line is paid for by cutting the caption "Eight services, each its own project, one holder:" under "The InkRouter example", which repeats the section's intro and its block. The handbook marker for README § Families is recomputed. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…es every reason THIS checkout cannot answer, and the notes are current to main bf1f099 with the 25 pull requests since de92614 (#563, #683) - W683 and RV-f's N4 on #742: README's exit-3 list (Keeping a project's shape current, :1208-1220) and its handbook twin (`doctor`) named only a pin whose commit this checkout does not carry. They now say, as shape-doctor.py's EXIT CODES does: a pin's commit it lacks, a partial clone missing what is compared (#683), a read that fails (`update-upstream-unreadable`), a crashed validator of its own (#278, #410), or its own naming policy not loading (#678). +1 line, with tests/readme-cap.d/683.txt; README.md is 1474, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 1). The two `Keeping a project's shape current` markers move: `doctor` text and marker, `current` marker only. - The notes, "What changed since v1.0.1", one entry per pull request from `git log --first-parent de92614..bf1f099`, each from its squash commit and the issues GitHub says it closes: - adopt-project.py: #699, #719, #749, #727, #717, #728 (with #621's hint), #743 + #720 + #718, #722, #721; - update-shape.py, bump-leg.py and a project's own scripts: #702 (with the pinned validators), #634; - scaffold, setup, family and the shared readers: #725, #671, #744; - shape-doctor.py: #742, #701, #737, #751, #736, #734, #700 + #705, #740; - install, CI and the docs: the exit-3 sentence above. - Counts (99 pull requests since bfed143; 109 of 135 milestone issues closed at 10:28Z), the "current to main" sha and the open list (26) are at main bf1f099, still placeholders for tag time. Left for round B: lane 5's eight lines (#630 issuecomment-6227129879), whose pull requests #726, #747, #682, #724, #746, #745 and #738 are open drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ck out fails the family bootstrap, every row's gitlink is asked before any checkout, and `--no-members` prints the case-variant move (#648) (#682) **Head f74f640.** This branch holds #639 (#611), which landed as `c6122e8`, and main 2fac979, merged in bad6c00. Its diff against main is this PR's own change. `git merge-tree --write-tree f74f640 origin/main` is clean at 2a33a56. **README PR.** It changes one sentence of README.md § Families, which this change makes false, and its twin in docs/handbook.html. The handbook's § Families marker is the segment's new blob, b3d19092. README stays at 1459 lines, its cap, and `tests/readme-cap.d/` is not raised. It lands in lane 3's README turn. **Pinned copies.** `templates/family-root/scripts/bootstrap.py` and `templates/family-root/scripts/validate-family.py` change. Each holder sees them as `upstream-changed` on its next `update-shape.py check`. No test pins either file's bytes or digest; the tests that copy them into a holder take them from the template. Rounds 2 and 3 change `bootstrap.py` alone. By Brett's 13:36:23Z word (R0) the family root's pinned copy is re-pinned once, in v1.1.0's shape bump, so this PR re-pins nothing. One commit, f74f640, on 23e1105, in the same two files. ### MM1. FIXED: a `.gitmodules` a merge left unmerged In a plain clone, a merge conflicts in `.gitmodules`, and the file is resolved in the work tree but not added. The index then holds it only at its three conflict stages (`git ls-files -u` lists 3). Git reads no `.gitmodules` at all while it is unmerged: `repo_read_gitmodules` asks `is_gitmodules_unmerged` before it reads any file. So git's update dies at the first gitlink, `No url found for submodule path 'members/Atlas'`, exit 128. On 23e1105, bootstrap read the work tree's file, found every section with its url, and ended `family bootstrap ok`, exit 0 (rv648's `p10-head.log`). `gitmodules_sections` now asks `git ls-files -u -z -- .gitmodules` first. Where the index holds the file at a conflict stage, it returns nothing, with `Gitmodules.unmerged` set. `unfetchable` asks that before every other reason, since no credential helps while git reads no section: ``` NOT CHECKED OUT, and not for want of a credential: members/Atlas (git reads no .gitmodules while a merge leaves it unmerged), members/Nova (git reads no .gitmodules while a merge leaves it unmerged). Resolve the merge in .gitmodules and `git add .gitmodules`, then run this again. ``` With the file added, the same run checks both members out. That is all the cure needs; committing the merge is the person's own step. **Test.** `test_a_gitmodules_a_merge_left_unmerged_says_to_add_it` builds MM1. The two sides each give OTHER's section another `branch`: `dev`, pushed from the holder, and `main`, committed in the clone. The merge exits 1, `ls-files -u` lists 3, and the work tree takes stage 2. The run exits 1, with the last two stderr lines exact and git's `No url found ... 'members/IRRS'`. Then `git add .gitmodules`, and the run is a good holder's. Against 23e1105's `bootstrap.py` the test fails on `exit 0`. **Mutants.** Two more, both killed: | mutant | result | tests that fail | |---|---|---| | U: the unmerged check dropped, so the work tree's file is read as before | 1 failed, 33 passed | MM1 | | U2: the file read as unmerged, but the reason not given, so the rows fall to the next one | 1 failed, 33 passed | MM1 | Round 2's eight were re-run on f74f640's two files. All are still killed, by the same tests. **Results on f74f640.** - rv648's lean set: `483 passed in 313.92s (0:05:13)`. That is 482 at 23e1105, plus MM1. - The four files that load the family `bootstrap.py` (`tests/test_update_shape_kept_helper.py tests/test_windows_paths.py tests/test_pinned_copy_text.py tests/test_cli_reference.py`): `259 passed in 41.20s`. - rv648's `p10.sh`: A1T and H2T as on 23e1105, each typed through, with the gate passing in all three modes. MM1 now exits 1 with the line above. **A sentence of this body was false.** "Not changed" said "A fresh clone, where bootstrap runs, has the work tree's file and no merge". MM1 is a fresh clone with a merge. That bullet now says what is left. **Candidate 6** (what git dies on while reading `.gitmodules`) is filed as a v1.1.1 issue (#768), with the approximations below. One commit, 23e1105, on 45c512e. It changes `templates/family-root/scripts/bootstrap.py` and `tests/test_family_bootstrap_gate_order.py` only. README, the handbook and `validate-family.py` are as they were. ### M1. FIXED: `.gitmodules` is read as git's own parser reads it `init_stop` read `.gitmodules` with `git config --file --list`. That keeps entries git's submodule parser drops or overrides. So in three states git's init died and bootstrap still said ok: | state (plain clone) | git's update | 45c512e | 23e1105 | |---|---|---|---| | D1: a second section `nova2` gives `members/Nova` with no url, after Nova's own | 128, `No url found ... 'members/Nova'` | `family bootstrap ok`, exit 0 | exit 1, both rows named, `--no-members` | | Q1: Nova's section is named `../Nova` | 128, `ignoring suspicious submodule name`, then `No url found` | ok, exit 0 | exit 1, both rows named, section written back | | O1: Nova's url is `-nowhere` | 128, `ignoring ... may be interpreted as a command-line option`, then `No url found` | ok, exit 0 | exit 1, both rows named, section written back | **The parser rules, as built.** `gitmodules_sections` now returns a `Gitmodules`. Its `read` (`{path: name}`) and `urls` (`{name: url}`) follow git 2.43's `submodule-config.c`, entry by entry, in file order: - A section whose name is empty, or has a `..` component (split on `/` and on a backslash), is ignored whole. This is `check_submodule_name`. - A `path` or `url` value that begins with `-` is dropped, and the value before it stands. This is `looks_like_command_line_option`. - Where two sections give one path, the later in the file decides (`cache_put_path` replaces the entry). A section that gives a path again lets go of the path it gave before, even where a later section gave that path too, and then git reads no section there (`cache_remove_path`). Its `given` counts every section that gives each path, as `validate-family.py` reads them. That count is what `member-gitmodules-absent` and `gitmodules-path-twice` see, so the line's cures stay true. `has_url` asks the section git reads for a path, then the git config under that section's name. Where git reads no section for the path, there is no url, whatever the config holds: git's init dies on `!sub` before it looks at the config. **Checked against git 2.43's own source** (`submodule-config.c`, `submodule.c`, `builtin/submodule--helper.c` and `config.c` at v2.43.0) **and measured** with git 2.43.0 in plain clones (`p8.log`): | `.gitmodules` form | git 2.43's update | |---|---| | name `""`, `nova\..\x`, `a/../Nova` | ignored; `No url found`, 128 | | name `..Nova..` (dots that are no `..` component) | read; exit 0 | | `path = -members/Nova` | dropped; `No url found`, 128 | | a good url, then `url = -x` | the `-x` dropped, the good url kept; exit 0 | | a second section with Nova's url first, Nova's own with no url after | the later read; `No url found`, 128 | | `nova1` gives the path, `nova2` gives it too, then `nova1` gives another path | `members/Nova` maps to nothing; `No url found`, 128 | | `[submodule.Nova]` (the old header) | read, under the name `nova`; exit 0 | **What else git does with `.gitmodules`.** It drops nothing else. But it DIES reading the file, exit 128 before it checks out any member, on these, which `git config --list` reads without complaint: - a `path`, `url`, `update` or `ignore` key with no value (`path` alone): `bad config line N`; - `update = !command`, or any `update` value it does not know: `invalid value for 'submodule.<name>.update'`; - a `shallow` value that is no boolean: `bad boolean config value`; - a `fetchRecurseSubmodules` value it does not know: `bad submodule.<name>.fetchrecursesubmodules argument`. `init_stop` does not model these, and bootstrap still says ok over them where every row's section has a url (`ignore = bogus` is only a warning). No tool here writes any of them. This is a checklist candidate below, not part of this round. **D1's cure.** Writing the section back would add a third section. So where more than one section gives the path git has no url for, the line names `validate-family.py --no-members`, whose `gitmodules-path-twice` names both sections and says to take the others out: ``` NOT CHECKED OUT, and not for want of a credential: members/Atlas (git stopped at members/Nova, a gitlink it has no url for), members/Nova (git has no url for that path). `python3 scripts/validate-family.py --no-members` names each path more than one section of .gitmodules gives, and how to take the others out. ``` With the duplicate taken out and committed, the same run checks both members out. **The stop's sentence** was "a gitlink no section of .gitmodules gives a url". That is false in D1, where Nova's own section does give one. It now says "a gitlink it has no url for", which is true in every state here. `test_doctor_legs_members_next.py`'s `git stopped at members/Ghost` still matches. ### S1. FIXED: A1, a member git has no url for, is its own reason In a subset clone (`--recurse-submodules=members/Atlas`) where Nova's section has no url, git exits 0. On 45c512e the line printed the exit-0 skip's `git submodule update --init --checkout --recursive -- members/Nova`, which exits 128 (`No url found`) when typed. "git has no url for that path" (`has_url`: the section git reads for it gives none, and the config has none under its name) is now its own reason. It is asked after "no section gives that path" and before "git exited 0", and its cure is the section written back. In A1: ``` NOT CHECKED OUT, and not for want of a credential: members/Nova (git has no url for that path). Write the section of .gitmodules for members/Nova back as `family.py add` writes one, with its path and url, and commit .gitmodules. ``` Written back and committed, the next run is the exit-0 skip, and its printed update, typed, checks Nova out. In U1, Q1 and O1 the member's own row now gives this reason too, and the other row gives "git stopped at members/Nova". The cure is said once. ### NIT 1. FIXED: a stop outside members/ (H2) A hand-mounted `vendor/Zed` stops git's init, and the line named `--no-members`, which reads only `members/` and says `family ok` there. Both cures were measured in H2's clone (`p9-head.log`): - `git rm --cached -- vendor/Zed` and a commit: bootstrap then checks both members out and ends ok, and the gate passes in all three modes. - A section with its path and url written for `vendor/Zed` and committed: bootstrap ends ok, but the gate refuses `gitmodules-path-unnamed` in all three modes. So the line now says: ``No row names vendor/Zed and no clone can check it out: `git rm --cached -- vendor/Zed` takes its gitlink out, then commit that.`` "Outside members/" is the validator's own test: the first path component, casefolded, is not `members`. ### NIT 2. FIXED: the `cures` docstring It now says that, members checked, the validator refuses `member-uninitialized` where every row's gitlink is there (a stray, a hand mount), and `member-gitlink-absent` with the move where a row's is only in another case (#363), which the validator sentence names. ### NIT 3. FIXED: this body This section; the parser rules in Decision 2 and in (a)'s reasons; and a new merge table, against main a27c371, #745 and #747. ### Tests (round 2) `tests/test_family_bootstrap_gate_order.py` has 33 tests now (23 before): - **U1, Q1 and O1, one parametrized test** (`no-url`, `dotdot-name`, `empty-name`, `backslash-name`, `option-url`). It replaces the U1 test. Each exits 1 with the last two stderr lines exact, git's `No url found` and its warning shown. Each is written back as `family.py add` wrote it and committed, and the same run ends ok. - **D1:** exits 1 with the `--no-members` sentence. `--no-members` names `gitmodules-path-twice` for both sections; the duplicate is taken out as that finding says and committed, and the run ends ok. - **What git reads past** (`duplicate-first`, which is D2; `dots-in-a-name`; `option-url-after-a-url`): with no credential for the members, each is still the credential's skip and ends ok; with the credential, each is a good holder's run. The no-credential run is what makes a member be asked at all, so these tests catch a rule read too widely. - **A1:** the write-it-back line, then the exit-0 skip's update line, typed as printed, then a good holder's run, stderr empty each time but for the two lines. - **H2:** the `git rm --cached` line, typed and committed; the run ends ok and the gate passes in every mode. A premise checks that `--no-members` says `family ok` there. - The `variant-plain`, stray, hand-mount and round-trip expectations take the new stop sentence. On 45c512e the new file gives `12 failed, 21 passed`. Q1 (three names), O1 and D1 fail on `exit 0`; A1 and H2 fail on the line; U1, `variant-plain`, the stray, the hand mount and the round trip fail on the reworded sentences. ### Mutation probes (round 2) Each was applied alone to a throwaway clone holding this commit's two files, and the test file was run against it. All eight are killed: | mutant | result | tests that fail | |---|---|---| | L: the first section for a path decides, not the later | 2 failed, 31 passed | D1; `duplicate-first` | | N: a section with a suspicious name is read | 3 failed, 30 passed | `dotdot-name`, `empty-name`, `backslash-name` | | N2: any `..` in a name, component or not, ignores the section | 1 failed, 32 passed | `dots-in-a-name` | | O: a value that begins with `-` is kept | 1 failed, 32 passed | `option-url` | | D: no `--no-members` sentence where two sections give the path | 1 failed, 32 passed | D1 | | A: "git has no url for that path" not asked | 7 failed, 26 passed | the five no-url cases, D1, A1 | | A2: that reason asked after "git exited 0" | 1 failed, 32 passed | A1 | | H: a stop outside members/ gets the `--no-members` sentence | 1 failed, 32 passed | H2 | N2 was not killed by a first version of the read-past tests, which ran with the credential: git then checks both members out and bootstrap asks nothing. They now run without it first. ### Probes (round 2) rv648's `p6.sh` and `p7.sh` and w648b's `p5.sh` were re-run on 23e1105, and this round's `p9.sh` was run on 45c512e and on 23e1105. All use local bare remotes and the `gh` stub, with git 2.43.0. "ok" is `family bootstrap ok`, exit 0. | state | git's update | 45c512e | 23e1105 | |---|---|---|---| | D1 | 128 | ok | exit 1, the `--no-members` cure; the duplicate taken out and committed, then ok | | D2 (the duplicate first) | 0, both checked out | ok | ok | | D3: the duplicate with Nova's url first, Nova's own with no url after | 128 | ok | exit 1, the `--no-members` cure | | Q1: names `../Nova`, `""`, `nova\..\x`, `a/../Nova` | 128 | ok | exit 1, section written back; then ok | | Q2: name `..Nova..` | 0 | ok | ok | | O1: url `-nowhere` | 128 | ok | exit 1, section written back; then ok | | O2: a good url, then `url = -x` | 0 | ok | ok | | O3: path `-members/Nova` | 128 | exit 1 | exit 1, the validator's cure, whose `member-gitmodules-absent` names it | | U1 | 128 | exit 1 | exit 1; written back, then ok | | A1 | 0 | exit 1, the update line, which exits 128 typed | exit 1, section written back; then the update line, exit 0 typed; then ok | | H2 | 128 | exit 1, `--no-members`, which says `family ok` | exit 1, `git rm --cached -- vendor/Zed`; typed and committed, ok, and the gate passes in all three modes | | G1, G3, S2 | 0 | ok | ok | | G2, X1, X2, I1 (no credential), W1 (a wrong url) | non-zero | the skip, ok | the skip, ok | | S1, H1, V1 | 128 | exit 1 | exit 1, the same rows and cures, with the stop's new sentence | | P1, N1 | 0 | exit 1, the update line, exit 0 typed, then ok | the same | Every line of `p5.sh` but S1's, H1's, U1's and V1's reworded sentences is the same on 45c512e and 23e1105, gate lines included. Round 2 rewords two of round 1's sentences: the stop's why is now "a gitlink it has no url for", and in U1 the member's own row gives "git has no url for that path". The examples below are round 1's. Three commits: bad6c00 merges main, 1a69505 answers the review, and 45c512e updates one doctor test that pinned the old `ok` (below). ### The merge with main (bad6c00) One file conflicted with #634 (#610): `templates/family-root/scripts/validate-family.py`, in two regions. This PR splits `_check_member` in two, and #634 made THE LOCKSTEP RULE a per-member value, `rule = _lockstep(root, project, str(row.get("id") or project))`, passed to the gitlink and tree checks. - `_check_member` passes `rule` to `_check_member_gitlink`, as #634 has it. - `Gitlinked` gains a sixth field, `rule`, which `_check_member` returns. - `_check_member_checkout` passes `asked.rule` to `_check_member_tree`. So each member's gitlink and digest findings end at the rule #634 gives them, naming that member. The closing count's `_lockstep(root)` is #634's and merged clean. This is rv648's resolution in `rv648-x634`, plus one line of `Gitlinked`'s docstring. It was built against #634's file, which is the same blob on main (519b71f). Mutant X1 below checks it. ### M1. FIXED: bootstrap no longer says `ok` where git died at another gitlink In a plain clone, `git submodule update --init` dies in its init step at the first gitlink, in index order, that no section of `.gitmodules` gives a url. It then checks out no member. `unfetchable` asked only each row's own gitlink and section, so every row passed as the credential skip. **Measured on 8b39cd1** (`p5-base.log`, rv648's states with local bare remotes and the `gh` stub): | state | git's update | 8b39cd1 | 1a69505 | |---|---|---|---| | S1: a stray `Members/Nova` beside `members/Nova`, plain clone | 128, `No url found ... 'Members/Nova'` | `family bootstrap ok`, exit 0 | exit 1, both rows named | | H1: `members/Zed` mounted by hand, no row, no section, plain clone | 128, `No url found ... 'members/Zed'` | ok, exit 0 | exit 1, both rows named | | U1: Nova's section with no url, plain clone | 128, `No url found ... 'members/Nova'` | ok, exit 0 | exit 1, both rows named | | V1: #363's state, plain clone | 128, at `Members/Nova` | exit 1, Nova alone | exit 1, Atlas and Nova | **rv648's prototype, measured before it was taken** (`p5-proto.log`). It fixed S1, H1 and U1, but failed three states that are a missing credential, where 8b39cd1 rightly says ok: | state | 8b39cd1 | prototype | 1a69505 | |---|---|---|---| | X1: the stray, recursive clone, no credential | ok | FAILED, "git stopped at Members/Nova" | ok | | X2: the hand mount, recursive clone, no credential | ok | FAILED, "git stopped at members/Zed" | ok | | I1: Nova's url registered by `git submodule init`, then gone from the work tree's section, no credential | ok | FAILED, "git stopped at members/Nova" | ok | The prototype also said "no section of .gitmodules gives that path" for U1's Nova, whose section gives the path and lacks only the url. **What changed.** `init_stop` asks the holder's records which gitlink git's init stops at, keeping two rules of git 2.43's init: - **`submodule.active`.** Where the git config sets it, as a recursive clone sets `.`, git asks only the gitlinks a section gives a path. A gitlink no section gives is never active, so it stops nothing there (X1, X2). - **The config's url.** A section with no url stops git only where the config has no url under the section's name, as an earlier init writes one (I1). `gitmodules_sections` replaces `gitmodules_paths` and reads each section's keys. Where git exited non-zero and init stops at a gitlink, each row not checked out is named `git stopped at <gitlink>, a gitlink no section of .gitmodules gives a url`, and the run fails. A row with its own reason (no gitlink, no section) keeps that reason. **What the line says cures it.** Each reason now has its own sentence: - **No gitlink, no section:** the sentence it had, `python3 scripts/validate-family.py` names the cause and the commit that cures it. - **Git stopped at a gitlink no row names (S1, H1, V1):** `python3 scripts/validate-family.py --no-members` names each gitlink in members/ that no row names, and the commit that cures it. - The plain validator would not help here: members checked, it refuses `member-uninitialized` for the first row (rv648's candidate 3, still open). - S1 and H1 are typed through: the finding's lines, then a commit. The hand mount's `family.py add` line is not typed, since it mounts the member again. The same run then checks both members out. - **Git stopped at a row's own gitlink (U1):** no mode of the validator names it. So the line says: Write the section of .gitmodules for members/Nova back as `family.py add` writes one, with its path and url, and commit .gitmodules. Written back and committed, the same run checks both members out. S1 now prints, for example: ``` NOT CHECKED OUT, and not for want of a credential: members/Atlas (git stopped at Members/Nova, a gitlink no section of .gitmodules gives a url), members/Nova (git stopped at Members/Nova, a gitlink no section of .gitmodules gives a url). `python3 scripts/validate-family.py --no-members` names each gitlink in members/ that no row names, and the commit that cures it. family bootstrap FAILED: Atlas (not checked out), Nova (not checked out) ``` **Unchanged** (`p5-head.log`): G1 and G3 (good holders, recursive and plain), G2 (plain, no credential), S2 (the stray in a recursive clone) and W1 (a wrong url, which looks like the missing credential). **Tests.** The `variant-plain` case and the round trip now name MEMBER too, with both sentences. One test each covers S1, H1 and U1, and two cover the misfires: X1 and X2 in one parametrized test, and I1. ### S1. FIXED: an exit-0 skip prints the update that checks the member out For "git exited 0 and checked nothing out there", the line now prints the update that checks the member out, in place of the `validate-family.py` sentence: ``` NOT CHECKED OUT, and not for want of a credential: members/Nova (git exited 0 and checked nothing out there). `git submodule update --init --checkout --recursive -- members/Nova` checks it out. ``` - **`--init` with the path** registers a member a subset clone left out. A pathspec lifts git's `submodule.active` filter, and init marks the member active. - **`--checkout`** overrides an `update = none`. Measured in P1 (a `--recurse-submodules=members/Atlas` clone) and N1 (`update = none` committed for Nova), on 8b39cd1 and on the head: - **8b39cd19:** the validator sentence, whose `member-uninitialized` remedy exits 0 and leaves Nova empty. - **1a69505d:** the printed line, typed as printed, exits 0 and checks Nova out (15 entries). Bootstrap then ends `every member and leg fetched`, ok, and the gate passes in every mode. `test_an_update_that_exits_0_and_skips_a_member_fails` is parametrized `update-none` and `subset`. It checks that the validator's remedy checks nothing out, types the printed line, and runs bootstrap again: exit 0, stdout exact, stderr empty. The behaviour change (a subset clone and an `update = none` member now fail `make bootstrap`) belongs in v1.1.0's release notes, lane 3's #630 (candidate 4). ### S2. FIXED: the credential test asserts no "every member fetched" `test_a_missing_credential_is_still_the_skip_it_was` asserts `" every member and leg fetched" not in result.stdout.splitlines()`. rv648's mutant R2 (`elif proc.returncode == 0:` made `else:`) now fails it (mutants below). ### NITs 1. **FIXED.** README: ``member's. `make bootstrap` reports and skips one it lacks a credential for.`` (75 characters, the line count unchanged). Handbook: `A member <code>make bootstrap</code> lacks a credential for is reported and skipped, not a failure.` § Families marker: c40baa82 → b3d19092, computed with `test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`. 2. **FIXED.** `gitmodules_paths` is now `gitmodules_sections`, whose docstring says "An empty mapping where there is no such file". 3. **FIXED.** Bootstrap's (a) docstring says "128 (`No url found for submodule path`) in a plain clone; a missing credential exits non-zero too". It also names the new reason and each cure. 4. **FIXED.** The validator's WHAT IS CHECKED says the move under `--no-members` is printed "where its row's path is its canonical `members/<Project>` and its pin a 40-hex commit". 5. **FIXED.** The merge table below replaces the stale one. ### (a) `bootstrap.py` no longer ends `ok` over members it could not check out After the fetch, bootstrap asks the holder's own records, never git's words, about each row whose member is not checked out. The first reason that holds is the row's: 1. A merge left `.gitmodules` unmerged in the index, so git reads none of it (`Gitmodules.unmerged`; MM1). This is asked first: it holds for every row, and no credential helps. 2. No gitlink is recorded at the row's path (`recorded_gitlink`, the reading `member-gitlink-absent` uses), as in #363's state. 3. No section of `.gitmodules` gives that path, as where HEAD lost the file. Every section counts here, as `validate-family.py` reads them. 4. Git has no url for that path: the section git reads for it gives none, and the git config has none under that section's name (`has_url`; U1, A1, D1, Q1, O1). No credential gives a url, whatever git exited. 5. Git exited 0, so it lacked nothing a credential gives. 6. Git exited non-zero, and its init stopped at a gitlink it has no url for (`init_stop`, M1 of both rounds). Reasons 4 and 6 read `.gitmodules` as git 2.43's own parser reads it, not as `git config --list` lists it (`gitmodules_sections`): a section whose name is empty or has a `..` component, split on `/` and on a backslash, is ignored; a `path` or `url` that begins with `-` is dropped, and the value before it stands; and where two sections give one path, the later in the file decides. Each such row is named on one line, with what cures each reason, and the run fails. "every member and leg fetched" is printed only where git exited 0 and every row is checked out. A row git could not fetch for another reason is still `NOT CHECKED OUT; skipped`, and the run still ends ok: that is the state a missing credential leaves. `test_family.py`'s `test_bootstrap_degrades_when_a_member_cannot_be_fetched` and bootstrap's docstring (c) say so. ### (b) The gate asks every row's gitlink before any row's checkout `_check_member` asked each row whole, gitlink then checkout, before it asked the next. In a plain clone of #363's state plus `git submodule update --init --recursive` (CI's and `make bootstrap`'s flow), the update dies at `Members/<P>` before it checks out any member. So where the variant's row was not first, the gate refused `member-uninitialized` for the first row, naming that same update. Now `_check_member` stops at the gitlink and returns the member (`Gitlinked`). `_check_member_checkout` asks each member's digest and identity once every row's gitlink has been asked. The refusal there is now #611's `member-gitlink-absent` with the move, whichever row it is, members checked and `--pins` alike. ### (c) `--no-members` prints the move members mode prints Under `--no-members`, the mode CI falls back to in (b)'s state, `member-gitlink-unnamed` for a member's only gitlink in the wrong case printed no command. Where the row's path is its canonical `members/<P>` and its pin is a 40-hex commit, it now prints the same text and two lines the row's members-mode refusal prints. One helper, `_wrong_case_move`, builds both. The commit is the row's pin, not the variant's gitlink, and every variant goes on one line. 1. **The holder's records decide, not git's exit or words.** Git exits 0 and 128 over the same fault (HEAD lost `.gitmodules`: 0 after a recursive clone, 128 in a plain one), and a missing credential exits non-zero too. `No url found` is git's own message, and git translates it. 2. **`init_stop` keeps git's rules, and no more.** rv648's prototype failed X1, X2 and I1, so the `submodule.active` filter and the config's url are kept (round 1's M1). Round 2 adds the three rules by which git's own parser reads `.gitmodules`, each checked against git 2.43's source and measured: the suspicious name, the value that begins with `-`, and the later section for a path (round 2's M1). It does not match `submodule.active`'s pathspecs: under a subset clone's value, a url-less section of a member outside the subset counts as a stop. That is reached only where git also fails for another reason (Not changed, below). 3. **The cure is per reason.** The validator's members mode refuses `member-uninitialized` in S1, H1 and U1, and nothing names U1, so one sentence cannot be true of every reason. Two sections for one path get `--no-members`, since a section written back would be a third (D1), and a gitlink outside members/ gets `git rm --cached`, the one cure measured to leave the gate passing (H2). Each sentence was typed through in its state (Tests below). 4. **The README sentence.** README is at its 1459-line cap, so the sentence fits its line. It cannot also name the new failures; bootstrap's docstring does. `tests/test_family_bootstrap_gate_order.py`, new, 34 tests. Round 1's 23 are listed here, and rounds 2's ten and 3's one in their own sections above: - **(a), four states:** HEAD lost `.gitmodules` and #363's state, each after a recursive clone and after a plain one. Each exits 1, stdout and the last two stderr lines are exact, and git's `No url found` shows in the plain ones. - **The good and the skipped:** a good holder's run, exact; the credential skip still ends ok, with no new line and no "every member fetched". - **Git exits 0 and skips a member:** `update = none` and a subset clone. The run fails on that member alone, and the printed update, typed, lets the run end ok. - **Git stops at another gitlink:** S1 and H1 (typed through `--no-members` and committed), U1 (the section written back), and the misfires X1 and X2 and I1, which are still the skip. - **A row written twice** is named once. - **The round trip:** a plain clone of #363's state with the variant at the SECOND row. Bootstrap fails, the gate refuses with the move, the lines are typed, and bootstrap then checks both members out, runs each one's own bootstrap and ends ok. The gate passes in every mode. - **(b):** a plain clone plus `git submodule update --init --recursive` (exit 128), with the variant in the first row and in the second. - **(c):** on a Linux disk and an emulated folding one, one variant and two, at a commit that is not the pin. A row whose pin is no commit still prints no command. Changed: `tests/test_validate_family_unnamed_mounts.py`, state (ii) under `--no-members`. It printed no command; it prints the move now. Results on 1a69505, Linux, `/usr/bin/python3 -m pytest` through `run9.sh` (`gh` stub first on PATH). 45c512e changes one test file, which was run again on it: - **Batch A**, the brief's named files: `tests/test_family_bootstrap_gate_order.py tests/test_validate_family_unnamed_mounts.py tests/test_family.py tests/test_pinned_copy_text.py tests/test_validate_family_branch_step.py tests/test_family_members_case_variants.py tests/test_family_head_lost_gitmodules.py tests/test_family_bump_to_local_only.py tests/test_repo_hygiene.py tests/test_readme_sections.py tests/test_template_text_placeholders.py tests/test_cli_reference.py`: `698 passed in 638.20s (0:10:38)`. - **Batch B**, every other test file that names `validate-family` or the family's `bootstrap.py` (49 files, `git grep -l`): `1 failed, 3797 passed, 32 skipped in 2483.22s (0:41:23)`. - The failure was `tests/test_doctor_legs_members_next.py::test_414_3_there_the_bootstrap_cures_nothing_and_the_git_line_cures_one`. It builds rv648's U1 state (a member, Ghost, whose section gives no url) and asserted bootstrap's exit 0: the false `ok` M1 ends. - 45c512e asserts exit 1 and `git stopped at members/Ghost`, and its point stands: the bootstrap cures nothing there, and the doctor's git line cures the other member. That file alone on 45c512e: `124 passed in 153.99s (0:02:33)`. - The skips are the Windows-only lines, as on main. - **Round 2, on 23e1105.** rv648's lean set, `tests/test_family_bootstrap_gate_order.py tests/test_validate_family_unnamed_mounts.py tests/test_family_head_lost_gitmodules.py tests/test_validate_family_branch_step.py tests/test_family_members_case_variants.py tests/test_doctor_legs_members_next.py tests/test_family_bump_to_local_only.py tests/test_repo_hygiene.py tests/test_readme_sections.py` and `test_family.py`'s three bootstrap tests: `482 passed in 263.30s (0:04:23)`. That is 472 at 45c512e plus round 2's ten. The files that load the family `bootstrap.py` by path or check its imports and text, `tests/test_update_shape_kept_helper.py tests/test_windows_paths.py tests/test_pinned_copy_text.py tests/test_cli_reference.py`: `259 passed in 30.69s`. Batch B was not re-run in round 2. - **Not run:** the 22 files that name only the assembly root's `bootstrap.py` (adopt, scaffold, `bump-leg.py` and setup tests), which this PR does not change. Main's six commits after 55c6ba7 were not run against this head either; `merge-tree` with them is clean. Round 2's eight are in its own section above. Each of round 1's mutants was applied alone to a throwaway shared clone at 1a69505 (exact text replacements), and its test file was run against it. Each one is killed: | mutant | result | tests that fail | |---|---|---| | M1a the "git stopped at" reason never given | 5 failed, 18 passed | variant-plain, S1, H1, U1, the round trip | | M1b `submodule.active` not asked | 2 failed, 21 passed | the recursive clones with no credential, stray and hand mount | | M1c the config's url not asked | 1 failed, 22 passed | the url an earlier init registered | | M1d a url-less section counts as having one | 1 failed, 22 passed | U1 | | M1e no `--no-members` sentence | 4 failed, 19 passed | variant-plain, S1, H1, the round trip | | M1f a row's own stop given the `--no-members` sentence | 1 failed, 22 passed | U1 | | S1a the validator sentence for an exit-0 skip | 2 failed, 21 passed | update-none, subset | | S1b no update line | 2 failed, 21 passed | update-none, subset | | S1c the update line without `--checkout` | 2 failed, 21 passed | update-none, subset | | S1d the update line without `--init` | 2 failed, 21 passed | update-none, subset | | R2 (rv648's) "every member and leg fetched" over a failed fetch | 1 failed, 22 passed | the credential skip | | X1 the digest finding's rule not the member's (the merge) | 1 failed, 22 passed | `test_validate_family_branch_step.py`'s `test_a_digest_s_rule_names_its_own_member` | | N1 the § Families marker left at c40baa82 | 1 failed, 76 passed | `test_repo_hygiene.py`'s `test_the_handbook_follows_the_readmes_outline` | S1c and S1d were also run with the test's expected line edited to match, so that only typing the printed line can catch them. Each is still killed, by the state that needs the flag: without `--checkout`, update-none fails (`1 failed, 1 passed`); without `--init`, subset fails (`1 failed, 1 passed`). | Merged with | Head | Result | |---|---|---| | origin/main | 2a33a56 | clean | | lane 5's fix/704-bootstrap-leg-moves (#745: #704, #694), restacked on 23e1105 | f4fa602 | clean | | lane 5's fix/681-validate-family-remedies (#747: #681), restacked on 23e1105 | 03e0b5a | clean | | lane 4's fix/690-siblings-inputs | 38a40ab | clean | | lane 5's fix/695-family-docs (#738) | c42286d | CONFLICT in `docs/handbook.html`, the § Families marker line only, as before. Rounds 2 and 3 change neither README nor the handbook. | | every other `fix/*` and `l3-*` branch on origin, 26 more | | clean | README is 1459 lines at f74f640 and in its merge with main. - **The validator's members mode in S1, H1 and U1.** It still refuses `member-uninitialized` for the first row, with a remedy that dies the same way, and no mode names U1. Bootstrap's line now names the cause in all three and the cure, so the person is no longer sent there. Candidate 3. - **What git dies on while reading `.gitmodules`.** Git 2.43 exits 128 before it checks out any member over a `path`, `url`, `update` or `ignore` key with no value, an `update` of `!command` or a value it does not know, a `shallow` that is no boolean, and a `fetchRecurseSubmodules` value it does not know (`p8.log`). `init_stop` does not model these, so where every row's section has a url, bootstrap still ends ok over them. Filed as a v1.1.1 issue (#768), which fixes the family validator in the same change. - **Where git reads `.gitmodules` from.** Git reads the work tree's file; where the work tree has none, the index's, then HEAD's; and none at all while a merge leaves it unmerged. Since round 3 the unmerged file is its own reason (MM1). The fallback to the index's or HEAD's file is still not followed: `gitmodules_sections` reads the work tree's alone. That is reached only where the work tree lacks the file while the index or HEAD holds it, as a deletion not yet committed or a sparse checkout that leaves it out leaves it. There every row git left not checked out is said to have no section, and the run fails, even where a credential was all git lacked. In the v1.1.1 issue. - **`submodule.<name>.active`.** Where `submodule.active` is set, git asks a member's own `active` key before the pathspec. `init_stop` asks neither, as round 1 did. In the v1.1.1 issue. - **`submodule.active` pathspecs.** `init_stop` takes the key's presence, not its patterns. Under a subset clone's `members/Atlas`, a url-less section of a member outside the subset counts as a stop, though git's init skips it. That is reached only where git's exit is non-zero for another reason, and then the line names that section, which is a real defect. In the v1.1.1 issue. - **macOS and Windows were not run** (CI is Linux only since #597). - The variant states are index records; the holder's own are asked again with `core.ignorecase=true`. - A clone of S1 gets `Members/Nova` and `members/Nova` as two empty mounts. On a disk that folds case, git checks them out as one directory and warns. No assertion reads either directory, and git's init dies at the stray on every disk. - Every file is written as bytes, and `url_edited` reads a CRLF checkout too. - The backslash-name case (`IRSS\..\x`): git splits names on both `/` and a backslash on every system (`is_xplatform_dir_sep`), so it is ignored on Windows too. 1. **A half-typed move** (rv648's 1). After typing the move and before committing, both modes print STRAY's `git rm --cached -- Members/Nova`, which fails `pathspec ... did not match` (exit 128). It is a v1.0.0 known limit. #681's branch says to commit a staged removal and prints no `git rm` there. 2. **The state a `git mv members/Nova Members/Nova` leaves** (rv648's 2). The move fixes only the gitlink, and two more rounds follow. 3. **Members mode in S1, H1 and U1** (rv648's 3). It refuses `member-uninitialized` for the first row, with a remedy that dies the same way. No mode names a section with no url. 4. **v1.1.0's release notes for #648**, in lane 3's #630. A subset clone, an `update = none` member, and S1, H1 and U1 now fail `make bootstrap`. The line prints the cure for each. 5. **`member-gitlink-unnamed` says "a recursive clone of this holder fetches it"** for a gitlink no section of `.gitmodules` gives. A recursive clone fetches nothing there, as S2 and X1 show for the stray. 6. **Bootstrap still says ok where git dies reading `.gitmodules`.** Filed as a v1.1.1 issue (#768). Review: rv648, lane 5's independent review, found 1 MUST, 2 SHOULD and 5 NIT on 8b39cd1, then 1 MUST, 1 SHOULD and 3 NIT on 45c512e, then 1 MUST on 23e1105; each fix round above answers one, and its verdict at f74f640 is DONE. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 f74f640 passed tests, tests-git-bash and SonarCloud; the head 5afb24b is a clean update-branch merge of main bf1f099, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 9399806 over main 1eeef3b: the doctor_legs_members_next, family_bootstrap_gate_order, validate_family_unnamed_mounts, readme_sections, readme_windows_quoting_sentence, repo_hygiene, agents_md_codes, cli_reference sets, 426 passed. Landed on Brett Heap's word of 2026-10-17T10:59:39Z, relayed by lane 3 on #473: "merge lane 5's approved v1.1.0 drafts". Closes #648 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y: the shape doc names the layout's folder after this holder's repository and the rule's subject as "This holder's parent folder", and the hand-edit passages, the folder rule, `member-gitlink-absent`'s raised refusal and `RESTORE_ROW`'s inline copy are read whole, so a one-word edit that puts the contradiction back fails a test (#763) Fix round 1 of #822, on rv763's review (NOT READY: 14 of 19 one-line mutants survived the first tests, two of them putting the issue's contradiction back). The sentences were true; the tests looked for words, not meaning. templates/family-root/AGENTS-shape.md, words only, in the pinned copy that already costs every holder one `upstream-changed` row: - the layout paragraph said "a plain folder named after the family". For `--family Quill --name QuillWorks` that reads as the display name. It now says "a plain folder named after this holder's repository", which the holder's README says is what `{{FAMILY}}` is. - the folder rule said "The parent folder must be named EXACTLY after its repository", where the folder is no repository and "its" has nothing to refer to. It now opens "This holder's parent folder must be named EXACTLY after its repository", as the standard's README says "The holder's parent folder". tests/test_family_docs_one_spelling.py: - each doc's hand-edit passage, from "Never hand-edit" or "Do not hand-edit" through "the commit is then the person's.", is held whole (`PASSAGES`), as `ONE_SPELLING` is. The old tests looked for loose words between the two ends, and "but for a row repair" turned into "not even for a row repair" passed them. - `FOLDER_RULE`, "parent folder must be named EXACTLY after its repository, case included, on every disk", is read in the standard's README, the handbook, the holder's README and AGENTS-shape.md, so a "need not be" or a "should be" fails; the shape doc's layout wording is held. - `member-gitlink-absent` is raised through `_check_member_gitlink` and its code and its remedy are read together, where the old test read a helper and asked whether the code appeared anywhere in the file. That drops the second copy of #738's set of string constants. - the inline copy of `RESTORE_ROW`'s words in `gitmodules-path-unnamed` is read against the constant, so the two cannot say different things. Lands after #738 and #747 (v1.1.0). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…its owner, "holder's parent folder must be named EXACTLY after its repository, case included, on every disk", in the standard's README, the handbook and both holder docs, so a rule given to "A member's" or to "The parent folder" fails (#763) Fix round 2 of #822, on rv763's NIT 9. `FOLDER_RULE` began at "parent folder must be", so a doc that gave the rule to another subject, "A member's parent folder" or "The parent folder" (which, in a doc about a plain folder that is no repository, leaves "its repository" nothing to refer to), still passed. It now begins at "holder's parent folder must be". All four places carry that text: "The holder's" in README.md, "holder’s" in the handbook (which `shown()` reads as "holder's") and "This holder's" in the holder's README and AGENTS-shape.md. Words in the tests only; no doc, tool or remedy changes. Lands after #738 and #747 (v1.1.0). Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…README § Families marker takes the merged section's fingerprint, so #738 no longer conflicts with #682 on that one line (#695) #682 (dc7d3fb) and this branch each changed README.md § Families, so each set a different `reads README.md § Families` marker on the handbook's `families` section: main's `b3d19092...`, this branch's `0364c579...`. Git saw one line changed two ways in docs/handbook.html and stopped. The merged README § Families is neither text, so the marker takes the `readme_fingerprint` of the merged section, `82d7f808...`, computed with `tests/test_repo_hygiene.py`'s own function over the merged README.md. Lane 2's note on #738 gave the same value. Nothing else was resolved by hand. README.md, AGENTS.md and the rest of docs/handbook.html merged clean, and carry exactly each side's own changes. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…s and #817's entries, #642's upgrade note for a holder's `cd` line, and v1.0.0's `--into` limit kept open, current to main 64ec47c (#563) RV630's pre-review of the B2 stage (lane 3's session): - SHOULD-1: the CI bullet said every test that clones a just-pushed local remote clones it safely (#554), but five plain-path `git submodule update --init` runs are left on main (tests/test_doctor_legs_members_next.py:631, :987, :1054; tests/test_family_bump_remove_rollback.py:337, :908). It now says "runs `git clone` on", which is what #554 changed. - NIT-1: #812's READMEs quote a local clone URL "that needs it". - NIT-2: #682's first sentence no longer repeats the lead ("there:"). Landed since 40e678e, each entry checked against its squash: - #747 (#681), 6626a3c, whose tree equals `merge-tree 40e678e ff7c59b`: a new family bullet, with RV630's NIT ("`member-gitlink-absent`'s remedy puts the gitlink back by hand"); v1.0.0's limits at v1.0.0.md:466 and :467 are said closed. - #817 (#708), 64ec47c, whose tree equals `merge-tree 6626a3c d63a267`, and whose own lines equal those of 8fd2fa6, the head RV630 read: RV630's text, appended to the YAML reader bullet. #738 (#695) is v1.1.1 (lane 5, #473 issuecomment-6236564004), so its docs entry is not taken, and v1.0.0's `--into` limit (v1.0.0.md:492) is listed as still open under Known limits. Its upgrade note is true of main since #642 (0ed1b84), so it goes in now, under "Install and upgrade": lane 5's sentence verbatim, and the README's `Y/Y` layout. Counts and the open list at main 64ec47c: 113 pull requests since bfed143; 124 of 135 milestone issues closed at 16:42Z; 11 open, each with a pull request open, #681 and #708 out, #695 marked as planned for v1.1.1. All still placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#813 and #723, v1.0.0's quoted-header and plain-path-clone limits closed and the counts current to main bad05d2 (#563) Each entry is the one RV630 corrected in its pre-review of the B2 stage, put at its anchor, and checked against its squash on main: the `git merge-tree --write-tree` of main before the squash with the head the entry was written from equals the squash's tree for all seven. - #810 (#674, `d17564d9`): a new bullet, "`apply`'s lines, its branch and its headers.", after "Headers, branches and messages.". It closes v1.0.0's quoted `files:`/`shape:` header (v1.0.0.md:439) and `apply`'s half of the paths listed as staged (:433); `bump-leg.py`'s half waits for #746. - #731 (#692, `d66a9054`): appended to "Folders, asked before they are made.". - #813 (#689, `f6d17bc5`): appended to "`--tracking-branch`.". - #809 (#688, `42aed788`): a new bullet, "What `family.py init` records and commits as.", after "Names that are not UTF-8.". - #816 (part of #684, `fa04d2cd`): a new bullet, "More holder states, named.", after "family.py refuses before it writes.". #816 closes no issue; #684 keeps its reftable case and is on v1.1.1. - #748 (#662, `431c8cf8`), then #723 (#663, `bad05d21`): in the CI bullet, after #713's sentence. With #554 and #578, #748 closes v1.0.0's limit on fixtures that clone a just-pushed remote by plain path (:530). The counts, in the header comment, the counts paragraph, "What changed" and the open list: main `bad05d2`; 120 pull requests since `bfed143`; at 18:02Z, 3 of the `v1.1.0` milestone's 133 issues open and 130 closed. The open list is #563 (#630), #672 (#746) and #686 (#795); #684 and #695 (#738) are on v1.1.1. #746 and #795 have not landed, so neither has an entry yet. Nothing since 2000488 touches pyproject.toml, src/openreposhape/, install.sh or .github/workflows/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranch changed different README sections, so five handbook markers take main's blobs, the Families marker keeps this branch's, and no line conflicts (#695) #630 (190133d) changed README.md in five sections and moved each one's `reads README.md §` marker in docs/handbook.html. This branch changes only README.md § Families (marker 82d7f808..., set by 75e35f1). Main's own § Families is still b3d19092..., what it was before this branch. No marker line changed on both sides, so git merged README.md, AGENTS.md and docs/handbook.html without a conflict, and nothing was resolved by hand. Each marker below is `readme_fingerprint` of its merged section, computed with tests/test_repo_hygiene.py's own functions over the merged README.md (`readme_segments`, then `readme_fingerprint`), never typed. Old is this branch's 75e35f1, new is this merge: - Starting a project in a new organisation (seven markers) old dd174d763a931052478f2fd15a7f22535524f6f4 new 45a3f886c0203e91f17a7851cf2c1c71a2681b0c - The three legs (three markers) old cce66b56eec9dffdd5a1a23bdd8e58d57f90b12e new 1d9f5bf98bddd1f70522c86515de3f15900fe141 - Adopting an existing repository (one marker) old ca4b6731cf6de1caeac426eeff5eb7184c53f562 new b23e9f9819cedc3e6ca6321c811b6d11da8de508 - Keeping a project's shape current (two markers) old 545639ebf5e9ab7837df5c012f216832dc7de56f new df780eb5a96cdcaf9b30a8ac8162bca00f90405b - Layout of this repository (one marker) old 94701efcccdb2a17e8aa80fbb3343c381ea71e4a new 9446cd710bf3a992751e12e318553b9b22914c21 Unchanged, and still equal to their sections: preamble ddadd23a..., Families: a holder for projects that ship separately 82d7f808..., The double pin, and the lockstep invariant 57fffa08..., Bootstrap is COPIED into the project, not fetched 41f2c883..., Carrying in-flight work to another workstation b84512ad..., The degrade rule e28bf2cd... and Licence 57e2469b.... The tree of this merge differs from origin/main by exactly #738's own change: `git diff origin/main <this commit>` and `git diff 24a977c 75e35f1` have the same patch-id (04fc641d...). #630 brings no new README heading (still twelve segments), so tests/test_family_docs_one_spelling.py and the docs edits need no new coverage. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 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 39 minutes by commenting |
e62b5e1 #738's final head merges main 2e476a7 into its reviewed change (the branch here held its older head c42286d). Between c42286d and 0b26ace #738 has only two merges of main (75e35f1 and 0b26ace), whose handbook markers it refreshed. This merge had no conflict: lane 2's sentences in AGENTS.md and the family-root README and AGENTS-shape compose with it as they are. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…above) #738 squash-landed on main as e62b5e1. Its final head 0b26ace is merged at 9b38816, and `git diff 0b26ace e62b5e1` is empty over each of #738's six files. The merge base here is main 2e476a7, older than 0b26ace, so three of #738's files conflicted: AGENTS.md (one hunk), templates/family-root/AGENTS-shape.md (one hunk) and tests/test_family_docs_one_spelling.py (five hunks, add/add). Each takes ours: no main commit between 2e476a7 and e62b5e1 other than the squash touches them, so ours already holds all of theirs. This merge changes none of #738's files; it brings main's commits 2c6543e to 112eeed. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both parents have landed (#747 as 6626a3c, #738 as e62b5e1, each merged above). This brings main up to 7b21059 (#842, #814 and #786's validate.yml change, whose one sentence in templates/family-root/README.md sits apart from this PR's). No conflict. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… that follow the host are quoted for the shell they are printed in, PowerShell or Git Bash, and names `init`'s `cd` line among `family.py`'s (#711) #711 quotes `shape-doctor.py`'s lines, setup's family lines and `family.py`'s NEXT, switch -c, add, cd and git rm -r --cached lines for the shell they are printed in: `sh`'s under Git Bash (`MSYSTEM` set), PowerShell's in PowerShell. README's Windows sentence and the handbook's Windows panel still said PowerShell's. The wording waited in docs-wording.md while #630 owned both files. #630 has landed (190133d) and is merged here with main b9b7069, and the plan is recomputed on it: the old text is byte-identical at README.md:158-161 and docs/handbook.html:737-741. - README.md: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash" is kept word for word. The exceptions name `init`'s `cd` line too, and are "quoted for the shell they are printed in, PowerShell or Git Bash, on Windows." One line more. - docs/handbook.html: the same words in `panel-windows`, and the limit `host_shell_dialect`'s docstring names, which README leaves out: a shell started from inside Git Bash inherits `MSYSTEM`, so a PowerShell started there is handed Git Bash's quoting, and cmd.exe is handed PowerShell's. - The seven markers of README's § Starting a project in a new organisation move together, 45a3f886 -> 4c523ffe. The page's other sections that read it (masthead, partway, ci, legs, doublepin, park) were re-read: none says which shell a printed line is quoted for. - tests/readme-cap.d/711.txt: +1, the line this adds. README goes 1474 -> 1475 and the cap 1475 -> 1476 (1422 + 2 + 5 + 12 + 22 + 10 + 1 + 1 + 1), one over README as main has it since #738 cut a line. tests/test_readme_windows_quoting_sentence.py's SENTENCE matches the new sentence once, and the handbook's panel holds it once, so the test does not change. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…trap_after_bump.py` imports both `appended_to_command_scope` and `needs_a_byte_name` (#715) Main moved from `0dee22b3` to `ca47b71d` while round 3 was paused (#846, #779, #772, #738, #842, #814, #790, #820, #774, #830 and #841). `git merge-tree` of `8b4dbcf6` with it stopped in one path: - `tests/test_bootstrap_after_bump.py`, its imports. Main's `aec3b38f` (tests append what they tell git to the command scope) added `appended_to_command_scope` to the `from conftest import (...)` list. This branch added `from test_bootstrap_spelling import needs_a_byte_name` below it (#715, round 1). Both are kept. `scaffold-project.py`, `scripts/bump-leg.py` and `scripts/shape_materialize.py` merged without a conflict. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s docs say its parent folder is named EXACTLY after its repository, case included, on every disk, and the never to hand-edit the `members:` block names the row repair `validate-family.py` asks for, whose commit is the person's (#763) (#822) Two sets of words now agree. Words only: no tool, no remedy and no --help text changes. The folder rule (N6 of #695's review). The holder's README and its pinned AGENTS-shape.md said the parent folder was "not named after the family", which reads as a match that ignores case. siblings.py compares the name exactly and has a separate branch for a folder that differs only in case. Both now say what the standard's README and the handbook say since #738: the folder must be named EXACTLY after its repository, case included, on every disk. The README is rendered once at `init`, so it reaches new holders only; AGENTS-shape.md is in FAMILY_COPIED_VERBATIM, so it rides in the shape bump as one `upstream-changed` row. The hand repair of a row. AGENTS.md and AGENTS-shape.md said never to hand-edit family.yaml's `members:` block, and validate-family.py tells a person to in two cases: RESTORE_ROW (a row deleted, or its `path:` edited, by hand is put back as git last had it) and `member-gitlink-absent`'s (#681, the row of a member that is to go is taken out). The issue's first option is taken, so no remedy changes: the never stays for the normal path, where `add`, `bump` and `remove` rewrite the block, and each doc says the validator may name a repair of a row and that the commit is then the person's. AGENTS-shape.md, which has the room, names both cases. AGENTS.md is at its cap of 492 lines, so item 3 was reflowed to cost no line: "on a branch you make first (see above): the tracking branch is refused (code)" became "on a branch made first (code refuses the tracking branch)" and "Push the branch" became "Push it". No other sentence of the file moved. AGENTS-shape.md also says its layout's folder is named after this holder's repository (it said "the family", which `--name` makes a display name), and its folder rule opens "This holder's parent folder", the folder being no repository. tests/test_family_docs_one_spelling.py gains 15 tests. Each doc's hand-edit passage is held whole, from its never to whose commit a repair is, as ONE_SPELLING is, so "but for a row repair" cannot turn into "not even for" and pass; the folder rule, subject and "must" included, is read in the standard's README, the handbook, the holder's README and AGENTS-shape.md; `member-gitlink-absent` is raised through `_check_member_gitlink` and its code and remedy are read together; `RESTORE_ROW` is read from the validator, and so is its inline copy in `gitmodules-path-unnamed`. A remedy that stops telling a person to edit a row, or a copy that says another thing, fails there and takes the docs' exception with it. It follows lane 5's #747 (in v1.1.0, as 6626a3c) and #738 (as e62b5e1); #682 has landed too. Closes #763 Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>



Lane: openreposhape-5 (openRepoShape-5)
Claim: #695 (comment)
The family docs now say what
family.py init,make siblingsand themembers/directory do since #488, #578 and #574. Base:8062810d. Head:c42286de: the work (73fa3ff4) and one commit for the review's nit on L20.Fix round: main merged in (2026-10-17)
The branch now holds
origin/mainat24a977c0, through one merge commit,75e35f14. No other commit was added, and nothing was rebased or amended.What failed
Since #682 landed (
dc7d3fbb),git merge-tree --write-tree HEAD origin/mainagainstc42286destopped on one line ofdocs/handbook.html:It is the
familiessection's marker,<!-- reads README.md § Families: ... blob <hash> -->. #682 changed a sentence of README § Families, so main's marker isb3d19092.... This PR changed other sentences of the same section, so its marker is0364c579.... Git saw one line changed two ways. Lane 2's note on #738 (2026-10-17T12:47:17Z) named the same line. I reproduced the conflict on the head I started from, then merged by hand.The fix
The merged README § Families is neither side's text, so the marker takes its fingerprint,
82d7f8087b9ee4d097d33e15fbfe5ae754a04a79. I computed it withtests/test_repo_hygiene.py's ownreadme_fingerprintover the mergedREADME.md. It is the value lane 2's note gave, and it did not move when main moved on to24a977c0.b3d19092...0364c579...82d7f808...I re-read both sides before taking the value:
make bootstrapreports and skips one it lacks a credential for."), and bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682 had already put the matching sentence on the page.Did the merge change README or handbook text beyond the conflict line?
No. I compared each changed file's diff against the parent that did not make it:
README.md: the merge againstorigin/mainshows exactly this PR's own change, and the merge againstc42286deshows exactly main's own change since8062810d(one line, bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's). No line differs.AGENTS.md: the same two comparisons, no line differs.docs/handbook.html: the same two comparisons differ in the marker line only, in both directions.tests/test_repo_hygiene.pypasses with the cap and the markers as they are.This PR still lands only in lane 3's README turn. If another README PR changes § Families first, the marker moves again, and the hygiene test names it.
Tests
All on
75e35f14, through the gh stub, with a bogus token and an emptyGH_CONFIG_DIR.test_readme_sections.py,test_family_docs_one_spelling.py(the one test file this PR changes),test_repo_hygiene.py,test_agents_md_codes.py,test_cli_reference.pytest_readme_windows_quoting_sentence.py,test_setup_by_hand_commands.py,test_update_add_codes_documented.pytest_pinned_copy_text.py,test_template_values_single_pass.py,test_family_init_values.py,test_family_init_landing.py,test_siblings_family_name.py,test_family_makefile_siblings.py,test_family_siblings_placement.pytest_bootstrap_spelling.py,test_bootstrap_without_git.py,test_bootstrap_leg_moves.pyMutants
This round adds no test and no code, so its one hunk is the marker value. Each mutant changes that value in a shared scratch clone at
75e35f14and runstests/test_repo_hygiene.py::test_the_handbook_follows_the_readmes_outline:b3d19092...0364c579...<<<<<<<, both lines,>>>>>>>)Merge checks
git merge-tree --write-tree HEAD origin/mainis clean at24a977c0.40e678e6(siblings.py: the printed move runs as typed past a taken staging name and names a dangling link to move aside first, a NUL or lone surrogate inproject:or url is a finding,.gitmodulesis read as git reads it, the warning's prose is spelled, and a tag-only branch is named (#690) #805,templates/family-root/scripts/siblings.pyandtests/test_siblings_inputs.py) after this merge. It touches no README, handbook or AGENTS text, and the merge-tree against it is clean.members:block names the row repairvalidate-family.pyasks for, whose commit is the person's #822 is stacked on this PR. Its reviewer found it merges clean with this PR landed at the same marker value.What changed
--intosentence (README.md § Families and the handbook). It gave the default, "the directory you are standing in", and not thatinitrefuses that default inside a checkout of this standard. It now says so and names the code:(the directory you are standing in, by default; refused inside a checkout of this standard, \family-landing-in-standard`)`. AGENTS.md already said it.make siblingsparagraph (README.md § Families and the handbook). It saidmake siblings"WARNS and prints the exactmv". Since siblings.py and family.py:make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578 it also clones nothing until themvis run, so the next run clones each member once, and a link of the family's name beside the folder passes. It now says that. Lane 3's two Tracking: shape-doctor.py, reporting a repository's compliance #460 sentences ride in it (L20, L21, which lane 3 marked-> #695):~/projects/InkRouterthe move makes the family folder~/InkRouter, beside~/projects, and the holder~/InkRouter/InkRouter, not~/projects/InkRouter/InkRouter.I wrote L20 with the paths spelled out. Lane 3's wording was "beside that folder's parent", which two readings can take. My first wording, "beside that folder, not inside it", still left one: the thing beside
~/projectsis the family folder, not the holder (rv695 N1). The printed move for that holder ismv ~/projects/InkRouter ~/InkRouter.holder,mkdir -p ~/InkRouter,mv ~/InkRouter.holder ~/InkRouter/InkRouter(probe below, andtest_family_siblings_placement.pypins it).members/. One sentence, the same words in five places: AGENTS.md (family item 2), README.md § Families, the handbook, the holder's README and the holder'sAGENTS-shape.md:Pinned copy: every holder re-pins
templates/family-root/AGENTS-shape.mdchanges by that one sentence. It is inFAMILY_COPIED_VERBATIM, so every holder readsupstream-changedfor it on its nextupdate-shape.py check. The issue's third line names it, and lane 3 asked that it ride with the next pinned-file edit. It can share the one v1.1.0 shape bump with the bootstrap fix.templates/family-root/README.mdis templated and not pinned: an existing holder keeps its own README.Caps: neither is raised
tests/readme-cap.d/); it was 1459 at73fa3ff4. The brief said 1449; that is the cap before README.md and the handbook: say that a source's ownsingle-repository.yamlrecord arrives asleg: drop, that the adopted Makefile'sCONTRACTS_DIRfollows the spec leg's mount, and that a second setup run is refused at the plan, so the three sentences #566, #572 and #573 left false or incomplete are true of main (#606) #627 landed. AGENTS.md is 492, its cap.tests/readme-cap.d/695.txtof about +8, which needs Brett's word. What I cut:--work-dirremains the override" is now "--work-diris the override";family.yamlis what tells the two apart" is now "family.yamltells the two apart";.gitfile carries an absolute path);make bootstrapparagraph;bumptakes the same repeatable--trailer" in family item 3 (stated in "Advancing a leg" step 6 and in the holder'sAGENTS-shape.md);--yeson your own initiative" in "Updating" step 2, now "Never pass--yesyourself", the wordstest_repo_hygiene.pyrequires elsewhere in the file.readme_segments/readme_fingerprint:89ef0ab5ec88...to0364c5794c46...(it wasdc85a9260a67...at73fa3ff4, before the L20 rewording and the caption cut). No other README section changed, so no other marker moves.Tests
New file
tests/test_family_docs_one_spelling.py, 17 tests: the one-spelling sentence in each of the five docs, the two codes held as strings byscripts/family.py, and the--into,make siblings, "named EXACTLY" and "straight in a folder" sentences in README and the handbook, plus the old "WARNS and prints" sentence gone from both. The handbook is read as the text a browser shows (<code>as backticks).8062810d(the file copied in): 15 failed, 2 passed. The two that pass are the code-exists checks.c42286de, after the L20 rewording:test_repo_hygiene.py,test_readme_sections.py,test_agents_md_codes.pyand the new file: 217 passed in 3.37s. The new file'sSTRAIGHT_IN_A_FOLDERis in the new README and not in the old one.8062810dplus the working tree that was then the head (73fa3ff4):test_repo_hygiene.py,test_readme_sections.py,test_agents_md_codes.py,test_cli_reference.py,test_readme_windows_quoting_sentence.py,test_setup_by_hand_commands.py,test_update_add_codes_documented.pyand the new file: 253 passed.test_family_init_landing,test_siblings_family_name,test_family_switch_branch_exists,test_family_tracking_branch,test_template_text_placeholders,test_template_values_single_pass,test_template_quoted_values,test_free_plan_hint,test_doctor_recorded_answers,test_update_shape_add,test_update_shape_header_comments,test_family_add_refusals,test_family_members_case_variants,test_family_member_path,test_doctor_legs_members_next): 987 passed.test_adopt_plan,test_adopt_github_exits,test_adopt_late_failure,test_setup_project_py,test_family,test_family_siblings_placement,test_family_makefile_siblings,test_park_resume_targets: 598 passed, 1 skipped (SHAPE_MEDXEHR_CLONEunset: a plan test needs a read-only clone of MedxSoft/MedxEHR, a cloud tool).test_update_shape.py -k "shape_line or stale_readme or check_says_what_apply or readme_with_no_shape": 7 passed (the tests that read the holder README's last line). The rest oftest_update_shape.pyandtest_shape_doctor.pywere not run: they copy the family-root tree and match none of the lines I changed (git grepof the changed sentences undertests/finds nothing), and a first run of them crawled under a load of 90-100 on the machine.Reproduction
python3 scripts/family.py init --org Acme --family Orbit --dry-runendsREFUSED family-landing-in-standard ... Remediation: pass \--into ..``. The README sentence did not say it would; it now does.parent_folder_warningof the holder'ssiblings.pyfor a holder at<t>/home/projects/InkRouterprintsNOTHING WAS CLONEDand the movemv .../projects/InkRouter .../InkRouter.holder,mkdir -p .../InkRouter,mv .../InkRouter.holder .../InkRouter/InkRouter. For a folder namedinkrouteron Linux it stops the same way: the name test is exact on every disk.test_family_members_case_variants.pyandtest_family_add_refusals.pydrive (60 + 65 tests, passed above).Mutation probes
My own run did not happen: a usage checkpoint stopped it before it began. The reviewer (rv695) ran 30 mutants against
73fa3ff4in a throwaway clone and all 30 were killed: five places, four mutants each for the one-spelling sentence, and five each for the README and the handbook sentences. The L20 sentence has changed since, andSTRAIGHT_IN_A_FOLDERwith it; no mutant was run on the new words. What the guards cover there: the sentence has a parametrized case per doc, so reverting it fails that case, and a reverted README hunk leaves the handbook marker stale.Merge checks (
git merge-tree --write-tree HEAD <ref>, atc42286de)55c6ba7d(it moved past2fac9790by shape-doctor.py:submodule_configandgitmodules_entryread a holder's.gitmodulesthrough onegit config -z --list, so a section name holding a space or a byte that is not UTF-8, or a url holding NEL or U+2028, gives the doctor the mount url git andmake siblingsuse, under any locale (#668) #700): clean. rv695 found73fa3ff4clean against2fac9790.pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 (lane 3), head6fd33a93: clean. It merged mainde926142, and its handbook conflicts at the three markers are gone. On the merged tree README.md is 1472 lines and AGENTS.md 494 (counted here; rv695 found them under README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630's own ledger at73fa3ff4).--no-membersprints the case-variant move (#648) #682 (lane 5), head8b39cd19: conflicts at one line, the handbook marker for README § Families (0364c5794c46...here,c40baa828dca...there). README.md and the rest of the handbook merge clean, and the merged paragraph reads correctly, because both PRs change that section. Whichever lands second recomputes the marker from the merged README: on this head plus bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682 the recomputed value isda54c8ec2665...(computed withreadme_fingerprint, not test-run).For lane 3: one sentence for the v1.1.0 release notes
(Brett Heap, 2026-10-16T13:36:23Z, on #459 line 141: "document it in the v1.1.0 release notes".
docs/release-notes/v1.1.0.mdexists only on #630, so this PR does not create it.)Such a README also shows
Yin its layout block and its doubled-name sentence, as prose; only thecdline fails when run.Left out
templates/family-root/README.md:73,templates/family-root/AGENTS-shape.md:99) and not "EXACTLY, case included", which the standard's README and handbook now say andsiblings.pytests (folder.name == family). Lane 3 asked for L21 only in the standard's README paragraph, so it is not owed here; it could ride in the same re-pin.Closes #695
🤖 Generated with Claude Code
Summary by Sourcery
Update family documentation and validation tests to accurately describe initialization refusals, sibling cloning behavior, and the required
members/spelling.Enhancements:
initandmake siblings, including their refusal conditions and cloning workflow.members/and the associated refusal codes across standard and generated family documentation.Documentation:
members/naming rule across README, handbook, AGENTS.md, and family-root templates.Tests:
members/rule, refusal codes, initialization behavior, and sibling placement and cloning guidance.