Repository navigation
shape-doctor.py: the the way in rows say a checkout's own contracts/repository-naming.yaml that does not load, or raises, is that checkout's, an n/a environment row, where a tab line was FINDING the way in refused and a pattern that does not compile ended the doctor in a traceback; and the EXIT CODES list says what INVALID and manifest-kinds name (#707) - #778
Conversation
…s/repository-naming.yaml` that does not load, or raises, is that checkout's: an `n/a` environment row with no NEXT, where a tab line was `FINDING the way in refused` and a pattern that does not compile ended the doctor in a traceback, exit 1; the EXIT CODES list says INVALID names its red gates, or every FINDING row when none is, and every `manifest-kinds` cause (#707) `way_in_project` called `NamingPolicy.load` with no handler and `run_checks` catches only a `Refusal` and an `OSError`. It now asks `own_naming_policy`, the one question the doctor puts to that file (#678's guard, which `own_naming_policy_refusal` is a view of), and gives back the project or the fault as a `Refusal`, a `policy-raised` one for what is not. `classify` raising on a policy that loaded is the same fault. `check_the_way_in` and `check_the_other_way_in` return `way_in_policy_row` for it, in the words of the `naming` row (`naming_policy_words`), and the verdict stays NOT A SHAPE ROOT, exit 2. A leg clone, which is offered no command, keeps its row. The EXIT CODES list takes RV-a's two nits on #701: INVALID names `merge` alone, else its red gates, or, when none is red, every row that is a FINDING; and `manifest-kinds` names all of its causes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR hardens shape-doctor against failures in the standard checkout's repository-naming policy when evaluating way-in rows, converting them into consistent non-finding environment output while preserving the NOT A SHAPE ROOT verdict, and adds comprehensive regression tests plus clearer exit-code documentation. Sequence diagram for safe way-in policy failure handlingsequenceDiagram
participant Doctor as shape-doctor
participant Policy as own_naming_policy
participant Loader as NamingPolicy.load
participant Project as project_token_for
participant Row as way_in_policy_row
participant Verdict as verdict_for
Doctor->>Policy: own_naming_policy(shape)
Policy->>Loader: NamingPolicy.load(NAMING_COPY)
alt policy refuses or raises
Loader-->>Policy: fault
Policy-->>Doctor: (None, fault)
Doctor->>Row: way_in_policy_row(refusal)
Row-->>Doctor: n/a environment row
else policy loads
Loader-->>Policy: policy
Policy-->>Doctor: (policy, None)
Doctor->>Project: project_token_for(identity, policy)
alt classify raises
Project-->>Doctor: fault
Doctor->>Row: way_in_policy_row(refusal)
Row-->>Doctor: n/a environment row
else project derived
Project-->>Doctor: project
end
end
Doctor->>Verdict: verdict_for(rows)
Verdict-->>Doctor: NOT A SHAPE ROOT
Flow diagram for way-in rows with an unusable naming policyflowchart TD
A[Check the way in] --> B[own_naming_policy]
B --> C{Policy loads and answers?}
C -->|Yes| D[Offer way-in project command]
C -->|No| E[naming_policy_refusal_of]
E --> F[way_in_policy_row]
F --> G[n/a environment row]
G --> H[No suggested_project and no NEXT]
H --> I[NOT A SHAPE ROOT, exit 2]
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-17T01:44:53Z) Independent review of #778 by RV-a (opus), on head Cases, each run over a plain directory, a git repository, a leg clone (
#701's and #410's rows do not move.
EXIT CODES is true to the code.
S1: a leg clone loses its leg row when the policy raises at load.
NITs:
Mutants (
Tests:
Merges: clean with Lane: openreposhape-3 (openRepoShape-3) |
…for any fault in the policy, so a pattern that does not compile no longer takes a leg clone's own-`AGENTS.md` route with it, and a policy that loaded and raised when asked is a row that "cannot be used" where it said "does not load" (#707) Round 2 of #778, RV-a's S1 and N1: - `shape_advisory._naming_policy` caught a refusal, an `OSError` and a `UnicodeDecodeError`, so a `re.error` escaped `read()` and `leg_clone` read a leg clone as "not a leg": it lost its leg row and the Triad row, and was offered the scaffold. The advisory never fails a report. - `way_in_project` tells a `classify` that raises as `policy-unusable`, and `way_in_policy_row` words it "cannot be used"; the loader's faults stay "does not load". Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-17T04:13:14Z) RV-a's re-verification of #778 round 2 (
Lane: openreposhape-3 (openRepoShape-3) |
…ile` skips on EILSEQ or a `UnicodeError` only and the U+202E directory has no guard, so a full disk or a read-only `tmp_path` fails the test, not SKIPPED (#834) (#845) Stacked on #806 (`fix/757-non-utf8-test-dirs`, `76ae6419`); retarget to main once it lands. Tests only: nothing a user runs changes, and no pinned file changes. One new commit on `9ba530a9`, one test file, one line moved. Main is not merged and neither is #806. **SHOULD 1 (SonarCloud python:S5778, MAJOR): done, the reviewer's way.** The `pytest.raises(pytest.fail.Exception)` block in `test_the_sweep_names_the_first_errno_that_fails_and_stops_there` held two calls that could raise, `Disk(first)` and `raised_for_every_errno(...)`. `disk = Disk(first)` is now built before the block, and the block holds `raised_for_every_errno(disk, hides_the_error, "a path")` alone. The assertions after the block are unchanged and read the same exception: `failed.value.msg` is still the `Failed` that `raised_for_every_errno` raises, and `asked` is still the list the caller was asked. The file's other `pytest.raises` block (in `the_error_of`) already held one call. ### Runs Through `run9.sh`, Python 3.12.3. Logs are in the scratchpad's `v118/w834/`. - The two files, `tests/test_non_utf8_oserror_sites.py` and `tests/test_non_utf8_name_callers.py`: 55 passed (`t-two.log`), as the reviewer measured. - The seven changed or importing files (`tests/test_non_utf8_git_reads.py`, `tests/test_adopt_template_values.py`, `tests/test_non_utf8_name_helpers.py`, `tests/test_non_utf8_name_callers.py`, `tests/test_non_utf8_oserror_sites.py`, `tests/test_adopt_git_reads.py`, `tests/test_family_init_asks_every_value.py`): 311 passed, 1 skipped (`t-B.log`), the same as round 2. The skip is the one in the PR body, from the run script's blank `user.name`; it does not happen in lane 4's runs. - `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py`: 147 passed (`t-C.log`). ### Mutants The four label and fail-fast mutants of round 2 are all still caught, each by `test_the_sweep_names_the_first_errno_that_fails_and_stops_there` alone, with the new line in place (`tmutants-summary.log`). A fifth, from lane 4's table, is caught too. | Mutant | Tests that fail | |---|---| | the sweep does not pass the label | 1 | | `the_error_of` drops the label on the way | 1 | | `fail_where_the_caller_ends` ignores the label | 1 | | the sweep swallows each failure and goes on | 1 | | the label goes after the message (lane 4's) | 1 | The first one reads `- errno 1 (EPERM): the caller ended SKIPPED: hidden` against `+ the caller ended SKIPPED: hidden`. ### Merge checks `git merge-tree --write-tree` of `8f6a38e3` against main `9f235da2`, #806 (`76ae6419`) and `9ba530a9`: clean. #778 is as before: the one `from conftest import` line of `tests/test_doctor_validator_verdicts.py`, which no round of this PR touches after `b18b243e`. One new commit on `efb17273`, two test files. Main is not merged and neither is #806. **SHOULD 1 (SonarCloud python:S8714 on `raised_for_every_errno`): done, the reviewer's way.** The `except pytest.fail.Exception` caught a failure only to fail again with the errno's name in front. The label is now passed in: - `fail_where_the_caller_ends` (`tests/test_non_utf8_name_callers.py`) takes a keyword-only `said`, put in front of its message, with `""` as the default. - `the_error_of` threads `said` through, and the loop passes `said=f"errno {code} ({errno.errorcode[code]}): "`. The loop has no `try`. - Nothing else changes. A failing errno still reads `errno 1 (EPERM): the caller ended SKIPPED: ...`, and the sweep still stops at the first errno that fails, since the failure ends the loop. The three other callers of `fail_where_the_caller_ends`, all in `tests/test_non_utf8_name_callers.py`, pass no `said` and are not touched: the only hunk in that file is the function's own. A failing errno still names itself, in a run's output. This is the mutant that re-raises only ENOSPC, EROFS and EACCES in `make_file`, on this commit: ``` E Failed: errno 1 (EPERM): the caller ended SKIPPED: this filesystem refuses a file name that is not UTF-8 (EILSEQ): [Errno 1] Operation not permitted: ... ``` **One new test** holds the label and the fail-fast, `test_the_sweep_names_the_first_errno_that_fails_and_stops_there`: a caller that skips where it should raise must fail with exactly `errno <first> (<NAME>): the caller ended SKIPPED: hidden`, and be asked once. It passes on `efb17273` as well, by design: that commit already gave the same message, and this round keeps it. So it cannot fail there. It fails on the mutants below that drop the label or the fail-fast. **NIT 2 stays, and is #854.** Under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0`, `test_execute_refuses_a_local_source_directory_holding_a_bidi_control` fails with a `UnicodeEncodeError` from `parent.mkdir()`, on `efb17273` and on main alike. It is filed as #854. This round does not change it, and the new test of the sweep does not touch that directory. ### Runs Through `run9.sh`, Python 3.12.3. Logs are in the scratchpad's `v117/w834/`. - The seven files that import the changed ones or are changed (`tests/test_non_utf8_git_reads.py`, `tests/test_adopt_template_values.py`, `tests/test_non_utf8_name_helpers.py`, `tests/test_non_utf8_name_callers.py`, `tests/test_non_utf8_oserror_sites.py`, `tests/test_adopt_git_reads.py`, `tests/test_family_init_asks_every_value.py`): 311 passed, 1 skipped (`s-B.log`). The skip is the one in the PR body, on the base too. The count is the 310 of the last round plus the one new test. - `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py`: 147 passed (`s-C.log`). - The four `non_utf8` files together under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0`: 110 passed, 15 skipped, all the ASCII file name encoding (`s-D-clocale.log`). ### Mutants Ten, each applied to `9ba530a9`'s test files and caught (`smutants-summary.log`): | Mutant | Tests that fail | |---|---| | `make_file` skips on any `OSError` again | 4 | | `make_file`: denylist of ENOSPC, EROFS, EACCES | 2 | | `test_cli_reference.py`: a `try` that re-raises 13, 28 and 30 | 1 | | `test_doctor_validator_verdicts.py`: the same | 1 | | U+202E `mkdir`: the same | 1 | | `conftest.py` helper: the probe's guard is a denylist | 3 | | the sweep does not pass the label | 1 | | `the_error_of` drops the label on the way | 1 | | `fail_where_the_caller_ends` ignores the label | 1 | | the sweep swallows each failure and goes on to the next errno | 1 | The first six are the earlier mutants that the sweep kills. The last four are new, and a failing run of the first label mutant reads `- errno 1 (EPERM): the caller ended SKIPPED: hidden` against `+ the caller ended SKIPPED: hidden`. ### Merge checks `git merge-tree --write-tree` of `9ba530a9` against main `190133d5`, #806 (`76ae6419`) and `efb17273`: clean. Against #778 (`fe7b30f6`): the one `from conftest import` line of `tests/test_doctor_validator_verdicts.py`, as before, and this round does not touch that file. One new commit on `b18b243e`, three test files. Main is not merged and neither is #806. Each point of the review, and what was done: **SHOULD 1: `REFUSALS` held three errnos, so a guard that re-raised those three and skipped the rest passed. Done.** The emulated disk (`a_disk_that_refuses`) now answers every errno that `errno.errorcode` holds but EILSEQ (129 here) and each caller must raise it, as Python makes it (`PermissionError` for EACCES). The errnos are drawn from `errno.errorcode` and not listed, so one nobody thought of is asked too. The sweep stops at the first errno that fails and names it, so a caller that goes on past a refused name runs its test once and not 129 times. The three-row families are gone, since the sweeps cover them: the new file has 18 tests where it had 32. Both of the reviewer's surviving mutants now fail, with the same mutants and the same run: - `make_file` with `exc.errno in (EACCES, ENOSPC, EROFS)` in place of `!= EILSEQ`: 2 tests fail (was 0). - `test_cli_reference.py` with a `try` that re-raises 13, 28 and 30 and skips the rest: 1 test fails (was 0). - Three more of the same kind also fail: the doctor test's `mkdir`, the U+202E `mkdir`, and the helper in `conftest.py` (a denylist there fails 3 tests, so the sweeps also hold #663's helper to it). **SHOULD 2: the U+202E docstring claimed more than `refused_then_moved` did. Made true, not narrowed.** The reviewer measured the gap with a `git` that fails `commit` as a full disk does. I measured it the same way, on `b18b243e` and on this commit: - `b18b243e`: `1 skipped`, "git will not work under '\u202ecaf' here: error: unable to write file .git/objects/xx: No space left on device". - `efb17273`: `1 failed`, `AssertionError: error: unable to write file ...`, raised at `tests/conftest.py:883`. Why true and not narrowed: the second skip exists for a name that is not UTF-8, which git may not work under (#757 kept it for that). U+202E is valid text, and git works under it wherever it works at all (reasoned for NTFS and APFS, not run). So the skip there hides a disk that filled after the `mkdir`, which is this issue's whole point, and the fix is one keyword. `refused_then_moved` takes `skip_where_git_fails` (default true, so the byte-name test is as #757 left it), and the U+202E test passes false. Three new tests hold it: - `test_a_failure_of_git_under_the_bidi_directory_fails_the_test`: a `make_source_repo` that raises the `AssertionError` must reach the test as a failure. It fails on `b18b243e`. - `test_a_failure_of_git_under_the_byte_directory_still_skips_the_test`: the control, and the known limit. The byte-name test still skips there, and a full disk after its `mkdir` still reads as SKIPPED. - `test_a_disk_that_refuses_the_bidi_directory_fails_the_test` is now a sweep over every errno. The docstrings of both tests and of `refused_then_moved` say which skip stays and why. **NIT 3: done.** `make_file` gives the two skips their own reasons: "this system's file name encoding cannot spell ..." for a `UnicodeError`, and "this filesystem refuses a file name that is not UTF-8 (EILSEQ)" for EILSEQ. The controls assert each one, and that the encoding reason does not say "filesystem". Both fail on `b18b243e`, and a mutant that restores either old wording fails. **NIT 4: stays.** The emulated disk is still written in four places. Moving `a_disk_that_refuses`, `fail_where_the_caller_ends` and the two fixtures into `tests/conftest.py` adds hunks to a file that #814 and #818 also edit, and Sonar reports 0.0% duplication on new code. It is a follow-up, once those land. **NIT 5: done.** The own-directory test is now `test_the_cli_reference_and_doctor_tests_fail_where_the_disk_refuses_their_own_directory`, which names its sites. A `-k` selection no longer picks up #757's test of the old name. **NIT 6: done.** The module docstring keeps `skip_where_a_name_cannot_be_not_utf_8` on one line. ### Tests The new file passes 18 of 18 on this commit. On `b18b243e`, with only the new file put in, 3 fail and 15 pass. The 3 are the two NIT 3 reasons and the U+202E git failure. The other 15 are mutant-killers for SHOULD 1, and they pass on code that is correct. Runs on Linux (Python 3.12.3) through `run9.sh`: - `tests/test_cli_reference.py`, `tests/test_doctor_validator_verdicts.py`, `tests/test_bootstrap_leg_moves.py`: 79 passed. - The changed files and the files that import them, `tests/test_non_utf8_git_reads.py`, `tests/test_adopt_template_values.py`, `tests/test_non_utf8_name_helpers.py`, `tests/test_non_utf8_name_callers.py`, `tests/test_non_utf8_oserror_sites.py`, `tests/test_adopt_git_reads.py` and `tests/test_family_init_asks_every_value.py`: 310 passed, 1 skipped. The skip is the one named in the PR body (`test_plan_names_the_elector_it_took_from_git_config`, on the base too). - `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py`: 147 passed. - The four `non_utf8` files together: 124 passed. Under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0`: 109 passed, 15 skipped, and every skip is the ASCII file name encoding. **Mutants.** 21 in all, one or more per hunk, each applied to the fixed tree and caught. The first eleven are the earlier table, rerun on the new tests (each fails 1 to 4 tests). The ten new ones: | Mutant | Tests that fail | |---|---| | `make_file`: denylist of ENOSPC, EROFS, EACCES (reviewer's) | 2 | | `test_cli_reference.py`: a `try` that re-raises 13, 28, 30 (reviewer's) | 1 | | `test_doctor_validator_verdicts.py`: the same | 1 | | U+202E `mkdir`: the same | 1 | | `conftest.py` helper: the probe's guard is a denylist | 3 | | `make_file`: the `UnicodeError` reason says "filesystem" again | 1 | | `make_file`: the EILSEQ reason does not name EILSEQ | 1 | | `refused_then_moved`: skips on git's failure whatever the caller asks | 1 | | U+202E test: does not ask for git's failure to raise | 1 | | byte-name test: asks for git's failure to raise | 1 | ### Merge checks `git merge-tree --write-tree` of `efb17273` against main `6f64eea2`, #806 (`76ae6419`) and `b18b243e`: clean. Against the 57 open `fix/*` and `l3-*` branches: 50 clean. The seven that conflict are six of the seven that conflicted at `b18b243e` (`fix/712-validate-pins-contracts` moved and is clean now) plus `fix/835-config-count-appends`, which conflicts in `tests/test_adopt_git_reads.py` as #818 does against #806. Every one conflicts the same against #806 or main, with one exception, below. The exception is #778, which conflicts with this branch only, on the one `from conftest import` line of `tests/test_doctor_validator_verdicts.py`, as at `b18b243e`: this round does not touch that file. This round changes no conflict region against #814 (one region in `tests/test_non_utf8_git_reads.py`, before and after). ### Not changed, and a candidate - **A candidate, not filed:** under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0`, `test_execute_refuses_a_local_source_directory_holding_a_bidi_control` fails on `b18b243e` and on #806 (`UnicodeEncodeError` from `parent.mkdir()`: the ASCII encoding cannot spell U+202E). #757 skips three tests of `tests/test_non_utf8_git_reads.py` that way with `skip_where_the_environment_cannot_carry`. The new git-failure test asks it for U+202E and skips there, so this round adds no failure to a C locale. The test of the directory itself is not changed, because the review did not raise it. - The two checklist candidates in the PR body stand, narrowed: `refused_then_moved` no longer belongs to the first one for U+202E, only for the byte-name test. Five tests make a directory or a file named with a byte that is not UTF-8, or with U+202E. Each turned ANY `OSError` into `pytest.skip`, beside the one helper #663 added, `skip_where_a_name_cannot_be_not_utf_8`. So a full disk, a read-only `tmp_path` or a folder nobody can write in read as SKIPPED, and no run fails on a skip. CI never has one of those, so nothing failed. #806 moved five other tests onto the helper and left these. Each line of #834, with the direction chosen: 1. **`tests/test_cli_reference.py`, `test_a_checkout_whose_path_is_not_utf8_is_a_checkout`.** It asks the helper on `tmp_path`, then makes its directory with no `try`. 2. **`tests/test_doctor_validator_verdicts.py`, `test_a_name_that_is_not_utf8_is_never_an_exception_quoted_as_a_finding`.** Same: the helper first, then `mkdir(parents=True)` unguarded. The docstring said "a disk that refuses one (APFS) skips"; it now says which skip and that every other error fails. 3. **`tests/test_bootstrap_leg_moves.py`, `test_a_branch_whose_name_is_not_utf8_is_read_back_on_every_run`.** The helper replaces the `write_bytes` probe that skipped on `OSError` and `ValueError`. The Windows `skipif` stays. 4. **`make_file` in `tests/test_non_utf8_git_reads.py`.** It keeps the `UnicodeError` skip (an ASCII file name encoding, #757's second line) and skips an `OSError` only where `errno` is `EILSEQ` (macOS). ENOSPC, EROFS and EACCES are raised, as the helper does for its `mkdir`. Every caller of `make_file` inherits the failure and not the skip. #757's `test_a_write_the_disk_refuses_still_fails_the_make_file_test` (in `tests/test_non_utf8_name_callers.py`) expected the `AssertionError` of the test's own "not a skip" assert; it now expects the `OSError` itself, with its errno. 5. **`tests/test_adopt_template_values.py`, the U+202E directory.** The `try` is dropped, so `mkdir` raises. The issue offered the other way, narrowing to EILSEQ and naming the system that refuses U+202E. None is known: U+202E is valid text. A `mkdir` of a name that starts with U+202E succeeds on this machine's overlay filesystem (measured), and NTFS and APFS are reasoned to hold it (read, not run on those two). So a narrowing would guard a refusal nobody has seen. `tests/test_non_utf8_oserror_sites.py` is new: 32 tests. It calls each site as a plain function, with the disk answering as it would on a system CI does not run (CI is Linux only, #597): - EILSEQ on the probe (macOS) skips the three tests that ask the helper, on the helper's reason. - EACCES, ENOSPC and EROFS on the probe, on the test's own directory, on `make_file`'s write and on the U+202E directory raise. The test asserts the error and its errno, and fails if the call skipped or ended without one. - An `os.fsencode` that gives a surrogate three bytes (Windows, emulated with `windows_fsencode`) skips the three helper callers before the disk is touched, and nothing is made under `tmp_path`. - `make_file` still skips on EILSEQ and on a `UnicodeError` (two controls). The disk is emulated by `os.mkdir` and `Path.write_bytes`, the two calls the base and the fix use to make a name, so the same row fails on the base for the reason it should. It reuses the two fixtures, `PROBE` and `fail_where_the_caller_ends` of #806's `tests/test_non_utf8_name_callers.py`. No Windows or macOS run was made. Runs, on Linux (Python 3.12.3), under `run9.sh`: - `tests/test_non_utf8_oserror_sites.py`, `tests/test_non_utf8_name_callers.py`, `tests/test_non_utf8_name_helpers.py`: 86 passed. - `tests/test_cli_reference.py`, `tests/test_doctor_validator_verdicts.py`, `tests/test_bootstrap_leg_moves.py`: 79 passed. - Those two sets with `tests/test_non_utf8_git_reads.py`, `tests/test_adopt_template_values.py`, `tests/test_adopt_git_reads.py` and `tests/test_family_init_asks_every_value.py` (the files that import a changed module): 324 passed, 1 skipped. The skip is `test_plan_names_the_elector_it_took_from_git_config`, which skips on the base too: the run script's blank `user.name` overrides its `GIT_CONFIG_GLOBAL`. - `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py`: 147 passed. `render-cli-reference.py --check` passes. - `tests/test_non_utf8_git_reads.py` under `PYTHONUTF8=0 LC_ALL=C PYTHONCOERCECLOCALE=0`: 40 passed, 12 skipped; every skip is the ASCII file name encoding, as before. Normal locale: 52 passed. **Fails on the base.** On `76ae6419` with only the two test files put in, 32 of the 68 tests of `tests/test_non_utf8_oserror_sites.py` and `tests/test_non_utf8_name_callers.py` fail: 30 of the new 32 and the 2 changed ones. The other 36 pass: the 2 new controls, which hold what both keep, and the 34 that are unchanged. **Mutants.** One per hunk, each applied to the fixed tree and caught: | Mutant | Tests that fail | |---|---| | `test_cli_reference.py`: `except OSError` put back around the `mkdir` | 3 | | `test_cli_reference.py`: helper call removed | 5 | | `test_doctor_validator_verdicts.py`: `except OSError` put back | 3 | | `test_doctor_validator_verdicts.py`: helper call removed | 5 | | `test_bootstrap_leg_moves.py`: the old `write_bytes` probe back | 5 | | `test_bootstrap_leg_moves.py`: helper call removed | 5 | | `make_file`: skips on any `OSError` again | 8 | | `make_file`: EILSEQ no longer a skip | 1 | | `make_file`: `UnicodeError` no longer a skip | 1 | | `test_adopt_template_values.py`: `except OSError` put back around the U+202E `mkdir` | 3 | | `test_non_utf8_name_callers.py`: the old `AssertionError` expectation back | 2 | `git merge-tree --write-tree` of this head against origin/main (`fc867207`, which has #746), against `bad05d21`, against #806 and against all 52 open `fix/*` and `l3-*` branches: clean, except seven branches. - **#778 (`l3-707-way-in-policy`) conflicts in one place, and only with this commit.** Both PRs add a name to the `from conftest import ...` line of `tests/test_doctor_validator_verdicts.py` (#778 adds `git`, this one adds the helper). Keep both names. #778 is clean against main and against #806. - **#818 (`fix/758-git-command-scope`)** conflicts in `tests/test_adopt_git_reads.py`, which this commit does not touch. It conflicts the same against #806 alone, and is clean against main. - **#814 (`fix/766-spelled-paths`)** conflicts in `tests/test_non_utf8_git_reads.py` and `tests/test_dry_run_parity.py`. It conflicts the same against main and against #806, with one region in each file with and without this commit. - **`fix/712-validate-pins-contracts`, `fix/798-adopt-legs-asked-first`, `l3-677-pins-hand-step-wip` and `l3-678-naming-checkout-wip`** conflict in files this commit does not touch, and conflict the same against main and #806. The scan of `tests/` for an `except` that names `OSError` and is followed by `pytest.skip` lists 31 other places. As #834 says, most ask something else (a symlink, a hard link, `locale -a`, a NEL or a line feed in a folder name) and are not changed here. Two kinds are the same defect as this issue's and are not lines of it. Nothing is filed; they are for the checklist. - **A skip on an `AssertionError` of a git call, kept by #757 as a second skip.** `refused_then_moved` (`tests/test_adopt_template_values.py`), `test_a_utf8_clone_of_a_source_whose_path_is_not_utf8_adopts` (`tests/test_adopt_git_reads.py`) and `test_a_source_directory_that_is_not_utf8_is_shown_spelled` (`tests/test_adopt_plan_escapes.py`) skip with "git will not work under ..." when `make_source_repo` raises `AssertionError`. A disk that fills after the directory is made fails that call the same way, and the test reads as SKIPPED. For the U+202E test of line 5 this means the `mkdir` now fails on a full disk, and a disk that fills a moment later can still skip it. - **A skip on any `AssertionError`, `UnicodeError` or `OSError` where the branch name is not UTF-8.** `with_default_branch` and `moved_to_a_branch_that_is_not_utf8` (`tests/test_adopt_git_reads.py`) make the branch with `git`, and skip if it fails for any reason. Review: lane 4's RV845, READY on b18b243 (issuecomment-6240020694), on efb1727 (issuecomment-6240528662) and on 9ba530a (issuecomment-6241540556); 8f6a38e applies RV845's S5778 fix exactly as RV845 probed it. `fc3cb5e9` restacks it onto #806's squash `d6071c7d` with three merges and the same `-U0` lines. CI (Brett Heap's ruling 27): H0 8f6a38e passed tests, tests-git-bash and SonarCloud; the head fc3cb5e merges #806's final head, then #806's squash d6071c7 (the branch's own side for #806's files), then main, with tests, tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree b572ef4 over main 576e170: the adopt_template_values, bootstrap_leg_moves, cli_reference, doctor_validator_verdicts, family_bootstrap_gate_order, family_unnamed_gitlink, non_utf8_git_reads, non_utf8_name_callers, non_utf8_oserror_sites, pin_validator, validate_family_unnamed_mounts, repo_hygiene, agents_md_codes, cli_reference sets, 524 passed, 1 skipped. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #834 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One conflict, tests/test_doctor_validator_verdicts.py, one hunk: the `from conftest import` line near the top. Main's side (#845, squash 0f69339) added `skip_where_a_name_cannot_be_not_utf_8` to it; this branch's side (#778, closes #707) added `git`. The resolution imports both names, `WINDOWS_SKIP, clone_just_pushed, git, run_script, skip_where_a_name_cannot_be_not_utf_8`, in main's wrapped form. Every other hunk of the file merged by itself, and no other file conflicted: #845's change to the non-UTF-8 name test and #778's #707 rows and tests both stay as each side wrote them. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ith #886, and #778 from its reviewed head (#883) Round 6 of #883, a refresh. Each line was checked against the code: - #886 (#884), from its squash e7039b7: a holder's `make bootstrap` reads a member's make on stderr a line at a time and names the innermost command's code, so a member whose make runs another no longer reads `exited 2` for a finding, and `make -k` keeps its `Error` line. A line in "What changed" and words in "What 1.1.1 fixes". The #884 limit is narrowed to what `run_make`'s and `MakeErrors`' docstrings keep: the pipe's three effects and the Windows decoding, the nested-make cases the line still misnames, the signal under dash, and make before 4.0 not run. - #778 (#707), from its reviewed head 8259ea1, not landed at the fetch just before this commit: outside a shape root, a naming policy that does not load, or cannot be used, in the doctor's own checkout makes the `the way in` rows `n/a` environment rows, where a tab line was a FINDING and a bad pattern a traceback, and a leg clone keeps its rows. A line in "What changed" and one in "What 1.1.1 fixes". "Before tagging" says the line comes from that head. - "The list runs to" names no commit now: the commit v1.1.1 is tagged on. The open list is #879 alone: #884 is closed by #886, and #707 by #778 once it lands. The shape-copy range runs to e7039b7; #886 changes the holder's bootstrap.py, on the lists, and #778's head no pinned copy. No other file changes. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
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 3 days and 5 hours by commenting |
…g keeps its rows, #802's readers what v1.1.0 already skipped, and the draft lines name #883 and #879's move (#883 follow-up) Lane 1's independent reviewer r893 found #893 NOT READY at b795ef7, with one MUST and two NITs. Each was checked against v1.1.0 (190133d) and the release commit 5017830 before the text changed: - MUST, #778's leg-clone sentence. r893's probe, run again here with one more target, a leg clone whose own AGENTS.md says so in a directory named Atlas, gives at 5017830: that clone keeps its leg row and its Triad row over a tab line, a pattern that does not compile and a policy that raises on its name; a leg known only by its name (Atlas-spec) gets two `n/a` "does not load" rows and the single-repository Triad row over a tab line or a bad pattern. At v1.1.0 the AGENTS.md clone's leg row was `FINDING the way in refused` over a tab line, its Triad row kept, and a bad pattern, or the policy raising on its name, ended in a traceback, exit 1. The sentence now says that. - NIT, #802's readers. At v1.1.0 the import reader already skipped an `if TYPE_CHECKING:` body and the binding reader did not; neither skipped `if False:`, `if 0:`, `if None:` or a statement after `raise`. At 5017830 both skip all of them. r893's wording ("as they skipped an `if TYPE_CHECKING:` one") is narrowed to the import reader. - NIT, "Before tagging" (draft lines only): #883 landed as the release commit 5017830, and #879 moved to v1.2.0 at 2026-10-18T14:10:11Z, after 2d372bb landed at 13:19:52Z. No other file changes. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the release commit, #778's leg-clone sentence and #802's readers sentence, each checked against runs at v1.1.0 and main (#883 follow-up) (#893) The v1.1.1 notes landed as the release commit `5017830` (#883), and RV883's round 5 held two SHOULDs and five NITs for after it (#883 issuecomment-6250558821). This applies them to `docs/release-notes/v1.1.1.md`, with two more sentences lane 1's review found: 1. **#778's leg-clone sentence** says what runs at `v1.1.0` and `5017830` show. A leg clone whose own `AGENTS.md` says so keeps its leg row and its Triad row over each fault, where v1.1.0 gave `FINDING the way in refused` over a tab line and a traceback, exit 1, over a pattern that does not compile or a policy that raised on its name. A leg known only by its name gets a repository's two `n/a` rows. 2. **"Is", not "stays", exit 2,** in the pattern case, which was exit 1 in v1.1.0. 3. **#802's clause is dropped:** v1.1.0's `_blocks_inside` already read a `try`'s handlers, `else` and `finally` outside its guard. 4. **"What 1.1.1 fixes"** names "or cannot be used", and `the other way in`. 5. **#815's leftover tracebacks** were measured on Python 3.12; 3.13 is read, not run. 6. **#871 lets `_` through for any owner,** an organisation's name included. 7. **#869 asks the disk** only about a name that is not UTF-8. 8. **#802's readers:** at v1.1.0 only the import reader skipped an `if TYPE_CHECKING:` body; the binding reader skips one now. 9. **A known limit names #940:** under the `C` or POSIX locale, the bash `openRepoShape` and `setup.sh`'s guards refuse a URL, path or ref with a letter past ASCII as "a control character", a regression from v1.1.0 in the launcher through #872 (#866); a UTF-8 locale takes such a URL or path. The draft-time lines in the sections marked "remove at tag time" say main `2d372bb`, where #778 landed file for file as its reviewed head `8259ea1`, and that #879 moved to v1.2.0. The release body never carries those sections. Review: lane 1's independent reviewer found `b795ef74` NOT READY, on the leg-clone sentence, and `ca49f125` READY, with each changed sentence checked against runs at `v1.1.0` and `5017830`. Lane 1's second reviewer found `6cdb5551`, which adds the #940 limit, READY, with each clause measured against the launcher at `64464d31` and at v1.1.0. It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `6cdb5551` is green on Linux and SonarCloud. The branch merged main once and was never rebased; the last merge is update-branch's, clean, giving f60654e, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 5 test files that read the release notes or the workflows (`test_repo_hygiene.py`, `test_release_workflow.py`, `test_workflow_concurrency.py`, `test_agents_md_codes.py` and `test_cli_reference.py`), 221 passed and 0 skipped. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #707 (comment)
Closes #707.
Lands after the v1.1.0 tag.
What was wrong
A follow-up to #678 (PR #701). In a directory that is not a shape root, a fault in this checkout's own
contracts/repository-naming.yamlreached thethe way inrows as an exception, becauseway_in_projectcalledNamingPolicy.loadwith no handler andrun_checkscatches onlyRefusalandOSError.Re-measured on main e313701 (a copy of the standard whose policy has one tab-indented line, or a pattern that does not compile, asked about a git repository with no commit and a plain directory, with a
ghthat exits 97):FINDING the way in refused: <checkout>/contracts/repository-naming.yaml: line 509: tab used for indentationandFINDING the other way in refused: ..., exit 2, nodetail.environment;pattern: "(^open(?:, plain directory: the doctor dies in a traceback,re.error: missing ), unterminated subpattern at position 0, exit 1, the code of a finding.Every claim in the issue held; the line numbers it cites have moved with main.
What changed (
shape-doctor.py, line numbers at head):2229own_naming_policy: the ONE place the doctor asks whether this checkout's naming policy loads:(policy, None), or(None, what it raised).:2248own_naming_policy_refusal, 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's helper and thenamingrows' guard, is now a view of it (aRefusalor None, so a policy that raises stays the naming script's crash row, shape-doctor.py's twonamingrows read only the naming script's exit code, so a crash of the STANDARD's ownvalidate-repository-naming.pyis a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 getsnaming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410). No second guard.:2262naming_policy_refusal_of: a fault as aRefusala row can quote: the loader's own, or apolicy-raisedone naming the exception (re.error: missing ), ...), so a policy that raises and one that refuses are quoted, and carried indetail.refusal, in one shape.:9194way_in_projectreturns(project, None)or(None, refusal).classifyraising on a policy that loaded is the same fault found a step later, and is caught there.:9222way_in_policy_row, and:9095check_the_way_inand:9250check_the_other_way_inuse it: ann/arow withdetail.environment,detail.refusal,suggested_project: nulland no NEXT, in the words of thenamingrow (:2337naming_policy_words, whichnaming_policy_rowat:2352now shares): "the standard's contracts/repository-naming.yaml does not load: <the loader's line>; that file is this checkout's, not this directory's, so nothing here is a statement about how this directory becomes a shape root". A plain directory gets one row, a repositorythe other way inas well. A leg clone, which is offered no command, keeps its row.verdict_fordecides it before it reads a row), and the text report prints noshape-doctor-cannot-answerunder it.run_checks(:9604) is untouched: no fault reaches it from these rows now, and a catch-all there would turn every check's bug into a row.:301-318EXIT CODES, RV-a's two nits on 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 (lane 3's comment on shape-doctor.py: in a directory that is not a shape root, thethe way inrows load this checkout's naming policy unguarded, so an unloadable policy shows asFINDING the way in refused: …and a policy whose pattern does not compile ends the doctor in a traceback, exit 1 #707 at 17:31:06Z):mergealone while the index holds an unmerged path (shape-doctor.py answers COMPLIANT, exit 0, for a root in the middle of a merge (UU contracts/spec-pin.yaml,UU spec, a MERGE_HEAD,git commitrefusing), because no row asks git for unmerged paths andrecorded_gitlinkanswers an unmerged gitlink from HEAD, so a root an agent cannot commit is reported compliant, including after thegit checkout --oursthat a conflicted pin's NEXT names #424); else its red gates,naming,manifest,pins,family,manifest-kindsandagent-files; or, when none of them is red, every row that is a FINDING", which ismerge_verdict,invalid_verdictand the catch-all ofverdict_for.manifest-kindsnames all of its causes: a manifest that does not load, akind:nothing here validates, a kind whose gate this root never runs or never reads at that path, afamily.yamlbesideproject.yaml, and a file declaring one kind where a gate here reads it as another and none checks the kind it declares.Two choices to look at:
n/aand notFINDING, where 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'snamingrow is a FINDING withdetail.environment. A FINDING there is what lets an assembly root's verdict read CANNOT ANSWER. These rows exist only where the verdict is NOT A SHAPE ROOT, the question they answer cannot be asked here, and nothing is wrong with the directory, which is whatn/ameans in this file.REFUSED policy-raised: <file>: re.error: ...: the doctor's own code, in the loader's line shape, so the two read alike.Tested
tests/test_doctor_validator_verdicts.py, #701's file:test_an_unusable_naming_policy_is_an_environment_row_in_the_way_in_rows(:577), six cases: a tab line, a pattern that does not compile, and a policy that loads and raisesKeyErrorinclassify, each over a plain directory and a repository. NOT A SHAPE ROOT, exit 2, no traceback; eachthe way inrown/a,detail.environment, no NEXT,suggested_projectnull, quoting the fault with this checkout's file and ending "not this directory's, ... how this directory becomes a shape root";namingan environment row too.test_an_unusable_naming_policy_ends_no_text_report_in_a_traceback(:615): the text run, with a repository and the bad pattern.contracts/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's builder becamenaming_policy_unusable(:324, withPOLICY_EDITS) and its raise test uses it; its docstring no longer points at this issue as open.All seven fail on main e313701 (the traceback,
FINDING ... refused:) and pass here. Edits are made in bytes and the pattern takes CRLF, so they hold on Windows. The full suite is CI's.scripts/render-cli-reference.py --check: docs/cli.md is what the generator produces. No NEXT changed, andtest_doctor_next_is_a_command.pypasses.Docs
None changed: README, AGENTS.md and the handbook do not say what
the way inrows do where the checkout's policy cannot load, and README is at its line cap.docs/release-notes/is not touched.Pinned copies
None.
shape-doctor.pyis not a pinned copy andscripts/repo_shape.pyis untouched, so no project sees anupstream-changedrow from this.Not settled here
Nothing found open. Other lane-3 PRs edit
shape-doctor.py(#771, #772, #773, W668's.gitmodulesreaders); this PR stays in the naming helpers (:2229-2360), thethe way inrows (:9095-9290) and the EXIT CODES list.Round 2 (RV-a's S1 and N1, #778 (comment))
Head fe7b30f.
scripts/shape_advisory.py:465_naming_policy: it caught only(Refusal, OSError, UnicodeDecodeError), so a pattern that does not compile raisedre.errorout ofshape_advisory.read, andleg_cloneread a leg clone as "not a leg": the clone lost its leg row and the Triad row, and was offeredthe other way inand the scaffold, though the leg's own-AGENTS.mdroute reads no policy. It answers None for any fault now (except Exception, the advisory never fails a report).scripts/shape_advisory.pyis not a pinned copy, so no project sees anupstream-changedrow.tests/test_doctor_validator_verdicts.py:622test_an_unusable_naming_policy_keeps_a_leg_clones_rows: a leg clone that says so in its ownAGENTS.md(the advisory tests' builder), over the three unusable policies. NOT A SHAPE ROOT, exit 2, no traceback;the way inkeeps its leg row (no NEXT, no environment mark),triad-advisorykeeps "this is a clone of the spec leg of a Triad", and there is noway-in-scaffold. Thepatterncase fails without theshape_advisory.pychange.shape-doctor.py:2340naming_policy_words,:2262naming_policy_refusal_of,:9199way_in_project,:9230way_in_policy_row: aclassifythat raises on a policy that DID load is told aspolicy-unusable, and the row says "the standard's contracts/repository-naming.yaml cannot be used: ..." where it said "does not load". The loader's faults keep "does not load". Theclassifycase oftest_an_unusable_naming_policy_is_an_environment_row_in_the_way_in_rows(:582) asserts the verb and the code, for a directory and a repository.re.PatternErroron Python 3.13.tests/test_shape_doctor.pywas not re-run this round (436 passed, 3 skipped, 1 xfailed at f98e610);scripts/render-cli-reference.py --checkis ok.🤖 Generated with Claude Code
Summary by Sourcery
Handle unusable naming policies as non-fatal environment conditions while preserving accurate way-in behavior and exit-code documentation.
Bug Fixes:
the way inchecks, including policies that refuse to load or raise while being evaluated.Enhancements:
manifest-kindsexit-code criteria.Tests: