Skip to content

shape-doctor.py: in a root that lost scripts/repo_shape.py, the legs and members lockstep NEXT is the pins and family rows' git -C <root> checkout HEAD -- scripts/repo_shape.py (none where HEAD no longer holds it), where it named the root's own gate, which dies on the import (#750) - #771

Merged
brettheap merged 4 commits into
mainfrom
l3-750-lockstep-next
Oct 18, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-3 (openRepoShape-3)
Claim: #750 (comment)

Closes #750.

Lands after the v1.1.0 tag. (It follows #737, which landed as a27c371.)

What changed and why. In a root that lost scripts/repo_shape.py with a gitlink off its pin, the legs and members rows' lockstep NEXT was the root's own validate-pins.py / validate-family.py line. That gate dies on import repo_shape and exits 1, the code it uses for findings, so the line could not do what its WHY said of it, while the pins and family rows beside it printed the checkout HEAD cure since #737. Measured on main e313701: the legs NEXT is python3 <root>/scripts/validate-pins.py --root <root>, and pasted with no PYTHONPATH it exits 1 with ModuleNotFoundError: No module named 'repo_shape'.

Tests (stub gh first on PATH that exits 97, GH_TOKEN=bogus, empty GH_CONFIG_DIR; /usr/bin/python3 -m pytest -q -p no:cacheprovider --basetemp=<scratch>/bt -o tmp_path_retention_policy=failed <files>):

  • tests/test_doctor_validator_rows.py (shape-doctor.py: in a root that lost scripts/repo_shape.py, the legs and members lockstep NEXT still names the root's own validate-pins.py line, which exits 1 with ModuleNotFoundError, while the pins row beside it prints the checkout HEAD cure #750 section): for a gitlink off its pin and a lost repo_shape.py, on legs (own validate-pins.py) and members (own validate-family.py): the NEXT is the cure and equals the pins / family row's NEXT, and the WHY names the gate; the cure pasted and then the lockstep line the row then names, both run as printed from / (the cure exits 0; the gate exits 1 with its pin-gitlink-mismatch / member-gitlink-mismatch finding and no ModuleNotFoundError); where HEAD lacks the file (committed deletion) there is no NEXT and the WHY names the file, the parent of the deleting commit, the gate and the sentence about why no checkout cures it. Before the change these failed on main: the legs NEXT was python3 .../validate-pins.py --root ..., and pasted died on ModuleNotFoundError.
  • tests/test_doctor_next_is_a_command.py: CASES entries lockstep_next-legs-restore and lockstep_next-members-restore, so the NEXT passes the property (no comment, backtick or live operator) and the tail guard.
  • tests/test_doctor_validator_rows.py tests/test_doctor_next_is_a_command.py tests/test_doctor_legs_members_next.py tests/test_repo_hygiene.py tests/test_cli_reference.py: 340 passed.
  • tests/test_shape_doctor.py (once): 436 passed, 3 skipped, 1 xfailed.

Docs. None state this NEXT (README.md, AGENTS.md, docs/handbook.html, docs/cli.md), so none changed.

Pinned copies. None touched; shape-doctor.py is in no copy list.

🤖 Generated with Claude Code

Summary by Sourcery

Make legs and members recommend restoring the root’s missing repo_shape.py before rerunning their validators.

Bug Fixes:

  • Restore the correct repo_shape.py checkout guidance for legs and members lockstep findings when their root validator cannot import the module.

Enhancements:

  • Clarify recovery guidance for validators that must be rerun after restoring repo_shape.py, including roots where HEAD no longer contains the file.

Tests:

  • Add coverage for lockstep recovery commands, rerunning the restored validators, and committed deletions of repo_shape.py.

…gs` and `members` lockstep NEXT is the `pins` and `family` rows' `git -C <root> checkout HEAD -- scripts/repo_shape.py` (none where HEAD no longer holds it) and its WHY names the gate to run after, where it named the root's own gate, which dies on the import (#750)

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

sourcery-ai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Reviewer's Guide

Updates lockstep_next so legs and members use the existing repo_shape.py restoration cure when their root-owned validators cannot import the module, while preserving normal behavior and adding comprehensive regression tests for both recoverable and unrecoverable cases.

Sequence diagram for restoring repo_shape.py before validation

sequenceDiagram
    participant Doctor as shape-doctor.py
    participant Gate as Root validator
    participant Cure as lost_repo_shape_cure
    participant Git as git

    Doctor->>Gate: imports_repo_shape
    alt Import succeeds
        Doctor->>Doctor: validator_command
        Doctor-->>Doctor: Return validator NEXT
    else Import fails
        Doctor->>Cure: lost_repo_shape_cure
        Cure->>Git: checkout HEAD -- scripts/repo_shape.py
        Git-->>Cure: Restoration command or no cure
        Cure-->>Doctor: NEXT and WHY
    end
Loading

Flow diagram for lockstep validator recovery

flowchart TD
    A[lockstep_next] --> B{Root validator imports repo_shape?}
    B -->|Yes| C[Return validator_command]
    B -->|No| D[lost_repo_shape_cure]
    D --> E{repo_shape.py exists in HEAD?}
    E -->|Yes| F[Return checkout HEAD cure]
    E -->|No| G[Return no NEXT]
    C --> H[Preserve lockstep WHY]
    F --> I[WHY names gate after restoration]
    G --> J[WHY names missing file and no checkout cure]
Loading

File-Level Changes

Change Details Files
Make lockstep validator rows fall back to the existing repository-shape restoration command when their root-owned gate cannot import repo_shape.
  • Detect an unusable root-owned validator with imports_repo_shape.
  • Reuse lost_repo_shape_cure to emit the git checkout HEAD restoration command, or no NEXT when HEAD no longer contains the file.
  • Preserve the lockstep explanation after the cure’s WHY and leave normal validators and unrelated repair paths unchanged.
shape-doctor.py
Add regression coverage for restored lockstep commands, executable command output, and committed deletions.
  • Cover both legs/pins and members/family lockstep pairs.
  • Verify the cure matches the neighboring row and that the restored gate reports the intended finding without ModuleNotFoundError.
  • Verify no NEXT is emitted when the missing file is absent from HEAD, while the WHY retains restoration and lockstep guidance.
  • Register command-shape cases ensuring generated NEXT values remain valid commands.
tests/test_doctor_validator_rows.py
tests/test_doctor_next_is_a_command.py

Assessment against linked issues

Issue Objective Addressed Explanation
#750 When the root’s own validator cannot import repo_shape, have lockstep_next use lost_repo_shape_cure rather than print a lockstep command that fails on the missing import. ✅
#750 Add coverage showing that the legs and members rows print the restore cure and that the cure, followed by the lockstep command it enables, both run as printed. ✅

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

(lane openRepoShape-3, 2026-10-17T01:19:58Z) Independent review of #771 by RV-a (opus), on head c90a137c: READY. No MUST. One SHOULD, which goes to a follow-up issue and does not block. Four NITs. It lands only after the v1.1.0 tag, once its Linux tests is green.

The issue's root, with each NEXT pasted through sh from / and no PYTHONPATH:

S1 (follow-up issue): the agent-files WHY still hands a line that dies. This is the writer's "not settled" item, and the only one RV-a's sweep found.

Lane 3 files S1 under v1.1.1.

NITs:

Mutants (-k lockstep; the head passes 13):

mutant result
m1, drop own_copy 13 passed. Equivalent: a template copy's repo_shape.py is the standard's own, which the doctor imports at start-up. Keep the guard anyway.
m2, give members the gate line again 4 killed
m3, drop None-on-deleted 2 killed
the head's tests against main's doctor 8 failed, 5 passed

Tests:

Merges: clean with e3137010, #769, #770, #772, and all chained (c689c8a1). Windows, Sonar, docs and pinned copies are clear.

Lane: openreposhape-3 (openRepoShape-3)

…his root committed", and the lockstep line of `legs` and `members`, which the reader never ran, says "run" and not "run again"; the test module's header says the tests read a why for the command and the file it names (#750)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-17T04:13:41Z) RV-a's re-verification of #771 round 2 (d4ddac85): READY.

Lane: openreposhape-3 (openRepoShape-3)

…root committed" as the other rows that restore `scripts/repo_shape.py` do, so a holder whose `family.yaml` does not load is not told "this project" (#750)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 17, 2026
@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-18T02:40:29Z) RV771n's check of #771's last commit f1234bc6: READY. It takes RV-a's round-2 NIT and nothing else. The unloaded-manifest row's WHY now says "the copy this root committed", as the shared cure's WHY does, and a mutant that reverts it fails test_every_converted_next_is_a_command_and_nothing_else[unloaded_manifest_row-restore]. Every commit on #771 now has a verdict.


RV771n check of #771 (#750) at f1234bc6

Verdict: READY for f1234bc6. It has no MUST and no SHOULD. I wrote nothing to GitHub.

1. The NIT, quoted from RV-a's round-2 comment (6229504137, by brettheap, 2026-10-17T04:13Z)

"New NIT: unloaded_manifest_row's WHY (shape-doctor.py:2110-2111) still says "the copy this project committed". Two rows restoring the same scripts/repo_shape.py now word it differently in one report, and a holder whose family.yaml does not load also gets "this project". Fix: "root" there too, with its CASES opening at tests/test_doctor_next_is_a_command.py:651-652."

2. The diff does exactly what the NIT asks and nothing else

  • git diff d4ddac85 f1234bc6 is +2/−2 across two files, and git diff --check is clean.
  • shape-doctor.py:2111 changes f"project committed and {own} imports, where " to f"root committed and {own} imports, where ".
  • tests/test_doctor_next_is_a_command.py:652 makes the same change in the CASES opening for unloaded_manifest_row-restore.
  • The new WHY now opens the way the other restore rows do. The sibling is lost_repo_shape_cure at shape-doctor.py:1851-1852, which legs, members and the validator crash rows share.
    • Sibling: it puts back the {relative} HEAD holds, the copy this root committed and {own} imports, where update-shape.py apply would copy a newer one. Then …
    • New: it puts back the {lost} HEAD holds, the copy this root committed and {own} imports, where update-shape.py apply would copy a newer one, then run {own} again: …
  • The two differ after "newer one", and that is intended. This row ends with "then run {own} again" and the sibling with ". Then {then}", and RV-a's round-2 N3 said the validator crash rows keep "again".
  • restore_from_head is called in only two places, shape-doctor.py:1850 and :2109, so these are the only two WHYs that restore scripts/repo_shape.py, and both now say "root".
  • The holder case is covered by the same line. check_family (shape-doctor.py:2860) calls the same unloaded_manifest_row and the same f-string.
    • No test exercises the holder spelling on its own. That is not a defect, since the one f-string serves both rows.

3. Remaining "this project" hits, none in scope

  • shape-doctor.py:1868: the validator_row docstring, "does this project pass its gate". It is not a WHY and is not about the restore.
  • shape-doctor.py:4621 and :9288: a docstring and a shape-advisory message. Both are unrelated to restoring repo_shape.py.
  • shape-doctor.py:2065: the unloaded_manifest_row docstring, "the project's own committed bytes". It is prose, not the WHY string. RV-a's NIT names only lines 2110-2111, and the sibling docstring at :1830 also says "the project's own command".
  • tests/test_shape_doctor.py:6742: "the copy this standard's lists name". It concerns a different copy and is unrelated.
  • tests/test_shape_doctor.py:256, :388 and :3783, tests/test_doctor_next_is_a_command.py:182, tests/test_doctor_partial_clone.py:70: fixture or docstring prose, unrelated.
  • No document under docs/, README.md or AGENTS.md carries either spelling of the WHY.
  • Every CASES opening that restores scripts/repo_shape.py (tests/test_doctor_next_is_a_command.py:651-668) now reads "root committed".

4. Tests at f1234bc6, run under a stub gh that exits 97, with GH_TOKEN=bogus and an empty GH_CONFIG_DIR

  • tests/test_doctor_next_is_a_command.py: 59 passed in 101.13s (0:01:41), exit 0.
  • tests/test_doctor_validator_rows.py: 55 passed in 115.89s (0:01:55), exit 0.

Mutant: I reverted only the shape-doctor.py line, using git diff d4ddac85 f1234bc6 -- shape-doctor.py | git apply -R. Running both files then gave 1 failed, 113 passed in 169.58s, exit 1. The failing test was:
tests/test_doctor_next_is_a_command.py::test_every_converted_next_is_a_command_and_nothing_else[unloaded_manifest_row-restore]
It failed at :840 on assert why and why.startswith(opens), with the WHY reading "the copy this project committed". That is the only failure. I restored the tree afterwards, and git diff --quiet f1234bc6 confirms it matches the commit.

5. CI and merge

  • gh pr checks 771 -R opensoft/openRepoShape:
    • SonarCloud Code Analysis: pass.
    • package: pass.
    • tests: pass (54m24s).
    • tests-git-bash: pass.
  • gh pr view shows the head is f1234bc6edc357a88913d581fd8bed59db9c6814, the PR is open and still a draft, and mergeability reads UNKNOWN.
  • origin/main is 3fb30a8f7767891485287c3dc946f5d976498335. The branch is 3 commits ahead (merge-base e3137010) and 49 behind.
  • git merge-tree --write-tree origin/main f1234bc6 exited 0 with no conflict markers. The merged tree is 0e615bc775626620ed315d551fb6ff278391b9ec.

Scratch paths

  • Clone, detached at f1234bc6: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV771n
  • Scratch dir: /workspace/projects/openRepoShape-worktrees/_lane3-scratch/RV771n-scratch (stub/gh, ghcfg/, bt-next, bt-rows, bt-mut, out-next.txt, out-rows.txt, out-mut.txt)

Lane: openreposhape-3 (openRepoShape-3)

@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap marked this pull request as ready for review October 18, 2026 05:17
@brettheap
brettheap merged commit 3bf2d3c into main Oct 18, 2026
4 checks passed
@brettheap
brettheap deleted the l3-750-lockstep-next branch October 18, 2026 05:17
@sourcery-ai

sourcery-ai Bot commented Oct 18, 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 1 day and 19 hours by commenting @sourcery-ai review. Upgrade to get a review now.

brettheap pushed a commit that referenced this pull request Oct 18, 2026
…step 1 of 3 after #771 squash-landed as 3bf2d3c)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 18, 2026
… 2 of 3; the squash's tree equals the final head's, so #780's own lines are kept)

shape-doctor.py, tests/test_doctor_next_is_a_command.py and tests/test_doctor_validator_rows.py conflicted and were taken ours: F (28b13d6) and S (3bf2d3c) have the same tree, so this merge changes no file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap added a commit that referenced this pull request Oct 18, 2026
…ent-files` row's WHY puts the file back before it names the root kind's own gate, `validate-pins.py` in an assembly root and `validate-family.py` in a family holder, to confirm the digest (or says why no command does), and names the standard's copy of that gate where the root has none, so a holder is no longer sent to a `validate-pins.py` it lacks (#776) (#780)

Closes #776.

In a root that lost `scripts/repo_shape.py`, the `agent-files` row for a
deleted pinned copy (`AGENTS-shape.md`) ended its WHY with "then
`python3 <root>/scripts/validate-pins.py --root <root>` to confirm the
digest". That line exits 1 with
`ModuleNotFoundError: No module named 'repo_shape'` there, and it named the
root's own `validate-pins.py` even where the root has none.

Now `confirm_digest` in `shape-doctor.py` ends the WHY by putting the file
back first, with #737's cure, and then naming the gate: the root kind's own,
`validate-pins.py` in an assembly root and `validate-family.py` in a family
holder, which has no `validate-pins.py`. Where HEAD no longer holds the file
it says why no command does and where the file comes back from. Where the
root has no gate of its own it names the standard's template copy of that
gate, and where no line can be named it names no gate. In an assembly root
that keeps its gate and `repo_shape.py` the WHY is byte for byte what it was;
in a holder that keeps its gate it names `validate-family.py` where it named
`validate-pins.py`. The row's NEXT, status and detail do not move in either.
`gate_line` is the reading `lockstep_next` ran, now shared.
`tests/test_doctor_validator_rows.py` runs the NEXT, the cure and the gate in
order, for an assembly root and a holder, and pastes the NEXT and the
standard's template line from `/`.

Lane 3's independent reviewer RV-a (opus) found it READY in its second and
third rounds (#780 issuecomment-6229763740 and -6229805934). After #771
landed, it was restacked onto #771's squash by three merges, and its changed
lines equal its delta over #771's head (#780 issuecomment-6246123568). 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>
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