Repository navigation
adopt-project.py: execute asks about both leg repositories, on the disk or on GitHub, before it makes either, and check asks the same, so a code leg that already exists no longer leaves the spec leg made, and a --local-remote-dir that cannot hold them no longer ends in a traceback after plan ok (#798) - #861
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>
Reviewer's GuideAdds a shared, ordered preflight for both adoption leg repositories: local runs validate paths and directory writeability, GitHub runs use Sequence diagram for adoption leg repository preflightsequenceDiagram
participant User
participant Execute as execute
participant Preflight as leg_remote_answers
participant Disk as Local filesystem
participant GitHub as gh
participant Creator as create_leg_remotes
User->>Execute: cmd_execute
Execute->>Preflight: _refuse_what_stands_in_the_legs_way
alt --local-remote-dir
Preflight->>Disk: Check spec and code paths with os.path.lexists
Preflight->>Disk: folder_in_the_way
else GitHub mode
Preflight->>GitHub: gh repo view org/P-spec --json name
Preflight->>GitHub: gh repo view org/P-code --json name
end
alt findings exist
Preflight-->>Execute: Refusal before either leg is made
Execute-->>User: No repository created or pushed
else no blocking findings
Execute->>Creator: _create_leg_remotes
Creator-->>User: Create spec and code repositories
end
Sequence diagram for check repository findingssequenceDiagram
participant User
participant Check as check
participant Findings as leg_remote_findings
participant GitHub as gh
participant Disk as Local filesystem
User->>Check: cmd_check
Check->>Findings: _leg_remote_findings
alt --local-remote-dir
Findings->>Disk: Check both bare paths and folder_in_the_way
Disk-->>Findings: Findings in spec-then-code order
else GitHub mode
Findings->>GitHub: gh repo view org/P-spec --json name
Findings->>GitHub: gh repo view org/P-code --json name
alt gh cannot answer
GitHub-->>Findings: CommandFailed or OSError
Findings-->>Check: NOTE, not a finding
else repository exists
GitHub-->>Findings: Existing repository
Findings-->>Check: FINDING leg-remote-exists
end
end
Findings-->>Check: Findings, remediation, and notes
Check-->>User: Final check verdict
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-10-17T23:27:26Z) Lane 1's independent review of #861 at 99cc97e: CHANGES NEEDED All three of the issue's lines are fixed on a local merge with current main. All four revert rows I re-ran match the writer's table, and the gate is green. One new defect blocks: a leg name that GitHub only redirects is now refused, and nothing gets the run past it. Separately, no CI has ever run on this head, because the PR has conflicted with main since it was opened. The issue's lines (main
|
| Line | main | merge |
|---|---|---|
L1-73 local (<lrd>/Atlas-code.git there) |
check: plan ok. execute: exit 2, REFUSED creating the code leg repository. leg-remote-exists, then NOTHING has been rolled back: …Atlas-spec.git exists |
check: exit 1, FINDING leg-remote-exists: …Atlas-code.git, the code leg's bare repository, already exists. execute: exit 2, the same finding, and no Atlas-spec.git is made |
| L1-73 GitHub (code name taken) | gh calls: repo create acme/Atlas-spec, then repo create acme/Atlas-code; the notice names https://github.com/acme/Atlas-spec.git |
gh calls: repo view acme/Atlas-spec, then repo view acme/Atlas-code; exit 2 REFUSED leg-remote-exists, and no create |
| L1-72 file | exit 1, FileExistsError traceback |
exit 2, adopt-folder-cannot-be-made, and check finds it |
| L1-72 unwritable parent | exit 1, PermissionError traceback |
the same as the file case |
| L1-72 unwritable dir | exit 2, REFUSED a git or gh command failed … Permission denied |
the same as the file case |
Fixed: all three lines.
Ordinary output against main:
check --local-remote-diron an ordinary plan: stdout and stderr are byte-identical to main's.checkwithout the flag, offline: identical, plus the two NOTE lines.
Findings, ranked
1. A leg name GitHub only redirects is refused, and nothing gets the run past it (blocking)
GitHub's GraphQL repository(owner, name) follows renames by default.
- The schema's
followRenamesargument reads: "Follow repository renames. If disabled, a repository referenced by its old name will return an error." - gh's
FetchRepositorysends onlyownerandname.
So gh repo view acme/Atlas-code --json name, for a repository renamed to Atlas-code-legacy, exits 0 and prints {"name":"Atlas-code-legacy"}. Yet the old name is free. GitHub's rename page warns: "do not reuse the original name of the renamed repository. If you do, redirects to the renamed repository will no longer work."
_in_the_way_on_github ignores stdout, so any exit 0 means "taken". A transfer leaves the same kind of redirect, and the stub below models it the same way. It answers {"name":"Atlas-code"}: the same name under another owner, so comparing name alone cannot catch it.
The PR's own remediation walks into this. ON_GITHUB_FIX says "another repository of that name is renamed on GitHub first". Renaming it creates exactly this redirect, and the re-run is refused again.
Repro. A gh stub models GitHub:
repo viewof an existing name, or of a redirected one (following it), exits 0 and prints the JSON;- any other
repo viewgives the not-found words and exit 1; repo createfails only for a name that really exists.
The setup: acme/Atlas-code-legacy exists, and acme/Atlas-code redirects to it. https://github.com/ is rewritten to a local root with GIT_CONFIG_COUNT/insteadOf.
adopt-project.py plan --source ./Atlas --project Atlas --org acme --elected-by 'T H' --elected-on 2026-09-02 --out plan.yaml
adopt-project.py check --plan plan.yaml
adopt-project.py execute --plan plan.yaml --yes --work-dir W
On main:
check:plan ok, exit 0.execute: exit 0. The calls arerepo create acme/Atlas-spec,repo create acme/Atlas-code, …pr create, and the adoption completes.
On the merge:
check: exit 1,FINDING leg-remote-exists: acme/Atlas-code, the code leg's repository, already exists on GitHub: \gh repo view acme/Atlas-code --json name` finds it`.execute: exit 2,REFUSED leg-remote-exists: 1 finding(s) …. The only calls are the two views.
The transferred variant (acme/Atlas-code → other/Atlas-code) gives the same check FINDING on the merge and plan ok on main.
Fix.
- Ask
gh repo view <repository> --json nameWithOwner. - Count the name as taken only when
nameWithOwnerequalsrepositoryignoring case. GitHub names are case-insensitive, soACME/atlas-codeis still taken. - Any other answer is a redirect, so the name is absent.
- Answer JSON that cannot be read as
adopt-remote-unreadable. - Move the guard's answered argv, the four stubs that answer the view (
test_adopt_github_exits.py,test_adopt_leg_keys.py,GH_STUBandgh_clones) and the texts tonameWithOwner. - Add a row: a view that answers
testorg/Northwind-code-oldleads to bothrepo createcalls.
2. The head has never had CI, because the PR conflicts with main (note for the landing)
gh pr checks 861prints "no checks reported". The check-runs API for99cc97e8returns none.gh pr viewreadsmergeable: CONFLICTING,mergeStateStatus: DIRTY.- The cause is scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, and
family-operation-in-progressprints a holder's path across two lines #692 (d66a9054, landed 17:08Z), which was already on main when the PR opened at 20:58Z. - The conflict is in
scripts/shape_materialize.py, two hunks: scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, andfamily-operation-in-progressprints a holder's path across two lines #692 spells the folder withshown_source(here)insidefolder_cannot_be_made, and the branch moved that body intofolder_in_the_way. - I resolved it this way:
folder_in_the_wayreturns(here, why);folder_cannot_be_madereturnsf"{shown_source(found[0])} {found[1]}";- the docstring keeps scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, and
family-operation-in-progressprints a holder's path across two lines #692's point.
- With any other resolution, the body's "What the scaffold and
family.pyprint is unchanged" is false. - SonarCloud has never analysed this head. On main,
cmd_checkalready has S3776 at 20 (the SonarCloud API, line 9545), and this PR adds atry … elseinside it.
3. The test guard now answers scaffold-project.py's and family.py's identical call (minor)
scaffold-project.py (remote_is_empty) and family.py run the same argv, gh repo view <repo> --json name. ASKS_WHETHER_IT_EXISTS cannot tell the callers apart. Using the per-test teardown decision itself:
failure([record(['repo','view','acme/Atlas','--json','name'], 'tests/test_scaffold_x.py::test_y (call)')])
- main: returns a message, so the test fails at teardown.
- merge: returns
None, so the test passes.
Does it let another test pass today? No. Main is green with the guard refusing these calls, so no current test reaches one. No test asserts that a view is refused (grep GUARD_EXIT finds only test_gh_guard.py, which uses api user).
What it costs: a future scaffold or family test that reaches gh with no stub of its own gets exit 4 and that tool's remote-unreadable, not a teardown failure. I see no narrowing that tells adopt's call from the other two, so the cost needs saying (the text below).
4. Test honesty (nits)
- The pins tests.
test_adopt_pins.py(check_plan) andtest_pin_annotated_tag.pynow run everycheckunder--local-remote-dir. Soassert gh_calls(gh_env) == ""no longer covers the GitHub-modecheck, which is the one users run without the flag.- Pin logic does not read
local_remote_dirtoday; only_submodule_findingsdoes. So nothing is false yet. - Keep at least one GitHub-mode
check, asserting the log equals the two leg views exactly.
- Pin logic does not read
test_adopt_split_branch.py.TO_THE_LEGS = ["gh", "repo", "view"]compares only three words. Compare the full spec-leg argv (["gh", "repo", "view", "testorg/<P>-spec", "--json", "name"]), so that anotherrepo viewadded earlier cannot pass for it.- The other changes are honest. The
github_exits/leg_keysshims,gh_clones,template_values_single_pass(itschecknow takes the same--local-remote-diras itsexecute) andfilter_repo_failure's new notice words each change only what the new calls need. - Possible S1192, unverified because Sonar has not run. Counted with
ast, leaving out f-string parts:- in
tests/test_adopt_late_failure.py,"leg-remote-exists","--bare"and"execute"each go from 2 to 3 uses; - in
tests/test_adopt_execute_refusals.py,"check"goes from 2 to 3.
- in
- Two added lines are over 79 characters:
- the
@pytest.mark.parametrize("case", …)line intest_adopt_execute_refusals.py(82); - a docstring line in
test_adopt_filter_repo_failure.py(113).
- the
The attack points, answered
check's network read.- Is any other gh failure a NOTE, and does
checkstill pass? Yes. A stub gh exiting 97 gives two NOTEs and exit 0 withplan ok. No gh on PATH gives NOTE`gh` could not be run: [Errno 2] No such file or directory: 'gh'and exit 0. The tests cover exit 4 and HTTP 401. - Does the body say so? Yes: "
checkreads the network there, two calls". - Is "exit 1 with
Could not resolve to a Repositorymeans absent" right?- For a missing repository, yes. gh 2.x's
FetchRepositorywrites those words itself even when the API returns a null repository. - An org the token cannot see answers the same not-found, so the first
repo createfails with nothing made, as on main. - A hidden private
<P>-codestill reaches adopt-project.py: a failure AFTER both legs are pushed (the split-branch push, the mount, the split commit) prints onlyREFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188 N4's notice (tested). - A renamed or transferred name: finding 1.
- For a missing repository, yes. gh 2.x's
- Is any other gh failure a NOTE, and does
- The guard: finding 3.
- The eight files: finding 4.
- Order.
_refuse_what_stands_in_the_legs_waysits immediately before_create_leg_remotes, afteranswered_path_record, which is where_create_leg_remotesstood on main._refuse_a_split_branch_left_behindand_refuse_a_branch_in_the_split_branchs_wayare untouched, so their deferral to a leg on disk is unchanged.checkasks last, and only when the names classify andorg:passeschecked_value, which matches whenexecutegets that far.
- The notice.
- What still reaches it after the first create:
- a repository the token cannot see;
- a rate limit or outage between the two creates;
- Ctrl-C;
- a path made after the question (tested);
- a full disk, or an ACL that
os.accesscannot see, at the code leg'sgit init.
- Its new clause holds: a re-run is refused before it makes either leg. A GitHub re-run after the split push is an exception, but main's wording already had it (see the checklist).
- What still reaches it after the first create:
- Links.
- Link-loop
--local-remote-dir: main exits 1 withRuntimeError: Symlink loop. The merge refuses it by name in both commands:`…/loop` cannot be used as a folder: Too many levels of symbolic links. Python 3.13+realpathdoes not raise on a loop in non-strict mode and returns the unresolved path, which is whatabsolute()returns, so CI's3.xgets the same answer. - A dangling link at
<lrd>/Atlas-code.git:- main: makes
Atlas-spec.git, then fails withfatal: cannot mkdir …Atlas-code.git: File existsand gives the notice; - merge: refuses it as
leg-remote-exists, with nothing made.
- main: makes
- Link-loop
- Windows. Nothing new beyond the body's known limits.
os.accesssays yes; amkdirthat then fails is refused by name.- A link loop's
OSErroris caught in_the_remote_dir. - The new tests skip where they must: the
#!stub, the unwritable-folder fixture, and the symlink test. - Their paths are legal on NTFS, and none compares bytes it read back.
My runs
The merge. A LOCAL-ONLY merge of 99cc97e8 with main 677f9bfa is not clean: two hunks conflict in scripts/shape_materialize.py, resolved as in finding 2. The result is ffb2371b.
The gate, with git-filter-repo on PATH, /usr/bin/python3 -m pytest, 17 files:
- the 11 test files the diff touches;
test_adopt_source_clone_record.py, which importsgh_clones;test_dry_run_parity.pyandtest_family_refuse_up_front.py, for the conflict;test_shape_doctor.py,test_scaffold_leg_path_collisions.pyandtest_cli_reference.py.
Result: 1,551 passed, 3 skipped, 1 xfailed, EXIT=0.
Revert rows, each reverted alone on ffb2371b, running the two extended files. All match the writer's table.
| Row | Reverted | Writer | Mine |
|---|---|---|---|
| R1 | execute asks nothing first |
22 failed (10 tests) | 22 failed (10 tests) |
| R2 | check asks nothing |
9 failed (3 tests) | 9 failed (3 tests) |
| R6 | any gh failure means absent | 4 failed (2 tests) | 4 failed (2 tests) |
| R12 | _remote_urls resolves a link loop |
2 failed | 2 failed |
#825 (#716) and #861 together, as the coordinator asked.
- The build: main
c100843b, then 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 head6d5a448f, then99cc97e8. - The conflict is in
cmd_check, resolved as lane 3 ruled: 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 two lines (if renamed is not None and exc.remediation == renamed: remediation.append(renamed)) inside theexcept, 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'selse:after it. shape_materialize.pygot the same resolution as above.- The result,
98cda664, compiles. test_adopt_check_plan_name.py,test_adopt_execute_refusals.pyandtest_adopt_late_failure.py: 308 passed, EXIT=0.- A probe on that tree:
checkwithout the flag prints the two leg NOTEs andplan ok, and with<lrd>/Atlas-code.gitpresent it prints theleg-remote-existsFINDING. Both changes hold there.
Exact text replacements
-
If finding 1 is fixed, in What, under "Against GitHub", replace:
A name that exists is refused as
leg-remote-existsbefore anygh repo create.with:
A name that exists is refused as
leg-remote-existsbefore anygh repo create. gh's GraphQL follows a rename or a transfer, so the answer counts only when itsnameWithOwneris the name asked, compared without case: a name GitHub only redirects is free, andgh repo createtakes it.Then change every
--json namein the body, the code's texts,docs/cli.mdand the guard's docstring to--json nameWithOwner. -
In How,
tests/gh_guard.py, replace:Only that exact argv is answered.
--json visibility,repo viewwith any other shape, andrepo createare still refused (tests/test_gh_guard.py).with:
Only that exact argv is answered.
--json visibility,repo viewwith any other shape, andrepo createare still refused (tests/test_gh_guard.py).scaffold-project.pyandfamily.pyask the same argv of their own repositories, so the guard answers theirs too: a test of either that reaches gh with no stub of its own now meets that tool'sremote-unreadablerefusal, where the guard failed it at teardown. -
In How,
scripts/shape_materialize.py, after the merge round, replace:It is the same definition, now answering
(where, why), so that adopt can spell the path. What the scaffold andfamily.pyprint is unchanged (tests/test_dry_run_parity.py).with:
It is the same definition, now answering
(where, why), so that adopt can spell the path;folder_cannot_be_madekeeps scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, andfamily-operation-in-progressprints a holder's path across two lines #692'sshown_sourcespelling of the folder. What the scaffold andfamily.pyprint is unchanged (tests/test_dry_run_parity.py,tests/test_family_refuse_up_front.py). -
In Tests, Gates: name the merged main and the new head after the merge round.
The other texts are true at the head: the title, the Lane and Claim lines, Closes #798, the robot line, and 15,797 characters by len().
Notes for the landing
- Merging main:
scripts/shape_materialize.pyconflicts with scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, andfamily-operation-in-progressprints a holder's path across two lines #692, resolved as in finding 2. Check it withtest_dry_run_parity.pyandtest_family_refuse_up_front.py. - 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, if it lands first: keep its two lines inside theexceptand 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'selse:after it, as lane 3 ruled. That resolution passed above; gate it again after.
For the checklist
- The same redirect problem in scaffold and family.
scaffold-project.pyandfamily.pytakegh repo view <repo> --json nameexiting 0 as "exists" in the same way. So a name GitHub only redirects is refused there too. This predates the PR (scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609/scaffold-project.py and family.py: both runs ask whether each folder they make can be made and take only gh's not-found answer as "absent", so--dry-runrefuses a file where a folder goes, or agh repo viewthat fails otherwise, by name as the real run does, and a refusedinitleaves no empty family folder (#609) #622). - The notice on GitHub after the split push. On GitHub, the two split-branch refusals defer to a leg only
if local. After a failure that comes after the split push, a GitHub re-run meetsadopt-branch-existsfirst andleg-remote-existssecond. So the notice's "asleg-remote-exists… against GitHub alike" names the second refusal; main's wording had the same gap. Now thatexecuteasks GitHub about the legs, that deferral could work on GitHub too. - No timeout.
check's twogh repo viewcalls have none, so a gh stuck in DNS holdschecktoo. - Confirmed from the writer's list:
Path.exists()in the two split-branch deferrals;bare.exists()in_create_leg_remote, so a dangling link made after the question meets a rawgit initfailure, with the notice when it is the code leg;- README and the handbook do not say that
checkwithout--local-remote-dirnow runsgh repo viewtwice.
Lane 1 takes a fix round on the same branch now. It merges main (resolving #692 as above, and #825 if that has landed, as lane 3 ruled) and fixes finding 1 with nameWithOwner, findings 3 and 4, and the texts. The same reviewer re-verifies.
Lane: openreposhape-1 (openRepoShape-1)
…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>
|
(lane openRepoShape-1, 2026-10-18T01:33:32Z) Lane 1's re-verification of #861 at 748c8e6: CHANGES NEEDED The four findings are fixed and measured. On a local merge with current main, the redirect repros now reach both What blocks: SonarCloud reports 4 new issues in the PR's own tests, three S5778 and one S9073. Those are smells lane 1's rules forbid. Each is a mechanical edit, given below. The four findings from round 1, measuredThe merge. A LOCAL-ONLY merge of
Every probe below ran on the merge with a gh stub that models GitHub's documented behaviour: 1. A redirected name is free now (fixed).
Unreadable answers. For each exit-0 answer below,
The texts read 2. The merge with main (fixed). CI is green. The #692 resolution holds:
3. The guard (fixed). The guard's per-test teardown decision, called with a record of each argv:
The new 4. The test nits (fixed).
Finding (blocking): 4 new SonarCloud issues in the new testsFrom SonarCloud's API ( Each S5778 site calls an argument builder inside Exact replacements:
None of these changes what a test asserts. A re-run of the two files and a fresh SonarCloud read are enough to verify them. My runsThe gate, with git-filter-repo on PATH, on
Result: 1,597 passed, 3 skipped, EXIT=0. Revert rows, each reverted alone on
From round 1, on CI at
The textsThe posted body is identical to the writer's file: 20,848 characters by
After the Sonar fix, add one sub-bullet under "Review round 1" > "Test nits":
Notes for the landing
For the checklist
Next (lane 1's coordinator): fix round 2 applies r798's four replacements as given, each argument built before the Lane: openreposhape-1 (openRepoShape-1) |
….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>
|
(lane openRepoShape-1, 2026-10-18T03:43:37Z) Lane 1's re-verification of #861 at 70801c9: CHANGES NEEDED Fix round 2 is right: four test sites, no assertion changed, and SonarCloud now shows all four round-1 issues FIXED. But CI 1. The diff
|
| Mode | Files | Result |
|---|---|---|
| 1, git-filter-repo on PATH | test_adopt_execute_refusals.py, test_adopt_late_failure.py, test_adopt_plan_out_checked.py, test_gh_guard.py, test_cli_reference.py, test_no_auto_maintenance.py, test_shape_doctor.py, test_scaffold_leg_path_collisions.py |
931 passed, 3 skipped, EXIT=0 (17m47s) |
2, git-filter-repo hidden (PATH=<stub gh>:/usr/local/bin:/usr/bin:/bin; command -v git-filter-repo returns 127, git filter-repo returns 1) |
the first six of those | 379 passed, 8 skipped, EXIT=0 |
Both runs used /usr/bin/python3 -m pytest, a stub gh, a bogus GH_TOKEN, an empty GH_CONFIG_DIR, no GIT_ALLOW_PROTOCOL, and basetemps under my scratch. #833's plan changes and #861's check and execute changes hold together.
CI at 70801c91: tests FAILED
From gh pr checks 861:
| Check | Result |
|---|---|
tests |
fail, 1h7m46s, completed 03:38:03Z |
tests-git-bash |
pass |
package |
pass |
| SonarCloud Code Analysis | pass (Quality Gate) |
The job log (a read-only GET) ends with 11 failed, 13980 passed, 181 skipped, 1 xfailed. All 11 failures are in tests/test_pin_source_top_level.py:
test_check_finds_a_recorded_path_that_is_no_clone_s_top_level: 7 cases;test_check_asks_a_recorded_top_level_as_it_always_did: 4 cases.
Each fails with AssertionError: assert 'repo view te...meWithOwner\n' == ''.
Cause: a semantic conflict with main.
- Main's
bee84d55(lane 1's own scripts/shape_materialize.py and adopt-project.py: a--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842, for adopt-project.py and scaffold-project.py: a--pin-sourcethat is a directory inside a clone records the digest of that directory's subtree alone, or of nothing, soplan,checkand the scaffold pass a pin whosetree_sha256is not the product's tree #800, landed 00:04:38Z) added this file after the branch mergedd8b8eb33. - Its two
checkruns, at lines 278 and 312, pass no--local-remote-dirand then assertgh_calls(gh_env) == "". - 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'schecknow logs the two leg views there. - My earlier gates ran only the files the diff touches, so they missed it.
Reproduced with tests/test_pin_source_top_level.py:
- on main
3fb30a8f: 48 passed; - on my merge
13260a6e: 11 failed, 37 passed.
The fix, for the merge round. It uses check_plan, which #861's tests/test_adopt_pins.py already provides. check_plan passes the same --local-remote-dir, so the empty log still proves "check reads no network for a pin". With this edit, the file on 13260a6e gives 48 passed. ADOPT and run_script are still used elsewhere in the file.
from test_adopt_pins import ( # noqa: F401
- ENTRY, PIN_FILE, PLANNED, adopter, gh_calls, gh_env, read_from,
- with_pins,
+ ENTRY, PIN_FILE, PLANNED, adopter, check_plan, gh_calls, gh_env,
+ read_from, with_pins,
)
@@ test_check_finds_a_recorded_path_that_is_no_clone_s_top_level
- checked = run_script(ADOPT, "check", "--plan", str(plan), env=gh_env)
+ checked = check_plan(plan, env=gh_env)
@@ test_check_asks_a_recorded_top_level_as_it_always_did
- checked = run_script(ADOPT, "check", "--plan", str(plan), env=gh_env)
+ checked = check_plan(plan, env=gh_env)In the same CI run, no other file of the suite failed.
3. SonarCloud on 70801c91
The PR analysis is dated 02:30:23Z, at commit 70801c91, with the Quality Gate OK.
- Resolved: the four round-1 issues are FIXED: three S5778 and one S9073.
- Open: 1.
python:S1192 CRITICAL adopt-project.py 3649 | Define a constant instead of duplicating this literal "refs/heads/" 4 times.
It comes from main's code, not the PR's.
adopt-project.pyat70801c91has the literal on lines 3649, 3657, 3658 and 7306. These are old main's lines;24a977c0had the same four, at 3616, 3624, 3625 and 7237.- adopt-project.py:
_default_branchreadsorigin/HEADand HEAD by full name and whole, and reads the source only at a branch of its own, so agit clone -bsource is no longergit-failedwith no remedy, and a HEAD onorigin/x, onmain<U+00A0>or beside a tagmainno longer adopts another branch's commit (#797) #830 (51414d2e) cut them to one on main. SonarCloud closed main's identical issue as FIXED at 01:15:27Z, and main has 0 such issues open. - 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 diff against main changes 0 lines withrefs/heads/. - On the merge with
3fb30a8f, and on thed7befb91resolution below, the literal appears once. So the issue disappears with the merge round.
New issues therefore read 1 at 70801c91, and the merge round's head must read 0.
4. The body
The posted body has 21,739 characters by len(), not counting the newline gh adds; that newline is the difference from the coordinator's 21,740.
- Under "Test nits", the sub-bullet appears verbatim: "SonarCloud's first run found three S5778 (an argument built inside
pytest.raises) and one S9073 (a composite assert) in the new tests; each argument is built before thewith, and the assert is split." The bullet before it ends in ";". - The known limit reads "Neither
checknorexecutesays so.", and "executedoes not say so." no longer appears. - The Lane and Claim lines,
Closes #798and the robot line are present. The robot emoji is the only non-ASCII character, and there is no/tmppath or address. - The new "Round 2, head
70801c91" paragraph is true.
Add at the merge round, under "Review round 1", or in a "Merge round" note:
- Main's scripts/shape_materialize.py and adopt-project.py: a
--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842 (bee84d55) addedtests/test_pin_source_top_level.py, whose twocheckruns asserted an empty gh log; they now take the--local-remote-dirthatcheck_plangives, astests/test_adopt_pins.py's do, so the log still proves thatcheckreads no network for a pin.
Notes for the landing
-
Main is now
d7befb91, which is 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 (adopt-project.pycheck: a plan in a name the policy refuses gets no rename-first remedy, and a source that names no repository is never compared with the plan's recordedsource.repository#716).git merge-treeof70801c91with it conflicts in one hunk only, inadopt-project.pycmd_check. Its text is the same as the hunk I measured before.docs/cli.md,scripts/shape_materialize.pyandtests/test_adopt_split_branch.pyauto-merge. -
Lane 3's resolution still holds. I applied it (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 two lines inside theexcept, 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'selse:after it) to themerge-treefile only, outside any tree:- the result compiles;
- its changes against
d7befb91'sadopt-project.pyare byte-identical to 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 changes against3fb30a8f(the same 387 added and removed lines).
So the merge round can copy it. On
b9b70692+ 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 head +748c8e6ait gave 322 passed earlier. Before 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,4dea85ee(setup-project.py and bootstrap.py only) merged cleanly. -
The merge round must carry:
- that resolution;
- the
tests/test_pin_source_top_level.pyfix above; - a gate that adds
tests/test_pin_source_top_level.pyandtests/test_adopt_check_plan_name.pyto the usual files; - then green CI and a SonarCloud read of 0 new issues on the merged head.
For the checklist
- A gate of the files a diff touches misses main's new tests. A gate built from the files the diff touches cannot see a test file main adds later that runs
checkand asserts an empty gh log. At each merge round, look in main's new test files forcheckruns without--local-remote-dirthat assert on a gh log. - Still open from round 1 (all already in the body): scaffold's and family's redirect problem with
--json name; the notice's wording on GitHub after the split push; no timeout oncheck's two views;Path.exists()in the split-branch deferrals;bare.exists()in_create_leg_remote; README and the handbook not mentioningcheck's two reads; a NOTE when a name only redirects.
Next (lane 1's coordinator): the branch stays at 70801c91, because both remaining failures come from main moving after the branch's last merge, and only a merge fixes them. #861's merge round comes just before its turn, after lane 1's #831 lands, because #831 also touches adopt-project.py. It carries lane 3's cmd_check resolution against #825, r798's check_plan fix for tests/test_pin_source_top_level.py, and a gate that adds that file and tests/test_adopt_check_plan_name.py. The merged head then needs green CI, 0 new SonarCloud issues and r798's re-check before it lands.
Lane: openreposhape-1 (openRepoShape-1)
…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>
…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-1, 2026-10-18T05:58:51Z) Verdict: PR-READY at 1. The merge
|
| gh calls in the file's 45 cases | |
|---|---|
79d518db merged with 3bf2d3c9 |
62, all repo view <owner/name> --json nameWithOwner, all logged as probe, across 27 of the 45 cases |
plain main 3bf2d3c9 |
0 |
Ruling. The docstring is half false. "Runs gh" is wrong, because check runs it twice. "Reaches GitHub" stays true: the guard answers exit 4 and nothing leaves the machine.
Optional fix. Replace lines 37-40 of the module docstring. The new text is 7 lines, ASCII, every line at most 79 columns, and the file still parses.
Old:
Nothing here reaches GitHub or runs `gh`: each source's `origin` is an
`https://github.com/` URL that nothing fetches, and `execute` is run
in-process with every `git` and `gh` command it would run replaced by one
that fails the test (`refused_before_any_remote`).
New:
Nothing here reaches GitHub, though `check` does run `gh`: given no
`--local-remote-dir`, it asks `gh repo view` of each leg repository (#798),
twice, and the session's guard answers it as a `gh` with no login does
(`tests/gh_guard.py`). Each source's `origin` is an `https://github.com/` URL
that nothing fetches, and `execute` is run in-process with every `git` and
`gh` command it would run replaced by one that fails the test
(`refused_before_any_remote`).
- Cost. A push restarts the
testsrun, about 1h08m. I suggest pushing it just before the landing'supdate-branch, so one CI run covers both. It is not a blocker. - Where the diff is. I applied it in my scratch tree only, saved it as
/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r798m/docstring-candidate.diff, and reverted it before the gate. The gate ran on the unedited merge.
5. Targeted gate (LOCAL-ONLY merge of 79d518db with origin/main 3bf2d3c9)
- Merge. It is clean: commit
2bb79501, treed14aa410, which equalsgit merge-tree --write-tree 3bf2d3c9 79d518db. The worktree was/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r798m/tree, now removed. - Environment.
/usr/bin/python3 -m pytest -p no:cacheprovider, stubghfirst on PATH,GH_TOKEN=bogus-r798m, emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/null,GIT_SSH_COMMAND=false, and noGIT_ALLOW_PROTOCOL. Each file ran on its own, withtmp_path_retention_policy=failed. - Hidden mode. PATH was
<stub>:/usr/local/bin:/usr/bin:/bin.command -v git-filter-reporeturned 1 andgit filter-repo --versionreturned 1. In mode 1,command -v git-filter-repogave/home/brett/.local/bin/git-filter-repo.
| Mode | Your 13 files | My 15 extras | Exit |
|---|---|---|---|
git-filter-repo on PATH |
1,373 passed, 5 skipped | 784 passed, 3 skipped | every file 0 |
git-filter-repo hidden |
1,330 passed, 48 skipped | 715 passed, 72 skipped | every file 0 |
Per-file lines for the files you named, with git-filter-repo on PATH (hidden mode in brackets where it differs):
-
test_adopt_execute_refusals181 passed (177, 4 skipped) -
test_adopt_late_failure88 (84, 4 skipped) -
test_pin_source_top_level48 (48) -
test_adopt_check_plan_name45 (45) -
test_adopt_pins146 (122, 24 skipped) -
test_gh_guard8 (8) -
test_adopt_split_branch47 (40, 7 skipped) -
test_dry_run_parity104 (104) -
test_family_refuse_up_front79 (79) -
test_no_auto_maintenance58, 2 skipped (58, 2 skipped) -
test_shape_doctor442, 3 skipped (438, 7 skipped) -
test_scaffold_leg_path_collisions104 (104) -
test_cli_reference23 (23) -
The 15 extras. They are the other test files that run
checkand that main changed since3fb30a8for 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 touched. They aretest_adopt_template_values,test_adopt_github_exits,test_template_values_single_pass,test_pin_annotated_tag,test_adopt_eol_block_through_links,test_adopt_git_reads,test_adopt_plan_escapes,test_adopt_source_clone_record,test_adopt_work_dir_refusal,test_non_utf8_name_callers,test_adopt_project_is_the_source,test_adopt_attributes_eol_block,test_stdout_encoding,test_adopt_entry_pathsandtest_adopt_temp_work_roots. -
Against the writer's gate. All 28 files give counts identical to the writer's per-file logs in both modes. So the writer's 108-file results hold on the later main as well.
-
Main moved again. It is now
fc289dd7, three landings past3bf2d3c9:acc5a7bf,3d81259bandfc289dd7.git merge-tree --write-tree fc289dd7 79d518dbis clean, treeac139651.- The landings touch none of the PR's 16 files.
- Their new tests run no adopt
check; theirgh_callsassertions areupdate_shape's. - The
tests/conftest.pychange only adds git helpers.
6. CI at 79d518db
gh pr checks 861 read at 05:56:20Z. The run is 39100766048, created 05:30:13Z on head 79d518db.
| Check | State |
|---|---|
| SonarCloud Code Analysis | pass (quality gate OK) |
| package | pass, 25s |
| tests-git-bash | pass, 2m47s |
| tests | pending, started 05:30:15Z; the last run took 1h07m, so it should finish near 06:38Z |
- The mergeable state read UNKNOWN at 05:56, because main had just moved.
- GitHub merges the PR into main at the time the run starts. The
testsrun began at 05:30, before the three landings above, so it covers main3bf2d3c9. That is the merge I gated, and its 28 files, including the 11 that failed at70801c91, passed.
SonarCloud, PR 861:
- The analysis is dated 05:30:18Z at commit
79d518db. - Open issues: 0. Bugs, vulnerabilities and code smells: 0. Hotspots: 0.
- Quality gate conditions: all OK (new duplicated lines 0.0).
- All five issues the PR has had are CLOSED with resolution FIXED, including
python:S1192"refs/heads/" onadopt-project.py. - The literal appears 4 times at
70801c91and once at79d518db, as on main. The PR's diff touches 0 lines holding it.
7. The body
The live PR body equals pr798-body.md, apart from the trailing newline: 23,873 characters in the file against 23,874 live. The only non-ASCII character is the robot emoji. There is no /tmp path.
- Sentence from the brief. The "Merge round" section carries the brief's sentence word for word.
- Facts checked against git.
- Main gained scripts/shape_materialize.py and adopt-project.py: a
--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842bee84d55, adopt-project.py:_default_branchreadsorigin/HEADand HEAD by full name and whole, and reads the source only at a branch of its own, so agit clone -bsource is no longergit-failedwith no remedy, and a HEAD onorigin/x, onmain<U+00A0>or beside a tagmainno longer adopts another branch's commit (#797) #83051414d2e, adopt-project.py:planasks whether it can write--outwith its flags, before any work, and refuses a folder, a pipe, a missing folder or one nobody can write in asadopt-out-unwritable, and saysplan written toonce the plan is on disk, so a mistyped--outno longer ends in a traceback after the whole plan is built (#799) #8333fb30a8f, 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) #825d7befb91, 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) #844d6475f96and 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) #83133419925inadopt-project.pysinced8b8eb33. - The 387-line claim and the 15-file claim hold.
git grep -l -E 'adopt-project|adopt_project|\bADOPT\b' tests/*.pyat79d518dbgives 106 files:conftest.py,gh_guard.pyand 104 test files.test_dry_run_parity,test_family_refuse_up_front,test_no_auto_maintenanceandtest_cli_referenceare outside it, so 104 + 4 = 108.test_pin_source_top_level.pygives 48 passed.- siblings.py: a file, or a folder
make siblingscannot list or enter, at../<Project>is NOT A CLONE, reported and left as it is, so the run no longer ends in a traceback atiterdirbefore the members after it are placed, andmake parkno longer ends in one over a folder it cannot enter (#735) #772 is112eeed2. Its message names siblings.py (make siblings): a FILE, or a directory it cannot list, at../<Project>ends in a traceback (NotADirectoryErrororPermissionErroratsiblings.py:558), exit 1, where the doctor calls the slot NOT A CLONE #735, and it came in with this merge: it is an ancestor of33419925and not ofd8b8eb33. The siblings.py (make siblings): a FILE, or a directory it cannot list, at../<Project>ends in a traceback (NotADirectoryErrororPermissionErroratsiblings.py:558), exit 1, where the doctor calls the slot NOT A CLONE #735 strict xfail is gone fromtest_shape_doctor.py.
- Main gained scripts/shape_materialize.py and adopt-project.py: a
- The two changed sentences are true.
- Review round 1 now says 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 "was not on main then. It landed first, and the merge round below resolvescmd_checkas lane 3 ruled on docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472) #473". - Round 2 now says "The revert table is at
748c8e6aand was not run again", and the table's own text says it was reverted on748c8e6a.
- Review round 1 now says adopt-project.py:
Non-blocking note
This is the writer's point 4, and I measured it. check asks the two leg questions in the else: of the names' try, so a plan that already got plan-not-the-source still gets them. execute refuses at plan-not-the-source before it asks (refused_before_any_remote).
The guard log shows 4 gh calls in each test_a_plan_edited_to_a_typo... case (two check runs, two views each), and 2 in the --source case, whose one check ends in plan-not-the-source.
It was true at 70801c91 and is not a regression. It is not in the body. A one-line addition under "For the checklist" would fit:
checkasks about the legs afterplan-not-the-source. Its two leg questions are in theelse:of the names'try, so a plan that names another repository still gets them, whereexecuterefuses atplan-not-the-sourcebefore it asks.
Housekeeping
- I pushed and posted nothing.
- My scratch worktrees (
r798m/treeandr798m/main-tree) are removed withgit worktree remove --force. The shared checkout shows only?? handoffs. - Logs and basetemps remain under
/workspace/projects/openRepoShape-worktrees/_lane1-scratch/r798m/(logs/,bt-*,docstring-candidate.diff). I did not userm -rf.
Next (lane 1's coordinator): #861 is PR-READY at 79d518db, on condition that CI tests (run 39100766048) finishes green. The docstring fix is NOT pushed now: a push would restart the 68-minute tests run and cost #861 its turn. Both of r798m's notes are in the body's "For the checklist" instead, and the docstring goes to a follow-up. #861 takes lane 1's next turn in lane 3's round-robin, gated over the main of that moment. #843's merge round is running.
Lane: openreposhape-1 (openRepoShape-1)
|
|
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 18 hours by commenting |
…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) `family.py init` set the holder's topic last, after `gh repo create` and the push, and asked none of GitHub's limits: 50 for the topic, 39 for `--org` and 100 for the holder's name. So a family of 40 characters made and pushed its holder, then failed at the topic. `init` now asks all three where GitHub is asked, before any `gh` call, so `--dry-run` refuses the same. There are four new codes: `family-name-too-long`, `family-org-too-long`, `family-bad-org` and `family-topic-too-long`. `adopt-project.py check` asked none of what `execute` asks of the names, the organisation and the topic, so it said `plan ok` over plans `execute` refused. `_what_cannot_be_held` is now the one list, in `execute`'s order. `execute` refuses its first refusal before either leg exists. `check` finds every one as a FINDING under the same code, with `execute`'s remediation. An `--org` GitHub gives no organisation is refused where GitHub is asked. This is option (a) of the issue: the rule is in the tools' plans, and no pinned file changes. `refuse_an_owner_github_cannot_spell` in `scripts/shape_materialize.py` refuses a character outside ASCII letters, digits, `_` and `-`, a trailing hyphen, and two hyphens in a row. Its codes are `scaffold-bad-org`, `adopt-bad-org` and `family-bad-org`. `_` is let by, for GitHub Enterprise Managed Users. None is asked under `--local-remote-dir`. GitHub's numbers and characters are from its documentation; no run here creates a real repository. Lane 3's independent reviewer RV871 (opus) found it READY (#871 issuecomment-6245358225), and RV871b (opus) found the fix round after it READY (#871 issuecomment-6245718752). After #861 and #859 landed, it was restacked onto both squashes by five merges, and its changed lines equal the reviewed delta (#871 issuecomment-6248107580). It lands on Brett Heap's word "merge lane 3's v1.1.1 drafts after the v1.1.0 tag" (2026-10-17T13:00:57Z). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…say that `check` asks `gh repo view` once of each leg once the plan's names and `org:` pass, which the session's guard answers, and what `execute`'s in-process runs replace (#880) (#881) Since #861 (#798), `check` asks `gh repo view <owner>/<name> --json nameWithOwner` once of the spec leg's repository and once of the code leg's, when it is given no `--local-remote-dir` and the plan's names and `org:` pass. Off Windows, the session's guard (`tests/gh_guard.py`) answers it as a `gh` with no login does, and nothing reaches GitHub. Six module docstrings said no `gh` ran, or said when it ran wrongly (#880): | File | Calls (cases that call / cases) | |---|---| | `tests/test_adopt_check_plan_name.py` | 62 (27/45) | | `tests/test_adopt_project_is_the_source.py` | 28 (10/22) | | `tests/test_adopt_tracking_branch.py` | 18 (8/17) | | `tests/test_adopt_origin_repository.py` | 4 (2/30) | | `tests/test_template_text_placeholders.py` | 20 (10/142) | | `tests/gh_guard.py` | "every `check` ... asks it twice", which 18 refused cases do not | Each now says when `check` asks, and that the guard answers off Windows. The first two also had two more false sentences: - each source's `origin` was "an `https://github.com/` URL". 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`'s every `git` and `gh` command was "replaced". Now: every command it runs through `run`. Module docstrings only: in each file, the AST outside the module docstring is unchanged. Review: lane 1's reviewer found it PR-READY at `8345de60`. Lane 5's cross-lane review found it READY there with two SHOULDs, two NITs and four more files, all taken at `1d21a850`, and lane 5's re-check found `1d21a850` READY. Its last NITs and one more file go to #938. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `1d21a850` is green on Linux and SonarCloud. The branch merged main once and was never rebased; the last merge is update-branch's, clean, giving 7cd1b7d, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 8 test files it changes or that read the guard (`test_adopt_check_plan_name.py`, `test_adopt_project_is_the_source.py`, `test_adopt_origin_repository.py`, `test_adopt_tracking_branch.py`, `test_template_text_placeholders.py`, `test_gh_guard.py`, `test_no_auto_maintenance.py` and `test_repo_hygiene.py`), 405 passed and 0 skipped. Closes #880 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-1 (openRepoShape-1)
Claim: #798 (comment)
A v1.1.1 draft. It lands only after the v1.1.0 tag, in the order agreed on #473.
What
executenow asks about both leg repositories before it makes either. It refuses everything that stands in their way at once, and says that no leg repository was created and nothing was pushed.--local-remote-dir, it asks two things.os.path.lexists, so a dangling link counts as taken.family.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609's question,folder_in_the_way, which makes nothing. A file at the path or above it, a folder above it this user cannot write in, a folder this user cannot write in, and a link loop above it are each refused by name. There is no traceback.gh repo view <org>/<P>-spec --json nameWithOwner, then<org>/<P>-code.Could not resolve to a Repository), as scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609 has it for the scaffold.leg-remote-existsbefore anygh repo create. gh's GraphQL follows a rename or a transfer, so the answer counts only when itsnameWithOwneris the name asked, compared without case: a name GitHub only redirects is free, andgh repo createtakes it.adopt-remote-unreadable, naming the command, its exit and gh's first line. That covers no login, a bad token, a rate limit, no network, and aghthat cannot be started. An answer with nonameWithOwnerto read (not JSON, or no such field) is refused the same way, naming what gh printed.checkreports the same findings, under the same flags and in the same order (#417's one verdict). They come last among its findings, asexecuteasks them last.--local-remote-dir,checkasks the disk, asexecute --local-remote-dirdoes. It reads no network.checknow asks GitHub the same twogh repo viewquestions, socheckreads the network there, two calls. A name that exists is a FINDING.execute: a gh failure other than not-found, or an answer with nonameWithOwnerto read, is a NOTE incheck, not a finding. The NOTE names the command and gh's answer, andcheckstill passes, so it stays usable offline.executerefuses the same answer asadopt-remote-unreadable.The texts, as the probes on the branch printed them (paths shortened to
<lrd>):A file at the path reads
/afile... cannot hold the two leg repositories:is a file, not a folder, and its remedy is to move what stands in the way aside.When the stub answers
acme/Atlas-code's view with{"nameWithOwner": "acme/Atlas-code-old"}, a rename's redirect,checkprintsplan ok, andexecutegoes on togh repo create acme/Atlas-specandgh repo create acme/Atlas-code. Answered withACME/atlas-code, the name is taken, and both print the FINDING above.Why (reproduced on main
bf1f099b)Each case was run with a stub
ghthat logs every call,GH_TOKENset to a bogus value, an emptyGH_CONFIG_DIRandGIT_ALLOW_PROTOCOL=file.<lrd>/Atlas-code.gitthere,check --local-remote-dir <lrd>saidplan ok.executethen exited 2 withREFUSED creating the code leg repository. leg-remote-exists: <lrd>/Atlas-code.git already exists, andNOTHING has been rolled backnamedAtlas-spec.git, which it had just made.repo createas GitHub does for a taken name.checksaidplan ok. The only gh callsexecutemade weregh repo create acme/Atlas-spec --private ...and thengh repo create acme/Atlas-code --private .... It exited 2, and the notice saidhttps://github.com/acme/Atlas-spec.gitexists.check --local-remote-dirsaidplan okin all three cases:executeexited 1 with aFileExistsErrortraceback;PermissionErrortraceback;REFUSED a git or gh command failed, thenfatal: cannot mkdir .../Atlas-spec.git: Permission denied.How
adopt-project.py, a new section before_create_leg_remotes:_leg_remote_answersasks the questions._in_the_way_on_diskand_in_the_way_on_githubare its two halves._in_the_way_on_githubasks--json nameWithOwnerand reads the answer with_name_with_owner. An answer naming another repository, compared without case, is a redirect, and counts as absent, as gh's not-found answer does._refuse_what_stands_in_the_legs_wayisexecute's refusal. Its code is the first finding's own, and its detail lists each finding ascheckprints it._leg_remote_findingsischeck's. GitHub is asked only of anorg:executewould put on aghcommand line (checked_value), and only when the names classified, sinceexecuterefuses both earlier._spelled(adopt-project.pycheckandexecuteprint a source path raw in a FINDING, so a\r,\n,\x85, U+2028 or ESC in a name overwrites, splits or escapes the line on a terminal, whereplan's questions spell the same path with_spelled#212).--local-remote-dirthat cannot be resolved, such as a link loop, is kept as given (_the_remote_dir), in_remote_urlstoo.executedied there withRuntimeError: Symlink loopbefore anything was asked. Nowfolder_in_the_waynames the loop in both commands.cmd_execute, right before_create_leg_remotes, after_confirm,_template_valuesandanswered_path_record. That is whereleg-remote-existswas always refused. The reasons:adopt-branch-existsandplan-split-branch-takenstill defer to a leg on disk, as they did;leg-remote-existsat the same point as before;gh repo createas possible.The cost: a run refused here has asked for the yes first, as a run refused with
leg-remote-existsalways had.mkdirof--local-remote-dir, which comes after the question, now refuses by name (_make_the_remote_dir,adopt-folder-cannot-be-made, before any leg exists). It covers what the question cannot foresee, such as a file put there since. It is no longer anOSErrortraceback.REFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188 N4's notice stays (decision 3). These failures can still come after the first create:gh repo createthen finds the code leg's name taken;--local-remote-dir, a path made since the question, or a full disk at the code leg'sgit init.The notice's clause about how a re-run is refused now says what is true: as
leg-remote-exists, before it makes either leg, on the disk and on GitHub alike.MOUNT_REMEDIATIONand_nothing_rolled_back's docstring say the same.scripts/shape_materialize.py:folder_in_the_wayis split out offolder_cannot_be_made. It is the same definition, now answering(where, why), so that adopt can spell the path;folder_cannot_be_madekeeps scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, andfamily-operation-in-progressprints a holder's path across two lines #692'sshown_sourcespelling of the folder. What the scaffold andfamily.pyprint is unchanged (tests/test_dry_run_parity.py,tests/test_family_refuse_up_front.py).tests/gh_guard.py: the session's guard now answersgh repo view <owner>/<name> --json nameWithOwner. Without this, decision 2 fails the suite: everycheckin it given no--local-remote-dir, in about 70 files, now asks that call twice, and the guard refused and failed each such test at teardown.ghwith no login gives: exit 4 and gh's own words. That is the state the guard already describes with its other two answers, so every suchcheckprints the NOTE above and still passes, the same on every machine.--json name,--json visibility,repo viewwith any other shape, andrepo createare still refused (tests/test_gh_guard.py).scaffold-project.pyandfamily.pyask whether their own repositories exist with--json name, so the guard answers adopt's call alone: a test of either that reaches gh with no stub of its own is still failed at teardown, as before this PR (a new case intests/test_gh_guard.py).tests/test_adopt_pins.py(check_plan),tests/test_pin_annotated_tag.pyandtests/test_template_values_single_pass.py: theircheckruns take the--local-remote-dirtheirexecuteruns use. So a log that holds no gh call still proves what it proved:checkreads no network for a pin. One pinscheck(test_the_pin_and_write_lines_say_how_and_not_where) runs as people run it, with no flag, and its gh log is exactly the two leg views.tests/test_adopt_github_exits.pyandtests/test_adopt_leg_keys.py: their shims answerrepo view <repo> --json nameWithOwnerfrom their fake GitHub, with the repository's own name.tests/test_adopt_temp_work_roots.py'sgh_clones, whichtests/test_adopt_source_clone_record.pyimports, answers the leg views (--json nameWithOwner, exactly) as not found and records only its other calls.tests/test_adopt_split_branch.py: the firstghcall of a run that went on to the legs is now the spec leg'sgh repo view, compared whole (TO_THE_LEGS).tests/test_adopt_filter_repo_failure.pyasserts the notice's new words for how a re-run is refused.tests/gh_guard.pyanswers an owner GitHub would refuse too, such asa+b, whichtests/test_adopt_manifest_values.pyplans with.--local-remote-dirhelps, anddocs/cli.md, regenerated.Review round 1 (r798, at
99cc97e8)d8b8eb33, inbbf2c3a8. The PR had conflicted with main since it opened, inscripts/shape_materialize.pyagainst scaffold-project.py, family.py and shape_materialize.py: #622's folder refusals print a folder name raw, so an ESC or a line break reaches the terminal, andfamily-operation-in-progressprints a holder's path across two lines #692, so no CI had run on it. It is resolved as How says. 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 (adopt-project.pycheck: a plan in a name the policy refuses gets no rename-first remedy, and a source that names no repository is never compared with the plan's recordedsource.repository#716) was not on main then. It landed first, and the merge round below resolvescmd_checkas lane 3 ruled on docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472) #473.gh repo viewcounted as taken, but GitHub answers a renamed or transferred repository's old name with the repository it became. Even this PR's own remedy, renaming the other repository first, left such a redirect. Now only an answer naming the name asked counts (What).--json namecall. WithnameWithOwnerit answers adopt's alone, and a test shows theirs still refused.checkruns in GitHub mode;test_adopt_late_failure.pyandtest_adopt_execute_refusals.py;pytest.raises) and one S9073 (a composite assert) in the new tests; each argument is built before thewith, and the assert is split.Merge round (main
33419925, head79d518db)33419925is merged, in77feec0c. Since the last merge (d8b8eb33), main gained scripts/shape_materialize.py and adopt-project.py: a--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842, adopt-project.py:_default_branchreadsorigin/HEADand HEAD by full name and whole, and reads the source only at a branch of its own, so agit clone -bsource is no longergit-failedwith no remedy, and a HEAD onorigin/x, onmain<U+00A0>or beside a tagmainno longer adopts another branch's commit (#797) #830, adopt-project.py:planasks whether it can write--outwith its flags, before any work, and refuses a folder, a pipe, a missing folder or one nobody can write in asadopt-out-unwritable, and saysplan written toonce the plan is on disk, so a mistyped--outno longer ends in a traceback after the whole plan is built (#799) #833, 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, 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 and 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 inadopt-project.py.cmd_check, against 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 (adopt-project.pycheck: a plan in a name the policy refuses gets no rename-first remedy, and a source that names no repository is never compared with the plan's recordedsource.repository#716) (d7befb91). Both changed thetry/exceptaround_check_names: 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 added its rename-first remedy inside theexcept, and this PR added anelse:that asks about the two leg repositories. It is resolved as lane 3 ruled on docs/release-notes/v1.0.0.md: the v1.0.0 release notes, a draft for Brett to amend before he tags, saying what 1.0 contains and promises, how to pin it by commit, what changed on the way grouped by the five tracking areas, and every known limit as the input that triggers it and what the person sees (#472) #473: 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 two lines inside theexcept, and this PR'selse:after it.adopt-project.pyadds and removes the same 387 lines this PR did againstd8b8eb33.docs/cli.md,scripts/shape_materialize.pyandtests/test_adopt_split_branch.pymerged without a conflict. Each of the 15 files the PR changed before the merge changes the same lines against main as it did againstd8b8eb33.79d518db: Main's scripts/shape_materialize.py and adopt-project.py: a--pin-sourcethat is no clone's top level is refusedpin-source-not-a-clone, andcheckfinds a recorded one before a partial clone's NOTE, so a folder inside a clone no longer records the digest of part of its tree, or of nothing (#800) #842 (bee84d55) addedtests/test_pin_source_top_level.py, whose twocheckruns asserted an empty gh log; they now take the--local-remote-dirthatcheck_plangives, astests/test_adopt_pins.py's do, so the log still proves thatcheckreads no network for a pin.checkrun with no--local-remote-dirthat asserts on a gh log.70801c91, S1192 on the literal"refs/heads/", was in main's old lines, which adopt-project.py:_default_branchreadsorigin/HEADand HEAD by full name and whole, and reads the source only at a branch of its own, so agit clone -bsource is no longergit-failedwith no remedy, and a HEAD onorigin/x, onmain<U+00A0>or beside a tagmainno longer adopts another branch's commit (#797) #830 cut to one. At this headadopt-project.pyholds that literal once, as main does, and the PR's diff touches no line holding it.Tests
New tests were written first. On main
bf1f099b, 22 of the first round's 25 cases failed, and the guard test did not exist. Round 1 added 7 more, and each fails with its change reverted (R13 to R16 below). 32 new cases in all:tests/test_adopt_execute_refusals.py, 28 cases:--local-remote-dirs that cannot hold them: a file, under a folder this user cannot write in, a folder this user cannot write in, under a link loop;check --local-remote-dirprinting, word for word, whatexecuterefuses, in six cases;repo create;ghthat cannot be run;checkagainst GitHub: a FINDING, and a NOTE that still passes (2);REFUSED <step>with no notice of what already exists and no stdout flush, so the combined log shows REFUSED before thebare …lines and the re-run meetsleg-remote-existsunexplained #188 N4's notice once the spec leg exists;checkpasses, andexecutemakes bothrepo createcalls (2);checkandexecutealike;nameWithOwnerto read (not JSON, no such field, nothing printed):executerefuses it, andchecknotes it and passes (3).tests/test_adopt_late_failure.py, 2 cases:--local-remote-dirthat cannot be made after the question passed is refused by name.tests/test_gh_guard.py, 2 cases:--json nameview is still refused and fails the test that reached it. This case passes on main too: it pins what this PR must not change.Gates, on head
79d518db(dirty 0; main33419925merged in77feec0c)./usr/bin/python3 -m pytest, a stubghfirst on PATH, a bogusGH_TOKEN, an emptyGH_CONFIG_DIR,GIT_CONFIG_GLOBAL=/dev/nullandGIT_SSH_COMMAND=false, and noGIT_ALLOW_PROTOCOL.grep -l -E 'adopt-project|adopt_project|\bADOPT\b' tests/*.pygives (the other two areconftest.pyandgh_guard.py). They includetest_gh_guard.py,test_shape_doctor.pyandtest_scaffold_leg_path_collisions.py, and main's newtest_pin_source_top_level.pyandtest_adopt_check_plan_name.py;test_dry_run_parity.pyandtest_family_refuse_up_front.py, for theshape_materialize.pymerge;test_no_auto_maintenance.py;test_cli_reference.py, fordocs/cli.md.git-filter-repoon PATHgit-filter-repohidden (PATH=<stub gh>:/usr/local/bin:/usr/bin:/bin)Earlier heads. At
748c8e6a, before the merge round, the same gate over the 102 files of that time gave 7,245 passed, 13 skipped, 1 xfailed withgit-filter-repoon PATH, and 6,857 passed, 401 skipped, 1 xfailed with it hidden. That xfail wastest_shape_doctor.py's strict one for #735, and main's #772 (112eeed2) fixed it and dropped the mark.Round 2, head
70801c91. This round changed only four test sites, three arguments built before thewithand one assert split (Review round 1, Test nits), intests/test_adopt_execute_refusals.pyandtests/test_adopt_late_failure.py; no test asserts anything new. At that headtest_adopt_execute_refusals.py,test_adopt_late_failure.pyandtest_no_auto_maintenance.pywere run again, each on its own and one at a time, in both modes: all passed (git-filter-repoon PATH: 181, 88 and 9 passed, 2 skipped in the last; hidden: 177, 84 and 9 passed, 4, 4 and 2 skipped). The revert table is at748c8e6aand was not run again.Revert table. Each change was reverted alone on
748c8e6aand the named files were run.executeasks nothing before it makes the legscheckasks nothing about the legsgh repo viewfailure means absentghthat cannot be run is not caughtmkdirafter the questiontest_gh_guard.py,test_adopt_execute_refusals.pycheckthere fails at teardown)checkwithout--local-remote-dirtest_adopt_pins.pytest_adopt_github_exits.py_remote_urlsresolves a link loopnameWithOwnermeans absent--json nameagain, scaffold's and family's calltest_gh_guard.py,test_adopt_execute_refusals.pyR1, R2 and R5 fail more cases than at
99cc97e8(22, 9 and 9) because round 1's cases depend on the same questions. R10 fails one fewer (50) because one pinschecknow runs without the flag.The
folder_in_the_waysplit changes no behaviour.tests/test_dry_run_parity.pyandtests/test_family_refuse_up_front.pypass in both modes.Windows
CI is Linux only (#597).
#!stubgh, so they skip on Windows, astests/test_adopt_github_exits.pydoes.write_textwrote.For the checklist
Path.exists()._refuse_a_split_branch_left_behindand_refuse_a_branch_in_the_split_branchs_waystill defer to a leg on disk withPath.exists(), notlexists. A dangling link at a bare path does not make them defer. The new question still refuses that link, later, with no leg made.bare.exists()in_create_leg_remote. A dangling link made after the question meets a rawgit initfailure, with the notice when it is the code leg.scaffold-project.pyandfamily.pytake an exit 0 ofgh repo view <repo> --json nameas "exists", so a name GitHub only redirects is refused there too. This predates the PR (scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609, scaffold-project.py and family.py: both runs ask whether each folder they make can be made and take only gh's not-found answer as "absent", so--dry-runrefuses a file where a folder goes, or agh repo viewthat fails otherwise, by name as the real run does, and a refusedinitleaves no empty family folder (#609) #622).if local.adopt-branch-existsfirst andleg-remote-existssecond.leg-remote-exists... against GitHub alike" therefore names the second refusal. Main's wording had the same gap.executeasks GitHub about the legs, that deferral could work on GitHub too.check's twogh repo viewcalls have none, so a gh stuck in DNS holdschecktoo.checkwithout--local-remote-dirnow runsgh repo viewtwice.checkasks about the legs afterplan-not-the-source. Its two leg questions are in theelse:of the names'try, so a plan that names another repository still gets them, whereexecuterefuses atplan-not-the-sourcebefore it asks. This was so before the merge round too.tests/test_adopt_check_plan_name.py, which main added (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), says "Nothing here reaches GitHub or runsgh". At this head itscheckruns pass no--local-remote-dir, so each asks the session guard'sgh repo viewtwice (62 calls in the file's 45 cases); nothing reaches GitHub, butghdoes run. Left for a follow-up, so that this head's CI run stands.Known limits
os.access, as scaffold-project.py andfamily.py init: the dry runs skip the target-exists and folder checks, and anygh repo viewfailure reads as "does not exist" #609's question is for the scaffold. Root, and any folder on Windows, are always told yes, and an ACL that says otherwise is met at themkdirorgit init. The first is now refused by name; the second still ends as a failed command after nothing, or after the spec leg, was made.checkwithout--local-remote-dirnow needs gh to answer for a clean read. Offline it still passes, with the NOTE.gh repo createtakes it over. That ends the redirect to the renamed or moved repository, as GitHub's rename page warns. Neitherchecknorexecutesays so.Closes #798
🤖 Generated with Claude Code
Summary by Sourcery
Preflight both leg repositories and their destination before adoption so conflicts are reported consistently without creating partial results.
New Features:
checkto report local repository conflicts and GitHub name conflicts, while treating unavailable GitHub queries as notes.Bug Fixes:
Enhancements:
nameWithOwnermatches the requested repository, case-insensitively.Documentation:
Tests:
Chores: