Skip to content

adopt-project.py: check, given no --local-remote-dir, asks GitHub nothing about the two legs of a plan for another repository and prints no NOTE that execute asks again, as execute refuses plan-not-the-source first and asks nothing; under --local-remote-dir it still asks the disk (#1232) - #1313

Open
brettheap wants to merge 8 commits into
mainfrom
l3-1232-not-the-source-asks-no-leg

Conversation

@brettheap

@brettheap brettheap commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Built on #1210's head 791106c2, then merged with #1210's final head 27c8f67b and its squash 106f92ff after #1210 landed.

What a person sees change

adopt-project.py check on a plan for another repository (plan-not-the-source), given no --local-remote-dir:

Nothing else changes:

  • The source's own plan is still asked gh repo view of each leg, and still gets its NOTE where gh cannot answer.
  • Under --local-remote-dir every output is the same as before.

The choice under --local-remote-dir, and why

#1232 left this open. Measured first (the table below):

  • What the disk is asked. Under --local-remote-dir, check asks the disk, not GitHub: is each leg's bare repository, <dir>/<name>.git, already there, and can the directory hold them?
  • The same path before and after the mend. Beside plan-not-the-source with the spec leg's bare repository present, check lists leg-remote-exists for <dir>/Northwind-spec.git. That is the very finding, word for word, it lists for the source's own plan under the same directory. The source's own plan is the one the remedy (plan --project Northwind --org testorg) writes.
  • GitHub is not like that. Without --local-remote-dir, the plan edited to org: acme is asked about acme/Northwind-spec. The plan the remedy writes is asked about testorg/Northwind-spec.

So the skip applies only without --local-remote-dir. Under it, the disk is still asked beside plan-not-the-source, for three reasons:

The other way, skipping under both, is mutant M2 below. The row test_a_local_remote_dir_is_still_asked_beside_another_repository kills it.

One case the skip is blunter than the question. A plan that names another repository only by its legs.assembly: (or project:) keeps the source's org: and the legs' names. On 791106c, check asked GitHub about testorg/Northwind-spec and -code, the legs the plan the remedy writes names (measured: the another-name probe). It is skipped there too:

Measured by RV1232, with a gh that answers {"nameWithOwner": ...}: on the legs.assembly: Elsewhere plan, main's check listed plan-not-the-source and a leg-remote-exists for each of testorg/Northwind-spec and -code (3 findings), and this head lists plan-not-the-source alone (1 finding). Those are the legs the remedy's plan names, so a person meets them after the mend, one round later. #1232's first "Done when" asks for no gh call on any plan for another repository, so the skip holds there too.

Measured, before and after

The probe is probe1232.py in my scratch. It builds each plan with the test suite's own helpers (planned_with with a held source, whose origin names testorg/Northwind). It runs check and, for the plans for another repository, execute --yes --work-dir, with:

  • a stub gh first on PATH that logs its arguments and exits 97;
  • GH_TOKEN=bogus and an empty GH_CONFIG_DIR;
  • GIT_SSH_COMMAND=false and an empty global git config;
  • git-filter-repo 2.47.0, and LANES_LANE unset.

"Another (org)" is org: acme. "Another (assembly)" is legs.assembly: Elsewhere. "lrd" is --local-remote-dir: absent (a directory not made yet), "taken" (holding Northwind-spec.git), or "a file".

Plan, mode check on 791106c check here execute (both heads)
another (org), no lrd exit 1, plan-not-the-source; gh: repo view acme/Northwind-spec, repo view acme/Northwind-code; 2 NOTEs exit 1, plan-not-the-source; gh: none; no NOTE exit 2, plan-not-the-source; gh: none
another (assembly), no lrd exit 1, plan-not-the-source; gh: repo view testorg/Northwind-spec, -code; 2 NOTEs exit 1, plan-not-the-source; gh: none; no NOTE exit 2, plan-not-the-source; gh: none
another (org), lrd absent exit 1, plan-not-the-source the same bytes exit 2, plan-not-the-source
another (org), lrd taken exit 1, plan-not-the-source, leg-remote-exists the same bytes exit 2, plan-not-the-source
another (org), lrd a file exit 1, plan-not-the-source, adopt-folder-cannot-be-made the same bytes exit 2, plan-not-the-source
another (assembly), the three lrd modes as the org rows the same bytes exit 2, plan-not-the-source
own, no lrd exit 0; gh: repo view testorg/Northwind-spec, -code; 2 NOTEs the same bytes not run
own, lrd absent exit 0 the same bytes not run
own, lrd taken exit 1, leg-remote-exists (the same line as in "another (org), lrd taken") the same bytes not run
own, lrd a file exit 1, adopt-folder-cannot-be-made the same bytes not run
  • Byte for byte, with paths and hashes masked: 10 of the 12 probe sources are identical, all 20 runs' exits included. The other 2 are the two "no lrd" plans for another repository. In each, only check changed:

    • its gh log, two repo view calls to none;
    • its stdout, the two NOTE lines gone.

    Its stderr, its exit and execute's whole output are the same. No probe made a leg.

  • Where the issue's measurement and mine differ: nowhere. RV1210 measured the org: edit on 90eca414, whose adopt-project.py is the same file as 791106c's (sha256 4c8c1c9eb0c5). I also measured the legs.assembly: edit and the three --local-remote-dir modes, which the issue did not.

  • Why execute was not run on the source's own plan: without --local-remote-dir it goes on to read the remote, which a probe must not.

What changed, in adopt-project.py

cmd_check only, the leg question (#798):

another = PLAN_NOT_THE_SOURCE in {problem.refusal.code
                                  for problem in unmended}
legs, fixes, unasked = (
    ([], [], []) if RENAMES_THE_LEGS.intersection(held_codes)
    or (another and local_remote_dir is None)
    else _leg_remote_findings(plan, names, local_remote_dir))

Rows

All in tests/test_adopt_plan_against_its_source.py, #1210's module for the rule between check and execute, for three reasons:

A short paragraph is added to the module docstring, because these rows are the exception to its "with the same --local-remote-dir". Each row runs the tool with a gh of its own first on PATH, ahead of the session's guard, that logs each call's arguments and exits 97 (with_a_logging_gh). They skip on Windows, where that #! script cannot run.

Row What it pins 791106c here
test_check_asks_github_nothing_about_another_repositorys_legs[org] no lrd, org: acme: no gh call by check or execute (the row fails if either log holds a repo view, or any call), no NOTE, check exit 1 and execute exit 2, both naming plan-not-the-source first FAILS: (['repo view acme/Northwind-spec --json nameWithOwner', 'repo view acme/Northwind-code --json nameWithOwner'], []) != ([], []) passes
test_check_asks_github_nothing_about_another_repositorys_legs[assembly] the same, legs.assembly: Elsewhere FAILS: the same, testorg/Northwind-spec, -code passes
test_a_local_remote_dir_is_still_asked_beside_another_repository the choice: under lrd with the spec leg's bare repository present, check lists plan-not-the-source then leg-remote-exists, the very finding it lists for the source's own plan; no gh call passes passes
test_check_still_asks_github_about_the_sources_own_legs the control (#1232's last "Done when"): the source's own plan, no lrd, is asked repo view testorg/Northwind-spec and -code, prints both NOTEs word for word, and passes passes passes
  • On 791106c's adopt-project.py (swapped in and restored with cmp): 2 failed, 2 passed.
  • Here: 4 passed; the module, 22 passed.

Mutants

Run on my commit e6f2526e, on #1210's head, before the restack. Each was applied alone to adopt-project.py, the module was run (22 rows), and the file was restored, checked by cmp and sha256 (81efae25c83b, OK each time).

Mutant Killed by
M1, the skip removed (791106c's behaviour) 2: both asks_github_nothing rows
M2, the skip under both modes (or another) 1: test_a_local_remote_dir_is_still_asked_beside_another_repository
M3, the skip for every plan without lrd (or local_remote_dir is None) 1: test_check_still_asks_github_about_the_sources_own_legs
M4, the skip keyed on plan-stale instead of plan-not-the-source 2: both asks_github_nothing rows

Gates

Run on this head cf658506 (tree c23c2160, the work tree clean), one pytest at a time and never the whole suite, in the environment the probe used:

  • a stub gh (exit 97) first on PATH;
  • GH_TOKEN=bogus and an empty GH_CONFIG_DIR;
  • GIT_SSH_COMMAND=false and an empty global git config;
  • git-filter-repo 2.47.0;
  • LANES_LANE unset;
  • git 2.43.0.

The groups:

Modules that import a fixture from another ran in one pytest with it.

Group Files Result
g1, the rows test_adopt_plan_against_its_source.py, test_adopt_plan_value_order.py, test_adopt_root_symlinks.py, test_adopt_pins.py 420 passed, 3 skipped
g2, plan-not-the-source test_adopt_check_plan_name.py, test_adopt_plan_and_remote_paths_spelled.py, test_adopt_project_is_the_source.py, test_adopt_relative_submodule_url.py, test_adopt_split_branch.py 298 passed (lane 3's run for RV1232's NIT-1; without test_adopt_plan_and_remote_paths_spelled.py, which came onto main in 8bcdb2c6 and onto this branch with the restack, the other four gave 150 passed)
g3, the legs test_adopt_execute_refusals.py, test_adopt_branch_names.py, test_adopt_e2e.py, test_adopt_filter_repo_failure.py, test_adopt_gitmodules_values.py 435 passed
g4, the legs test_adopt_history_gitlinks.py, test_adopt_late_failure.py, test_adopt_mount_ignored.py, test_adopt_root_shape_paths.py, test_ruleset_hint_tracking_branch.py 305 passed
g5, unreadable and folder test_adopt_github_exits.py, test_adopt_source_clone_record.py, test_adopt_temp_work_roots.py, test_adopt_unsearchable_source.py 176 passed
g6, limits and collisions test_github_limits_init_check_org.py, test_github_name_limits.py, test_scaffold_leg_path_collisions.py 264 passed
g7, the guards test_repo_hygiene.py, test_cli_reference.py, test_agents_md_codes.py, test_git_output_decoding.py, test_stdout_encoding.py, test_path_guard_hygiene.py, test_no_auto_maintenance.py, test_update_add_codes_documented.py 470 passed, 3 skipped
m1305, both PRs' rows, on the resolved merge dd699cc2 with #1305 g1's four and test_adopt_entry_paths.py 521 passed, 3 skipped (the same 3 reftable skips as g1). Its module holds both PRs' rows: the 4 here and #1305's at 2edbfcb5.
g8, the doctor test_shape_doctor.py 446 passed, 3 skipped in 904 s (all 3 at test_shape_doctor.py:10516, "the PowerShell line is printed, and powershell.exe exists, only on a Windows host")

The skips are the host's, not this PR's:

  • g1's 3 are in test_adopt_root_symlinks.py: "the reftable backend needs git 2.45 or later".
  • g7's are test_stdout_encoding.py:292 ("the default this is about is Windows' own") and 2 at test_no_auto_maintenance.py:222 (the run sets GIT_CONFIG_GLOBAL).

Before the restack, on my commit e6f2526e alone, g1 gave 368 passed and g2 150 passed. That chain was stopped when #1210 landed, and everything was run again here.

Merges

The restack, as new commits, never a rebase:

git merge-tree --write-tree (git 2.43.0) of this head cf658506:

PR     head       with this head                         main alone
#1171  ce1dd661f  clean (tree 3814f2720)                 clean (tree 292ca9412)
#1194  906172b41  clean (tree 96ae61f27)                 clean (tree e24da7a3b)
#1225  cc230f2f1  clean (tree aa789ac39)                 clean (tree f4e9727e1)
#1237  fa6d189ff  clean (tree 3ef7d65c7)                 clean (tree 9e3db4161)
#1249  3e5d1fde1  clean (tree 441d5c687)                 clean (tree f6e21799c)
#1265  39b21c07e  clean (tree 03c9d022a)                 clean (tree 2fcc3c189)
#1280  5cb1abdbc  CONFLICT adopt-project.py              CONFLICT adopt-project.py
#1294  cf07ba0d1  clean (tree 4ba47dd60)                 clean (tree f9a1c9d6f)
#1297  2e020b458  clean (tree a81e10be9)                 clean (tree 1e8b34927)
#1304  df942efee  clean (tree 47edaa910)                 clean (tree 34d58d883)
#1305  2edbfcb54  CONFLICT the test module               CONFLICT adopt-project.py and the test module

Independent review

RV1232, lane 3's independent opus review (issuecomment-6769621392), reads READY AFTER FIXES at cf6585063. The fixes are to the body only, and it found no MUST. It judged both choices right:

Its gates ran from lane 3's main loop at this head with RV1232's own commands, one pytest at a time:

  • its g1: 420 passed, 3 skipped;
  • its g2: 298 passed;
  • its g3, the guards: 470 passed, 3 skipped.

Taken here: SHOULD-1, the bullet on leg paths; SHOULD-2, the paragraph measured by RV1232; and NIT-1, the g2 row.

Not taken:

Closes #1232

Lane: openreposhape-3 (openRepoShape-3)

🤖 Generated with Claude Code

Summary by Sourcery

Align check’s remote-leg validation with execute by skipping unreachable GitHub checks for plans targeting another repository while retaining local and source-plan validation.

Bug Fixes:

  • Prevent adopt-project.py check from querying GitHub or emitting misleading notes about remote legs when the plan is for another repository and execute will refuse it before any remote access.

Enhancements:

  • Continue checking local bare repositories under --local-remote-dir while preserving GitHub checks for the source repository’s own plan.

Tests:

  • Add coverage for GitHub-query suppression, local remote-directory checks, and unchanged source-plan behavior.

Brett Heap and others added 8 commits October 21, 2026 13:59
… right after its three repository names, so a bad one beside an unanswered entry, a moved source, a refused name or another repository is that one refusal in both, and a `pins:` entry that is no pin is said as `_read_as` says it (#1020)

#901 made `check` and `execute` name the same first problem among a plan's options. Outside them, the two still named different problems first (#1020, found by lane 1's writer on #901).

1. **One rule, the refusal before what `check` lists.** `check` refuses a bad `org:` or `id:` outright, exit 2, and lists none of its findings. `execute` refuses at the first of them, but it asked the two only after it had refused four things that `check` lists as findings: a plan for another repository (`plan-not-the-source`), a name the naming policy refuses (`naming-unclassified`), an unanswered entry (`plan-unresolved`) and a source that has moved on (`plan-stale`). And `check` asked the two only where the policy took the plan's names (`_manifest_values`), so an unsafe `org:` beside a refused name was a finding about the name, or about another repository. Both commands now ask the two first of the plan's values, right after the three repository names (`plan.names()`, then `_checked_org_and_id`): `execute` in `cmd_execute`, ahead of `_refuse_a_plan_for_another_repository`, and `check` at its head, after the triad advisory. `_checked_plan_values` no longer asks them, since `cmd_execute` has.
2. **#716's L4-48, decided.** An `org:` that `checked_value` refuses, held to a repository, was `unsafe-value` to `check` and `plan-not-the-source` to `execute`. Such an `org:` names no repository, on GitHub or in the manifest, so it is not compared with the source's. It is `unsafe-value` in both commands now, held to a repository or not. Once it is mended, a plan for another repository is still `plan-not-the-source` in both. This reverses three tests in `tests/test_adopt_check_plan_name.py`, which pinned the old answers on purpose:
   - `test_an_unsafe_org_is_unsafe_in_check_and_compared_by_execute_if_held` expected `plan-not-the-source` from `execute` in its six held rows. As `test_an_unsafe_org_is_unsafe_in_check_and_in_execute_held_or_not`, all twelve expect `unsafe-value` from both, in the same words.
   - `test_check_compares_an_unsafe_org_where_no_manifest_is_built` expected `check` to compare `org: acme labs` beside the refused name `bad_name`: `plan-not-the-source` and `naming-unclassified`, exit 1. As `test_an_unsafe_org_is_asked_before_the_plans_refused_names`, it expects `unsafe-value`, exit 2, from `check` and from `execute`.
   - `test_check_asks_an_unsafe_org_first_where_a_kept_submodule_url_is_relative` expected `plan-not-the-source` from `execute`. It now expects `unsafe-value`, in `check`'s words.
3. **A `pins:` entry that is no pin** was spelled with `repr` (`` `pins:` entry 1 is 7 ``, `True`, `None`). #901 made `pins:` itself read as `_read_as` reads it, and an entry reads the same way now: "the number 7", "the boolean true", "null", "a list of 2 item(s)".

Docstrings and comments now say this: `_checked_org_and_id`, `_checked_plan_values`, `_manifest_values`, `_held_findings`, `_not_the_source`, `_refuse_a_plan_for_another_repository`, `_read_pins` and the #499 block comment. No finding is added or removed: only which problem is named first changes, and the spelling of an entry.

Measured on main at `6da757cb` and on this branch, with a stub `gh`, a bogus token, an empty `GH_CONFIG_DIR`, `GIT_SSH_COMMAND=false` and `--local-remote-dir`. No run made a leg.

| The plan has | `check` on main | `execute` on main | both, here |
|---|---|---|---|
| a bad id beside an unanswered entry | `plan-bad-id` | `plan-unresolved` | `plan-bad-id` |
| a bad id beside a source that has moved on | `plan-bad-id` | `plan-stale` | `plan-bad-id` |
| a bad id beside a name the policy refuses | `plan-bad-id` | `naming-unclassified` | `plan-bad-id` |
| a bad id beside another organisation, held to a repository | `plan-bad-id` | `plan-not-the-source` | `plan-bad-id` |
| no `id:`, and `legs.assembly: north_wind` (a derived id the policy refuses too) | `plan-bad-id` | `naming-unclassified` | `plan-bad-id` |
| an unsafe `org:` held to a repository (L4-48) | `unsafe-value` | `plan-not-the-source` | `unsafe-value` |
| an unsafe `org:` beside an unanswered entry | `unsafe-value` | `plan-unresolved` | `unsafe-value` |
| an unsafe `org:` beside a source that has moved on | `unsafe-value` | `plan-stale` | `unsafe-value` |
| an unsafe `org:` beside a refused name | finding `naming-unclassified`, exit 1 | `naming-unclassified` | `unsafe-value` |
| the same, held to a repository | finding `plan-not-the-source`, exit 1 | `plan-not-the-source` | `unsafe-value` |

Each "here" row is exit 2 from both, word for word. The last two rows agreed on main. They change because `check` now asks the two where the policy refuses the names, as `execute` now does: without that, moving `execute` alone would have made them disagree. Plans with only one of these problems (an unanswered entry, a moved source, a refused name, another repository) are named as on main by both.

**Tests.**
- `tests/test_adopt_plan_value_order.py` gains 16 rows:
  - the 10 above, through both commands, comparing the first line;
  - four `pins:` entries that are no pin: number, boolean, null and list;
  - two guard rows, `id` and `org`: a leg name `checked_value` refuses (`legs.spec: "-spec"`) beside a bad `id:` or `org:` is that name's `unsafe-value` in both, so the three names are still asked first.
- The 14 non-guard rows all fail against `6da757cb`'s `adopt-project.py` and pass here. The two guard rows pass on both, by design.
- Existing rows changed for the new behaviour:
  - `test_adopt_check_plan_name.py`: the three tests above, and its docstring's L4-48 line.
  - `test_adopt_pins.py`: `a-number`.
  - `test_adopt_execute_refusals.py`: the three entry rows of `PIN_ENTRIES`.

Closes #1020

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… right after the plan's keys and before `_work_root`, so a bad one beside an unwritable `--work-dir` is that one refusal in both (#1020)

RV1125's SHOULD-1 on #1125. A bad `id:` or `org:` beside a `--work-dir`
this user cannot write in was `plan-bad-id` (or `unsafe-value`) to `check`
and `adopt-work-dir-unwritable` to `execute`: `check` lists the directory
as a finding (#524), and `execute` refuses it before it opens the source,
ahead of the two asks, which #1125 had placed after `_work_root`. The
comments in `cmd_execute` and `_checked_org_and_id` said that cannot
happen.

- `cmd_check` asks `plan.names()` and `_checked_org_and_id(plan)` right
  after its mistyped-plan question, before `_work_root`, and `cmd_execute`
  asks them right after `_refuse_a_mistyped_plan`, before `_work_root`.
  Both read only the plan. `execute` now asks them before
  `_require_filter_repo()` too.
- A side effect, said in the body: `check` printed the Triad advisory,
  once it had opened the source, before it refused a bad `org:` or `id:`;
  it prints nothing now, since the refusal comes before the source is
  opened.
- The comments and docstrings that placed the asks "at its head, after the
  triad advisory" or "right after the names" say where they are now.
- tests/test_adopt_plan_value_order.py: `both_on` takes a `--work-dir` for
  both commands; one row per bad `id:` and bad `org:` beside an unwritable
  `--work-dir` (skipped where `chmod` cannot keep this user from writing);
  and, for NIT-2, one row per value that asks `check` alone and holds
  stdout empty, the position being told by the Triad advisory the unedited
  plan prints first.
- tests/test_adopt_check_plan_name.py: the docstring of the kept-submodule
  row says it is a regression row since `check` asks `org:` at its head
  (NIT-1).

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…blems against its source, in the order a person mends them (another repository, a refused name, a moved source, an unanswered entry), so `check`'s first finding is `execute`'s refusal for each pair (#1113)

`check` listed a moved source first, then the plan's entries, then
another repository and then the names; `execute` refused another
repository, a refused name, an unanswered entry and then a moved source.
So for five of the six pairs of the four, `check`'s first finding was not
`execute`'s refusal: the four of #1113's table, and a refused name beside
a moved source.

`_against_its_source` asks the four in one order, and both commands read
it: `execute` refuses its first problem, and `check` lists each, in that
order and in its own words, before the split branch and the entries.
Each of the first three is mended by running `plan` again, which takes
back every answer, so an unanswered entry is named last; another
repository comes first, before the naming policy or any remote is asked
(#499); and a refused name is mended by a `plan` that reads the source
as it is now, so it comes before a moved source.
`_refuse_an_unrunnable_plan` asks a moved source before an unanswered
entry. No refusal's code or wording changes: only which is named first.

tests/test_adopt_plan_against_its_source.py runs both commands on one
plan per pair, on all four, and on each of the four alone. In
tests/test_adopt_relative_submodule_url.py, six rows hand-edit a plan's
`org:` to another organisation beside a relative url, and pinned
`check`'s old order, the url first. Such a plan is for another
repository, which `execute` refuses first (#499), so the rows now expect
`plan-not-the-source` first to `check` too. A new row runs `check`, with
and without `--local-remote-dir`, and `execute` on one such plan, and
asserts that each names it first and that no leg is made.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le's problems beside the split branch taken and beside an unsafe `tracking_branch:` pins that `check` lists the rule's problem, and its remediation, first and `execute` refuses it first, so the rule moved after either finding fails a row (#1113)

RV1210 measured two placements of the rule that no row pinned, each
bringing back a disagreement #1210 fixes:
- `check` listing the rule's problems after the split branch: another
  repository, a refused name and a moved source beside a branch `adopt`
  in the source, as `test_adopt_split_branch.py` makes it
  (`another-and-taken`, `name-and-taken`, `moved-and-taken`);
- `execute` asking the rule after `_checked_plan_values`,
  `_repository_urls` and `_refuse_an_unwritable_clone_url`: each of the
  four beside `tracking_branch: "a b"`, which `BAD_TRACKING_BRANCH` from
  `test_adopt_plan_value_order.py` edits in (`another-and-tracking`,
  `name-and-tracking`, `moved-and-tracking`, `unanswered-and-tracking`).

Each row asserts that `check` exits 1 and names the rule's problem first,
and lists the ordinary finding after it, and that `execute` exits 2 and
refuses the rule's problem first. `another-and-taken` also asserts that
`check`'s first `Remediation:` line is the rule's (NIT-4), so the
remediation lines follow the findings' order. `unanswered-and-taken` stays
out: it is #1197's.

Test only; adopt-project.py is unchanged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… nothing about the two legs of a plan for another repository and prints no NOTE that `execute` asks again, as `execute` refuses `plan-not-the-source` first and asks nothing; under `--local-remote-dir` it still asks the disk (#1232)

On #1210's head 791106c, `check` on a plan for another repository, given
no `--local-remote-dir`, ran `gh repo view <org>/<Project>-spec --json
nameWithOwner` and the same for `-code`, under the plan's own `org:`, and
where gh could not answer it printed a NOTE that `execute` asks again
before it makes either leg. `execute` refuses `plan-not-the-source` first
and asks no remote anything (#499). `cmd_check` now skips the leg question
(`_leg_remote_findings`) there, as it skips it after a finding in
`RENAMES_THE_LEGS`. The source's own plan is asked as before.

Under `--local-remote-dir` the disk is still asked beside
`plan-not-the-source`: it asks no remote, and a leg's bare repository is
`<dir>/<name>.git`, which no `org:` is part of. Measured, the
`leg-remote-exists` it lists beside `plan-not-the-source` names the very
path the source's own plan, the one the remedy writes, is refused for.

Tests: tests/test_adopt_plan_against_its_source.py gains 2 rows that fail
on 791106c (a plan for another repository by its `org:` and by its
`legs.assembly:`, under a `gh` that logs each call), a row for the choice
under `--local-remote-dir`, and a control for the source's own plan.

Lane: openreposhape-3 (openRepoShape-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The first of the three restack merges after #1210 landed by squash
(106f92f): its final head, 791106c merged with main 3add78e.

Lane: openreposhape-3 (openRepoShape-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second of the three restack merges: #1210 as it landed on main. Its
tree is the final head's (ee6de2a), so this merge changes no file.
tests/test_adopt_plan_against_its_source.py, which #1210 added, met as an
add/add conflict and was taken ours: the squash holds 791106c's bytes of
it, and ours holds those bytes and this branch's own change, nothing else.

Lane: openreposhape-3 (openRepoShape-3)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Nov 10, 2026

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Skip leg-remote checks for plans refused as plan-not-the-source when no local remote directory is supplied, while retaining disk checks for local directories.

  • adopt-project.py L14641-14657

Tests

Document the exception and add coverage for skipping GitHub questions, retaining local-disk checks, and preserving checks for the source repository’s own legs.

  • tests/test_adopt_plan_against_its_source.py L52-63, L68-75, L190-307

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-11-10T14:15:00Z) RV1232: CHECKPOINT at cf65850

LAST STEP reached me during item 4, the gates. Items 1, 2, 3, 5 and 6 are measured. None of item 4 is. Verdict so far: READY AFTER FIXES. The fixes are to the body only, and the verdict holds only if the gates pass. I found no MUST.

PR: #1313, #1313. Branch l3-1232-not-the-source-asks-no-leg. The head reads back from GitHub as cf65850634768426f36af3bd00a0a513433d2da9, and main is 106f92ff9585f00ddbb2151f8a3a4140ae6c4e69 both locally and on GitHub.

The two choices

Choice 1: under --local-remote-dir, the disk is still asked beside plan-not-the-source. RIGHT, and it is what #1232's "To settle" asks. The issue asked the PR to say which way and to put a row on it. The PR does both:

One of the three reasons the body gives goes too far (SHOULD-1). "The plan the remedy writes is asked of the same two paths" is true only when the plan's legs.spec: and legs.code: are the source's own names. I measured the case where they are not: a plan written for another project, with project: Thing, legs: Thing / Thing-spec / Thing-code, and <dir>/Thing-spec.git present. On both heads, check lists:

  • plan-not-the-source;
  • FINDING leg-remote-exists: <dir>/remotes/Thing-spec.git, the spec leg's bare repository, already exists;
  • Remediation: Choose an empty --local-remote-dir, ....

The plan the first remedy writes (--project Northwind --org testorg) never names that path. The second remedy ("plan from a clone of the one that is") does keep it. So keeping the question is still defensible, but the body should say so.

Choice 2: a plan edited only in legs.assembly: is skipped too. ACCEPTABLE. Two things require it:

The body says why in three plain bullets. I measured what its "Not measured" paragraph leaves open, with a gh that answers nameWithOwner:

  • Main: 3 findings: plan-not-the-source, then leg-remote-exists for testorg/Northwind-spec and for testorg/Northwind-code, with ON_GITHUB_FIX.
  • Head: 1 finding(s), plan-not-the-source only.

These are the repositories the remedy's plan names, so the person now meets them one round later. That is acceptable, and the body should say it as measured (SHOULD-2).

Findings

SHOULD-1 (body). In "The choice under --local-remote-dir", replace the bullet "No org: is part of a leg's path. ..." with:

No org: is part of a leg's path. The plan the remedy writes is asked of the same two paths wherever the plan keeps the source's own leg names: for an org: edit, the one #1232, #1210's rows and the relative-url rows measure, and for a legs.assembly: or project: edit. A plan written for another project, with its own legs.spec: and legs.code:, is asked of that project's paths: measured by RV1232, with <dir>/Thing-spec.git present, check lists leg-remote-exists for it beside plan-not-the-source on both heads. That is a path the plan plan --project <source> writes does not name, and the one the other remedy, a plan from the repository the plan is about, keeps. It is kept: it is true of the disk, and it asks no remote. adopt-folder-cannot-be-made names the directory, which no mend changes.

SHOULD-2 (body). Replace the paragraph "Not measured. By the code, not by a run: ..." with:

Measured by RV1232, with a gh that answers {"nameWithOwner": ...}: on the legs.assembly: Elsewhere plan, main's check listed plan-not-the-source and a leg-remote-exists for each of testorg/Northwind-spec and -code (3 findings), and this head lists plan-not-the-source alone (1 finding). Those are the legs the remedy's plan names, so a person meets them after the mend, one round later. #1232's first "Done when" asks for no gh call on any plan for another repository, so the skip holds there too.

NIT-1 (body, Gates). g2 leaves out tests/test_adopt_plan_and_remote_paths_spelled.py. It names plan-not-the-source and has an another-repository row in its "github" mode (:350). It came onto main in 8bcdb2c6 (2026-11-09) and onto this branch through the restack; it is not in 791106c2. So the body's "every module that greps a code whose findings this change gates" does not hold yet. Lane 3 should run it (command below) and add it to g2 with its result line.

NIT-2 (body). After the robot line, the Sourcery bot added a "## Summary by Sourcery" block. Its lines 212 and 218 each hold U+2019. Everything else in the body is ASCII except the robot line, U+1F916 at line 208.

NIT-3 (code, optional; the head would change). The test module names 791106c2 4 times (:57, :233, :257, :286). That is #1210's PR head, and git merge-base --is-ancestor 791106c2 106f92ff says it is not on main, so a plain clone cannot show it. 106f92ff, #1210's squash, behaves the same in my probe, so "Before #1232", or naming 106f92ff, would point at something a reader can find.

NIT-4 (out of scope). The docs/cli.md:684 help still says that without --local-remote-dir, check asks GitHub. It also did not mention the existing RENAMES_THE_LEGS skip, so the new skip is consistent with it, but the sentence now has two exceptions it does not state.

Item 2: before and after (measured)

The probe ran check and, where marked, execute with main's adopt-project.py (sha256 f42c0e973c33) and with the head's (7f0a247ffb65) on the same plan. Each case had a fresh base, a logging gh stub first on PATH, and a proxy that goes nowhere. The stub's modes were exit 97, "exists" (answers nameWithOwner) and "notfound". The result is 16 cases, 28 runs on each head:

  • Changed: 5 check runs, all of them with no --local-remote-dir on a plan for another repository: org 97, org exists, assembly 97, assembly exists, whole-plan 97.
    • The 97 runs only lose their two repo view calls and the two NOTE lines on stdout. Stderr and exit are unchanged.
    • The "exists" runs also drop their two leg-remote-exists findings and the ON_GITHUB_FIX line, and the count goes from 3 to 1.
  • Identical: the other 11 check runs and all 12 execute runs (SAME True). That includes:
    • the source's own plan without --local-remote-dir: on 97, gh was asked repo view testorg/Northwind-spec and -code, 2 NOTEs, exit 0; on exists, two leg-remote-exists, exit 1; on notfound, exit 0;
    • its execute: exit 2 adopt-remote-unreadable without the flag, and exit 2 leg-remote-exists with the flag and a spec leg already there;
    • every run under --local-remote-dir.
  • No probe made a leg. remotes after held only what the probe put there.

This matches the body's table and its "the same bytes" claims.

Item 3: the rows and mutants (measured)

Run Result
the 4 new rows at the head 4 passed, 18 deselected in 7.78s
the 4 rows with main's file swapped in, then restored (cmp OK, 7f0a247ffb65) 2 failed, 2 passed, 18 deselected in 7.20s; the two failures are the asks_github_nothing diffs (acme/... and testorg/...)

My three mutants were each applied alone. The whole module ran (22 rows), and the file was restored and checked with cmp (RESTORED 7f0a247ffb65 each time).

Mutant Result Killed by
M5: gh still asked, only the NOTE dropped beside another repository 2 failed, 20 passed both asks_github_nothing rows
M6: skip only where org: is not the source's own 1 failed, 21 passed asks_github_nothing[assembly] only, so the parameter earns its place
M7: cmd_execute runs gh repo view <org>/<spec> before refusing 2 failed, 20 passed both rows, through execute's log (At index 1 diff: ['repo view testorg/Northwind-spec --json nameWithOwner'] != [])

Item 5: merges (git 2.43.0, measured)

Item 6: the body (measured)

  • closingIssuesReferences reads [1232], and Closes #1232 is the only closing keyword.
  • It is ASCII except as NIT-2 says.
  • It says what a person sees change, both choices, and the restack.
  • Every number I re-measured matches, except the conflicted-tree id noted in item 5.

Not measured

  • g1 was stopped at LAST STEP (SIGTERM, exit 143) after 67 rows passed with no F or E printed. No pytest is running.
  • g2 and g3 were never started.
  • The doctor was not run (optional; the writer ran it).

Run these from the clone, one at a time:

X=/workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch
cd /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232 || exit 1
$X/envrun.sh /dev/null /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=/tmp/claude-1000/l3bt/RV1232-g1 -rs tests/test_adopt_plan_against_its_source.py tests/test_adopt_plan_value_order.py tests/test_adopt_pins.py tests/test_adopt_root_symlinks.py > $X/g1.log 2>&1
$X/envrun.sh /dev/null /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=/tmp/claude-1000/l3bt/RV1232-g2 -rs tests/test_adopt_check_plan_name.py tests/test_adopt_plan_and_remote_paths_spelled.py tests/test_adopt_project_is_the_source.py tests/test_adopt_relative_submodule_url.py tests/test_adopt_split_branch.py > $X/g2.log 2>&1
$X/envrun.sh /dev/null /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=/tmp/claude-1000/l3bt/RV1232-g3 -rs tests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_agents_md_codes.py tests/test_git_output_decoding.py tests/test_stdout_encoding.py tests/test_path_guard_hygiene.py tests/test_no_auto_maintenance.py tests/test_update_add_codes_documented.py > $X/g3.log 2>&1

Paths

  • Clone: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232, detached at the head, clean. adopt-project.py matches the saved head copy by cmp.
  • Scratch: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch:
    • checkpoint: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/CHECKPOINT.md
    • probe: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/probe/rvprobe.py
    • probe results: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/probe/out/SUMMARY.txt
    • mutants: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/mut/
    • merges: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/merge/
    • main worktree: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV1232-scratch/wt-main, at 106f92ff
  • Basetemps: /tmp/claude-1000/l3bt/RV1232-*

Lane 3: g1, g2 and g3 run now from lane 3's main loop with RV1232's own commands, one at a time at cf6585063. Their lines will follow on this thread, and the body fixes wait for them.

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-11-10T14:34:09Z) READY at cf6585063. Lane 3's main loop ran RV1232's three gates at this head with RV1232's own commands, one pytest at a time, each exit 0:

  • g1: 420 passed, 3 skipped;
  • g2, with tests/test_adopt_plan_and_remote_paths_spelled.py (NIT-1): 298 passed;
  • g3, the guards: 470 passed, 3 skipped.

The logs are in RV1232-scratch. So RV1232's verdict holds: READY AFTER FIXES, with the fixes to the body only.

Lane 3 took SHOULD-1, SHOULD-2 and NIT-1 into the body word for word, and added an "Independent review" section. It did not take two:

  • NIT-3, to keep the reviewed head;
  • NIT-4, which is out of scope; it is a follow-up candidate for Brett Heap.

closingIssuesReferences reads [1232], and the body is ASCII above the robot line. With the fixes in, #1313 is READY: v1.2.1 is set, and it is marked ready. Lane 3 places it 31st by its READY time, after #1305 (#473).

@brettheap brettheap added this to the v1.2.1 milestone Nov 10, 2026
@brettheap
brettheap marked this pull request as ready for review November 10, 2026 14:34
@sourcery-ai

sourcery-ai Bot commented Nov 10, 2026

Copy link
Copy Markdown

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 4 days and 6 hours by commenting @sourcery-ai review. Upgrade to get a review now.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant