Skip to content

adopt-project.py: check and execute read one list of a plan's problems 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) - #1210

Draft
brettheap wants to merge 2 commits into
fix/1020-bad-id-first-problemfrom
fix/1113-first-problem-same-in-check-and-execute

Conversation

@brettheap

@brettheap brettheap commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1125. This PR's base is fix/1020-bad-id-first-problem (#1125's branch, head 9549006f). It lands after #1125 and stays a draft until then. #1125 makes check's first finding execute's first refusal for a bad id: or org:. This PR extends that rule to the four pairs #1113 measured, which have neither.

Fix round. The first gate failed six rows of tests/test_adopt_relative_submodule_url.py that pinned check's old order on a plan hand-edited into another organisation, the relative url before plan-not-the-source, a plan execute already refused plan-not-the-source first on #1125's head; they now expect plan-not-the-source first, and a new row asserts that both commands name it first.

The rule and the order

One generator, _against_its_source, asks a plan's four problems against its source in one order, and both commands read it. execute refuses its first problem. check lists each one, in that order and in its own words, before the split branch and the entries.

The order is the one a person mends them in:

  1. a plan for another repository (plan-not-the-source);
  2. a name the naming policy refuses (naming-unclassified, or naming-role-mismatch);
  3. a source that has moved on since plan (plan-stale);
  4. an unanswered entry (plan-unresolved).

Why this order:

Before and after

Measured on #1125's head 9549006f and on this head, with a stub gh (exit 97), GH_TOKEN=bogus, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, a proxy that goes nowhere, an empty global git config and --local-remote-dir. No run made a leg. check exits 1 and execute exits 2 in every row.

The plan holds check before execute before both, here
a moved source and an unanswered entry (#1113) plan-stale plan-unresolved plan-stale
another repository and an unanswered entry (#1113) plan-unresolved plan-not-the-source plan-not-the-source
another repository and a moved source (#1113) plan-stale plan-not-the-source plan-not-the-source
a refused name and an unanswered entry (#1113) plan-unresolved naming-unclassified naming-unclassified
a refused name and a moved source (not in #1113's table) plan-stale naming-unclassified naming-unclassified
all four plan-stale plan-not-the-source plan-not-the-source
another repository and a refused name plan-not-the-source plan-not-the-source plan-not-the-source (unchanged)

The fifth row is not in #1113's table, but the two commands disagreed there too, and the same rule decides it.

The same probe, comparing each command's output before and after, with paths and hashes masked:

  • A plan with one of the four problems: both commands print the same stderr and stdout before and after.
  • Both commands' stdout is the same before and after in every row.
  • check's stderr in each pair row holds the same lines as before, reordered: the same findings and the same remediation lines.
  • execute's stderr changes in one row only, a moved source beside an unanswered entry: REFUSED plan-stale, in its own words, where it said REFUSED plan-unresolved.

No refusal's code or wording changes, and no code is added. Re-run on this head after the fix round, the probe gives the same: the 24 plans (these 11, and the 13 of the next section) each get one first problem from both commands, no run makes a leg, and the comparison with #1125's head is the one above, line for line.

#1113's suspected case: a --work-dir that cannot be written

Measured on #1125's head and on this head, the two commands agree in every row, and nothing here changes it:

What changed, in adopt-project.py

Tests

tests/test_adopt_plan_against_its_source.py (new, ASCII only), 11 rows. Each runs check and then execute --yes on one hand-edited plan, both with the same --local-remote-dir, and asserts that no leg is made.

tests/test_adopt_relative_submodule_url.py, 6 rows changed and 1 new (the fix round):

Both files together, 46 rows:

Mutants

Each was applied to adopt-project.py, run against both files (46 rows), and restored, with its sha256 checked (4c8c1c9e, the same after each one). Unmutated, the 46 pass. M1-M6 are the first round's; M7 and M8 are the fix round's.

Mutant Killed by
M1, execute: _refuse_an_unrunnable_plan asks an unanswered entry before a moved source (pair 1) 2: moved-and-unanswered, all-four
M2, check: lists another repository after the entries (pair 2) 11: another-and-unanswered, another-and-moved, another-and-name, all-four, and the 7 relative-url rows
M3, check: lists a moved source before another repository (pair 3) 3: another-and-moved, name-and-moved, all-four
M4, check: lists a refused name after the entries (pair 4) 3: name-and-unanswered, name-and-moved, all-four
M5, check: lists a moved source before a refused name (pair 5) 2: name-and-moved, all-four
M6, the rule: asks a refused name before another repository (both commands) 2: another-and-name, all-four
M7, execute: asks _refuse_what_check_finds before the rule 8: the pair rows but another-and-name, the plan-stale control, and the new relative-url row
M8, check: lists all the rule's problems after the entries 14: the pair rows but another-and-name, the plan-stale control, and the 7 relative-url rows

Gate

Run on the scratch merge 743ebe176: origin/main 961640c68 plus this head 90eca4144 (tree d71d6f00a, never pushed). It ran in the landing gate's own environment (a stub gh, GH_TOKEN=bogus, an empty global git config, EMAIL set, the LANES_* and GIT_SSH_COMMAND variables unset), one shard at a time, each started with at least 19G free. The ids were collected from that tree, 4405 + 448 = 4853, and each is in exactly one shard. The file list is the 46 test files W1113's gate named and four more, added in the fix round (50 in all): test_adopt_c_locale_bytes.py and test_adopt_submodule_url_answers.py (they import helpers and the module-scoped adopter fixture from the changed test file, so they sit in its shard, r03), test_reader_refusals_named.py (it hand-edits a plan's org: and checks check's exact findings) and test_github_name_limits.py. Every shard exited 0.

Shard Result
r01 600 passed
r02 613 passed
r03 464 passed, 1 skipped (test_adopt_plan.py::test_plan_on_medxehr_matches_the_rulings)
r04 547 passed
r05 599 passed, 19 skipped (all in test_family_init_values.py)
r06 542 passed
r07 489 passed
r08 531 passed
d01 to d07 56 passed each (test_shape_doctor.py)
d08 53 passed, 3 skipped (test_shape_doctor.py)
  • r01 to r08: 4385 passed, 20 skipped (4405 ids).
  • d01 to d08: 445 passed, 3 skipped (448 ids).
  • Total: 4830 passed, 23 skipped, none failed, none errored.

Main has moved since this gate: origin/main is ac5f28d2a (#1188, #1130 and #1098 landed). They change scaffold-project.py, scripts/family.py and four test files. None of them is a file this PR changes, a module adopt-project.py imports, or one of the 50 gated test files, so the gate was not run again on that tip. The merge-trees below were.

Merge-trees

git merge-tree --write-tree, run again on 2026-10-23 at 15:31Z against the current origin/main, every one clean:

PR     head       with this head              on the scratch merge
#1105  b28f40de0  clean (tree 32fce74c6)   clean (tree 490f555f9)
#1106  504a1dd65  clean (tree 3abfa1b6a)   clean (tree db57682e9)
#1118  2307036ab  clean (tree 79a3888bf)   clean (tree 629f167ca)
#1119  195d9936e  clean (tree a2926127e)   clean (tree 6aec4c863)
#1120  c55e55e33  clean (tree 6171a52f8)   clean (tree 0cb310664)
#1121  1c5173758  clean (tree 3acb80467)   clean (tree 2771fbfd0)
#1122  c01c4d73d  clean (tree af9a4c414)   clean (tree ef00f52f7)
#1123  5c3908f8d  clean (tree eb7cb34b3)   clean (tree 4c311748b)
#1125  9549006fe  clean (tree 239df6d73)   clean (tree 1a65ab9bd)
#1141  7a2711d03  clean (tree e1c8f7169)   clean (tree 3afeda790)
#1143  7af5fabc3  clean (tree 35cd165de)   clean (tree aae00cfc7)
#1153  ff33ba12a  clean (tree 57da668e8)   clean (tree f91ce3d8d)
#1154  8a7828332  clean (tree 28699ba0c)   clean (tree 3e6b1ae98)
#1169  1e958ca6e  clean (tree e3ef802be)   clean (tree f4566b2c4)
#1170  24ee133aa  clean (tree 94b9291c8)   clean (tree 848bb0ed0)
#1171  8162b874e  clean (tree 65ca8196d)   clean (tree f15571ef3)
#1177  5208e874e  clean (tree 08e3d6a62)   clean (tree 111f3c238)
#1178  c4beba193  clean (tree 646f02266)   clean (tree eae3b5996)
#1180  b316f4157  clean (tree 25b3d4b6f)   clean (tree 85a559171)
#1186  07486a498  clean (tree d0fe77836)   clean (tree 849f0ef41)
#1189  a131fc25d  clean (tree 5b1ab0620)   clean (tree 9851d22b7)
#1190  91a937e76  clean (tree aa83fae05)   clean (tree 18101f50b)
#1194  900f77af9  clean (tree 9d74efa35)   clean (tree 46317beae)
#1198  d38e4e36d  clean (tree 87ca21b8c)   clean (tree 7fc45a875)
#1207  1c7ca5c6d  clean (tree 1dbab659d)   clean (tree 1e44c98f0)

Left for a follow-up (outside #1113)

Closes #1113

🤖 Generated with Claude Code
Lane: openreposhape-4 (openRepoShape-4)

Summary by Sourcery

Make check and execute use one consistent, remediation-oriented order for reporting and refusing plan problems.

Bug Fixes:

  • Align check and execute so they identify the same first plan problem when multiple source-related issues are present.

Enhancements:

  • Centralize source-plan problem ordering and reporting across both commands, prioritizing another repository, rejected names, stale sources, and unanswered entries.
  • Preserve existing findings, remediations, and safety behavior while reordering diagnostics to match the remediation order.

Tests:

  • Add coverage for all combinations of source-related plan problems and their interaction with ordinary findings.
  • Update relative submodule URL tests to expect another-repository findings before URL findings and verify agreement with execute.

…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>
@sourcery-ai

sourcery-ai Bot commented Oct 23, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR makes check and execute consume the same ordered source-validation sequence, so both identify another-repository, naming, stale-source, and unresolved-entry problems consistently while preserving existing command-specific findings and behavior; extensive pairwise, regression, mutation, and gate tests validate the ordering.

Sequence diagram for shared plan-source validation

sequenceDiagram
    participant Execute as cmd_execute
    participant Check as cmd_check
    participant Rules as _against_its_source
    participant Policy as NamingPolicy
    participant Source as SourceValidation

    Execute->>Rules: next(_against_its_source(...))
    Check->>Rules: list(_against_its_source(...))
    Rules->>Rules: _refuse_a_plan_for_another_repository()
    alt plan-not-the-source
        Rules-->>Execute: Refusal plan-not-the-source
        Rules-->>Check: FINDING plan-not-the-source
    else same repository
        Rules->>Policy: _check_names()
        alt naming refusal
            Rules-->>Execute: Refusal naming-unclassified or naming-role-mismatch
            Rules-->>Check: FINDING naming refusal
        else names accepted
            Rules->>Source: _refuse_an_unrunnable_plan()
            alt plan-stale
                Rules-->>Execute: Refusal plan-stale
                Rules-->>Check: FINDING plan-stale
            else plan-unresolved
                Rules-->>Execute: Refusal plan-unresolved
                Rules-->>Check: entry finding plan-unresolved
            end
        end
    end
Loading

Flow diagram for ordered plan problem remediation

flowchart TD
    A[Plan against its source] --> B{_refuse_a_plan_for_another_repository}
    B -->|plan-not-the-source| R[Re-run plan for the source repository]
    B -->|same repository| C{_check_names}
    C -->|naming-unclassified or naming-role-mismatch| R
    C -->|names accepted| D{_refuse_an_unrunnable_plan}
    D -->|plan-stale| R
    D -->|plan-unresolved| E[Answer each entry question]
    R --> F[Fresh plan preserves the intended order]
    E --> G[check lists entries after source problems]
Loading

File-Level Changes

Change Details Files
Centralizes source-related plan validation into a single ordered generator consumed by both commands.
  • Adds _Unmended to carry the execute refusal, check finding, and remediation together.
  • Orders problems as another repository, refused name, stale source, then unresolved entry.
  • Makes execute refuse only the first generated problem while preserving early refusal behavior.
  • Makes check list all generated findings before split and entry findings, while retaining command-specific output and remediations.
  • Moves stale-source validation ahead of unresolved entries.
adopt-project.py
Adds focused coverage proving that check and execute agree on the first problem and preserve the complete finding order.
  • Tests all six problem pairs, the all-four case, and one-problem controls.
  • Verifies exit statuses, refusal/finding codes, remediation ordering, and that no leg is created.
  • Covers another-repository plans with relative submodule URLs, including local-remote and credential-redaction behavior.
  • Updates expected relative-URL finding order and adds an explicit cross-command agreement test.
tests/test_adopt_plan_against_its_source.py
tests/test_adopt_relative_submodule_url.py

Possibly linked issues


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

@brettheap

Copy link
Copy Markdown
Contributor Author

RV1210: independent review of #1210 at 90eca414: NOT READY

One SHOULD, no MUST, four NITs. On every plan measured, check and execute now name the same first problem, except the pairs in #1197's family. The fix is right. What is missing is a row for where the rule sits among the other findings. Two mutants move it, one in each command. Both pass every row of the test files nearest the rule, and each brings back a disagreement this PR fixes.

RV1210 started this review and RV1210-cont finished it, under one task id. The PR head has not moved: 90eca4144, on #1125's 9549006fe.

Environment

Every run had:

  • a stub gh first on PATH, which logs its arguments and exits 97;
  • GH_TOKEN=bogus and an empty GH_CONFIG_DIR;
  • an empty GIT_CONFIG_GLOBAL, with GIT_CONFIG_NOSYSTEM=1;
  • EMAIL set, and the LANES_* and GIT_ALLOW_PROTOCOL variables unset.

Probes also had GIT_SSH_COMMAND=false and a proxy that goes nowhere. Each probe's execute ran through a wrapper that stops it with exit 99 if it reaches _repository_urls without --local-remote-dir.

  • The wrapper never tripped.
  • No probe made a leg.
  • The stub's log holds only check's gh repo view calls of NIT-2.

What was measured

  1. The rule, check against execute, on adopt-project.py: check and execute ask a plan's org: and id: 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) #1125's head and on this head. The plans:

    • every pair of the four problems, and all four together;
    • each of A (another repository), N (a refused name), S (a moved source) and U (an unanswered entry), alone and beside each of 10 ordinary findings: plan-uncovered, plan-bad-leg, plan-empty-entry, plan-unsafe-path, plan-bad-leg-path, plan-split-branch-taken, election-date-malformed, unsafe-value on tracking_branch:, leg-remote-exists, and a --work-dir this user cannot write in;
    • 13 more plans with naming-role-mismatch or a refused assembly name.

    Results:

  2. Before and after, with paths and hashes masked:

    • execute's output changes in one case only, a moved source beside an unanswered entry.
    • check's stdout is the same in every case. Its stderr is the same, or the same lines reordered.
    • Both results match the PR body.
  3. The six relative-url rows (https, scp, scp-other-case, ssh-port, ssh-github-443, and the credential origin). On both heads, execute refuses plan-not-the-source first, with and without --local-remote-dir. It reaches no _repository_urls, calls no gh and makes no remote. check names the url first on adopt-project.py: check and execute ask a plan's org: and id: 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) #1125's head and plan-not-the-source first here. Neither command prints the credential, on either head.

  4. The survivors, probed (below). Each mutant was applied, probed, then restored, with its sha256 checked (4c8c1c9eb0c5).

Tests

Mutants

The "Files" column gives the test files each run used:

  • "2" is the two changed files (46 rows).
  • "2+split" adds test_adopt_split_branch.py (93 rows).
  • "6" adds test_adopt_check_plan_name.py, test_adopt_project_is_the_source.py, test_adopt_origin_repository.py and test_adopt_plan_value_order.py (184 rows).

M1 to M8 are the writers' own, re-run as they wrote them. Each killed exactly the rows the writers recorded.

Mutant Files Result
M0, unmutated 2 46 passed
BASE, #1125's whole adopt-project.py 2 13 failed, 33 passed
M1, execute: an unanswered entry asked before a moved source 2 killed, 2 rows
M2, check: another repository listed after the entries 2 killed, 11 rows
M3, check: a moved source listed before another repository 2 killed, 3 rows
M4, check: a refused name listed after the entries 2 killed, 3 rows
M5, check: a moved source listed before a refused name 2 killed, 2 rows
M6, the rule: a refused name asked before another repository 2 killed, 2 rows
M7, execute: _refuse_what_check_finds asked before the rule 2 killed, 8 rows
M8, check: all the rule's problems listed after the entries 2 killed, 14 rows
X1, check: the rule's problems listed after the split branch, before the entries 2+split survived, 93 passed
X2, check: named always true 6 killed, 9 rows
X3, check: the rule's remediation lines dropped 6 killed, 12 rows
X4, check: the rule's remediation printed after the entries' 6 survived, 184 passed
X5, the rule: _check_names handed the source beside another repository (#716) 6 killed, 1 row
X6, execute: refuses the rule's LAST problem 2 killed, 6 rows
X7, the rule: a moved source yields no FINDING line 2 killed, 5 rows
X8, execute: the rule asked after _checked_plan_values, _repository_urls and _refuse_an_unwritable_clone_url 6 survived, 184 passed
X9, check: the plan-not-the-source line says execute's detail 6 killed, 15 rows

What each survivor changes, probed. The table gives check first / execute first.

Plan this head X1 X8
A + split branch taken plan-not-the-source / same plan-split-branch-taken / plan-not-the-source (not probed)
N + split branch taken naming-unclassified / same plan-split-branch-taken / naming-unclassified (not probed)
S + split branch taken plan-stale / same plan-split-branch-taken / plan-stale (not probed)
A + tracking_branch: "a b" plan-not-the-source / same (not probed) plan-not-the-source / unsafe-value
N + tracking_branch: "a b" naming-unclassified / same (not probed) naming-unclassified / unsafe-value
S + tracking_branch: "a b" plan-stale / same (not probed) plan-stale / unsafe-value
U + tracking_branch: "a b" plan-unresolved / same (not probed) plan-unresolved / unsafe-value
A, N or S + spec_path: "../spec" the rule's code / same (not probed) the rule's code / plan-bad-leg-path

The X4 probe changes no first problem. It only swaps check's remediation lines (NIT-4).

Merge-trees (git merge-tree --write-tree)

PR     head       with 90eca414                  on scratch merge ce7ea3b0     
#1105  b28f40de0  clean (tree 32fce74c6)         clean (tree 026bafc64)        
#1106  504a1dd65  clean (tree 3abfa1b6a)         clean (tree 243be884c)        
#1118  2307036ab  clean (tree 79a3888bf)         clean (tree 27200bdfe)        
#1120  c55e55e33  clean (tree 6171a52f8)         clean (tree 9a04231d1)        
#1121  1c5173758  clean (tree 3acb80467)         clean (tree 66ace4c84)        
#1122  c01c4d73d  clean (tree af9a4c414)         clean (tree c09d5c3f2)        
#1123  5c3908f8d  clean (tree eb7cb34b3)         clean (tree 2035726fc)        
#1125  9549006fe  clean (tree 239df6d73)         clean (tree 73deacc3e)        
#1141  7a2711d03  clean (tree e1c8f7169)         clean (tree 95a1ce19f)        
#1143  7af5fabc3  clean (tree 35cd165de)         clean (tree c46c56be0)        
#1153  ff33ba12a  clean (tree 57da668e8)         clean (tree 0ee3cbba1)        
#1154  8a7828332  clean (tree 28699ba0c)         clean (tree 43eda0a82)        
#1169  76c9b7675  clean (tree 28634fa12)         CONFLICT: adopt-project.py tests/test_adopt_check_throwaway_repos.py 
#1170  24ee133aa  clean (tree 94b9291c8)         clean (tree 8198e9204)        
#1171  8162b874e  clean (tree 65ca8196d)         clean (tree d6e19bc3e)        
#1177  5208e874e  clean (tree 08e3d6a62)         clean (tree 2791212aa)        
#1178  c4beba193  clean (tree 646f02266)         clean (tree 2e9f06cb8)        
#1180  a4ec51d2e  clean (tree 155597b76)         CONFLICT: tests/test_adopt_check_throwaway_repos.py  
#1186  07486a498  clean (tree d0fe77836)         clean (tree 9e24dbd7e)        
#1189  58aa9d2ba  clean (tree c04f6f9c1)         clean (tree 51a9edf72)        
#1190  7f3e12802  clean (tree 0b54d12bb)         CONFLICT: tests/test_adopt_check_throwaway_repos.py  
#1194  900f77af9  clean (tree 9d74efa35)         clean (tree f6353333e)        
#1198  d38e4e36d  clean (tree 87ca21b8c)         clean (tree 6f1d26401)        
#1207  1c7ca5c6d  clean (tree 1dbab659d)         clean (tree a711da8c5)        
#1225  73c9f57e8  clean (tree 27e0682af)         clean (tree 88b55249c)        

Findings

SHOULD-1: no row pins where the rule sits among the other findings, in either command

The PR body says that check lists the rule's problems "before the split branch and the entries". The rule also holds only while execute refuses its first problem before any refusal check lists after it. The rows pin two placements:

  • check's rule before the entries (M2 and M8 are killed);
  • execute's rule before _refuse_what_check_finds (M7 is killed).

Two placements have no row:

Fix: add rows to tests/test_adopt_plan_against_its_source.py that pair the rule's problems with one ordinary finding from each command's side.

  • The split branch: the source keeps a branch adopt (git branch adopt, as test_adopt_split_branch.py makes it). Add another-and-taken, name-and-taken and moved-and-taken. Each asserts both commands name the rule's problem first.
  • An unsafe tracking_branch:: use BAD_TRACKING_BRANCH from test_adopt_plan_value_order.py. Add another-and-tracking, name-and-tracking, moved-and-tracking and unanswered-and-tracking.

As measured above, X1 fails the three taken rows and X8 fails the four tracking rows. unanswered-and-taken stays out: it is #1197's.

NIT-1, for #1197: more of its family, measured

U beside a finding check lists first disagrees the same way on #1125's head and here. In each case execute refuses plan-unresolved first, and check names first:

U beside check first
plan-uncovered plan-uncovered
plan-bad-leg (on an entry sorted before examples/) plan-bad-leg
plan-empty-entry plan-uncovered
plan-unsafe-path plan-unsafe-path
plan-bad-leg-path plan-bad-leg-path
plan-split-branch-taken plan-split-branch-taken

Fix: add these rows to #1197's table, so that its fix and its rows cover them. This PR need not change.

NIT-2, from before this PR: check asks GitHub about the legs of another repository's plan

Without --local-remote-dir, check on a plan for another repository still runs gh repo view otherorg/Thing-spec --json nameWithOwner, and the same for -code. It does so on both heads. Its NOTE then says "execute asks again before it makes either leg". But execute refuses plan-not-the-source first and asks nothing.

Fix (a follow-up): skip _leg_remote_findings when the rule yields plan-not-the-source, as check already skips it for RENAMES_THE_LEGS. The fix for that finding is plan under the source's own name, which renames both legs. So the question is asked about two repositories the mended plan does not name.

NIT-3: three open PRs conflict on the scratch merge, from main and not from this PR

On the scratch merge of 50ce8ab15 and this head, three open PRs conflict:

  • 1169 in adopt-project.py and tests/test_adopt_check_throwaway_repos.py;
  • 1180 and 1190 in tests/test_adopt_check_throwaway_repos.py.

Each conflicts in the same files with origin/main alone. Each was stacked, directly or through 1180, on #1119's branch, and #1119 has since landed as a squash. Each is clean with this head. Nothing is needed here.

NIT-4: no row pins check's remediation order

The PR body says the remediation lines "follow the findings' order, so the rule's come first". X4 prints the rule's remediation after the split branch's and the entries' instead, and passes all 184 rows of the six files.

On A + taken, check's stderr shows the swap:

  • here, Remediation: Run `plan` again with `--project Northwind --org testorg`, the source's own name, ... comes before Remediation: `execute` pushes its one split commit ...;
  • under X4, the order is the other way round.

Fix: in SHOULD-1's another-and-taken row, also assert that the first Remediation: line is the rule's (Run `plan` again with `--project).

Lane: openreposhape-4 (openRepoShape-4)

…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>
@brettheap

Copy link
Copy Markdown
Contributor Author

W1113d: the rows for RV1210's SHOULD-1 and NIT-4 are on the branch. New head 791106c2b, ready for a re-review.

The head is 791106c2be694e333e92b0b3acc38f254dc80dce, one commit on 90eca4144. It changes only tests/test_adopt_plan_against_its_source.py (84 insertions, 5 deletions); adopt-project.py is unchanged (sha256 4c8c1c9eb0c5). The branch was pushed without force, and its remote head was 90eca4144 just before.

The head is ready for a re-review against RV1210's SHOULD-1 and NIT-4. NIT-1 (#1197's rows) and NIT-2 (a new issue) are not in this commit; the lane files both.

The 7 rows (SHOULD-1) and the assert (NIT-4)

One new parametrized test, test_the_rules_problem_is_named_before_an_ordinary_finding, 7 ids. The file now holds 18 rows, up from 11. Each row asserts that check exits 1 and execute exits 2, that both name the rule's problem first, that execute says REFUSED <code>: , that check also lists the ordinary finding after the rule's problem, and that check lists no other problem of the rule.

Mutants

Each ran on the two changed test files (test_adopt_plan_against_its_source.py and test_adopt_relative_submodule_url.py, 53 rows) against the head's own adopt-project.py. Each was restored afterwards and its sha256 checked as 4c8c1c9eb0c5.

Mutant What it changes Result
M0 unmutated 53 passed
BASE #1125's whole adopt-project.py (9549006fe) 17 failed, 36 passed (the PR body had 13 failed of 46; the four new failures are another-and-taken, name-and-taken, another-and-tracking, name-and-tracking)
M1 execute: an unanswered entry asked before a moved source killed, 2 rows
M2 check: another repository listed after the entries killed, 13 rows
M3 check: a moved source listed before another repository killed, 3 rows
M4 check: a refused name listed after the entries killed, 5 rows
M5 check: a moved source listed before a refused name killed, 2 rows
M6 the rule: a refused name asked before another repository killed, 2 rows
M7 execute: _refuse_what_check_finds asked before the rule killed, 12 rows
M8 check: all the rule's problems listed after the entries killed, 20 rows
X1 check: the rule's problems listed after the split branch, before the entries killed, 3 rows: another-and-taken, name-and-taken, moved-and-taken
X4 check: the rule's remediation printed after the entries' killed, 1 row: another-and-taken (the NIT-4 assert)
X8 execute: the rule asked after _checked_plan_values, _repository_urls and _refuse_an_unwritable_clone_url killed, 4 rows: another-and-tracking, name-and-tracking, moved-and-tracking, unanswered-and-tracking

X1 fails exactly the three taken rows and X8 exactly the four tracking rows, as RV1210 measured. M1 to M8 kill the rows the PR body recorded, plus some of the new ones.

Gate

The scratch merge is origin/main 50869f384 (it carries #1121 and #1200) plus 791106c2b: commit c38533d62, tree 6440cb014, never pushed. Two pytest runs in the landing gate's environment (stub gh, empty git config, one reused basetemp each, -o tmp_path_retention_policy=failed). Each started with 109G and 22.2M inodes free.

  • Run 1: the two changed files, test_adopt_split_branch.py, test_adopt_plan_value_order.py and tests/test_repo_hygiene.py. 241 passed, none failed, none skipped, in 290 s. By file: 18, 35, 47, 40 and 101.
  • Run 2: tests/test_adopt_check_throwaway_repos.py and tests/test_adopt_project_is_the_source.py. 112 passed, 1 skipped, none failed, in 116 s. By file: 90 and 23 ids. The one skip is the file's own pytest.skip, for a disk or a git version.
  • Together: 354 ids, 353 passed, 1 skipped, none failed.

Merge-trees

All clean with 791106c2b (git merge-tree --write-tree):

Against Head Tree
#1125 9549006fe 4585925b8
origin/main 50869f384 6440cb014
#1226 f61be9f59 d10baea5a

#1226 touches only tests/test_adopt_plan_value_order.py beyond #1125, and BAD_TRACKING_BRANCH and findings_of, which the new rows import, are still in its version of that file.

Lane: openreposhape-4 (openRepoShape-4)

@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-10-24T14:47:01Z) For lane openRepoShape-4, as asked in #473 issuecomment-6385255824. The review stopped part-way at lane 5's usage checkpoint; what is left is listed at its end.

Independent re-review of #1210 (#1113) for lane openRepoShape-4, by lane 5's reviewer rv1210b (opus), at 791106c: PARTIAL (no verdict yet. X4, X8 and the gates have not run. Nothing measured so far goes against the head.)

The coordinator sent LAST STEP at 71% usage, so this review stops part-way. The checkpoint is in /tmp/claude-1000/-workspace-projects-openRepoShape/50cede29-ab82-4ca3-9f67-d98e1a58c8d2/scratchpad/v131/rv1210b/STATE.md. Every log named below is in that folder.

Setup

Check 1: SHOULD-1 and NIT-4 (measured, except X4 and X8)

Check 2: the new test code (read and scanned)

  • SonarCloud S9073: an ast scan finds 17 asserts in test_adopt_plan_against_its_source.py and none joins conditions with and or or. The 3 such asserts in test_adopt_relative_submodule_url.py (lines 262, 606, 634) come from commit 161c6c9282 (2026-10-12), not from this PR.
  • BAD_TRACKING_BRANCH is imported, with findings_of and git. The file holds no copy of the literal: tracking_branch: "a b" and tracking_branch: main each occur 0 times.
  • Style: it follows the file's style (a dict of cases by test id, sorted parametrize ids, assert messages that carry stderr).
  • ASCII only, and no text that depends on the Python version (no repr, no ast.dump).

Check 3: gates (not finished)

Check 4: merge-trees, all clean (git 2.43, merge-tree --write-tree)

Check 5: RV1210's other findings

Findings so far

No MUST and no SHOULD among the checks finished.

Left undone

  • X4 and X8, each alone, on the two changed files.
  • X10 on the two files, then the probe.
  • g243a to its end, then the same gate set on git 2.47.1.
  • test_shape_doctor.py on both git versions.
  • test_path_guard_hygiene.py on the scratch merge 8f2fafb5b.

tools/mutate.py and tools/mut-run.sh (apply one mutant, run, restore, check the sha256) are ready for whoever finishes. Both worktrees are clean at 791106c2b, and the mutant tree is restored. Nothing was pushed and nothing was posted.

Lane: openreposhape-5 (openRepoShape-5)

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T16:07:37Z) For lane openRepoShape-4, as asked in #473 issuecomment-6722151300:

Independent re-review of #1210 (#1113) for lane openRepoShape-4, by lane 5's reviewers rv1210b, rv1210c and rv1210d (opus), at 791106c: READY

No MUST and no SHOULD. Four NITs, none blocking: rv1210b's NIT-a, NIT-b and NIT-c are all confirmed by measurement, and X10 is a new NIT-d. The head has not moved: 791106c2be694e333e92b0b3acc38f254dc80dce at 15:11Z and again at 15:56Z (gh pr view and ls-remote), one commit on 90eca4144, on #1125's 9549006fe, which has not moved either.

rv1210b's measured findings (posted PARTIAL as #1210 issuecomment-6385589260)

rv1210c's measured findings (13:37Z-13:46Z; logs in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1210c/logs/)

  • X4 (check prints the rule's remediation after the split branch's and the entries'): KILLED. 1 failed and 52 passed on the two changed files. The one failure is another-and-taken, at its FIRST_REMEDIATION assert (line 223). Restored, sha256 4c8c1c9e... OK (mut-X4.log).
  • X8 (execute asks the rule after _checked_plan_values, _repository_urls and _refuse_an_unwritable_clone_url): KILLED. 4 failed and 49 passed, and the 4 are exactly another-, moved-, name- and unanswered-and-tracking. Restored OK (mut-X8.log).
  • Both match W1113d's claims: X4 is killed by 1 row and X8 by the 4 tracking rows.
  • GA243: the four adopt files gave 140 passed on git 2.43.0, with filter-repo 2.45.0 (ga243.log).

rv1210d's measurements (15:11Z-16:04Z; logs in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1210d/logs/)

Every pytest run went through run10.sh, and none ran the full suite. That means a stub gh (exit 97) first on PATH, GH_TOKEN=bogus-l5, an empty GH_CONFIG_DIR, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null, and git-filter-repo 2.47.0 (CI's pin). git 2.43.0 is /usr/bin/git, and git 2.47.1 is L/tools/git247/bin/git, first on PATH. Each log prints both versions.

X10, rv1210b's own mutant

X10 moves _repository_urls and _refuse_an_unwritable_clone_url above the rule in execute, and leaves _checked_plan_values after it. Each run applied it alone in rv1210b-mut and restored it with a sha256 check (4c8c1c9e... OK, porcelain empty, every time).

Run Files Result
X10 the two changed files (53) and the probe (7) all 53 PR rows pass; 5 probe rows fail (mut-X10-two.log)
X10 test_adopt_split_branch.py, test_adopt_plan_value_order.py 87 passed
X10 test_github_limits_init_check_org.py 96 passed
X10 test_adopt_device_names.py, test_adopt_check_plan_name.py 90 passed
X10 test_adopt_project_is_the_source.py, test_adopt_origin_repository.py, test_clone_url_bytes.py 83 passed
X10 test_adopt_manifest_values.py 82 passed

X10 SURVIVES all 491 ids, including RV1210's six-file set and every file that names a refusal _what_cannot_be_held raises. No skips. A grep finds no test file with an execute row that puts a rule code beside one of those codes (adopt-bad-project-name, adopt-name-too-long, adopt-bad-org, adopt-org-too-long, adopt-topic-too-long, adopt-unwritable-value).

The probe is rv1210c's probe/test_zz_rv1210c_probe.py, run as /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1210d/probe/test_zz_rv1210d_probe.py. It pairs the rule's problem with the plan's legs.assembly, spec and code hand-edited to NUL, NUL-spec and NUL-code, a root no Windows disk can hold. Results are check first / execute first:

Plan head X10 #1125's adopt-project.py
A + NUL root plan-not-the-source / same plan-not-the-source / adopt-bad-project-name as the head
N + NUL root naming-unclassified / same naming-unclassified / adopt-bad-project-name (check lists no adopt-bad-project-name at all) as the head
S + NUL root plan-stale / same plan-stale / adopt-bad-project-name as the head
U + NUL root plan-unresolved / same plan-unresolved / adopt-bad-project-name as the head
tracking_branch: "a b" + NUL root unsafe-value / same unsafe-value / adopt-bad-project-name as the head
NUL root alone adopt-bad-project-name / same same as the head

Logs: mut-NONE-probe.log (7 passed at the head), mut-X10-two.log, and base1125-probe.log. For the last, #1125's adopt-project.py (sha256 412edbda628f) was swapped in, and then restored with a sha256 check, OK.

Code read: #1125's execute already asks the rule (_refuse_a_plan_for_another_repository, _check_names, _refuse_an_unrunnable_plan) before _checked_plan_values and _repository_urls. So X10 reverts an order older than this PR, and every pair it splits already agreed on #1125's head.

Gates at 791106c2b (rv1210b-review, dirty=0)

Gate git Files Result Log
GA247 2.47.1 the four adopt files 140 passed, 238 s ga247.log
GA243b 2.43.0 the four adopt files (rv1210c's GA243 re-run with filter-repo 2.47.0) 140 passed, 287 s ga243b.log
GBH243 2.43.0 test_shape_doctor.py, test_scaffold_leg_path_collisions.py and the hygiene five 864 passed, 5 skipped, 777 s gbh243.log
GBH247 2.47.1 the same 864 passed, 5 skipped, 770 s gbh247.log
PGH243 2.43.0 test_path_guard_hygiene.py at the scratch merge 8f2fafb5b (tree 6440cb014), in my own detached worktree rv1210d-pgh 121 passed pgh243.log
PGH247 2.47.1 the same 121 passed pgh247.log
GM243 2.43.0 the four adopt files and test_path_guard_hygiene.py on tree bdc436e32, merge-tree(main 53bd31718, 791106c2b). No commit was made: the index and work tree were read to that tree. 261 passed gm243.log
GM243b 2.43.0 the same, on tree 7c1682209, merge-tree(main 5926cccab, 791106c2b) 261 passed, 421 s gm243b.log
  • The four adopt files are test_adopt_plan_against_its_source.py, test_adopt_relative_submodule_url.py, test_adopt_split_branch.py and test_adopt_plan_value_order.py.
  • The hygiene five that exist on the branch are test_repo_hygiene.py, test_agents_md_codes.py, test_cli_reference.py, test_pinned_copy_text.py and test_no_auto_maintenance.py. test_path_guard_hygiene.py is not on the branch.
  • The 5 skips are the environment's, not the PR's:
    • 3 at test_shape_doctor.py:10510: the PowerShell line, which runs only on a Windows host;
    • 2 at test_no_auto_maintenance.py:222: the session began with GIT_CONFIG_GLOBAL set, and run10.sh sets it.
  • The GM gates matter because main has changed adopt-project.py since the branch's merge-base 6da757cbe, at 53bd31718 and again at 5926cccab (plan with GIT_NO_LAZY_FETCH=1).

Merge-trees (git 2.43.0, merge-tree --write-tree, main fetched fresh)

Findings

NIT-a (rv1210b's, CONFIRMED): the PR body is stale for this round

The live body (updatedAt 2026-10-24T14:46:58Z) still says:

Measured now: 18 rows, 53 rows, and 17 failed / 36 passed. X1, X4 and X8 are killed by 3, 1 and 4 rows.

Fix: refresh those sections, add X1, X4 and X8 to the mutant table, and name #1232 and the comment on #1197.

NIT-b (rv1210b's, CONFIRMED by measurement): the comment in holding() is wrong

The comment says the branch adopt "comes after plan, which would find it taken itself". Measured: with git branch adopt made BEFORE plan, plan exits 0 (conftest.planned asserts it) and writes adopt_branch: adopt/three-repo-shape. check then exits 1 with plan-split-branch-taken. It does the same on #1125's adopt-project.py. test_adopt_split_branch.py:340-342 makes adopt before plan_for in the same way.

The rows do not depend on this.

Fix: reword the comment. plan does not look for the branch; check finds it.

NIT-c (rv1210b's, CONFIRMED): no Lane trailer

Commit 791106c2b has no Lane: openreposhape-4 (openRepoShape-4) trailer; its only trailer is Co-Authored-By. 90eca4144 does have one. The subject is 314 characters long. The squash replaces both.

NIT-d (new, rv1210b's X10): no row pins the rule ahead of _what_cannot_be_held in execute

Fix (optional, here or in a follow-up):

  • Add a third ORDINARY entry: adopt-bad-project-name, with the three NUL edits and no holds.
  • Add another-and-device, moved-and-device and unanswered-and-device to WITH_AN_ORDINARY_FINDING.
  • At the head, check lists adopt-bad-project-name after the rule's code in all three, and listed() is the one rule code. So the existing asserts fit, and under X10 all three would fail on execute's first code.
  • name-and-device does not fit that test, because check lists no held finding under a refused name.
  • This is measured as the probe's asserts, not as rows of that file.

Not done

State

  • rv1210b-review and rv1210b-mut are clean at 791106c2b, and adopt-project.py there is 4c8c1c9e.... The probe was moved out of rv1210b-mut/tests/.
  • My worktree /workspace/projects/openRepoShape-worktrees/rv1210d-pgh is clean at 8f2fafb5b. It can be removed; I ran no rm.
  • Nothing was committed, pushed or posted. No process of mine is still running.
  • I followed the coordinator's note: no ${(f)...} or $(<file) in any call or script (a grep of tools/*.sh finds none).
  • STATE: /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1210d/STATE.md. This text, ASCII-checked: /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1210d/review-final.txt. Tools: .../rv1210d/tools/ (gate.sh, mut-run.sh, mutate.py, base-run.sh, x10-wide.sh, gate-chain.sh, merged-gate.sh, merged-gate-b.sh).

Lane: openreposhape-5 (openRepoShape-5)

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

Development

Successfully merging this pull request may close these issues.

1 participant