Repository navigation
family.py, adopt-project.py and the four tools' --org: init asks GitHub's limits on the topic, the --org and the holder's name before any repository exists, check finds what execute refuses from one list, and an --org GitHub gives no organisation (., a trailing hyphen, --) is refused where GitHub is asked (#863) - #871
Conversation
…ject.py `execute` asks about both leg repositories before it makes either, and `check` asks the same (#798) State at the checkpoint: - tests/test_adopt_execute_refusals.py and tests/test_adopt_late_failure.py hold the new tests, written first; 22 of them fail on bf1f099 as intended (red run before any product change). - scripts/shape_materialize.py: `folder_in_the_way` split out of `folder_cannot_be_made`, no change in what the scaffold or family.py say. - adopt-project.py is NOT changed yet: the question, check's findings and the mkdir guard are still to write, then tests/gh_guard.py's answer for `gh repo view <repo> --json name`, the shims other tests use, docs/cli.md. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t makes either, on the disk or on GitHub, and `check` asks the same (#798) `_refuse_what_stands_in_the_legs_way` runs right before `_create_leg_remotes` and refuses everything at once: a leg's bare repository already there and a `--local-remote-dir` that cannot be made and written in (`folder_in_the_way`, #609), or, against GitHub, a leg name `gh repo view` finds, and a view that fails otherwise than gh's not-found answer. `check` prints the same findings last, and a NOTE where gh cannot answer. The mkdir that follows refuses by name. tests/gh_guard.py answers `gh repo view <owner>/<name> --json name` as a gh with no login does; test_adopt_pins.py's `check` runs take the test path's `--local-remote-dir`, and test_adopt_github_exits.py's shim answers the view from its fake GitHub. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e question and is refused by name, in `execute` as in `check` (#798) `_remote_urls` resolved it with `Path.resolve`, which raised `RuntimeError: Symlink loop` out of `execute` before anything was asked, where `check` named it. Both now keep a path that cannot be resolved as given (`_the_remote_dir`), and `folder_in_the_way` names the loop. A path that resolves is unchanged. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… new `gh repo view` of each leg answer it as GitHub does, and the notice's re-run words are asserted as they now read (#798) - tests/gh_guard.py answers `repo view <owner>/<name> --json name` for an owner GitHub would refuse too (`a+b`, test_adopt_manifest_values.py). - test_adopt_leg_keys.py's stub and test_adopt_temp_work_roots.py's `gh_clones` give gh's not-found answer for a leg; `gh_clones` records only the other calls. - test_adopt_split_branch.py: the first gh call of a run that went on to the legs is now the spec leg's `gh repo view` (`TO_THE_LEGS`). - test_pin_annotated_tag.py and test_template_values_single_pass.py give `check` the `--local-remote-dir` their runs use, so a log of no gh call still says what it said. - test_adopt_filter_repo_failure.py asserts the notice's words for how a re-run is refused: before it makes either leg, on the disk and on GitHub alike. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails `--add-topic` after the repositories exist (#838) GitHub keeps a repository's name to 100 characters, a topic to 50 and a user's or an organisation's name to 39. These are GitHub's documented limits; no run here measured them (none creates a real repository). Nothing asked them. The scaffold sets `xf-project-<id>` last, so an id over 39 characters, which a `--project` of 40 or more gives by default, failed `gh repo edit --add-topic` with all three repositories created and seeded, and `adopt-project.py execute` failed there after both legs and the split were pushed. A `--project` of 96 to 246 characters, or an `--org` of 40 to 255, reached the first `gh repo create`. shape_materialize.py: `refuse_unstorable_names`, the helper that asks the disk's limit, takes the names GitHub will be asked to create and refuses one over 100 under the same code, its remediation naming the id's bound too; `refuse_a_topic_github_cannot_set` and `refuse_an_owner_github_cannot_have` ask the topic and the organisation. The numbers live there, in the plans, so nothing pinned is re-pinned. scaffold-project.py asks all three in its plan, before any process is started and under `--dry-run` alike, without `--local-remote-dir` only: `scaffold-name-too-long`, `scaffold-org-too-long` and `scaffold-topic-too-long`, a derived id named as derived and mended with `--id`. adopt-project.py `execute` asks them before either leg exists: `adopt-name-too-long` (the two legs), `adopt-org-too-long` and `adopt-topic-too-long`. setup-project.py hands the three values to the scaffold's plan at step (5), which refuses them there; its `--help` says the three numbers, as the two tools' `--help` does, and docs/cli.md is regenerated. tests/test_github_name_limits.py holds each bound at N and N+1, on each way in, local and not; tests/test_scaffold_manifest_values.py's GitHub half of the longest-name test now asks GitHub's longest, 95 with a short `--id`. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of 96 to 100 characters, is mended by renaming the source on GitHub first, so `adopt-name-too-long` no longer says to shorten a `--project` an in-place adoption cannot change (#838) Fix round 1 on #859, after rv859's review. adopt-project.py: without `--local-remote-dir` the assembly root is the source's own repository on GitHub, which GitHub lets be named with up to 100 characters, so a source of 96 to 100 gives legs of 101 to 105, which `adopt-name-too-long` now refuses. Its remediation said "Use a shorter --project", which plans another repository; `git ls-remote` could not read it. Where a leg over 100 is named after the root, the remediation now says GitHub's numbers and then `_rename_first`'s way out, as `adopt-bad-id` says it: rename the source on GitHub, to at most 95 characters, and plan again. A leg the plan was edited to name otherwise, and the disk's limit under `--local-remote-dir`, keep their remediations word for word. Comments and docstrings: the base planned a `--project` of 96 to 250 characters in GitHub mode, not 96 to 246. Of the three numbers only the 100 and the 50 are documented on docs.github.com; for 39 the docs give the limit on a user's name (Enterprise Cloud's username considerations) and nothing for an organisation's. The scaffold's GitHub refusals come before any process only where `--elected-by` or GIT_AUTHOR_NAME names the elector; otherwise `git config user.name` runs first. The previous commit's message says otherwise on all three. tests/test_github_name_limits.py: a source named with 96 and with 100 characters each refuses with the rename-first remediation, and a leg the plan names otherwise and the disk's limit keep theirs; `execute` without `--yes`, on a stdin that is not a terminal, refuses `adopt-topic-too-long` and `adopt-org-too-long`, not `adopt-unconfirmed`, with a control that reaches `adopt-unconfirmed`; and the scaffold's and `execute`'s own remediations are held word for word. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e_way` answers `(where, why)` and `folder_cannot_be_made` names the folder as #692's `shown_source` names it (#798) scripts/shape_materialize.py conflicted with #692 (d66a905) in two hunks. Resolved as the review ruled: `folder_in_the_way` returns `(here, why)`; `folder_cannot_be_made` returns `f"{shown_source(found[0])} {found[1]}"`, and its docstring keeps #692's paragraph on why the folder is spelled. #825 (#716) is not on main, so `cmd_check` merged without a conflict. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew --json nameWithOwner` answers with that very name, compared without case, so a name GitHub only redirects after a rename or a transfer is free, and an answer with no `nameWithOwner` to read is `adopt-remote-unreadable` (#798) gh's GraphQL follows renames and transfers, so `gh repo view <old name>` exits 0 for a name that is free; any exit 0 was "taken", and the PR's own remedy (rename the other repository first) walked into it. The view now asks `nameWithOwner`; an answer naming another repository is a redirect, and the name is free. The test guard answers the new argv only, so it no longer answers scaffold-project.py's and family.py's `--json name` view (a new guard test shows theirs still refused); the four stubs answer `nameWithOwner`; new tests cover a rename, a transfer, another case and three unreadable answers. `"check"` in test_adopt_execute_refusals.py is a constant now, and its long parametrize line is wrapped. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-dir`, and its gh log is the two leg views exactly; the split-branch tests compare the spec leg's whole view; repeated literals are constants and a long docstring line is wrapped (#798) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
….raises` in three tests, and a composite assert is split in two, as SonarCloud's first run on #861 asked (S5778, S9073) (#798) SonarCloud's first analysis of this PR found four issues in its tests: three S5778 (a call that can raise, `against_github(world)` or `execute_args(tmp_path, plan)`, inside `pytest.raises`) and one S9073 (`assert len(notes) == 1 and told(CODE, what) in notes[0]`). Each argument is now built on the line before the `with`, and the assert is two. Four tests change: `test_a_name_github_only_redirects_is_free`, `test_an_answer_with_no_name_to_read_is_unreadable` and `test_a_name_github_hides_still_gets_the_notice_once_the_spec_leg_exists` in test_adopt_execute_refusals.py, and `test_a_code_leg_made_after_the_question_still_gets_the_notice` in test_adopt_late_failure.py. No test asserts anything new, and the four older copies of those two lines in test_adopt_late_failure.py are left as they were. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ory can be named, keeps "Use a shorter --project", and the rename-first remedy says GitHub's 95 once, so a --project nobody compared with a source is no longer told to rename one (#838) #859's re-review of 7193663 left three NITs, all in the rename-first hunk of `_repository_urls`. NIT 3. `_a_leg_named_after_a_long_root` took any root over 95 characters. A root of 101 or more is no repository GitHub can have, so there is nothing on GitHub to rename: it comes only where the plan's `legs.assembly` is not held to a GitHub `origin` (a source with none, or a path `origin`) and a long `--project` was typed, and "Use a shorter --project" was the better advice. The predicate now also asks `len(names["assembly"]) <= GITHUB_NAME_MAX`. A root of 251, whose legs the disk cannot hold, keeps the disk's remediation. `SOURCE_NAME_TOO_LONG` said 95 twice; `GITHUB_LIMITS` says it, and the sentence before it no longer does. NIT 1. No test covered a plan where one leg is named after the source and the other was edited to a short name. `test_one_leg_named_after_the_source_is_enough` does, for each leg, and `test_a_leg_the_plan_names_otherwise_keeps_its_remediation` gains a leg that starts with the root without being `<root>-spec`. NIT 2. `test_a_leg_named_after_the_source_is_mended_by_renaming_it` asserts the refusal's detail, which the rename-first line hands on from `refuse_unstorable_names`. `test_a_root_github_cannot_have_is_not_the_sources_to_rename` holds the roots of 101 and 251. With the root bounded, `--local-remote-dir`'s guard in the handler is reached only by a disk refusal of an edited leg beside a leg named after a source of 96 to 100; the one test of it, a root of 247, no longer got that far, so `test_a_leg_named_after_the_source_is_not_the_disks_trouble` holds it. `SHORTER_PROJECT` names the first words of the "Use a shorter --project" remediations once, and `DISK_REMEDY` is built from it with the same bytes. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…GitHub's limits on the topic, the `--org` and the holder's name before any repository exists, `check` finds what `execute` refuses from one list, and an `--org` GitHub gives no organisation (`.`, a trailing hyphen, `--`) is refused where GitHub is asked (#863) `family.py init` set `xf-project-<family id>` last, after `gh repo create` and the push, and asked none of GitHub's limits: a family of 40 characters made and pushed its holder and then met GitHub's refusal of the topic. `_checked_family_identity` now asks them, where GitHub is asked and before `_creator_name` and any `gh` call, so `--dry-run` refuses the same: `family-name-too-long` (100), `family-org-too-long` (39), `family-bad-org` and `family-topic-too-long` (50), with #859's helpers and the scaffold's words. `init --help` says the three bounds; `docs/cli.md` is regenerated. `adopt-project.py check` asked none of what `execute` asks of the names, the organisation and the topic, the disk's 255 bytes included, so it said `plan ok` over a plan `execute` refused. `_what_cannot_be_held` is now the one list: `execute` refuses its first refusal in `_repository_urls`, and `check` finds every one (`_held_findings`) under the same code, before #861's leg question, which it no longer asks after one. The topic question moves from just before `_confirm` into that list, so `execute` asks it with the names and the organisation. `refuse_an_owner_github_cannot_spell` (shape_materialize.py) refuses an owner holding a character outside letters, digits, `_` and `-`, ending in a hyphen or holding two in a row: `scaffold-bad-org`, `adopt-bad-org` and `family-bad-org`, asked beside the 39 and only without `--local-remote-dir`; `setup-project.py` takes the scaffold's answer at its step (5). `_` is let by until a real account is probed. No pinned file changes. GitHub's numbers and characters are from its documentation and are not measured here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-18T03:50:22Z) Lane 3's independent review of its own #871 (#863) at
RV871 review of PR #871 at head The head did not move while I worked: re-read at the end, #871 is still SHOULDS1.
S2. SonarCloud reports three new issues in the new test file. The gate passes (duplication 0.1%), but #861 fixed the same rules in its own round.
NIT
Brief checks
Outside this PR's scope, a candidate for a later issue: a family of 252 to 255 characters under Test commands and resultsEvery run used
MutantsEach was applied alone to
Scratch paths
For lane 1 (openRepoShape-1)
For lane 5 (openRepoShape-5)
Lane: openreposhape-3 (openRepoShape-3) |
…estion beside a topic finding and skips it only after a name or an `org:` finding, whose fix renames both legs, and a misspelled `org:` past GitHub's 39 is shown cut (#863, #871's review) #871's first head skipped #861's leg question after any of #863's findings, a topic-only one too: a plan whose id was 40 characters and whose code leg was already taken on GitHub listed `adopt-topic-too-long` alone, and `leg-remote-exists` only once the id was mended, though an id names no repository (RV871 S1, lane 3's fix (a)). `_held_findings` now gives the codes too, and `check` skips the leg question only after `adopt-name-too-long`, `adopt-org-too-long` or `adopt-bad-org` (`RENAMES_THE_LEGS`): a shorter --project or another `org:` changes `<org>/<Project>-spec` and `-code`, so the question would be asked of names the mended plan does not use. `refuse_an_owner_github_cannot_spell` shows a value past GitHub's 39 as `refuse_an_owner_github_cannot_have` shows it, cut with `_both_ends` and its length, so `check` no longer prints all of a 255-character `org:` (N3). Tests: `[id]` and `[derived-id]` expect the two leg views; a topic and a taken leg are found in one round (S1); one `org:` both too long and misspelled is listed length first by `check` and refused length first by `execute`, `init`, the scaffold and setup's step (5) (N2); a cut value (N3); and a plan whose names do not classify gets no held finding (N5). The arguments are built before each `pytest.raises` and the composite assert is split (S2: S5778, S9073). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-18T04:37:28Z) Lane 3's independent re-check of #871's fix round 1 ( RV871b re-check of PR #871, fix round 1 ( #871's head did not move while I worked: it was still
I made no GitHub write. The only network use was MUSTNone. SHOULDNone. NIT
Follow-up candidate (not this PR's)N7's wording is left as the reply says. Brief checks1. S1, fix (a): holds.
The probe runs
The same probe on 2. S2: gone.
3. N1, N2, N3, N5, N6: each as the reply says.
4. N4 and N7: "no change" is acceptable for both.
5. Nothing else changed. I read every hunk of the round:
6. Tests: all passed. Every run used
The probes passed: 11 passed for check/execute, 15 passed for N2 across 7. Mutants: each was applied alone to
The second of my mutants is killed only in GitHub mode. Under 8. Merges (
Scratch paths
Lane: openreposhape-3 (openRepoShape-3) |
…825's rename-first remedy inside the `except` and asks about the two leg repositories in the `else:` after it (#798) adopt-project.py conflicted with #825 (#716, d7befb9) in one hunk, in `cmd_check`'s `try`/`except` around `_check_names`. Resolved as lane 3 ruled on #473: #825's comment and its two lines (`if renamed is not None and exc.remediation == renamed: remediation.append(renamed)`) go inside the `except`, after its `findings.append`, and this PR's `else:`, which asks `_leg_remote_findings`, follows it. The added and removed lines of the merged adopt-project.py against main are the same 387 as this PR's against d8b8eb3. docs/cli.md, scripts/shape_materialize.py and tests/test_adopt_split_branch.py merged without a conflict, each with this PR's lines unchanged. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…take the `--local-remote-dir` that `check_plan` gives, as `test_adopt_pins.py`'s do, so the empty gh log still proves that `check` reads no network for a pin (#798) Main's #842 (bee84d5) added tests/test_pin_source_top_level.py after this branch's last merge. Its two `check` runs passed no `--local-remote-dir` and asserted `gh_calls(gh_env) == ""`; since this PR, `check` without that flag asks GitHub whether each leg repository exists, so the stub logged the two views and CI's `tests` failed 11 cases (7 of `test_check_finds_a_recorded_path_that_is_no_clone_s_top_level`, 4 of `test_check_asks_a_recorded_top_level_as_it_always_did`). Both now run `check_plan(plan, env=gh_env)`, imported from `test_adopt_pins`, as r798's re-verification gave it. No assertion changed. The file gives 48 passed. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-18T09:14:07Z) #871 is restacked onto main by lane 3's R871 (sonnet), now at
Lane: openreposhape-3 (openRepoShape-3) |
|
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 5 days and 13 hours by commenting |
…ith #877, #865 and #871 (#883) Round 4 of #883, a refresh after RV883 found 34e0f5b READY (#883 issuecomment-6248756609). Each line comes from its squash body and was checked against the code at main a249910: - #877 (#856): a holder's bootstrap reads an index or HEAD `.gitmodules` up to a line git cannot parse, as git does, and a parity test holds the value rules both family scripts write to one answer. The #856 limit is retired. - #865 (#840): `family.py add` names first, with a remedy that types as printed, the states #840 lists, and its closing guard takes `validate-family.py`'s form. The limit on the two guards and on the case-spelling states is retired; three states #865's body leaves unruled are a narrower limit. - #871 (#863): `family.py init` asks GitHub's limits before any `gh` call, `adopt-project.py check` finds what `execute` refuses from one list, and an `--org` GitHub gives no organisation is refused where GitHub is asked. The #863 clauses are retired; that the characters are from GitHub's documentation, with `_` let by, joins the limit that says none was measured. RV883's optional NIT: the `adopt-work-dir-shared` limit no longer says "what no owner or mode bit shows", since group write is a mode bit. 63 pull requests; 64 of the milestone's 72 issues closed at 11:05Z, and the open list is the 8 that read leaves: #675, #707, #755, #764, #853, #854, #879 and #884. The shape-copy lists stand. No other file changes. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e and the innermost command named, so a member whose make runs another no longer reads `exited 2` for a finding, and `make -k` keeps its `Error` line (#884) (#886) #884's three lines, from rv876's NIT 2, NIT 3 and NIT 4 on #876 (#876 issuecomment-6246717354), with fix round 1 from rv884's review of this pull request (issuecomment-6248806027). The change is in the family holder's pinned `templates/family-root/scripts/bootstrap.py` and in `tests/test_family_holder_bootstrap_texts.py`, and nothing else. Base: origin/main `281df263`, where #876 has landed as `41baf920`. Each line was probed there first, under the holder's own `make bootstrap`, with GNU Make 4.3, dash 0.5.12 as `/bin/sh` and git 2.43.0. The probes reuse rv876's `p2/ptyrun.py` and its member Makefiles. **1. Five mutants of #876's code survived its tests, and one docstring claimed what no test checked.** Tests only; no code changes for this line. - **R10 and R11** (`credential_advice` ignoring `unreadable` or `refused`): `test_no_advice_beside_no_member_updated_with_every_member_checked_out`. Every member is checked out, as in the holder itself, and HEAD's `.gitmodules` is given `update = None` or a conflict marker. Bootstrap's own stderr lines after the fetch line are asserted exactly: the "NO MEMBER UPDATED, and not for want of a credential" line and the failure, with no advice between them. `tests/test_family_gitmodules_values.py` asks only the last two lines, which is why the mutants passed there. - **R1** (`named[0]`) **and R8** (the regex anchored on `^make: `): `test_the_line_names_the_code_of_the_member_s_innermost_command[last-under-make]`. `bootstrap.py --no-fetch` runs under a real parent `make -k`, as the holder's `make bootstrap` runs it, so the member's make prints `make[1]:`. One member exits 1. Another has two commands that exit 1 and then 2, and the line must name the last. - **R7** (`passed_on`'s `sink.flush()` dropped): `test_a_member_s_question_shows_before_its_answer_is_typed`. Bootstrap runs on a pseudo-terminal (its stdin, stdout and stderr). A member recipe asks `Continue? ` on stderr and `read`s the answer. The test types `yes` only once the question has shown, and fails if it has not shown within 10 s. It asserts the output before the answer and after it exactly. Measured on the pty: the question showed at 0.13 s at head; with R7 on `281df263` it showed only at 8.1 s, after the blind answer, when the run ended. - **The store:** `test_a_git_mv_staged_with_work_in_the_checkout_is_undone_as_printed` now asserts what its docstring said. After every line is typed, the checkout's commit (the new commit, or the pin where only an untracked file was added) is still in `.git/modules/members/IRRS`. A helper, `kept`, is shared with `test_a_checkout_moved_on_at_the_variant_is_moved_with_f`'s own store assert. - On `281df263`, with rv876's own `mut/mutants.py` unchanged, every one of the five is caught by the test named above, and those tests pass on the unmutated code: | rv876's mutant on `281df263` | caught by | |---|---| | R1 `named[0]` | `[last-under-make]` | | R7 no `sink.flush()` | `test_a_member_s_question_shows_before_its_answer_is_typed` | | R8 `^make: ` | `[last-under-make]` | | R10 advice ignores `unreadable` | `test_no_advice_beside_no_member_updated_with_every_member_checked_out[unparsed]` | | R11 advice ignores `refused` | `test_no_advice_beside_no_member_updated_with_every_member_checked_out[value-refused]` | **2. The final line misled in three cases** (`run_make`, `MakeErrors`). - Measured on `281df263` and on this head, each member under the holder's `make bootstrap` and with `bootstrap.py` run straight (`-k` where the case needs it). The last five rows came from rv884's review: | member | `281df263` | head | |---|---|---| | `$(MAKE) inner`, inner command exits 1 | `exited 2` | `exited 1` | | `$(MAKE) inner`, inner command exits 2 | `exited 2` | `exited 2` | | `$(MAKE) nothere`, the inner make's own failure | `exited 2` | make printed no `Error <code>` line for a command of the member's | | `$(MAKE) -s nothere; exit 3` | `exited 3` | `exited 3` | | `make -k`: `Error 1`, then 5000 bytes on stderr | make printed no `Error <code>` line (false) | `exited 1` | | `make -k`: two commands exit 1, then 3 | `exited 3` | `exited 3` | | a command killed by SIGKILL under dash | `exited 137` | `exited 137` | | `kill -TERM $$` in a recipe | make printed no `Error <code>` line | the same | | `$(MAKE) -s nothere \|\| exit 1` | `exited 1` | `exited 1` | | `$(MAKE) -k inner`, whose second target prints after the inner `Error 1` | `exited 2` | `exited 1` | | `make -k`: a finding, then a command that `kill -KILL $$` ends | `exited 1` | `exited 1` | | `-$(MAKE) inner` (inner exits 1), then the recipe's own `exit 2` | `exited 2` | `exited 2` (`82a84f88` read `exited 1`) | | `printf 'x: *** y' >&2; exit 1`, so make's line follows it on one line | `exited 1` | `exited 1` (`82a84f88` named none) | - **A recursing member: the line names the innermost command's code.** Why: the inner make exits 2 for any failure, which is the very code #839 set out to see past. The command the member's author wrote, and whose code tells a finding (1) from a refusal (2), is the innermost one. - The depth comes from make's own prefix (`make[N]:`, or `make:` for 0). - An `Error 2` at a shallower depth than the last `***` line before it, whatever lines come between, is taken for that make's parent reporting it, and is passed over. - Where the inner make failed on its own (`No rule to make target`), no command's code is named, and the line is the one for make's own failure. - A command that runs a failing make and then fails itself, with 3 or with 1, is still named. - Two failures at the same depth under `-k` name the last, as before. - Where a `-` let the inner make fail, make prints `[Makefile:N: bootstrap] Error 2 (ignored)`, with no `***`. That line now puts the depth back at the parent's, so the parent's own `Error 2` after it is named. rv884 offered this as an option, to adopt only if it changed no other case. Measured: across rv884's 133 case runs (its `tools/cases1.py` to `cases6.py`, at both depths), head with it differs from `82a84f88` only in that case and in the last-match case below. Across 21 more of mine it differs only in that case, in `-$(MAKE) nothere` then `exit 2`, and in the last-match case, each now as `281df263` read it. An inner make that ignores a failure of its own prints the same kind of line at its own, deeper depth; that line changes nothing (measured: `$(MAKE) inner; exit 2`, where inner ignores an `exit 2`, reads `exited 2`). - **The last match on a line** (rv884's NIT 1). A member's own text with no newline, `x: *** y`, followed by make's `Error 1` on the same line, named no code at `82a84f88`. `said` now takes the last match on a line (`findall`), and that case reads `exited 1`, as `281df263` read it. - **Where the line is not the innermost command's code.** This is now written in `MakeErrors`' docstring, not only in this body (rv884's NIT 2): - a recipe that runs a make, lets its failure pass in the shell (`|| true`, or `;`), or with a `-` in a make run with `-s` (which prints no `(ignored)` line), and then exits 2 itself: that `Error 2` is read as the inner make's report, and the inner command's code is named. Measured: `$(MAKE) inner || true; ...; exit 2` and `-$(MAKE) inner` under the holder's `make -s` read `exited 1`; `281df263` read `exited 2`; - an inner make run with `env -u MAKELEVEL`, which prints its parent's depth: the parent's `Error 2` is named, as on `281df263`; - under `-j`, a job of the parent's that fails while the inner make is still running: its line comes between, and the parent's `Error 2` is named, as on `281df263`; - a member's own line shaped like a deeper make's (`x[3]: *** y`) before it exits 2 itself: its `Error 2` is passed over. - The module docstring's (b) now says the code is the innermost command's "as far as make's lines tell it". - **More than 4 KiB after the `Error` line:** `run_make` kept the last 4096 bytes of make's stderr and read that tail at the end. `MakeErrors` now reads each line as it passes. It keeps only the last 4096 bytes of a line that no newline has ended yet, so memory stays bounded for a progress meter that writes only `\r`. rv884's `tools/chunks.py`, pointed at this head, fed 174 captured streams to `MakeErrors` in 81,406 chunkings; none read differently from the whole. - **A signal:** the docstring is now true of rv884's 24 cases (`probe2.log`: dash 0.5.12 and bash 5.2.21; a command run with no shell, one command, `kill $$`, a command then `echo`, a command then `exit $?`, `echo` then a command; SIGKILL and SIGTERM; at both depths). - Where the signal kills what make started, make prints its name, `Killed` or `Terminated`, and names no code. That is: a command make runs with no shell; the shell a `kill -KILL $$` kills; or a command bash runs in its own place, which is a line's last command, a line of one command included. - Where the shell outlives the command, the shell prints the signal's name itself, `Killed` or `Terminated`, and exits 137 or 143, which make names. dash outlives every command; bash outlives one that is not a line's last. - Where the line goes on after the command, the shell exits as the rest of the line does: `; echo after` ends the recipe with 0. - The line itself was not changed for a signal. The signal's name is printed right above make's `Error 137`, and a command may also exit 137 by itself. - **Tests:** `test_the_line_names_the_code_of_the_member_s_innermost_command`, with four sets: - `[innermost-under-make]` and `[innermost]` run `INNERMOST`'s 11 members, once under a parent make and once with the member's make at depth 0. They are the first five rows of the table, the last five, and an inner make that ignores an `exit 2` of its own, then `exit 2` (`InnerLets`, run without `-s`, which would keep make from saying so); - `[last-under-make]` is line 1's R1 and R8 test; - `[signal]` runs one of each signal case in a form that does not hang on which shell `/bin/sh` is: `sh -c 'kill -KILL $$'; exit $?`, which the shell outlives (137), and `kill -KILL $$`, which kills the shell (no code). - Each set asserts the run's final line exactly. Under the parent make, a premise checks that no member line reads `make:`. - `test_a_line_that_comes_in_pieces_is_read_whole` (the pieces test) feeds `MakeErrors` an `Error` line cut in two, and a line ended by no newline, which stays at `LINE_KEPT` bytes. - The existing `test_a_finding_a_refusal_and_make_s_own_failure_are_told_apart` (depth 0, no `-k`) passes unchanged. It now shares `with_members` and its line helpers with the new tests. **3. What passing make's stderr on changes.** I chose to document it in `run_make`'s docstring, after measuring both ways. | effect | `281df263` and head (documented) | reading only until make exits (prototype, not committed) | |---|---|---| | `test -t 2` in the member | false: the pipe | false | | stdout and stderr order | out of order on the pty: one run printed `err 1` after `out 4`, another all six `out` lines first | the same | | recipe `sleep 6 &` | 6.14 s | 0.29 s | | `( sleep 1; echo late >&2 ) &` | 1.12 s, `late` shown | `late` lost, and the writer died at that write: its status file was never written | | non-UTF-8 bytes (`caf` and 0xE9) | passed on as written | the same | - **The background holder.** Stopping once make has exited and nothing is waiting fixes only the hold, and only by closing the pipe. The left-running process's next stderr write then meets a closed pipe and dies of SIGPIPE, silently. Holding the run is visible, ends when the process lets go of stderr, and the recipe can give the process its own stderr (`2>log`), as the docstring says. - **The terminal.** Giving make the terminal back would lose its `Error <code>` line. Measured: `make --debug=j` prints only "Reaping losing child" on stdout, with no code, and no other output make writes carries the code. That would reopen line 2 of #839, which this change keeps closed: make still runs. - **The line order and the Windows decoding** cannot be fixed by the reader. The docstring says each, with the Windows case read and not run. - **Tests that hold the docstring:** - `test_what_a_member_s_make_leaves_on_stderr_is_passed_on_as_written` (the leaves test) asserts bootstrap's stderr bytes exactly: the non-UTF-8 line as written, then `late`, before the run ends; - the pty test asserts `stderr: no terminal` and `stdout: a terminal` from the member. **Pinned copy and docs.** - `bootstrap.py` is in `FAMILY_COPIED_VERBATIM`, so each holder takes one `upstream-changed` row for it on its next update. Its mode stays 100755. - Its imports from `repo_shape` are unchanged, so the skew rule (`tests/test_pinned_copy_text.py`) and `tests/test_update_shape_kept_helper.py` hold. - No `--help` text changed, so `docs/cli.md` is unchanged. - No sentence of `AGENTS.md`, `templates/family-root/AGENTS-shape.md`, `templates/family-root/README.md` or `README.md` speaks of the make line or of stderr (grep). **Fail on base.** The test file at this head, run on `281df263`'s code: 3 failed, 54 passed. On `82a84f88`'s code (this pull request before fix round 1): 2 failed, 55 passed. - On `281df263`, the 3 failures are the tests that pin line 2's change: `[innermost-under-make]`, `[innermost]` and `test_a_line_that_comes_in_pieces_is_read_whole`. On `82a84f88`, the 2 failures are both `[innermost*]`. - These pass on `281df263` by design: - the pure mutant-killers `[last-under-make]` (R1, R8), the pty test (R7) and both `test_no_advice_beside_no_member_updated_with_every_member_checked_out` cases (R10, R11); - the tests that hold a docstring: `[signal]`, `test_what_a_member_s_make_leaves_on_stderr_is_passed_on_as_written`, and the store assert in both `test_a_git_mv_staged_with_work_in_the_checkout_is_undone_as_printed` cases. - Each `INNERMOST` member, run straight with `-k` (depth 0): | member | expected | `281df263` | `82a84f88` | head | |---|---|---|---|---| | `Recurse` | 1 | 2 | 1 | 1 | | `RecurseRefuse` | 2 | 2 | 2 | 2 | | `InnerOwn` | none named | 2 | none named | none named | | `InnerThenOwn` | 3 | 3 | 3 | 3 | | `InnerThenFinding` (X12) | 1 | 1 | 1 | 1 | | `Long` | 1 | none named | 1 | 1 | | `KeepsGoing` (X11) | 1 | 2 | 1 | 1 | | `ThenKilled` (X7) | 1 | 1 | 1 | 1 | | `LetFail` | 2 | 2 | 1 | 2 | | `InnerLets` | 2 | 2 | 2 | 2 | | `OwnStars` | 1 | 1 | none named | 1 | - rv884's own X7, X11 and X12, its saved copies of `82a84f88` with each mutant, read their member wrong: `ThenKilled` says none, `KeepsGoing` says 2 and `InnerThenFinding` says none. **Mutants at this head**, each run against the whole test file: 36 made, 33 caught. The 3 survivors are the equivalents rv884 named: X5 (`\s*$` dropped; make's `***` lines carry nothing after the code), X6 (the unended remainder read at EOF; make always ends its line) and X9 (passed over only one level deeper; makes nest one level at a time). | mutant | caught by | |---|---| | R1, first `Error` line kept | `[last-under-make]`, both `[innermost*]` | | R7, no `sink.flush()` | the pty test | | R8, regex anchored on `^make()?: ` | `[last-under-make]`, both `[innermost*]`, the pieces test | | R10, R11 | the matching `test_no_advice_beside_no_member_updated_with_every_member_checked_out` case | | H1 nothing passed over; H2 any code passed over; H4 depth never read; H6 depth not kept; H8 a `***` line with no code not read; H9 the 4096-byte tail again | both `[innermost*]` | | H3, `<=` for `<` in the depth test | `[last-under-make]`, both `[innermost*]` | | H5, `make:` read as depth 9 | `[innermost]` | | H7, depth starting at 99 | both `told_apart` cases, both `[innermost*]` | | H10 an unended line dropped; H11 kept whole | the pieces test | | H12, only a chunk's last line read | `[last-under-make]` | | D1 reading stopped once make exits; D2 the bytes decoded as UTF-8 | the leaves test | | rv884's X1 (`!=`), X2 (depth only on coded lines), X4 (depth group dropped), X7, X11, X12 | both `[innermost*]` (X1 also both `told_apart` cases) | | rv884's X3 (`[:LINE_KEPT]`) | the pieces test | | rv884's X8 (`splitlines`) | 7 tests | | rv884's X10 (one-digit code) | `[signal]` | | N1, the first match on a line | both `[innermost*]` (`OwnStars`) | | N2, the `(ignored)` line not read | both `[innermost*]` (`LetFail`) | | N3, the `(ignored)` line always sets the depth; N5, it falls through | both `[innermost*]` (`InnerLets`) | | N4, `max` for `min` | both `[innermost*]` (`LetFail`, `InnerLets`) | **Runs**, each through `run9s.sh` with `SHARDS=3` at this head, with no tracked changes. The files: `tests/test_family_holder_bootstrap_texts.py`, `tests/test_family_bootstrap_gate_order.py`, `tests/test_bootstrap_spelling.py` and `tests/test_pinned_copy_text.py`; the two that `git grep -l 'family-root/scripts/bootstrap' -- tests` names, `tests/test_family.py` and `tests/test_update_shape_kept_helper.py`; the trio, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py` and `tests/test_cli_reference.py`; and five more that run the holder's bootstrap: `tests/test_family_gitmodules_values.py`, `tests/test_family_head_lost_gitmodules.py`, `tests/test_family_members_not_a_list.py`, `tests/test_bootstrap_without_git.py` and `tests/test_family_makefile_siblings.py`. - git 2.43.0: 1049 passed, rc 0 (shards 218, 394 and 437), at `dbd3a6dd`. - git 2.47.1: 1049 passed, rc 0 (shards 218, 394 and 437), at `dbd3a6dd`. **Merge checks**, `git merge-tree --write-tree` of this head, after a fresh fetch: | against | result | |---|---| | origin/main `71f36fe0`, where lane 3's #877 has landed as `c9ca9a45`, editing other parts of `bootstrap.py` | clean, tree `8b5826db` | | origin/main `201c6f09`, after #865, #871 and #815 too | clean, tree `8c3d8e13`; on a commit at that tree, this test file, `tests/test_family_gitmodules_values.py` and `tests/test_pinned_copy_text.py` gave 291 passed, rc 0 (git 2.43, rv884's re-check, #886 issuecomment-6249507909) | | #877's head `35b214b2` | clean, tree `b80a2ba0` | **Sonar-style checks.** - No duplicated block: no 10-line window that touches an added line appears anywhere else in `tests/`, `scripts/` or `templates/`. At 6 lines, only the import block matches another file. - No assert joins conditions with `and`. The one `or` assert in the file is #876's. - Cognitive complexity, counted by hand with Sonar's rules: no changed function is over 15, so no `def` line had to stay as it was. `said` counts each boolean sequence: `depth_said or 0`, `code == b"2" and ...`, and `code and not ...`. | function | `281df263` | `82a84f88` | head | |---|---|---|---| | `run_make` | 2 | 1 | 1 | | `MakeErrors.read` | new | 1 | 1 | | `MakeErrors.said` | new | 5 | 6 | | `read_until` (test) | new | 9 | 9 | | `made`, `with_members` (test) | new | 2, 1 | 2, 1 | | `test_the_line_names_the_code_of_the_member_s_innermost_command` | new | 4 | 4 | | `test_a_finding_a_refusal_and_make_s_own_failure_are_told_apart` | 3 | 2 | 2 | | `test_a_git_mv_staged_with_work_in_the_checkout_is_undone_as_printed` | 3 | 3 | 3 | **Not here.** - No Windows or macOS run, since CI is Linux only (#597). The Windows decoding is read, not run. - Make before 4.0 was not run. The prefix and the `Error` line keep make 3.81's form by its source, but the line-2 test already asserts make 4's quoting. - The innermost rule's limits are in `MakeErrors`' docstring and listed under line 2 above. A make that speaks another language still gets the line for make's own failure. Review: rv884, lane 5's independent review, READY at `82a84f88` (issuecomment-6248806027), with no MUST, one SHOULD (three surviving mutants) and four NITs. w884's fix round 1, `dbd3a6dd`, answered each (issuecomment-6249230294), and rv884's re-check found it READY with no MUST or SHOULD (issuecomment-6249507909). Its two NITs on the `(ignored)` rule, which cover rarer nested-make shapes, go to a follow-up. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 dbd3a6d passed tests, tests-git-bash and SonarCloud; the head 85c2078 is a clean update-branch merge of main 0d5d2b1, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 5c273a1 over main 0d5d2b1: the family_holder_bootstrap_texts, repo_hygiene, agents_md_codes, cli_reference sets, 205 passed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #884 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…source.py, tests/test_adopt_tracking_branch.py, tests/test_adopt_origin_repository.py, tests/test_template_text_placeholders.py and tests/gh_guard.py: the docstrings say that `check` asks `gh repo view` of each leg only once the plan's names and `org:` pass, that a source's `origin` need not be a GitHub URL, and that `execute` is held to what it runs through `run` (#880) The cross-lane review of #881 (lane 5, READY, no MUST) found two false sentences carried into the first two docstrings and four more files with the same fault as #880. All counts are from the session's guard log, kept with `tmp_path_retention_policy=all`, at the unchanged code; every call is `repo view <owner>/<name>-spec|-code --json nameWithOwner`, a probe. - "Each source's `origin` is an `https://github.com/` URL" is false for most cases: it is now "where a source has an `origin`, it is a string `plan` and `check` read from the source's own config, and nothing fetches it". - "`execute` is run in-process with every `git` and `gh` command it would run replaced" is false: only what goes through `run` is replaced (`refused_before_any_remote`'s own docstring says so). - `check` asks only given no `--local-remote-dir`, once `_check_names` passes and the `org:` passes `checked_value` (on main since #871, and once no finding that renames the legs comes first): 18 of the 45 cases of test_adopt_check_plan_name.py run `check` without it and ask nothing. - Off Windows, because a bare `["gh", ...]` skips `gh.cmd` (`tests/conftest.py`'s guard comment). - test_adopt_tracking_branch.py said "Nothing here runs `gh`": 18 calls in 8 of 17 cases. test_adopt_origin_repository.py said "Nothing here fetches or runs `gh`": 4 calls in 2 of 30, both params of test_a_path_origin_is_not_compared_and_plan_says_so. test_template_text_placeholders.py said its `--local-remote-dir` runs "never call `gh`": true of them, but its two adoption tests also run `check` without one: 20 calls in 10 of 142 cases. gh_guard.py said "Every `check` ... given no `--local-remote-dir` asks it twice": now once the names and `org:` pass, once of each leg. Only docstrings change; each file's AST outside its module docstring is the same, and gh_guard.py's paragraph is reflowed. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… the release commit, #778's leg-clone sentence and #802's readers sentence, each checked against runs at v1.1.0 and main (#883 follow-up) (#893) The v1.1.1 notes landed as the release commit `5017830` (#883), and RV883's round 5 held two SHOULDs and five NITs for after it (#883 issuecomment-6250558821). This applies them to `docs/release-notes/v1.1.1.md`, with two more sentences lane 1's review found: 1. **#778's leg-clone sentence** says what runs at `v1.1.0` and `5017830` show. A leg clone whose own `AGENTS.md` says so keeps its leg row and its Triad row over each fault, where v1.1.0 gave `FINDING the way in refused` over a tab line and a traceback, exit 1, over a pattern that does not compile or a policy that raised on its name. A leg known only by its name gets a repository's two `n/a` rows. 2. **"Is", not "stays", exit 2,** in the pattern case, which was exit 1 in v1.1.0. 3. **#802's clause is dropped:** v1.1.0's `_blocks_inside` already read a `try`'s handlers, `else` and `finally` outside its guard. 4. **"What 1.1.1 fixes"** names "or cannot be used", and `the other way in`. 5. **#815's leftover tracebacks** were measured on Python 3.12; 3.13 is read, not run. 6. **#871 lets `_` through for any owner,** an organisation's name included. 7. **#869 asks the disk** only about a name that is not UTF-8. 8. **#802's readers:** at v1.1.0 only the import reader skipped an `if TYPE_CHECKING:` body; the binding reader skips one now. 9. **A known limit names #940:** under the `C` or POSIX locale, the bash `openRepoShape` and `setup.sh`'s guards refuse a URL, path or ref with a letter past ASCII as "a control character", a regression from v1.1.0 in the launcher through #872 (#866); a UTF-8 locale takes such a URL or path. The draft-time lines in the sections marked "remove at tag time" say main `2d372bb`, where #778 landed file for file as its reviewed head `8259ea1`, and that #879 moved to v1.2.0. The release body never carries those sections. Review: lane 1's independent reviewer found `b795ef74` NOT READY, on the leg-clone sentence, and `ca49f125` READY, with each changed sentence checked against runs at `v1.1.0` and `5017830`. Lane 1's second reviewer found `6cdb5551`, which adds the #940 limit, READY, with each clause measured against the launcher at `64464d31` and at v1.1.0. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `6cdb5551` is green on Linux and SonarCloud. The branch merged main once and was never rebased; the last merge is update-branch's, clean, giving f60654e, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 5 test files that read the release notes or the workflows (`test_repo_hygiene.py`, `test_release_workflow.py`, `test_workflow_concurrency.py`, `test_agents_md_codes.py` and `test_cli_reference.py`), 221 passed and 0 skipped. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Closes #863.
The stack, and what it lands after
fix/838-github-name-limits, lane 5's scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859 (scaffold-project.py, adopt-project.py and setup-project.py: nothing asks GitHub's topic and organisation limits, so an id over 39 characters fails--add-topicafter all three repositories are seeded, and the direct tools pass a name over 95 #838), at495d4b33. All three lines call what scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859 adds toscripts/shape_materialize.py:GITHUB_NAME_MAX,GITHUB_TOPIC_MAX,GITHUB_OWNER_MAX,GITHUB_ID_MAX,refuse_unstorable_names(..., github=),refuse_a_topic_github_cannot_setandrefuse_an_owner_github_cannot_have.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861 (adopt-project.pyexecute: each leg repository is asked about only as it is made, so a code leg whose name is taken leaves the spec leg created, on GitHub as under--local-remote-dir, and a--local-remote-dirthat is a file or cannot be written ends in a traceback afterchecksaidplan ok#798),fix/798-adopt-legs-asked-first. Its head70801c91is merged in (9ff260fb), on scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's head495d4b33, its NIT round. I first branched from71936633and merged748c8e6a. Both heads moved while I worked (scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's NIT round, and a SonarCloud round of tests on adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861), so before the first push I rebuilt this branch on the new heads and cherry-picked the one commit.git merge-tree --write-treeof scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's head with adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's was clean at both pairs of heads, so the second line is here, beside adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's leg-repository findings incheck.--add-topicafter the repositories exist (#838) #859 and adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861. Until they land, its diff againstfix/838-github-name-limitsalso shows adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's commits and the main commits adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861 merged. The change of family.pyinit, adopt-project.pycheckand the--orgof all four tools:initsets thexf-project-<id>topic last and asks no GitHub limit,checkpasses a nameexecuterefuses, and--org my_org.xpasses every plan #863 itself is two commits:66fc8f6b, the change (9 files, 1101 insertions, 46 deletions);99afbc3d, the fix round for lane 3's independent review RV871 (comment): 3 files, 224 insertions, 26 deletions.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's merge with main. adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's head conflicts with adopt-project.py:checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825's squash incmd_check, and this pull request edits the same lines, so it conflicts with main through adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861 (see "Merge checks").What changed, by the issue's lines
Line 1:
family.py initasks GitHub's limits before any repository exists.initset the holder's topic,xf-project-<family id>, last:gh repo edit --add-topicran aftergh repo createand the push. It asked neither GitHub's 50 for a topic, nor its 39 for--org, nor its 100 for the holder's name. So a family of 40 characters made and pushed its holder, then failed at the topic, with no NEXT STEPS printed._checked_family_identitynow calls_refuse_what_github_cannot_hold, only where GitHub is asked (args.local_remote_dir is None). That is before_creator_nameand before anyghcall, so--dry-runrefuses the same. The only process started before it isgit check-ref-format, the questionrefuse_bad_branchasks of--tracking-branch.--add-topicafter the repositories exist (#838) #859's pattern:family-name-too-long: a--familyover 100. It is a check of its own besidefamily-bad-holder-name, becauserefuse_unstorable_names' remediation names--projectand the three repositories of a project.family-org-too-long: scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859'srefuse_an_owner_github_cannot_have.family-bad-org: line 3's helper.family-topic-too-long: scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859'srefuse_a_topic_github_cannot_set. A derived id is named asfamily-bad-idnames one,--id (not given, so --family lowercased), and its remediation is an explicit--id. A passed--idis told to pass a shorter one.init'snothing was created, and --dry-run refuses the same.init --helpstates the three bounds where each value is typed, as scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859 does for the other three tools.docs/cli.mdis regenerated.family.py's module docstring.Line 2:
adopt-project.py checkfinds whatexecuterefuses, from one list.checkasked none of whatexecuteasks of the names, the organisation and the topic, the disk's 255 bytes included, so it saidplan okover plansexecuterefused._what_cannot_be_heldis now the one list, inexecute's order:adopt-name-too-long: the disk's under--local-remote-dir, GitHub's 100 without it);--local-remote-dir, theorg:(adopt-org-too-long, thenadopt-bad-org);adopt-topic-too-long).executerefuses its first refusal, in_repository_urls, before either leg exists.checkfinds every one, through_held_findings, as a FINDING under the same code and withexecute's remediation. A finding exits 1, a refusal 2. This is the pattern_refuse_what_check_findsalready follows for the manifest values. The list is a generator, soexecuteasks nothing after its first refusal.checkthey come after its other findings and before adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's leg question, in theelse:of the naming question. That is where the issue asked for them ("last among its findings beside adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's"), but it is not the orderexecuteasks them in, as the issue and this body first said.executeasks them in_repository_urls, after_check_namesand the plan's values. That is before_refuse_what_check_finds(the manifest's values andcheck's other findings), before the symlink, seeding-consent and occupied-work-directory refusals, and before thegit ls-remotereads.checklists the other finding first andexecuterefuses this one. Fororg: "@x"in GitHub mode,checklistsplan-manifest-invalidand thenadopt-bad-org, andexecuterefusesadopt-bad-org; before this pull request it refusedplan-manifest-invalid. The verdicts still agree: exit 1 and exit 2.initalso refuse--org @xfor its characters first now, asscaffold-bad-organdfamily-bad-org, where main saysscaffold-manifest-invalidandfamily-manifest-invalid(measured by RV871). It is a visible change, and the same in every tool.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's leg question is still asked beside a topic finding.checkskips it only afteradopt-name-too-long,adopt-org-too-longoradopt-bad-org(RENAMES_THE_LEGS). Their fix, a shorter--projector anotherorg:, changes<org>/<Project>-specand<org>/<Project>-code, so the question would be asked of names the mended plan does not use. The fix ofadopt-topic-too-longis anotherid:, which names no repository, so one round shows both. A plan with an id of 40 characters whose code leg is already on GitHub now getsFINDING adopt-topic-too-longandFINDING leg-remote-exists; the first head listed the topic alone (family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, S1).else:of the naming question, becauseexecuteasks_check_namesfirst. A plan whose names do not classify gets the naming finding alone (family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, N5, now pinned by a test).plan-bad-id, wherePlan.project_idalways refused it. Anorg:checked_valuerefuses is left tounsafe-value, as adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's_leg_remote_findingsleaves it.Line 3: an
--orgGitHub gives no organisation is refused where GitHub is asked. I took option (a) of the issue's "Decide first": the rule is in the tools' plans, beside the 39, and no pinned file changes. Soscripts/repo_shape.pyand the two validators'QUALIFIED_REare untouched, no project is re-pinned, and a manifest that already names such an owner reads as it did. The issue asked for the choice to be stated on #863 first. Lane 3 stated it there, in its comment of 03:26:32Z.refuse_an_owner_github_cannot_spellinscripts/shape_materialize.py, besiderefuse_an_owner_github_cannot_have. It is a helper of its own, rather than a clause inside scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's, so that each cause has a code that names it, and scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's signature and pinned tests are unchanged._and-; a trailing hyphen; and two hyphens in a row. It names each character and each rule broken. A leading hyphen waschecked_value'sunsafe-valuealready._both_endsand with its length, as scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's length refusal shows it, socheckno longer prints all of a 255-characterorg:(family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, N3)._is let by. A GitHub Enterprise Managed User's login holds one (<handle>_<shortcode>), and the issue asks for a probe against a real account (lane 1) before refusing it.., the trailing hyphen and--are refused regardless, as it asks.scaffold-bad-org(in the scaffold's_refuse_what_github_cannot_hold),adopt-bad-org(in_what_cannot_be_held, socheckandexecuteboth ask it) andfamily-bad-org(line 1's function).setup-project.pytakes the scaffold's answer at its step (5), the scaffold's own--dry-run. None is asked under--local-remote-dir.check(family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, N2). Take an--orgof 39oand a.:checklists both:adopt-org-too-long, thenadopt-bad-org.init, the scaffold,executeand setup's step (5) each refuse the length first (family-org-too-long,scaffold-org-too-long,adopt-org-too-long), and the characters on the next run, once the length is mended.checkwould then say it twice.test_scaffold_manifest_values.py'smy_org.xtest runs under--local-remote-dir, so it still passes; only its docstring changes.test_adopt_manifest_values.py's plan test now also assertscheck'sadopt-bad-org, and still noplan-manifest-invalid.GitHub's numbers and characters are from its documentation, and none is measured against GitHub here. As the issue says, no run of this repository creates a real repository, and I did not run the real
gh. The fakeghin the new tests refuses by the documented numbers and characters and answersrepo viewas not found; the runs that refuse assert it was never called. A probe against a real organisation, for the numbers and for_, is lane 1's.Where #859's and #861's own lines changed, and why
--add-topicafter the repositories exist (#838) #859,adopt-project.py_repository_urls: its names block and its organisation call move into_refuse_names_nothing_keepsand_what_cannot_be_held, so thatcheckreads the same list. The words and codes are unchanged. The rename-first repository is spelledf"{org}/{names['assembly']}", which is the valuerepositories["assembly"]held.--add-topicafter the repositories exist (#838) #859,cmd_execute:adopt-topic-too-longmoves from just before_confirminto that list, in_repository_urls. Soexecutenow refuses it with the names and the organisation, before thegit ls-remotereads and_refuse_what_check_finds. It is still before_confirm, before anyghcall and before either leg, and scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's tests ([id-unconfirmed]among them) pass unchanged. Once this lands, scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's body sentence that one of those reads "can be shown first" stops being true.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861,cmd_check(for lane 1): the leg question is not asked after a name or an organisation finding of this list, and is still asked beside a topic finding (S1, above). It is one expression,([], [], []) if RENAMES_THE_LEGS.intersection(held_codes) else _leg_remote_findings(...), and_held_findingsnow gives the codes beside the findings and the remediation.tests/test_reader_refusals_named.py:test_check_names_a_hand_edited_orgrunscheckwithout--local-remote-dironorg: "@x". It now also expectsFINDING adopt-bad-org, becauseexecuterefuses that there.--add-topicafter the repositories exist (#838) #859 has it (family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, N7):refuse_a_topic_github_cannot_setsays "the topic this run sets on each repository", andinitsets it on one, the holder. It is scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's sentence in scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's helper, so this pull request does not change it.fake_githubis a copy oftests/test_github_name_limits.py's (family.py, adopt-project.py and the four tools'--org:initasks GitHub's limits on the topic, the--organd the holder's name before any repository exists,checkfinds whatexecuterefuses from one list, and an--orgGitHub gives no organisation (., a trailing hyphen,--) is refused where GitHub is asked (#863) #871's review, N4). Importing it would mean editing scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's test file while scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859 still moves. SonarCloud's duplication stays under 3%.Tests
The new file is
tests/test_github_limits_init_check_org.py, 93 tests: 82 from the first head and 11 from the fix round. Each bound is tested at N and N+1, and each refused character against theok-organdok_orgcontrols:family.py init, in process, with onlygit check-ref-formatallowed to start, so a refusal that passes came before_creator_name. Through the flags, a derived, an empty and a passed--id,--dry-runand the real run, each remediation word for word, and--local-remote-dirasking none of them.family.py initend to end against the fakegh: noghcall, and no work directory, for each refusal, under--dry-runand the real run. The control sets the topic at 39.no_process), andsetup-project.pyat its step (5), for the characters.adopt-project.py:executerefuses the first,checkfinds each, inexecute's order, and a bad id is left toplan-bad-id;checkthenexecuteon the same plan in GitHub mode, through scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859'sgithub_run: one code from both, andexecutemakes noghcall and no leg. After a name or an organisationcheckmakes noghcall; after a topic it asks the two leg views;checksaysplan okand asks adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's two leg views;checkunder--local-remote-dirat 246 and 247 characters, the disk's limit.ghcall (N5);org:both too long and misspelled is listed length first bycheck, and refused length first byexecute, byinitand the scaffold in both runs, and by setup's step (5) (N2);On
9ff260fb, the stack before this change, with only the first head's file put in:62 failed, 20 passed in 60.61s. The same 62 fail on0b4251b8, the first build of the stack (71936633with748c8e6a). The 20 that pass are the controls.On
66fc8f6b, the first head, with only the fix round's file put in:6 failed, 87 passed in 26.57s. The 6 are[id]and[derived-id]oftest_check_finds_what_execute_refuses_under_its_codeandtest_check_asks_the_legs_beside_a_topic_it_finds(S1);test_an_owner_past_the_length_bound_is_shown_cut_as_its_length_isandtest_check_lists_an_organisations_length_then_its_characters, which asserts N3's cut too; andtest_check_and_execute_read_one_list, because_held_findingsnow gives the codes as well. The other N2 tests and the N5 test pass there: they pin what the first head already did.Every run was on Linux, under a stub
ghfirst on PATH that exits 97, withGH_TOKEN=bogusand an emptyGH_CONFIG_DIR, each wrapped intimeout 2400, withpython3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt-<name> -o tmp_path_retention_policy=failed:99afbc3d, the head, with a clean tree:test_github_name_limits.py,test_reader_refusals_named.py,test_scaffold_manifest_values.py,test_adopt_manifest_values.pyandtest_adopt_github_exits.py, with the docs guardstest_agents_md_codes.py,test_cli_reference.py,test_update_add_codes_documented.pyandtest_repo_hygiene.py:562 passed in 306.80s (0:05:06);executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861'stest_adopt_execute_refusals.py,test_adopt_split_branch.pyandtest_gh_guard.py:236 passed in 329.80s (0:05:29);test_shape_doctor.py, once, becauseshape-doctor.pyimportsshape_materialize.py, withtest_scaffold_leg_path_collisions.py:540 passed, 3 skipped, 1 xfailed in 638.50s (0:10:38).99afbc3dmerged with maind6475f96, the one hunk resolved by hand and not committed: the new file, adopt-project.py:checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825'stest_adopt_check_plan_name.py,test_reader_refusals_named.py,test_github_name_limits.py,test_cli_reference.pyandtest_agents_md_codes.py:316 passed in 147.80s (0:02:27); andscripts/render-cli-reference.py --checkthere prints[ok] docs/cli.md is what the generator produces.66fc8f6b, the first head, with a clean tree:test_github_name_limits.py,test_scaffold_manifest_values.py,test_adopt_manifest_values.py,test_adopt_github_exits.py,test_adopt_project_is_the_source.pyandtest_reader_refusals_named.py, with the same four docs guards:573 passed in 378.82s (0:06:18);test_family.py,test_family_init_values.py,test_family_dry_run.py,test_free_plan_hint_reads.pyandtest_adopt_plan.py:390 passed, 20 skipped in 321.15s (0:05:21);executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's test files (test_adopt_execute_refusals.py,test_adopt_filter_repo_failure.py,test_adopt_late_failure.py,test_adopt_leg_keys.py,test_adopt_pins.py,test_adopt_split_branch.pyandtest_adopt_temp_work_roots.py), withtest_derived_id_refusals.py,test_family_init_landing.py,test_family_refuse_up_front.py,test_family_holder_states.py,test_dry_run_asks_remotes.py,test_dry_run_parity.pyandtest_setup_project_py.py:1039 passed in 1079.89s (0:17:59);test_shape_doctor.py, once, withtest_scaffold_leg_path_collisions.py:540 passed, 3 skipped, 1 xfailed in 777.21s (0:12:57).fdf5174c, the first head's commit on the first build of the stack: the first set,567 passed in 409.20s (0:06:49), and the second,390 passed, 20 skipped in 352.37s (0:05:52).Mutants
The fix round's 8 mutants, one at a time, each applied alone to
99afbc3din a scratch worktree and run against the new file, first with-xand then without it against the tests written for each item. All 8 were killed, and the worktree was left clean.s1-skip-after-any-finding: the leg question skipped after any finding, as at the first head (S1)test_check_finds_what_execute_refuses_under_its_code[id],[derived-id],test_check_asks_the_legs_beside_a_topic_it_findss1-topic-skips-too:adopt-topic-too-longadded toRENAMES_THE_LEGS(S1)test_check_finds_what_execute_refuses_under_its_code[id],[derived-id],test_check_asks_the_legs_beside_a_topic_it_findss1-never-skip: the leg question asked after every finding (S1)test_check_finds_what_execute_refuses_under_its_code[project],[org]and the four[org-*],test_check_lists_an_organisations_length_then_its_charactersn2-adopt-characters-before-length:_what_cannot_be_heldasks the characters before the length (N2)test_the_list_holds_an_organisations_length_before_its_characters,test_check_lists_an_organisations_length_then_its_characters,test_execute_refuses_an_organisations_length_firstn2-family-characters-before-length: the same infamily.py(N2)test_init_refuses_an_organisations_length_first[dry-run],[real]n2-scaffold-characters-before-length: the same inscaffold-project.py(N2)test_the_scaffold_refuses_an_organisations_length_first[dry-run],[real],test_setup_refuses_an_organisations_length_firstn5-held-after-a-name-refusal: the list also asked in theexceptof the naming question, RV871'sr-l2-held-after-a-name-refusal(N5)test_check_asks_none_of_them_of_names_that_do_not_classifyn3-whole-value: a misspelled value past 39 printed whole (N3)test_an_owner_past_the_length_bound_is_shown_cut_as_its_length_is,test_check_lists_an_organisations_length_then_its_charactersThe first head's 21 mutants, one at a time. Each was applied to
66fc8f6bin a scratch worktree and run with-xagainst the new file; the two that changeexecute's list also ran against #859's file. All 21 were killed, and the same 21 were killed onfdf5174c. The worktree was left clean. Since the fix round,l2-check-legs-after-a-findingis the rule for a name or an organisation finding only.l1-family-call-dropped:_checked_family_identityno longer calls the questionstest_init_one_past_githubs_bound_is_refused_before_any_process[derived-id-dry-run]l1-family-local-guard-dropped: asked under--local-remote-dirtootest_init_asks_none_of_them_of_a_local_run[o*40]l1-family-name-off-by-one:>=100test_init_at_githubs_bound_goes_on[name]l1-family-topic-droppedtest_init_one_past_githubs_bound_is_refused_before_any_process[derived-id-dry-run]l1-family-org-length-droppedtest_init_one_past_githubs_bound_is_refused_before_any_process[org-dry-run]l1-family-derived-named-as-passedtest_init_names_a_derived_id_as_derived_and_mends_it_with_an_idl1-family-help-dropped:--org's help loses its boundtest_init_help_says_each_bound_where_the_value_is_typedl2-check-call-dropped:checkno longer reads the listtest_check_finds_what_execute_refuses_under_its_code[project]l2-check-legs-after-a-finding: #861's leg question asked after onetest_check_finds_what_execute_refuses_under_its_code[project]l2-execute-ignores-the-listtest_check_and_execute_read_one_listl2-list-drops-the-topictest_the_list_holds_each_refusal_in_executes_orderl2-check-local-flag-flippedtest_check_finds_what_execute_refuses_under_its_code[project]l2-check-first-only:checkfinds only the firsttest_check_and_execute_read_one_listl2-bad-id-not-left:plan-bad-idtaken into the listtest_a_bad_id_is_left_to_its_own_refusall3-spell-never: the helper never refusestest_an_owner_github_does_not_spell_is_refused_by_name[dot]l3-underscore-refusedtest_an_owner_github_spells_is_not_refused[ok_org]l3-trailing-hyphen-let-bytest_an_owner_github_does_not_spell_is_refused_by_name[trailing-hyphen]l3-two-hyphens-let-bytest_an_owner_github_does_not_spell_is_refused_by_name[two-hyphens]l3-scaffold-call-droppedtest_the_scaffold_refuses_an_org_github_does_not_spell[dot-dry-run]l3-adopt-call-droppedtest_the_list_holds_each_refusal_in_executes_orderl3-family-call-droppedtest_init_refuses_an_org_github_does_not_spell[dot-dry-run]Merge checks
git merge-tree --write-treeof99afbc3d:d6475f96inadopt-project.py, one hunk:cmd_check'selse:afterexcept Refusal as exc:.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's conflict with adopt-project.py:checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825's squash (d7befb91). adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861's head70801c91conflicts with main in that one hunk, and so does this stack before this change (9ff260fb). scaffold-project.py, adopt-project.py and setup-project.py: the plan asks GitHub's limits on a name, a topic and an organisation where GitHub is, so an id over 39 characters no longer fails--add-topicafter the repositories exist (#838) #859's head merges with main cleanly.checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825's two lines inside theexcept, and theelse:after them. Resolved that way, the mergedadopt-project.pycompiles and the tests above pass.executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861.--add-topicafter the repositories exist (#838) #859 (495d4b33), adopt-project.py:executeasks about both leg repositories, on the disk or on GitHub, before it makes either, andcheckasks the same, so a code leg that already exists no longer leaves the spec leg made, and a--local-remote-dirthat cannot hold them no longer ends in a traceback afterplan ok(#798) #861 (70801c91), bootstrap.py: a validator's exit 2 endsbootstrap REFUSED, exit 2, an unreadable upstream path, cache or pin is no answer and a reftable leg's cherry-pick or revert is read from its ref store, so no traceback or ended pick follows (#764) #815 (05a1ebb3), repo_shape.py strips only YAML's space and tab, so a value a Unicode space opens reads as PyYAML reads it, and validate-manifest, bootstrap, bump-leg and update-shape refuse a branch holding a character that does not print or is white space (#715) #823 (7c20ba96), family.py add: the move back of a member's gitlink points its section of.gitmodulesback, sets aside first what the index holds in its way and puts back a member whose removal is staged, its closingmvis guarded and leaves a checkout to delete by hand, and--sparsefollows the sparse rules, so no printed line leaves a section naming another spelling or un-registers a member (#761) #828 (90649e53), adopt-project.py: a--source, or a plan'ssource.local_path, that names no repository's top level is refused by name before git reads anything else in it, so a file, a directory inside a repository, an unknown~user, an empty value,owner/.gitand a lost blob no longer end in a traceback, plan a subtree or reachgh(#796) #831 (db202f53), adopt-project.py: the cloneplanandcheckmake is claimed with the run's ownmkdirand removed only while it is still that directory, and a--work-diranother user can rename in is refused first, so a swappedsource-readonlyno longer turns the removal onto another directory (#803) #843 (08f7a283), update-shape.py, family.py and bump-leg.py: the branch HEAD is on is read as the bytes git wrote, so a branch ending in U+00A0 is no longer read as its neighbour, and a name that is not UTF-8 is refused by name before anything is written (#765) #849 (d98df568), family.py add: a section astray beside its gitlink, a variant of a row's path undermembers/, a link or checkout in a move's way, an unmerged entry and a staged or unmerged.gitmodulesare named first, so each remedy types clean (#840) #865 (f7072d08), tests: six skips on a failed git call ask the system first, so a failed git call fails the test and an encoding that cannot spell U+202E skips the bidi test (#853, #854) #869 (40531e19) and shape-doctor.py, family.py and the family root's bootstrap.py and validate-family.py: a holder'smembers:that is not a list, or holds an entry that is not a mapping, is said in the gate's words, so the doctor'smembersrow waits on thefamilyrow instead of calling the family empty,add,bump,remove,make bootstrapandmake siblingsrefuse it by name where each ended in aTypeError, read no members or committed the entry away, and--no-membersfinds the entry (#864) #870 (03fe51e6).checkprints the rename-first remedy asexecutedoes, and a plan's recordedsource: repository:holds its names where the live source names none, socheckandexecuteboth refuseplan-not-the-source(#716) #825 and adopt-project.py and shape_materialize.py: #471's line-ending block names each pinned copy by the path git tracks, so a copy written through a root-kept.github -> cino longer checks out with CRLF under* text=auto eol=crlfand failsvalidate-pins.pyafterexecuteverified (#807) #844 have landed, asd7befb91andd6475f96.Lane: openreposhape-3 (openRepoShape-3)
🤖 Generated with Claude Code
Summary by Sourcery
Validate GitHub naming constraints before remote creation and align adoption checks with execution refusals.
New Features:
adopt-project.py checkreport the same name, organisation, and topic constraints thatexecuteenforces.Bug Fixes:
checkfrom reporting plans as valid whenexecutewill reject their names or GitHub-related values.Enhancements:
checkreports all applicable findings whileexecutestops at the first refusal.Documentation:
Tests: