Repository navigation
shape-doctor.py: an own-copy validator row whose crash is a root's lost scripts/repo_shape.py names git -C <root> checkout HEAD -- scripts/repo_shape.py as its NEXT (none where HEAD no longer holds it) and the validator in its WHY (#697) - #737
Conversation
…st `scripts/repo_shape.py` names `git -C <root> checkout HEAD -- scripts/repo_shape.py` as its NEXT (none where HEAD no longer holds it) and the validator in its WHY, so a reader whose own PYTHONPATH reaches another `repo_shape` is not sent to a line that passes for them (#697) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR changes own-validator crash findings caused by a missing root Flow diagram for missing repo_shape.py validator recoveryflowchart TD
A[Own validator crashes] --> B{Root scripts/repo_shape.py missing?}
B -- No --> C[Keep validator command as NEXT]
B -- Yes --> D{HEAD holds the file?}
D -- Yes --> E[Run git -C root checkout HEAD -- scripts/repo_shape.py]
E --> F[Run validator named in WHY]
D -- No --> G[No NEXT]
G --> H[Restore from parent of deleting commit]
H --> F
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
(lane openRepoShape-3, 2026-10-16T23:10:12Z) Lane 3's independent review of #737 at Measured with a fixture probe over roots that lost
Unchanged where it should be:
SHOULD → follow-up (v1.1.1): NITs (optional):
Mutants:
Tests (stub
Merges: clean with main (tree CI at the head: green. Lane: openreposhape-3 (openRepoShape-3) |
There was a problem hiding this comment.
Hey - I've found 6 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="shape-doctor.py" line_range="1801-1802" />
<code_context>
+ "repo_shape sees it run on that one, which says nothing about "
+ "this root's own copy")
+ if head_holds(ctx.root, relative):
+ return (restore_from_head(ctx.root, relative),
+ f"it puts back the {relative} HEAD holds, the copy this "
+ f"project committed and {own} imports, where update-shape.py "
+ f"apply would copy a newer one. Then {then}")
</code_context>
<issue_to_address>
**Staged changes are discarded**
When the working-tree file is missing but the index contains staged changes for it, `restore_from_head` emits `git checkout HEAD -- <path>`, which resets both the worktree and index to HEAD and discards the staged version. Running the suggested NEXT loses those staged changes.
Restore only the working-tree path from HEAD, leaving the index unchanged.
Also at `shape-doctor.py:1309-1310`.
</issue_to_address>
### Comment 2
<location path="shape-doctor.py" line_range="1914" />
<code_context>
"imports from beside itself, is not there"
if own_copy else f"; {lost} is not there")
+ if own_copy:
+ command, why = lost_repo_shape_cure(ctx, own, lost, command)
if not own_copy:
detail["environment"] = True
</code_context>
<issue_to_address>
**Restore advice misses the crash**
When the own validator has an independent crash before importing `repo_shape` while `repo_shape.py` is absent or not a regular file, `validator_row` routes the crash through `lost_repo_shape_cure` without establishing that the missing module caused it. The restore command leaves the independent crash in place, while the row no longer presents the validator command as its NEXT.
Offer the restore cure only when the crash is caused by the missing or unusable `repo_shape.py`.
Also at `shape-doctor.py:1915`.
</issue_to_address>
### Comment 3
<location path="shape-doctor.py" line_range="1805-1808" />
<code_context>
+ f"it puts back the {relative} HEAD holds, the copy this "
+ f"project committed and {own} imports, where update-shape.py "
+ f"apply would copy a newer one. Then {then}")
+ return None, (f"HEAD does not hold {relative}, so no git checkout HEAD "
+ f"puts it back and {own} would only crash again: a "
+ "deletion already committed comes back from the parent of "
+ f"the commit that made it. Then {then}")
+
+
</code_context>
<issue_to_address>
**Recovery source does not exist**
When the root has no usable HEAD, Git cannot inspect HEAD, or the path was never present in its history, `head_holds` returns false, so `lost_repo_shape_cure` describes a committed deletion and points to its commit's parent even when no such commit or recovery source exists. The reader gets inapplicable recovery instructions.
Distinguish these cases from a committed deletion and give recovery guidance that applies to the actual state.
</issue_to_address>
### Comment 4
<location path="shape-doctor.py" line_range="1801-1802" />
<code_context>
+ "repo_shape sees it run on that one, which says nothing about "
+ "this root's own copy")
+ if head_holds(ctx.root, relative):
+ return (restore_from_head(ctx.root, relative),
+ f"it puts back the {relative} HEAD holds, the copy this "
+ f"project committed and {own} imports, where update-shape.py "
+ f"apply would copy a newer one. Then {then}")
</code_context>
<issue_to_address>
**Checkout is blocked by obstruction**
When a directory or untracked obstruction occupies the missing path while HEAD holds the file, `restore_from_head` recommends checking out the file over the obstruction, but Git cannot write the tracked file at that path. The cure fails and the validator remains broken.
Detect the obstruction and avoid recommending checkout until it is safely handled.
</issue_to_address>
### Comment 5
<location path="shape-doctor.py" line_range="1800-1801" />
<code_context>
+ "reader whose own PYTHONPATH or site-packages reaches another "
+ "repo_shape sees it run on that one, which says nothing about "
+ "this root's own copy")
+ if head_holds(ctx.root, relative):
+ return (restore_from_head(ctx.root, relative),
+ f"it puts back the {relative} HEAD holds, the copy this "
+ f"project committed and {own} imports, where update-shape.py "
</code_context>
<issue_to_address>
**Dangling symlink stays unusable**
When HEAD tracks `scripts/repo_shape.py` as a symlink whose target is absent, `head_holds` accepts the symlink and `restore_from_head` checks it out, but restoring the dangling link does not provide an importable module. The validator continues to fail.
Check whether the tracked symlink resolves to a usable module before offering the restore cure.
</issue_to_address>
### Comment 6
<location path="tests/test_doctor_validator_rows.py" line_range="815" />
<code_context>
+ assert row["detail"]["exit"] == 1, row
+ assert "ModuleNotFoundError" in row["reason"], row
+ assert result.returncode == 1, result.stdout + result.stderr
+ assert payload_of(result)["verdict"].startswith("DRIFTED"), result.stdout
+ assert command_of(row)[:2] == ["git", "-C"], row["next"]
+ assert Path(command_of(row)[2]).resolve() == root, row["next"]
</code_context>
<issue_to_address>
**Wrong drift count passes**
When the doctor reports a DRIFTED verdict with an incorrect copy-missing count, `test_a_gate_that_lost_repo_shape_is_sent_to_put_it_back` accepts any verdict beginning with `DRIFTED`; a broken verdict such as `DRIFTED (2 copy-missing)` therefore passes despite the expected `DRIFTED (1 copy-missing)`.
Assert the exact expected verdict string.
</issue_to_address>Sourcery assessment
Approval pending. 6 findings to address first.
Blocking findings: shape-doctor.py:1802, shape-doctor.py:1914, shape-doctor.py:1808, shape-doctor.py:1802, shape-doctor.py:1801, and 1 more
| return (restore_from_head(ctx.root, relative), | ||
| f"it puts back the {relative} HEAD holds, the copy this " |
There was a problem hiding this comment.
🟡 Medium · Staged changes are discarded
When the working-tree file is missing but the index contains staged changes for it, restore_from_head emits git checkout HEAD -- <path>, which resets both the worktree and index to HEAD and discards the staged version. Running the suggested NEXT loses those staged changes.
Restore only the working-tree path from HEAD, leaving the index unchanged.
Also at shape-doctor.py:1309-1310.
Prompt for AI agents
In `shape-doctor.py` at lines 1801-1802:
**Staged changes are discarded**
When the working-tree file is missing but the index contains staged changes for it, `restore_from_head` emits `git checkout HEAD -- <path>`, which resets both the worktree and index to HEAD and discards the staged version. Running the suggested NEXT loses those staged changes.
Restore only the working-tree path from HEAD, leaving the index unchanged.
Also at `shape-doctor.py:1309-1310`.| "imports from beside itself, is not there" | ||
| if own_copy else f"; {lost} is not there") | ||
| if own_copy: | ||
| command, why = lost_repo_shape_cure(ctx, own, lost, command) |
There was a problem hiding this comment.
🟡 Medium · Restore advice misses the crash
When the own validator has an independent crash before importing repo_shape while repo_shape.py is absent or not a regular file, validator_row routes the crash through lost_repo_shape_cure without establishing that the missing module caused it. The restore command leaves the independent crash in place, while the row no longer presents the validator command as its NEXT.
Offer the restore cure only when the crash is caused by the missing or unusable repo_shape.py.
Also at shape-doctor.py:1915.
Prompt for AI agents
In `shape-doctor.py` at line 1914:
**Restore advice misses the crash**
When the own validator has an independent crash before importing `repo_shape` while `repo_shape.py` is absent or not a regular file, `validator_row` routes the crash through `lost_repo_shape_cure` without establishing that the missing module caused it. The restore command leaves the independent crash in place, while the row no longer presents the validator command as its NEXT.
Offer the restore cure only when the crash is caused by the missing or unusable `repo_shape.py`.
Also at `shape-doctor.py:1915`.| return None, (f"HEAD does not hold {relative}, so no git checkout HEAD " | ||
| f"puts it back and {own} would only crash again: a " | ||
| "deletion already committed comes back from the parent of " | ||
| f"the commit that made it. Then {then}") |
There was a problem hiding this comment.
🟡 Medium · Recovery source does not exist
When the root has no usable HEAD, Git cannot inspect HEAD, or the path was never present in its history, head_holds returns false, so lost_repo_shape_cure describes a committed deletion and points to its commit's parent even when no such commit or recovery source exists. The reader gets inapplicable recovery instructions.
Distinguish these cases from a committed deletion and give recovery guidance that applies to the actual state.
Prompt for AI agents
In `shape-doctor.py` at lines 1805-1808:
**Recovery source does not exist**
When the root has no usable HEAD, Git cannot inspect HEAD, or the path was never present in its history, `head_holds` returns false, so `lost_repo_shape_cure` describes a committed deletion and points to its commit's parent even when no such commit or recovery source exists. The reader gets inapplicable recovery instructions.
Distinguish these cases from a committed deletion and give recovery guidance that applies to the actual state.| return (restore_from_head(ctx.root, relative), | ||
| f"it puts back the {relative} HEAD holds, the copy this " |
There was a problem hiding this comment.
🟡 Medium · Checkout is blocked by obstruction
When a directory or untracked obstruction occupies the missing path while HEAD holds the file, restore_from_head recommends checking out the file over the obstruction, but Git cannot write the tracked file at that path. The cure fails and the validator remains broken.
Detect the obstruction and avoid recommending checkout until it is safely handled.
Prompt for AI agents
In `shape-doctor.py` at lines 1801-1802:
**Checkout is blocked by obstruction**
When a directory or untracked obstruction occupies the missing path while HEAD holds the file, `restore_from_head` recommends checking out the file over the obstruction, but Git cannot write the tracked file at that path. The cure fails and the validator remains broken.
Detect the obstruction and avoid recommending checkout until it is safely handled.| if head_holds(ctx.root, relative): | ||
| return (restore_from_head(ctx.root, relative), |
There was a problem hiding this comment.
🟡 Medium · Dangling symlink stays unusable
When HEAD tracks scripts/repo_shape.py as a symlink whose target is absent, head_holds accepts the symlink and restore_from_head checks it out, but restoring the dangling link does not provide an importable module. The validator continues to fail.
Check whether the tracked symlink resolves to a usable module before offering the restore cure.
Prompt for AI agents
In `shape-doctor.py` at lines 1800-1801:
**Dangling symlink stays unusable**
When HEAD tracks `scripts/repo_shape.py` as a symlink whose target is absent, `head_holds` accepts the symlink and `restore_from_head` checks it out, but restoring the dangling link does not provide an importable module. The validator continues to fail.
Check whether the tracked symlink resolves to a usable module before offering the restore cure.| assert row["detail"]["exit"] == 1, row | ||
| assert "ModuleNotFoundError" in row["reason"], row | ||
| assert result.returncode == 1, result.stdout + result.stderr | ||
| assert payload_of(result)["verdict"].startswith("DRIFTED"), result.stdout |
There was a problem hiding this comment.
🟡 Medium · Wrong drift count passes
When the doctor reports a DRIFTED verdict with an incorrect copy-missing count, test_a_gate_that_lost_repo_shape_is_sent_to_put_it_back accepts any verdict beginning with DRIFTED; a broken verdict such as DRIFTED (2 copy-missing) therefore passes despite the expected DRIFTED (1 copy-missing).
Assert the exact expected verdict string.
Prompt for AI agents
In `tests/test_doctor_validator_rows.py` at line 815:
**Wrong drift count passes**
When the doctor reports a DRIFTED verdict with an incorrect copy-missing count, `test_a_gate_that_lost_repo_shape_is_sent_to_put_it_back` accepts any verdict beginning with `DRIFTED`; a broken verdict such as `DRIFTED (2 copy-missing)` therefore passes despite the expected `DRIFTED (1 copy-missing)`.
Assert the exact expected verdict string.…alidate-family.py` and not the assembly root's `validate-pins.py` (and the holder gets #737's cure for a lost `repo_shape.py`), says to run the gate once the file is back "to confirm the digest" where HEAD lacks it, says the standard's copy also reports the root's missing gate, and the tests parse the lines they run with `shlex` (#776) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
main gained 25 pull requests since de92614: #634, #699, #671, #719, #701, #717, #700, #702, #722, #720, #725, #721, #728, #705, #727, #718, #744, #743, #749, #740, #737, #734, #742, #751 and #736. No conflict: AGENTS.md, adopt-project.py and docs/cli.md merged as text, and README.md and docs/handbook.html are unchanged by the merge. README.md is 1473 lines, its cap; AGENTS.md 494, its cap; every handbook marker is current; `render-cli-reference.py --check` is ok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es every reason THIS checkout cannot answer, and the notes are current to main bf1f099 with the 25 pull requests since de92614 (#563, #683) - W683 and RV-f's N4 on #742: README's exit-3 list (Keeping a project's shape current, :1208-1220) and its handbook twin (`doctor`) named only a pin whose commit this checkout does not carry. They now say, as shape-doctor.py's EXIT CODES does: a pin's commit it lacks, a partial clone missing what is compared (#683), a read that fails (`update-upstream-unreadable`), a crashed validator of its own (#278, #410), or its own naming policy not loading (#678). +1 line, with tests/readme-cap.d/683.txt; README.md is 1474, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 1). The two `Keeping a project's shape current` markers move: `doctor` text and marker, `current` marker only. - The notes, "What changed since v1.0.1", one entry per pull request from `git log --first-parent de92614..bf1f099`, each from its squash commit and the issues GitHub says it closes: - adopt-project.py: #699, #719, #749, #727, #717, #728 (with #621's hint), #743 + #720 + #718, #722, #721; - update-shape.py, bump-leg.py and a project's own scripts: #702 (with the pinned validators), #634; - scaffold, setup, family and the shared readers: #725, #671, #744; - shape-doctor.py: #742, #701, #737, #751, #736, #734, #700 + #705, #740; - install, CI and the docs: the exit-3 sentence above. - Counts (99 pull requests since bfed143; 109 of 135 milestone issues closed at 10:28Z), the "current to main" sha and the open list (26) are at main bf1f099, still placeholders for tag time. Left for round B: lane 5's eight lines (#630 issuecomment-6227129879), whose pull requests #726, #747, #682, #724, #746, #745 and #738 are open drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… verification row's colon, `run_validator`'s "costs no child an import" and the decoy test's `sitecustomize` claim say what main does (#666) (#730) Four comments and docstrings said something main no longer does. They now say what the code beneath them does. A fix round after review added two more of the same kind (see "Fix round"). Comments and docstrings only: no behaviour, no `--help` text, no pinned copy, so no README, handbook or `docs/cli.md` change. - **`adopt-project.py`, the module docstring.** It said the verification table reads `code: 0 of N source paths (seeded from template)`. The printed row has no colon (` {leg:<6} {count:>5} of {N} source paths{note}`). It now says the table's `code` row reads `0 of N source paths (seeded from template)`, the words `AGENTS.md` and `README.md` use since #505 and the docstring of the verification function uses too. `git grep 'code: 0 of'` finds nothing now. - **`shape-doctor.py`, `submodule_config`.** The last sentence of the docstring gave a reason that is true of the string and not decisive: "It is also what the row PRINTS, and a lone surrogate is a string no UTF-8 stream can write." The table goes through `ascii_text` and `--json` through `json.dumps`, so neither would raise on a lone surrogate. I dropped the sentence, the issue's first direction. The comparison with `origin_url`'s reading of a clone's remote is the reason, and the paragraph already gives it first. **This hunk carries the bytes of lane 3's #700**, which landed as `55c6ba7d`: the two removed lines and the one added line (` working clone.`) are the same. So the hunk is on main already and drops out of the squash (see "Evidence"). - **`shape-doctor.py`, `run_validator`.** "It costs no child an import" holds for an unedited copy only. A new paragraph says a root whose own copy of a validator gained an `import yaml` read `passes` while `site` was on, and now reads `crashed before reaching a verdict (exit 1): ModuleNotFoundError: No module named 'yaml'`, the crash its own CI gets, because `validate.yml` installs nothing. It calls that row a fair answer and says `-S` does not ask whether a copy was edited. No code change. - **`tests/test_shape_doctor.py`, the decoy test's two comments** (the block above `DECOY_REPO_SHAPE` and the inline comment before `reached`). `run_validator` starts every child as `[sys.executable, "-S", script, ...]`, so no child imports a `sitecustomize`. The comments now say that, say that replace-not-prepend and the #306 review S1 case are held by `test_run_validator_gives_the_child_exactly_the_pythonpath_it_is_given`, and say that the decoy test holds the template row's real verdict under an exported `PYTHONPATH`. The review of the first two commits approved with nits. Two new commits take S1 and N1: `7f2bb36b` makes both edits, and `426877bf` rewrites the last sentence of the N1 comment, which did not parse ("...a future removal of `-S`, which `test_run_validator_starts_every_child_with_no_site` ... is the test that holds"). It now ends "...a future removal of `-S`; `test_run_validator_starts_every_child_with_no_site` (in `tests/test_doctor_validator_site.py`) is the test that holds `-S`." Both are comments and docstrings only. - **S1, a third stale sentence.** The docstring of `test_run_validator_gives_the_child_exactly_the_pythonpath_it_is_given` said that for the callers which pass no `pythonpath`, "only a probe like this one, or (b)'s decoy, can see that they have none". That is false under `-S`: a `run_validator` that keeps the variable for those callers passes the decoy test in all three cases and fails the probe test twice (the second row of the table below). It now says only a probe like this one can see that they have none, and that the decoy of (b) could before `-S` (#342). It is the same defect as the issue's, in a place the issue did not name. "Could" holds for the `manifest` and `pins` cases: with `-S` removed, 2 of the 3 decoy cases fail, and the `family` case never caught it. - **N1, the inline comment overstated the loop.** The comment above `reached` said "What this loop still holds is that no row quotes the decoy or says it exited 86." Under `-S` the loop cannot fire. It now says so, says the loop is kept as a tripwire for a future removal of `-S`, and names `test_run_validator_starts_every_child_with_no_site` in `tests/test_doctor_validator_site.py` as the test that holds `-S`. That test is on main. - **N2, this body.** The merge checks and the notes on #630 were stale (they cited `de926142` and a conflict that is gone). They are rerun and rewritten below. **Only comments and docstrings changed.** `git diff 8062810 426877b` has 74 changed lines (44 added, 30 removed) in three files. A script checked each changed line against the file's comment tokens and docstring spans (both sides of the diff): 0 outside. A second check parsed each file at the base and at the head, blanked every docstring, and compared the ASTs: identical in all three files. For the two fix-round commits together, the AST of `tests/test_shape_doctor.py` with docstrings blanked is identical to the one at `a2113cff`, `git diff --check` is clean, and the longest added line is 75 columns (73 in the second commit). **What the squash holds.** `git merge-tree --write-tree` of this head with origin/main `20c8873b` gives the tree `6ead2ffc`. `git diff 20c8873 6ead2ffc` is 71 changed lines in three files, and `shape-doctor.py` has 8 of them: the new `run_validator` paragraph and nothing else. `git diff` of the two trees has no hunk that mentions `submodule_config`, `surrogate`, `PRINTS` or `working clone`. On main, `submodule_config`'s docstring has no "PRINTS" sentence and ends in `working clone.`. The same two checks on that tree: 0 lines outside comments and docstrings, ASTs identical. On main `2d988200` with the head `426877bf` the squash is the same: 71 changed lines, 8 of them in `shape-doctor.py`, 0 outside comments and docstrings, ASTs identical, and no hunk that mentions `submodule_config`. **`python3 scripts/render-cli-reference.py --check`**, at `426877bf`: `[ok] docs/cli.md is what the generator produces`. Only the first line of a module docstring is help text, and none of the first lines changed. **The decoy comments, measured.** I did not take the issue's reading on trust. In a throwaway clone at the base I put each #306 regression into `run_validator` and ran the two tests (`test_an_exported_pythonpath_does_not_reach_a_template_validator`, three cases, and `test_run_validator_gives_the_child_exactly_the_pythonpath_it_is_given`, six): | mutant | with `-S` (main) | with `-S` removed | |---|---|---| | the standard's `scripts/` put in FRONT of the operator's value | decoy test: 3 of 3 pass. Probe test: 1 of 6 fails | decoy test: 3 of 3 fail | | the variable kept for the callers that pass none (S1) | decoy test: 3 of 3 pass. Probe test: 2 of 6 fail | decoy test: 2 of 3 fail | | the operator's directory AHEAD of the standard's | decoy test: 3 of 3 fail. Probe test: 1 of 6 fails | not run | So on main the decoy test catches neither #306 regression and the probe test catches both, as lane 4's R342 measured on #636's head. The third row is why the comment says the raising `repo_shape.py` "still bites": it kills an operator-first order. The review re-ran these mutants on `a2113cff` and got the same counts. **The `run_validator` paragraph, measured.** A root whose own `validate-manifest.py` is `import yaml` plus a print, run through `run_validator` from this tree: exit 1 and `ModuleNotFoundError: No module named 'yaml'`. The same call with `-S` taken out of the child's argv: exit 0, `manifest ok`. `yaml` is importable from the machine's site-packages and not without `site`. With the real `gh` unreachable (a stub first on PATH, a bogus token, an empty `GH_CONFIG_DIR`). On `a2113cff`, before the fix round: - `tests/test_cli_reference.py`, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_scaffold_leg_path_collisions.py`: 250 passed (57.5 s). - `tests/test_doctor_gitmodules_not_utf8.py`, `tests/test_doctor_validator_rows.py`, `tests/test_doctor_validator_site.py`, `tests/test_doctor_validator_verdicts.py`, `tests/test_adopt_spec_only.py`, `tests/test_adopt_entry_paths.py`, `tests/test_adopt_submodules.py` (the files that drive `run_validator`, `submodule_config` or the seeded-leg row): 334 passed (28 min 18 s). - `tests/test_shape_doctor.py`, in two halves run side by side (the first 204 test ids, then the last 204): 204 passed in 11 min 31 s, and 201 passed, 3 skipped in 4 min 58 s. That is all 408 tests. The 3 skips are the PowerShell-line tests, which need `powershell.exe` on a Windows host. - The review ran all of `tests/test_shape_doctor.py` there: 405 passed, 3 skipped (512 s). On the predicted merge with main `2fac9790` it ran `test_shape_doctor.py`, `test_doctor_validator_site.py`, `test_doctor_validator_rows.py`, `test_doctor_validator_verdicts.py`, `test_doctor_gitmodules_not_utf8.py`, `test_repo_hygiene.py`, `test_cli_reference.py` and `test_agents_md_codes.py`: 632 passed, 3 skipped (760 s). On `426877bf`, after the fix round, which changes comments and docstrings only, so the runs above stand: - The two tests the fix round touches, `test_an_exported_pythonpath_does_not_reach_a_template_validator` (3 cases) and `test_run_validator_gives_the_child_exactly_the_pythonpath_it_is_given` (6 cases), run by node id: 9 passed (11.7 s). The same 9 also passed (16.1 s) at `7f2bb36b`. - `tests/test_repo_hygiene.py`: 77 passed (1.9 s). It also passed (2.0 s) at `7f2bb36b`. No test fails on the base, because nothing here changes behaviour. The AST comparison above is the proof in their place. `git merge-tree --write-tree` of this head against origin/main and against every ref on origin that could meet it. The full run was on 2026-10-16, with the head `7f2bb36b` (the one before the last comment commit) and main `20c8873b`: the 41 branches (`fix/*`, `l3-*`, `wip/*`, `docs/*` and `release/v1.0`) and the 55 PR heads from #630 up. That is 96 refs. I ran #731's head `ab55c29f` through the same list. After `426877bf` I re-ran main, now `2d988200`, and the six heads that edit `tests/test_shape_doctor.py`, the one file that commit touches (#705, #737, #740, `l3-669-members-row`, `l3-680-doctor-signals` and `l3-697-crash-cure-next`), and #630: all clean. - **origin/main: clean.** At `20c8873b`, with the head `7f2bb36b`, the predicted tree is `6ead2ffc`. At `2d988200` (three `adopt-project.py` commits later, with their tests and a change in `scripts/shape_materialize.py`), with the head `426877bf`, it is `7ee5caf4`, and the squash is the same three files (43 added, 28 removed). #731's head `ab55c29f` is clean with `2d988200` too. This head with #731's head: clean. - **#630's head `6fd33a93`: clean.** It conflicted with main in `docs/handbook.html` when this body was first drafted, and no longer does, with or without this change. - **#700 and #702 have landed** (`55c6ba7d` and `20c8873b`), and so has #701, and #699, #717 and #719 for `adopt-project.py`. Their heads still merge clean with this head. - **Every ref that edits `shape-doctor.py`, `tests/test_shape_doctor.py` or `adopt-project.py` merges clean with this head**, except the two old heads named in the bullet after next. They include the heads of PRs not on main yet (#705, #721, #737 and #742, and lane 3's `l3-680`, `l3-683`, `l3-687`, `l3-691` and `l3-697`, which were not on origin when this body was first drafted). #673 has no branch or PR head on origin. - **Four refs conflict with origin/main on their own**, and so with this head: `docs/release-notes-v1.0.0` and `release/v1.0`, and the pre-squash heads of the landed #637 and #641. Their conflicts do not come from this change. - **Two landed pre-squash heads conflict with this head only because they are old.** #632 conflicts with main on its own. #636 merges clean with main, but its old head conflicts with this head in `run_validator`'s docstring, next to the text #636 added; main has that text, and this head merges clean with main. - **Refs that conflict with main on their own are not made worse.** #682, #745, #747, `fix/648`, `fix/681` and `fix/704` conflict with main in `templates/family-root/scripts/validate-family.py`; #742 and `l3-683` in `shape-doctor.py`. Each merges clean with this head. - **Every other open head and branch: clean.** Nothing from the issue. Its four lines are done. The decoy test's `sitecustomize` machinery is dead code under `-S` and I left it, because the issue says comment only. The comment above `reached` now says it is kept as a tripwire. - **The decoy test's `sitecustomize.py` and exit-86 trap never run.** `DECOY_SITECUSTOMIZE`, `os._exit(86)` and the `(exit 86)` and `DECOY` clauses in `reached` can only fire for a child started without `-S`. They are kept as a tripwire and the comment says so. The other choice is to remove them; `test_run_validator_starts_every_child_with_no_site` already holds `-S`. Small, tests only. Review: rv666, lane 5's independent review, approved a2113cf with nits; 7f2bb36 and 426877b take its S1 and N1 (above), and codex found no major issue at a2113cf. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 426877b passed tests, tests-git-bash and SonarCloud; the head 32722b3 is a clean update-branch merge of main bf1f099, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 55d5d3b over main dcf62ed: the shape_doctor, scaffold_leg_path_collisions, repo_hygiene, agents_md_codes, cli_reference sets, 686 passed, 3 skipped, 1 xfailed. Landed on Brett Heap's word of 2026-10-17T10:59:39Z, relayed by lane 3 on #473: "merge lane 5's approved v1.1.0 drafts". Closes #666 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d A3 (three SHOULDs, four nits), and notes entries for the seven pull requests landed since bf1f099, current to main 2000488 (#563, #683) Round A4, from lane 2's review (#630 issuecomment-6233033833, READY): - SHOULD 1: the doctor's exit-3 list names "a validator of its own that crashed or could not be run", so the case of one that cannot be given its `PYTHONPATH` (shape-doctor.py's EXIT CODES, #278) is listed too: README.md:1208-1222, its handbook twin (`doctor`) and the notes' docs line. The paragraph re-wraps to one more line: tests/readme-cap.d/683.txt is +2, and its text now says which causes the EXIT CODES block names and that #742 added the partial clone and #701 the naming policy. README.md is 1475 lines, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 2). Both `Keeping a project's shape current` markers move to c728c970: `doctor` text and marker, `current` marker only. - SHOULD 2: #727's entry names the leg description, whose em dash stopped every `execute` against GitHub at the first leg. - SHOULD 3: #749's entry names `source-ambiguous`, for an `org/repo` that is also a directory here. - Nits: the doctor section's three near-duplicate pairs are merged (#636 with #737, #632 with #700 and #705, #586 with #736); #718 has its own bullet; #634's entry names `git_out`'s spelling for every caller and #744's the gate's `--link-source-label` and `--link-remedy`; the header comment says 2026-10-17 at main 2000488. Wave B1, `git log --first-parent bf1f099..2000488`: #706 (#664), #703 (#696), #670 (#646), #682 (#648), #726 (#667), #730 (#666) and #724 (#665), each from its squash commit; #726's and #682's from lane 5's lines (#630 issuecomment-6227129879), #682's with the unmerged `.gitmodules` its last round added, and #724's with lane 5's note that v1.0.0's test-hygiene line on the generator's `.pyc` files closes. The counts (106 pull requests; 116 of 135 milestone issues closed at 11:53Z), "current to main" and the open list (19) are at main 2000488, still placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…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), where it named the root's own gate, which dies on the import (#750) (#771) Closes #750. 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` or `validate-family.py` line. That gate dies on `import repo_shape` and exits 1, so the line could not do what its WHY said of it, while the `pins` and `family` rows beside it already printed the `checkout HEAD` cure (#737). Now `lockstep_next` in `shape-doctor.py` returns that cure where the root's own copy cannot import `repo_shape`: `git -C <root> checkout HEAD -- scripts/repo_shape.py`, or no NEXT where HEAD no longer holds the file. Its WHY names the gate to run once the file is back. Every other lockstep line, the verdict and the exit code are unchanged. The cure's WHY, which the `pins`, `family` and `manifest` crash rows and the unloaded-manifest row share, now says "the copy this root committed" where it said "this project"; in `legs` and `members` it says "run" where the crash rows say "run again". `tests/test_doctor_validator_rows.py` pastes the cure and then the gate from `/`. Lane 3's independent reviewer RV-a (opus) found it READY on `c90a137c` (#771 issuecomment-6227877943) and again on `d4ddac85` (-6229504137). Its one SHOULD, the `agent-files` row's WHY, which still names a gate that dies, is #776, in #780. The last commit, `f1234bc6`, takes round 2's one new NIT, the unloaded-manifest row's "this project"; RV771n found it READY (#771 issuecomment-6244822339). 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>
…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>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #697 (comment)
Closes #697.
What changed and why. Option C, chosen by Brett Heap at 2026-10-16T13:11:50Z ("C, and no objection to #678"). A root that lost
scripts/repo_shape.pyreadsDRIFTED (1 copy-missing), and its own-copymanifest,pinsandfamilyrows each say the gate crashed onModuleNotFoundError: No module named 'repo_shape'. The NEXT of those rows was the gate. Since #636/#342 the doctor's child has no inheritedPYTHONPATHand nosite; a pasted line has neither guard, so a reader whose ownPYTHONPATH(or a.pthin whose site-packages) reaches arepo_shaperan the NEXT and sawmanifest ok: Atlas (atlas), 3 legs, exit 0, under a FINDING row (measured on de92614;pinsandfamilyprint a different finding rather than a pass under the same variable).shape-doctor.pyvalidator_row(:1903-1922, the crash branch): where the crash is an own copy that cannot import itsrepo_shape.py(the condition the row's reason already names,imports_repo_shape), the NEXT isgit -C <root> checkout HEAD -- scripts/repo_shape.py, the line the unloaded-manifest row prints for the same loss (shape-doctor.py: for a root whoseproject.yamldoes not load AND whosescripts/repo_shape.pyis gone, themanifestrow's NEXT promises its validator "refuses it the same way until it loads", but pasted it dies onModuleNotFoundError: No module named 'repo_shape', exit 1, so the person is sent to a command that cannot say what the row quoted #298), and the WHY says to run the validator again once the file is back and names that command. Every other crash keeps the validator's command andCRASH_WHY.shape-doctor.pylost_repo_shape_cure(:1769-1808, new): builds the(NEXT, why)pair. Wherehead_holdssays HEAD no longer holds the file (a committed deletion, or no repository) there is no NEXT, and the WHY says why, where the file comes back from, and the validator to run after.shape-doctor.pyrestore_from_head(:1296-1311, new): the one spelling ofgit -C <root> checkout HEAD -- <relative>, with aplatformargument likebump_leg_command's so both dialects are asserted from either host.unloaded_manifest_row(:2060) now calls it; its output is byte-identical (the one touch outside the crash path, and it is a call, not a behaviour change).-Eor-Sis added anywhere (option B stays declined). The verdict and the exit code do not move:DRIFTED (1 copy-missing), exit 1, for the three rows, before and after.with_gitlink_repairs/ the functions near :2235-2346 (validate-pins.py and shape-doctor.py: one leg's gitlink off its pin beside another leg's digest or workflow reference names the step that comes first, that gitlink put back by hand, and then the other leg'sbump-leg.pyline, so neither finding says "First clear that finding" over a finding no line can clear first (#677) #702) orcheck_naming(shape-doctor.py: a checkout whose owncontracts/repository-naming.yamldoes not load is CANNOT ANSWER, exit 3, for a project, where it readINVALID (naming), exit 1; any other naming exit 2 "refuses"; and the EXIT CODES list names an unfinished merge (#678) #701) is touched.Tests (all assertions are on what a command does, plus the facts the issue asks the WHY to carry):
tests/test_doctor_validator_rows.py(new section, shape-doctor.py: an own-copy validator row for a root that lostscripts/repo_shape.pynames the validator as its NEXT, so a reader whose Python reaches anotherrepo_shapesees the pasted line pass; the NEXT becomes the cure,git checkout HEAD -- scripts/repo_shape.py#697): a root that lost the file gets the cure as its NEXT on each ofmanifest,pins,family; run, the cure exits 0 and the tree matches HEAD, the validator the WHY names then exits 0 and its row isok; the same two lines pasted intoshfrom/; a root whose HEAD lacks the file (deletion committed) gets no NEXT and a WHY naming the file, HEAD, the parent of the deleting commit and the validator; a crash with another cause keeps the validator command andCRASH_WHY;restore_from_headin both dialects (plain and quoted paths); and the unloaded-manifest row prints the same line.tests/test_doctor_next_is_a_command.py: aCASESentry (validator_row-crashed-restore), so the new NEXT passes the property (no comment, no backtick, no live operator) and the#tail guard that tests/test_doctor_next_is_a_command.py: the comment-tail guard reads each string expression whole, across+and an f-string's pieces, and flags a#led by two or more blanks or a tab with or without a blank after it and a#that opens text after a part that is not a constant, socmd #x,cmd + " " + "# x"andf"{cmd}{pad}# x"no longer reach a NEXT unseen (#679) #698 landed.tests/test_shape_doctor.py:test_an_own_validator_without_repo_shape_is_a_finding_the_paste_agreespasted the NEXT and expected the import error. That NEXT is now the cure, so it pastes the validator line the WHY names; it still shows the row, the line and the project's own gate agreeing on the exit code and the line.Run, with a stub
ghfirst on PATH that exits 97,GH_TOKEN=bogus, an emptyGH_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: 49 passed.tests/test_doctor_next_is_a_command.py tests/test_doctor_validator_site.py tests/test_doctor_validator_verdicts.py tests/test_repo_hygiene.py tests/test_cli_reference.py: 193 passed.tests/test_doctor_gitlink_repairs.py -k "crashed or template_alone": 2 passed.tests/test_shape_doctor.py: 405 passed, 3 skipped in 1295.81sDocs. None state the crash row's NEXT (README.md, AGENTS.md, docs/handbook.html and docs/cli.md were grepped for
crash,repo_shape.py, own-validator NEXT wording), so none changed;docs/cli.mdis unaffected (no flag or help text moved).Pinned copies. None touched.
shape-doctor.pyis in no copy list.Not settled. The
legsandmembersrows'lockstep_nextalso print the ownvalidate-pins.py/validate-family.pyline for a gitlink that is off its pin; in a root that has also lostscripts/repo_shape.pythat line crashes the same way. It is another row's NEXT (issue scope was the own-copy validator rows' crash path) and is left as it is.🤖 Generated with Claude Code
Summary by Sourcery
Ensure validator crash findings provide a safe recovery path when the root’s own
repo_shape.pyis missing.Bug Fixes:
scripts/repo_shape.pyfrom HEAD before rerunning the validator, preventing misleading results from readers’ Python environments.Enhancements:
Tests: