Repository navigation
shape_advisory.py and shape-doctor.py: a leg's AGENTS.md this user cannot read leaves whether it is a leg unknown, so the way in offers neither command and points at what is here, and the other way in and the Triad row are silent (#1110) - #1159
Conversation
…he root that is a link into a folder this user cannot search is said once, `<name>: cannot be read (<reason>)` with the chmod cure, never absent, and no way in is offered over it (#1110) Slice A of #1110: the eight sites in `shape-doctor.py` and `scripts/shape_advisory.py` that asked `exists()` or `is_file()` of a name directly in the root now ask `is_a` / `_is_a`, which raise a refused `stat`. - `what is here` names each name it cannot read in validate-pins.py's words, with this doctor again as the NEXT and the gate's advice as the WHY: the one row that carries the cure. - `the way in` offers neither command while the `.git` cannot be read, and points at `what is here`; `the other way in` and the Triad row are silent. - `naming` classifies the folder's own name and says origin's was not read. - `origin_org`, `repo_local_origin_name` and `repository_names` raise the refused `stat` (`carries_git`, `_is_a`) where they answered as for a folder with no repository. - `shape_advisory.read()` answers `not-answered` (silent) with the `.git`'s line in `why`, where 3.14 said `not-a-repository`; no new state. - `agent files` says the line with the cure: a `note` for a rendered file nothing pins, a FINDING for the pinned `AGENTS-shape.md`. The eight entries leave `KNOWN_OPEN`; the three family sites stay, slice B. New rows: tests/test_doctor_unsearchable_links.py. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…le is still a repository to the doctor's `.git` questions, and the advisory names a `.git` refused for another reason than permission (#1110) Two rows the first round of mutants showed missing: `carries_git` asked for a directory only (D8) and `_read` catching only `PermissionError` (A2) both passed the new rows. The `.git` questions now also answer for a `--separate-git-dir` checkout, whose `.git` is a file, and the advisory row runs for an I/O error as well as a permission. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he review found alive: a `.git` that loops or dangles is no repository, a pinned agent file beside an absent one is a FINDING, `naming` says origin was not read on every exit, and the advisory reads a gitdir file as a repository (#1110) The review of #1144 (r1144, READY on condition) ran 29 mutants of the new code against the 17 rows; R01, R07, R13, R14, R15, R27 and R28 survived. Eight rows and one control kill them, and all 47 mutants (the writer's 18 and the review's 29) are now killed. - `.git -> .git` (ELOOP) and `.git -> ../nowhere/.git` (ENOENT): controls that hold on main (`the way in` is the scaffold, `what is here` an ordinary directory, no `origin` asked, the advisory `not-a-repository`), and `carries_git` False (R01, and the hygiene scan's R29 as well). - A pinned `AGENTS-shape.md` it cannot read beside an absent `CLAUDE.md`: a FINDING, the verdict `INVALID (agent-files)` (R07). - `naming` with the `.git` refused, for a name that classifies, one the policy does not classify (exit 1) and a naming script that refuses (exit 2): the status is the policy's, `ok` for the first, and the clause that origin's name was not read is on every branch (R13, R14, R15). - The advisory row gets the `--separate-git-dir` control: `_read` and `repository_names` take a gitdir file for a repository (R27, R28). - Line 98 is cut to the file's 79 columns. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e split, one per condition, so SonarCloud's python:S9073 has nothing to say of the new rows (#1110) One is a new row's `not row["why"] or PERMISSION_WHY not in row["why"]`, now `PERMISSION_WHY not in (row["why"] or "")`; the other is the first round's `"AGENTS-shape.md" in pinned and "AGENTS.md" not in pinned`, now two asserts. No behaviour changes: the 25 rows and the 47 mutants give the same results. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…cannot read leaves whether the folder is a leg unknown, never no leg's file, so `the way in` offers neither command and points at `what is here`, which names it, and `the other way in` and the Triad row are silent (#1110) #1110's twelfth site, found by r1144: `shape_advisory._leg_by_agents_md` read any exception as "no leg's file" (`except Exception: return None`). Over a clone of acme/Orbit whose AGENTS.md is the spec or the code leg's, linked into a folder at 000 or itself at 000, #1144's head offered `adopt-project.py plan ... --project Orbit` in `the way in`, `setup.sh` in `the other way in`, and the Triad advice, on 3.12 and on a 3.14-like shim. - `_leg_by_agents_md` raises a refused `stat` or read (#430's rule: only ENOENT, ENOTDIR, EBADF and ELOOP, and Windows' 21, 123 and 1921, are nothing there). Bytes that are not UTF-8 still declare no leg; anything else ends in `read()`'s net. - `_read` answers `not-answered`, with `AGENTS.md: cannot be read (<reason>)` in `why` and the OSError in the new `Reading.refused`, unless the folder's name is a leg's, which still answers. The `.git` case carries `refused` too. - `leg_clone` raises `refused`; `the way in` (`way_in_unread_row`, shared with the `.git` row) offers neither command, and `the other way in` is silent. - `what is here` reads AGENTS.md through (`read_through`), so a file at 000 is named there with the cure. tests/test_doctor_unsearchable_links.py: 20 new rows (3 mode, 17 in process), and `leg_clone` over a refused `.git` now raises in #1144's row. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideData structuresAdd
ImplementationCentralize the distinction between an absent path and a refused read, and apply it consistently to path checks and leg
Carry refused
UIRead a regular
Make “the way in” offer neither command when a leg's identity is unknown because its
TestsAdd regression and control coverage for refused leg files, absent or non-regular files, UTF-8 handling, name-based identification, doctor output, and commit-based reads.
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
(lane openRepoShape-1, 2026-10-22T15:30:29Z) Independent review r1159 of #1159 at Review of PR #1159 (head 0f9aff0, base #1144's head 89486f6)Verdict: NOT READY. The code is correct as I measured it, and nothing is a MUST. Two SHOULDs are left: one PR-body edit and one missing control row in the tests. There are also five NITs. I stopped at your LAST STEP word. The mutant that was already running (M18b) finished and restored itself, and I started nothing after it. FindingsSHOULD-1: PR body, "Not in this PR", lines 181-183
SHOULD-2:
NIT-1: PR body, Tests, line 110
NIT-2:
NIT-3: PR body, Tests, lines 118 and 120-125. Base figures are stale and incomplete.
NIT-4:
NIT-5:
Observations, not findings
MutantsEach ran alone in a clone, on 3.12, over
Test counts
Every base failure is on the row's own assertion. The control rows (leg by name, Doctor-report comparison256 report files, paths normalised.
Greps and static checks
Not done (outside this review's list): a re-run of the writer's 18 mutants, and the Files
Lane: openreposhape-1 (openRepoShape-1) |
…ere` to reading through a regular `AGENTS.md` alone: a directory or a named pipe of that name is listed and not read, the pipe's doctor run under a timeout, and a refused `Makefile` is listed like any other name (#1110) The review of #1159 (r1) found two mutants of `shape-doctor.py` alive against all 45 rows: - M3, `read_through` reading any kind of file (`is_a(path, lambda _m: True)`): over a repository whose `AGENTS.md` is a directory, `what is here` said `AGENTS.md: cannot be read (Is a directory)`, and over one whose `AGENTS.md` is a named pipe the doctor never returned (SHOULD-2); - M14, `what is here` reading through every name it lists (NIT-4). New rows, each a control that holds on #1144's head as here: - `test_an_agents_md_that_is_no_regular_file_is_listed_and_not_read` [directory, named-pipe]: the doctor run as a person runs it, given `DOCTOR_SECONDS` (120) by `doctor_within`, so a run that opens the pipe fails the row instead of hanging the suite. `what is here` says `a git repository, an AGENTS.md`, nothing says `cannot be read`, and the clone is offered both ways in. The pipe row skips where `os.mkfifo` is absent, and nothing opens the pipe in the test's own process. - `test_what_is_here_reads_through_agents_md_alone`: a leg's clone whose `Makefile` read is refused, in process: `what is here` says `a git repository, an AGENTS.md, a Makefile`, and no row says `cannot be read`. `offered_both_ways_in` now reads its rows through `offers_both_ways_in`, which a whole run's rows (with `machine`) can be given too. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lder named as a leg is still that leg, as it was before #1159 and as it is where the disk's file is refused, so `adopt-project.py plan` and `check` print the leg's note over it again (#1110) The review of #1159 (r1, NIT-2) measured that a `Refusal` raised while reading a commit's `AGENTS.md` (`Tree.text`, `cat-file blob`) no longer fell back to the leg's name. #1144's head read it as no leg's file and then asked the name, so a folder named `<P>-spec` was `leg-clone`; #1159's head sent the `Refusal` straight to `read()`'s net, `not-answered`, and `adopt-project.py` stopped printing the leg's note there. `_read` now catches a `Refusal` from `_leg_by_agents_md` as it catches a refused `OSError`: the folder's name answers where it is a leg's, and with none the `Refusal` goes on to `read()`'s net, `not-answered`, as on #1159's head. This is the review's mutant M17. `refused` stays None: no `OSError` was refused. New row, a control that holds on #1144's head as here: `test_a_leg_by_its_name_is_one_while_git_cannot_give_its_agents_md`, a committed clone named `Orbit-spec` whose `cat-file blob` of `AGENTS.md` raises `Refusal`: `leg-clone`, by name, the spec leg. On #1159's head `0f9aff05` it fails with `not-answered`. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ot read keeps `origin_org` in its `--json` detail, as the leg's own row and the policy row keep it with no command, where it said None while `origin` was read (#1110) The review of #1159 (r1, NIT-5) found that the `AGENTS.md` row gave `origin_org: null` while the `naming` row of the same run read `origin`, and that the docstring's reason, "None because no command is offered with them", did not hold: the leg's own row keeps `origin_org` with no command, and so does the policy row (`way_in_policy_row`). `way_in_unread_row` now takes `org`: None for a `.git` that cannot be read, whose `origin` is not read either (unchanged), and `origin_org(root)` for the `AGENTS.md` row, read only where `carries_git` answered and `leg_clone` raised. `suggested_project` stays None, since no `--project` is offered, and the docstring says so. `no_leg_way_in` now expects `origin_org: "acme"` (ORIGIN is `acme/Orbit`), so the 7 rows that use it pin the value: with the doctor of `34ed0a95` they fail, 7 failed and 42 passed. Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts name where git cannot give its `AGENTS.md` no longer asks `Reading.refused`, which #1144's `Reading` does not have, so it is the control on #1144's head its docstring says it is (#1110) Measured with the file laid over #1144's head `89486f69`, the row's state, `by` and role checks pass there, and only `assert reading.refused is None` failed, with `'Reading' object has no attribute 'refused'`. `refused` is set only together with `not-answered`, so the line asked nothing of a `leg-clone` reading. Without it the row passes on `89486f69` and on this head, and still fails on `0f9aff05`, whose `Refusal` skips the name (`not-answered`, at the state check). Part of #1110 Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-22T20:18:30Z) Independent re-check r1159b of #1159 at Verdict: READY. f1159's round answers every finding from r1159 (SHOULD-1, SHOULD-2 and NITs 1 to 5). My measurements show no new MUST or SHOULD. Two optional NITs are below. One thing outside the PR: GitHub's Linux The live PR still matches what we expected: head The r1 findings
New problemsNone at MUST or SHOULD.
MeasurementsEverything ran on 3.12 in the probe environment, one basetemp per call, at most 2 calls at once.
Mutants I re-ran at
These all match the body's table. The body's gate rows I did not re-run: the PR body: title 245 characters; no close, fix or resolve form before an issue number; no State: my checkout FilesAll in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/r1159b:
Lane: openreposhape-1 (openRepoShape-1) |
… row names its sibling row on one line, so a grep for the row's name finds the reference (#1110) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-1, 2026-10-24T14:43:48Z) d1159, lane 1's independent delta checker (not the writer): READY carries from
The full delta checkd1159 reportPart 1 is complete: READY carries from Part 2 is partial. #1164's head has not moved since the review that names it, and I re-measured nothing on it. I stopped at lane 1's LAST STEP. No runs of mine are live. Part 1: #1159 (
|
| commit | parents | what it changes | only what its message says? |
|---|---|---|---|
66e68f948 "tests/test_doctor_unsearchable_links.py: the docstring of the control row names its sibling row on one line, so a grep for the row's name finds the reference (#1110)" |
0ca336110 (one parent, not a merge) |
One file, +4/-3. It rewraps the docstring of test_a_leg_by_its_name_is_one_while_git_cannot_give_its_agents_md, so the backticked row name sits on one line. This is r1159b's NIT-B. |
Yes for the change. See NIT-2 for the grep wording in the message. |
Commands and output:
git log --format='%h %p %s' 0ca33611..66e68f94prints that one commit.git rev-list --countgives 1 and--mergesgives 0.git diff --name-only 0ca33611 66e68f94printstests/test_doctor_unsearchable_links.pyalone.- Every other file is byte-identical. A hash of
git ls-tree -r, with that file excluded, isf8d8b6d9858e3836c015e86ffb7d380cc6fc6290at both commits. Soshape-doctor.pyandscripts/shape_advisory.pydid not change. - The AST of the old and new test file, with docstrings blanked, is equal:
AST equal with docstrings blanked: True. Exactly one docstring differs, and the old and new texts are equal after whitespace normalisation and healing the_ be_break. Only one string constant differs, and the file is one line longer (1089 to 1090). - Nothing in the repo cites a line number of the file (
grepfortest_doctor_unsearchable_links.py:[0-9]finds nothing). - No added line is non-ASCII or over 80 characters.
The delta is not a behaviour change, so the brief's "at least 3 mutants of the changed lines" does not strictly apply. I ran 4 spot-check mutants anyway, to show the rows still bite at 66e68f94.
Test runs
Run in the probe environment: stub gh, bogus token, empty GH_CONFIG_DIR, GIT_CONFIG_GLOBAL=/dev/null, GIT_SSH_COMMAND=false, env -u LANES_LANE, one basetemp per call, at most 2 calls at once. I ran df -h /workspace and df -i / and counted pytest processes before each call; the minimum free was 16G.
| call | result |
|---|---|
links file at 66e68f94, 3.12 |
49 passed (149.8 s) |
links file at 66e68f94, 3.14-like shim |
49 passed (147.6 s) |
tests/test_shape_advisory.py, tests/test_path_guard_hygiene.py, tests/test_no_auto_maintenance.py at 66e68f94 |
154 passed, 2 skipped (280 s). The collect counts are 47 + 49 + 60 = 156. |
base 89486f69 plus head's test file, 3.12 |
17 failed, 32 passed |
base 89486f69 plus head's test file, shim |
17 failed, 32 passed |
Base comparison:
- The failing set on 3.12 equals r1159b's set at
0ca33611(diffempty), and the shim's set equals the 3.12 set. - The base file has 25 rows and head has 49, so "gains 24 rows" is right.
- The shim was loaded:
shim loaded: True. On a file inside a mode-000 folder, plain 3.12is_fileraisesPermissionErrorand the shim answers False.
GitHub checks on 66e68f94: tests, tests-git-bash, package and SonarCloud all completed with success.
Mutants
Spot-check, not required by the delta. Run on 3.12 in a clone at 66e68f94. Each file was restored in a finally, and the blob matched HEAD's afterwards.
| mutant | file | rows failed | blob restored |
|---|---|---|---|
M3: read_through reads any kind of file |
shape-doctor.py |
2 failed, 47 passed (directory row and named-pipe row) | True |
N2: except Refusal disabled |
scripts/shape_advisory.py |
1 failed (..._by_its_name_is_one_while_git_cannot_give_its_agents_md), 48 passed |
True |
N5: the AGENTS.md way-in row drops origin_org |
shape-doctor.py |
7 failed, 42 passed | True |
M14: what is here reads through every name |
shape-doctor.py |
1 failed (test_what_is_here_reads_through_agents_md_alone), 48 passed |
True |
All 4 were killed, with the same counts as the body's table. After M3 no stray doctor process was left over the FIFO.
The body
- Closing form. Exactly one: line 1,
Part of #1110.closingIssuesReferencesis[]. A regex for close, fix or resolve before an issue number finds nothing. The only other#Nforms are "Filed as shape_advisory.py: a committedAGENTS.mdthat is a symlink is read, inadopt-project.py's commit reading, as the text of its target's path, so a leg cloned with itsAGENTS.mdas a link is told it is a single repository #1163" (line 210) and mentions of scripts/shape_advisory.pyrepository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385, EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430 and shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144. There is no@. - Stacking. Right. The base is
fix/1110-doctor-advisory-unsearchable-git, shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144's head is still89486f69, and shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144 is open. - Live PR. Head is
66e68f94, still a draft, 6 commits, title 245 characters. The body is byte-equal to the copy I read at the start. - Claims about the new commit. There are none. See NIT-1.
Findings
No MUST and no SHOULD.
NIT-1: the PR body does not name the delta.
- PR body lines 16, 67, 125, 151 and 189 label head, "Head
0ca33611" and the measurements "at0ca33611". The body names neither66e68f94nor a docstring-only commit ('66e68f94' in bodyis False, and so is "docstring"). - All of those measurements still hold. I re-measured the counts that matter at
66e68f94(49/49, 17/32, 47 + 49). - Fix: add one sentence, for example "
66e68f94changes one docstring intests/test_doctor_unsearchable_links.pyand nothing else (r1159b's NIT-B); every figure above carries from0ca33611."
NIT-2: the commit message overstates the grep claim.
tests/test_doctor_unsearchable_links.py:1046now reads`..._by_its_name_is_one_while_its_agents_md_cannot_be_read`). It stays abbreviated with..._.grep -n "test_a_leg_by_its_name_is_one_while_its_agents_md_cannot_be_read"finds only thedefat line 885. That is r1159b's own example string. A grep for the suffixby_its_name_is_one_while_its_agents_md_cannot_be_readfinds both lines 885 and 1046.- r1159b's fix, "break the line before the backticks", was applied as asked, so the NIT is answered as asked.
- Optional fix: spell the full
test_a_leg_by_...name in the docstring.
NIT-3 (observation): non-ASCII in the body.
- The body holds one
'(U+2019) besides the emoji. It is in the Sourcery-generated block ("doctor's"), not in lane text. - r1159b said the emoji was the only non-ASCII character. If the squash message is cut from that block, replace it.
Not covered in Part 1
- I did not re-run the
test_shape_doctorshards,test_doctor_next_is_a_command, the 7-file list, the doc trio, or the doctor-report comparison. Their inputs are unchanged: all non-test files are byte-identical, and the test file is AST-equal apart from one docstring. They carry from0ca33611. - I did not check landing mechanics. shape_advisory.py and shape-doctor.py: a leg's
AGENTS.mdthis user cannot read leaves whether it is a leg unknown, sothe way inoffers neither command and points atwhat is here, andthe other way inand the Triad row are silent (#1110) #1159 still lands after shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144, after the v1.2.0 tag, on a later word. A stacked PR after its parent's squash needs the three-merge procedure.
Part 2: #1164 (read-only checks, no test or mutant run)
- The review names the current head. issuecomment-6371395973 (brettheap, 2026-10-23T17:59:47Z) says "READY at
74096eb4". The PR head is74096eb4783384ec46b366f9d6098426a5aa9f28, the full sha the brief gives. - No commits after the named head.
gh pr viewlists 7 commits, from4352ac2fdto74096eb47. The last was committed 2026-10-23T07:41:46Z, before the review at 17:59Z. There are no GitHub reviews and no review comments. So there is no delta to check, and the READY carries by identity. - GitHub checks on
74096eb4: SonarCloud,package,testsandtests-git-bashall completed with success. - The body took r1164c's two NITs.
- Line 113 now says "PR bootstrap.py, validate-family.py and siblings.py: a root or holder whose
.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172 for bootstrap.py, and the family root's validate-family.py, bootstrap.py and siblings.py: a root whose own.gitthis user cannot read is a PermissionError traceback on 3.12, ornot-a-git-repowhereexists()answers False, not refused by name #1148, now at5aa0f116", with themerge-treequoted at74096eb47. bootstrap.py, validate-family.py and siblings.py: a root or holder whose.gitthis user cannot read is refusedgit-repo-unreadable, exit 2, not a traceback ornot-a-git-repo, and the doctor'smembersrow names it once, never drift (#1148) #1172's current head is5aa0f116d, so that matches. - Lines 84 and 90 now say "(4 at
74096eb47, with thefamily.yamlrows)".
- Line 113 now says "PR bootstrap.py, validate-family.py and siblings.py: a root or holder whose
- Closing form. Line 1 is
Part of #1110, andclosingIssuesReferencesis[]. I read the body for other close, fix or resolve forms and saw none, but I did not run a regex on it. The title is 249 characters.
Flags, not findings
- Body line 113 says "this head merges with
b75babee3cleanly". shape-doctor.py, validate-pins.py and validate-manifest.py: a root whose own.gitthis user cannot read is named.git: cannot be read (<reason>), so the doctor never reads it as DRIFTED, and both pinned validators refuse it, exit 2 (#1146) #1147's head is now569ea0565. It wasb75babeewhen r1164c measured, and I did not check when it moved. I did not rungit merge-treeagainst569ea0565. - family.py and the family root's siblings.py: a holder's
.gitmodulesthis user cannot read, as through a link into a folder it cannot search, isgitmodules-unreadable, never no section forremove's rollback or no url formake siblings(#1110) #1164's base038dafba9is 20 commits behind main (comparesaysdiverged, ahead 7, behind 20), andmergeable_statereadsunknown. I did not test a merge with the current main. - Suggested check for lane 1:
git merge-tree --write-tree --name-only 74096eb47 569ea0565, and the same against main.
Not covered in Part 2: I re-ran no tests, mutants, gate rows or pin measurements. I did not diff #1164 against anything, because there is no later commit.
State
- Review checkout
/workspace/projects/openRepoShape-worktrees/l1-d1159: detached at66e68f94,status --shortempty. - Scratch clones
mandm2are at66e68f94and clean. Clonebis at base89486f69and carries the head test file overlaid, plus an untrackedtests/test_zzbase_links.py(I run norm). Both are intentional. - No process of mine is running.
Files
All in /workspace/projects/openRepoShape-worktrees/_lane1-scratch/d1159/:
checkpoint.mdlogs/(the run logs:links-head-312.log,links-head-shim.log,links-base-312.log,links-base-shim.log,gate-adv-hyg-nam.log,mut-M3.log,mut-N2.log,mut-N5.log,mut-M14.log)probes/mut.pyshim/sitecustomize.pypenv.sh,run.shbody.md,body1164.md,pr-meta.json,pr-view.json,pr1164.json
Lane: openreposhape-1 (openRepoShape-1)
Lane: openreposhape-1 (openRepoShape-1)
…py: an `id:`, a date or a `repository:` ending in a line feed is refused by name, and a pin's `source_repository:` that is no path or `owner/repo` name is named as the pin's (#1139) (#1166) The value patterns the issue names refuse a value that ends in a line feed, by name, as #1126 (#1017) makes `COMMIT_RE`, `SHA256_RE` and `COMMIT_40` do: - `DATE_RE` and `QUALIFIED_RE` in `validate-manifest.py` and `validate-family.py`, `MEMBER_RE` in `scripts/family.py`, `DATE_RE` in `scripts/shape_advisory.py` and `REPOSITORY_RE` in `update-shape.py`; - `PROJECT_ID_RE`, read with `fullmatch` where the two validators and `family.py add` read an id from a file. A shape pin's `source_repository:` that is neither a path on this machine nor an `owner/repo` name is refused in the pin's name, not `--upstream`'s. Values without a line feed keep their outcome, and no refusal code is added or renamed. Two inputs do move from one existing code to another (see "Behaviour changed", below). **What this PR does not cover, and the title does not claim:** - the naming-policy patterns that `scripts/repo_shape.py` reads from `contracts/repository-naming.yaml`, which `validate-repository-naming.py` still reads with `.match`; - `update-upstream-not-a-repo`, which still says `--upstream` when a pin names a folder that exists and is not a repository. Both are follow-ups, with the other findings of the independent review, under "Follow-ups (not in this PR)". This does not need #1126 and does not touch its files or its test file. Measured on main `038dafba` (Python 3.12.3, git 2.43.0) by a probe of each tool, before any change. Each value is written in the file as a YAML double-quoted `"...\n"`, which the reader takes as the value and a line feed. | Read from | Value | On main | |---|---|---| | `project.yaml` (`validate-manifest.py`) | `id: "atlas\n"` | only `FINDING manifest-topic: topic is 'xf-project-atlas', expected 'xf-project-atlas\n' derived from id 'atlas\n'`, exit 1 | | `project.yaml` | `elected_on: "2026-09-02\n"` | `manifest ok`, exit 0 | | `project.yaml` | a leg's `repository: "testorg/Atlas-spec\n"` | `manifest ok`, exit 0 | | `family.yaml` (`validate-family.py`) | `id: "inkrouter\n"` | `family ok: InkRouter (inkrouter` and a line break, exit 0 | | `family.yaml` | `created_on: "2026-09-04\n"` | `family ok`, exit 0 | | `family.yaml` | a member's `repository: "InkRouter/IRRS\n"` | `FINDING member-repository-name: ... whose name is 'IRRS\n'`, exit 1 | | a member's `project.yaml`, read by `family.py add` | `id: "irqs\n"` | mounted and committed, exit 0, with the row `id: "irqs\n"`; the holder's gate then said `family ok` | | `contracts/shape-pin.yaml`, `update-shape.py check` with no `--upstream` | `source_repository: "opensoft/openRepoShape\n"` | `REFUSED unsafe-value: --upstream is 'opensoft/openRepoShape\n'`, exit 2 | | the same | `source_repository: "opensoft/open Repo"` | `REFUSED update-upstream-unresolvable: --upstream 'opensoft/open Repo' is neither ...`, exit 2 | | the same | `source_repository: "-opensoft/openRepoShape"` | `REFUSED unsafe-value: --upstream is '-opensoft/openRepoShape'`, exit 2 | | an adoption plan (`adopt-project.py check`) | `id: "northwind\n"` | `FINDING plan-manifest-invalid: id is 'northwind\n', which the project's own validate-manifest.py refuses: manifest-topic: ...`, exit 1; `execute` refused it, exit 2, before any leg | In-process on the same main: `repo_shape.PROJECT_ID_RE.match('atlas\n')`, `shape_advisory.DATE_RE.match('2026-09-02\n')` and `REPOSITORY_RE.match('a/b\n')` each matched, and `date.fromisoformat('2026-09-02\n')` raised `ValueError`. - **`DATE_RE` and `QUALIFIED_RE`** in `templates/assembly-root/scripts/validate-manifest.py` and `templates/family-root/scripts/validate-family.py` end in `\Z`. So do `MEMBER_RE` in `scripts/family.py` (the third spelling `tests/test_family_member_name.py` holds equal to the two `QUALIFIED_RE`), `DATE_RE` in `scripts/shape_advisory.py` and `REPOSITORY_RE` in `update-shape.py`. - **`PROJECT_ID_RE` keeps its `$`.** Every id refusal prints its text (`must match ^[a-z0-9][a-z0-9-]*$`, pinned in six test files), the validators run against each project's own copy of `repo_shape.py`, and its definition sits inside #1126's hunk. The three callers that read an id from a file ask it with `fullmatch` instead: `validate-manifest.py` (`manifest-id`), `validate-family.py` (`family-id`) and `family.py`'s reading of a member's `project.yaml` (`member-bad-id`, which `add` now says before anything is mounted). - **`update-shape.py`.** `open_upstream(spec, pin_path=None)` takes the pin the value was read from, which `prepare` passes when no `--upstream` was given. Then `update-upstream-unresolvable` says ``<root>/contracts/shape-pin.yaml: `source_repository:` is '...', which is neither a path on this machine nor an `owner/repo` name``, with a remedy to write the pin's field or pass `--upstream`, and `checked_value` names the same field. **Code kept:** `update-upstream-unresolvable`, the existing code for a value that is not an `owner/repo` name. A line feed now reaches it, since the pattern no longer takes one. Before, `checked_value` refused it as `unsafe-value`. - **One existing test line:** `tests/test_family_init_values.py` finds validate-family's `DATE_RE` line by its text, which now ends `\Z`. - **One existing stand-in:** the `temporaries` fixture in `tests/test_update_shape_unreadable.py` stands in for `open_upstream` with `def opening(spec)`, which the new two-argument call refused with `TypeError: ... takes 1 positional argument but 2 were given`. It now takes `pin_path=None` and hands it on to the real `open_upstream`, so `test_a_read_refused_in_prepare_closes_the_upstream[check|apply]` asks what it asked before. - **The rows** are in a new file of their own, `tests/test_value_pattern_line_break.py`. - **A second guard, in a second commit (`5102b4294`, a test only, no production file).** The first guard holds the four pattern names this PR ends in `\Z`, and nothing held `PROJECT_ID_RE`, which keeps its `$`. `test_every_reader_of_the_id_pattern_is_in_the_table` reads every tracked `.py` outside `tests/` with `ast` (it runs none) and holds each call of a method of `PROJECT_ID_RE` (`PROJECT_ID_RE.x(...)` or `repo_shape.PROJECT_ID_RE.x(...)`) to the table `ID_READERS`: - the three `fullmatch` readers of an id from a file (`validate-manifest.py`, `validate-family.py`, `scripts/family.py`); - the modules that still `match` it, each with its reason: argv `--id` in `scaffold-project.py` and `scripts/family.py`, where `checked_value` refuses a line feed after the pattern; argv `--id` in `adopt-project.py`, which the project's own `validate-manifest.py` refuses (`adopt-manifest-invalid`, below; `ID_READERS` still gives it the `checked_value` reason); and `adopt-project.py`'s two `plan-bad-id` readers, of the plan's `id:` and of the id derived from `legs.assembly`, which a line feed never passes (below). A new reader of `PROJECT_ID_RE`, `fullmatch` included, fails it by file and method until it is listed in `ID_READERS` with its reason. The table is exact both ways, so a reader taken away fails it too. **Left as they are, and why:** - the argv readers of `PROJECT_ID_RE` (`--id` in `scaffold-project.py`, `family.py init` and `adopt-project.py`). The first two hold the id to `checked_value` after the pattern, which refuses a line feed as `unsafe-value` naming `--id`, and `tests/test_derived_id_refusals.py` pins that answer for them. `adopt-project.py` never calls `checked_value` on `--id`: `plan` with `--id` set to `northwind` and a line feed exits 2 with `adopt-manifest-invalid`, in the words of the project's own `validate-manifest.py` (`manifest-id`), and writes no plan (lane 5's re-check, issuecomment-6371408915, NIT 2; measured on `plan` only); - `adopt-project.py`'s reading of a plan's `id:`: `check` and `execute` hold it to the project's own `validate-manifest.py`, which refused it on main (above) and now names it in `manifest-id`'s words. It is the same value, refused under the same code, `plan-manifest-invalid`, before any leg exists. The id derived from `legs.assembly` is read with `.match` too, and a line feed in `legs.assembly` is `unsafe-value` before that id is asked (the review measured both, on main and here alike). This keeps the open PRs on `adopt-project.py` (24 at 12:02Z) clear; - `PIN_ARG_RE`, and `NAME_RE`/`REF_RE`/`PATH_RE` in `setup-project.py`, as the issue says; - `update-upstream-not-a-repo` still says `--upstream`. A pin's value reaches it only when it names a path that exists, not through the pattern. **Behaviour changed, stated plainly, and what the issue's Verify means by "No refusal code changes":** the issue's Verify line reads as no code added or renamed, and none is. Two inputs do move from one existing code to another, exit 2 both before and after: - `update-shape.py --upstream "opensoft/openRepoShape<LF>"` was `unsafe-value` and is now `update-upstream-unresolvable`, still naming `--upstream`; - a pin's `source_repository:` that ends in a line feed, with no `--upstream`, was `unsafe-value` naming `--upstream` and is now `update-upstream-unresolvable` naming the pin's field. No test pinned either old answer: every gate below passed. `family.py add --member` is stripped before `MEMBER_RE`, so `add` answers as before. **Not changed, seen while measuring:** with `--upstream` passed, `update-shape.py` does not read the pin's `source_repository:` through `REPOSITORY_RE` at all, and it keeps the value as the upstream's repository name. `validate-family.py` does not hold a member row's `id:` to `PROJECT_ID_RE`; it compares the row with the member's manifest. Neither is a `$` question. Both are under "Follow-ups (not in this PR)". All in `tests/test_value_pattern_line_break.py`, run against main's production code: the five production files swapped in place from `origin/main` (`961640c68`), then put back with their sha256 matching (`dirty=5` in the run's START line is those five files). On the head `5102b4294` (30 rows) the result is **20 failed, 10 passed in 23.12s, git 2.43.0**. The 10 that pass are baselines: the seven `test_a_pattern_takes_its_value` rows, the spelling-table guard and the two double-quoted-without-a-line-feed rows. The first 19 are the first commit's rows (19 failed, 10 passed in 22.12s on its 29 rows at `038dafba`). The independent review reproduced them from main's bytes of the same five files (19 failed, 10 passed in 26.78s, the same 19 ids). The 20th, `test_every_reader_of_the_id_pattern_is_in_the_table`, is the second commit's: it fails on main because the three readers of an id from a file are `.match` there, not `.fullmatch`. - `test_a_pattern_refuses_its_value_and_a_line_feed[manifest-DATE_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[manifest-QUALIFIED_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[family-DATE_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[family-QUALIFIED_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[family.py-MEMBER_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[shape_advisory-DATE_RE]` - `test_a_pattern_refuses_its_value_and_a_line_feed[update-shape-REPOSITORY_RE]` - `test_a_manifest_value_that_ends_in_a_line_break_is_a_finding[id]` - `test_a_manifest_value_that_ends_in_a_line_break_is_a_finding[elected_on]` - `test_a_manifest_value_that_ends_in_a_line_break_is_a_finding[leg-repository]` - `test_adopt_check_names_a_plan_id_that_ends_in_a_line_break_as_an_id` - `test_a_family_value_that_ends_in_a_line_break_is_a_finding[id]` - `test_a_family_value_that_ends_in_a_line_break_is_a_finding[created_on]` - `test_a_family_value_that_ends_in_a_line_break_is_a_finding[member-repository]` - `test_add_refuses_a_member_whose_id_ends_in_a_line_break` - `test_a_pins_source_that_is_no_owner_repo_is_named_as_the_pins[line-feed]` - `test_a_pins_source_that_is_no_owner_repo_is_named_as_the_pins[space]` - `test_a_pins_source_checked_value_refuses_is_named_as_the_pins` - `test_an_upstream_passed_with_a_line_feed_is_named_as_passed` - `test_every_reader_of_the_id_pattern_is_in_the_table` **13 of 13 killed.** Each mutant was applied alone to one file of the branch's production bytes (the same bytes as the first commit and as the head, which adds a test only), the rows were run, and the file was put back with its sha256 checked (`restored=yes` for all 13). Each run is `tests/test_value_pattern_line_break.py` plus `tests/test_family_member_name.py::test_every_spelling_of_the_member_name_rule_is_the_same_pattern`, git 2.43.0. | Mutant | File | Killed by | |---|---|---| | M1 `DATE_RE` back to `$` | `validate-manifest.py` | 2: `test_a_pattern_refuses_its_value_and_a_line_feed[manifest-DATE_RE]`, `test_a_manifest_value_that_ends_in_a_line_break_is_a_finding[elected_on]` | | M2 `QUALIFIED_RE` back to `$` | `validate-manifest.py` | 3: `...[manifest-QUALIFIED_RE]`, `...[leg-repository]`, `test_every_spelling_of_the_member_name_rule_is_the_same_pattern` | | M3 the id read with `.match` | `validate-manifest.py` | 2: `test_a_manifest_value_that_ends_in_a_line_break_is_a_finding[id]`, `test_adopt_check_names_a_plan_id_that_ends_in_a_line_break_as_an_id` | | M4 `DATE_RE` back to `$` | `validate-family.py` | 2: `...[family-DATE_RE]`, `test_a_family_value_that_ends_in_a_line_break_is_a_finding[created_on]` | | M5 `QUALIFIED_RE` back to `$` | `validate-family.py` | 3: `...[family-QUALIFIED_RE]`, `...[member-repository]`, `test_every_spelling_of_the_member_name_rule_is_the_same_pattern` | | M6 the id read with `.match` | `validate-family.py` | 1: `test_a_family_value_that_ends_in_a_line_break_is_a_finding[id]` | | M7 `MEMBER_RE` back to `$` | `scripts/family.py` | 2: `...[family.py-MEMBER_RE]`, `test_every_spelling_of_the_member_name_rule_is_the_same_pattern` | | M8 a member's id read with `.match` | `scripts/family.py` | 1: `test_add_refuses_a_member_whose_id_ends_in_a_line_break` | | M9 `DATE_RE` back to `$` | `scripts/shape_advisory.py` | 1: `...[shape_advisory-DATE_RE]` (its pattern row alone: `date.fromisoformat` refuses the line feed after the pattern, so no tool row sees it) | | M10 `REPOSITORY_RE` back to `$` | `update-shape.py` | 3: `...[update-shape-REPOSITORY_RE]`, `test_a_pins_source_that_is_no_owner_repo_is_named_as_the_pins[line-feed]`, `test_an_upstream_passed_with_a_line_feed_is_named_as_passed` | | M11 `prepare` passes no pin | `update-shape.py` | 3: `test_a_pins_source_that_is_no_owner_repo_is_named_as_the_pins[line-feed]` and `[space]`, `test_a_pins_source_checked_value_refuses_is_named_as_the_pins` | | M12 `checked_value` names `--upstream` again | `update-shape.py` | 1: `test_a_pins_source_checked_value_refuses_is_named_as_the_pins` | | M13 the pin's refusal sentence removed | `update-shape.py` | 2: `test_a_pins_source_that_is_no_owner_repo_is_named_as_the_pins[line-feed]` and `[space]` | No mutant is added for the stand-in in `tests/test_update_shape_unreadable.py`: it is a test change. The independent review re-ran M3, M8, M11 and M12, and one mutant of its own (X1: `prepare` names the pin even when `--upstream` is passed). All five were killed, and each file was put back with its sha256 checked. The other nine of the 13 are this writer's runs alone. **The second commit's guard: 6 of 6 probes caught.** Each was applied alone to one tracked file at `5102b4294`, `test_every_reader_of_the_id_pattern_is_in_the_table` was run, and the file was put back with its sha256 checked (`restored=yes` for all six). The first commit's guard, `test_every_spelling_of_a_value_pattern_is_in_the_table`, was run with each plant too: it passes every one of them, which is the gap the second guard closes. | Probe | File | The new guard | The first guard | |---|---|---|---| | P1 a new `PROJECT_ID_RE.match(value)` reader appended | `update-shape.py` | failed: `('update-shape.py', 'match'): 1` is more than the table holds | passed | | P2 a new `repo_shape.PROJECT_ID_RE.search(value)` reader appended | `scripts/shape_advisory.py` | failed | passed | | P3 `fullmatch` back to `match` | `templates/assembly-root/scripts/validate-manifest.py` | failed | passed | | P4 `fullmatch` back to `match` | `templates/family-root/scripts/validate-family.py` | failed | passed | | P5 `fullmatch` back to `match` | `scripts/family.py` | failed | passed | | P6 main's bytes of those three files together (the 20th row above) | the three | failed | not run | **The second commit `5102b4294` (dirty=0)**, Python 3.12.3, with the same stub `gh`, bogus token and no global git config: | Run | Git | Result | |---|---|---| | `tests/test_value_pattern_line_break.py` | 2.43.0 | **30 passed** in 23.00s | | `tests/test_value_pattern_line_break.py` | 2.47.1 | **30 passed** in 23.30s | | `tests/test_pinned_copy_text.py`, `tests/test_path_guard_hygiene.py`, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py` | 2.43.0 | **306 passed** in 61.54s | That commit changes only the test file (62 lines added, no assert that joins conditions with `and` or `or`, ASCII), so the first commit's runs below are not repeated: they hold the production files and every other test file, and those are unchanged. CI on `5102b4294`, and an independent look at the second commit, were not done when this was written. **The independent review of the first commit** (rv1166c, READY, read-only) reports these gates on `2593952c9`, all passing: the stand-in gate's 6 files (298 passed), the new file on 2.47.1 (29 passed), the five hygiene files (306 passed), 15 more test files that name a changed function, pattern or code (1611 passed, 19 skipped) and `tests/test_shape_doctor.py` (445 passed, 3 skipped). It also read CI on that head at about 20:04Z: `tests` (the full suite on 3.14), `tests-git-bash`, `package` and SonarCloud all passed. **The first commit's gates ran on `2593952c` (dirty=0)**, Python 3.12.3, with the real `gh` unreachable (a stub first on `PATH`, a bogus token) and no global git config: | Run | Git | Result | |---|---|---| | the files that held every failure and setup error of the shards below: `tests/test_family.py`, `tests/test_update_shape_pr_base.py`, `tests/test_update_shape_unreadable.py`, `tests/test_update_shape_header_comments.py`, `tests/test_update_shape_messages.py` and the new `tests/test_value_pattern_line_break.py` | 2.43.0 | **298 passed** in 343.60s | | `tests/test_value_pattern_line_break.py` | 2.47.1 | **29 passed** in 21.31s | | `tests/test_pinned_copy_text.py`, `tests/test_path_guard_hygiene.py`, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py` | 2.43.0 | **306 passed** in 69.12s | **The two shards, on the uncommitted tree** holding the same production bytes as the first commit (the 7 files' sha256 checked before and after), git 2.43.0: the validators' test files, the `REPOSITORY_RE`/`source_repository` files and the `update-shape.py`, `shape_advisory.py` and `family.py` files, 125 in all. - shard 1 (62 files): 4 failed, 5290 passed, 13 skipped in 56 min; - shard 2 (63 files): 3 failed, 4 setup errors, 3761 passed, 29 skipped in 50 min. Every failure and setup error is in the five existing files of the first row of the table above, which all pass on the first commit: - 2 were real: `test_a_read_refused_in_prepare_closes_the_upstream[check|apply]`, the `opening(spec)` stand-in under the two-argument call. The stand-in is fixed in the first commit. - The other 5 failures and the 4 setup errors were `update-conflict` on `scripts/validate-family.py` ("not a verbatim copy ... at the pinned commit"). This is an artifact of an uncommitted tree: `tests/test_family.py`'s `upstream_clone` commits the working tree into its clone, while `family.py init` pins the holder at the real `HEAD`, so an uncommitted `templates/family-root/` file is never verbatim at its pin. On the first commit they pass. Every other file of the two shards passed on these production bytes, so the shards were not re-run. `git merge-tree --write-tree <other> 5102b42` (git 2.43.0) at 2026-10-23T12:08Z, after re-fetching main (still `961640c6`; read again at 12:10Z, just before the push) and re-listing the open PRs at 12:02Z (55 open). Main has moved from the `038dafba` that this body measured to `961640c6` by one file, `docs/release-notes/v1.2.0.md`. Each open PR that touches one of this PR's 8 paths, at its current head, plus #1126: | Against | Head | Shares | Result | |---|---|---|---| | origin/main | `961640c6` | | clean, tree `4613729516ff` | | #1098 | `dad7961a` | `scripts/family.py` | clean, tree `4d1ff9517588` | | #1117 | `10dc011c` | `scripts/family.py` | `scripts/family.py` merges clean; conflict in `docs/handbook.html` only, which this PR does not touch (see below) | | #1124 | `8dfa32bd` | `validate-family.py` | clean, tree `cb2ec1073646` | | #1126 | `4c1f80cf` | none (its `scripts/repo_shape.py` and tests) | clean, tree `e82c8945ac37` | | #1133 | `a7bfee1b` | `update-shape.py` | clean, tree `a710870d1ffc` | | #1144 | `89486f69` | `scripts/shape_advisory.py` | clean, tree `2d3e3e1ef43d` | | #1147 | `b75babee` | `validate-manifest.py` | clean, tree `a82ac3b47a3f` | | #1159 | `66e68f94` | `scripts/shape_advisory.py` | clean, tree `4abeb7b38665` | | #1164 | `74096eb4` | `scripts/family.py` | clean, tree `ee285e1cb8cf` | | #1172 | `5aa0f116` | `validate-family.py` | clean, tree `aa32d41e6e2c` | | #1187 | `cc8a87ee` | `update-shape.py`, `tests/test_update_shape_unreadable.py` | clean, tree `25711eb1cac4` | | #1201 | `eac177af` | `scripts/family.py` | `scripts/family.py` merges clean; conflict in `docs/handbook.html` only (see below) | - #1117 and #1201 conflict in `docs/handbook.html` alone. #1201 is stacked on #1117 (their merge-base is #1117's head `10dc011c`). Each of them against origin/main alone gives the same conflict in the same file, so it is theirs: #1117's, from its older base `6da757cb`, which #1201 inherits. - #1187 and #1201 are new since the first merge-trees. #1187 shares `tests/test_update_shape_unreadable.py` with this PR; its diff adds or removes no line that names `open_upstream`, `def opening` or `REPOSITORY_RE`, so it brings no one-argument stand-in for the two-argument call. That was read for all 55 open heads at 12:12Z; only this PR's own lines name them. - No other open PR adds or removes a call of a method of `PROJECT_ID_RE`, which the second guard counts: the lines each of the 55 open heads adds or removes outside `tests/`, `docs/` and `*.md` were read at 12:04Z. Only this PR's own name it, and #1201's one new `MEMBER_RE.fullmatch` is another pattern. So a sibling landing first does not move the guard's table. Each is a candidate for its own issue. They come from the independent review (rv1166c, READY at `2593952c9`, issuecomment-6352173084), which measured each of them on main and at the head unless the item says it did not. The review's finding numbers are in brackets. - **The naming-policy patterns [1].** `scripts/repo_shape.py` reads the families, the roles and the topic pattern of `contracts/repository-naming.yaml` with `.match` (`family["_re"].match(name)`, `family["_split_re"].match(name)`, `role["_re"].match(name)`, and `topic_pattern`, which defaults to `^xf-project-[a-z0-9-]+$`). `validate-repository-naming.py --project project.yaml` runs in the root template's `Makefile` and `validate.yml`. At the head, and on main, it exits 0 on a leg `repository: "testorg/Atlas\n"` and prints the line feed inside its table; `-spec` and `-code` the same. The project's gate as a whole still fails, because `validate-manifest.py` now refuses that manifest. - **`update-upstream-not-a-repo` with a pin [3].** With no `--upstream`, a pin whose `source_repository:` names a folder that exists and is no repository (the review measured `contracts` and `scripts/repo_shape.py`) is `REFUSED update-upstream-not-a-repo: --upstream contracts exists but is not a git repository`, exit 2, on main and here. `pin_path` is in scope at `update-shape.py:686-687`, so that refusal can name the pin as `update-upstream-unresolvable` now does. - **A pin's line feed when `--upstream` is passed [9].** `update-shape.py check --upstream <clone>` over a pin whose `source_repository:` ends in a line feed exits 0, `nothing to do`. The value is kept as `upstream.repository`, which reaches the README shape line's check, `apply`'s commit subject and the pull request words (`update-shape.py:2525`, `3017` and `5121`). The review measured `check`, not `apply`. It belongs with the previous item. - **A member row's `id:` [4].** On main, `add` of a member whose `project.yaml` reads `id: "irqs\n"` commits the row `id: "irqs\n"`, and the holder's `validate-family.py` then says `family ok`. Under mutant M8 (this branch with main's `.match` in `add`) the same row is written, and the gate still says `family ok`. By code reading, not measured end to end: `validate-family.py` compares a row's id with the member manifest's by `str` equality (`member-id-mismatch`, `validate-family.py:1608`), and `_check_member_ids_unique` does the same, so neither asks `PROJECT_ID_RE` of a row. This PR makes `add` refuse such a member, so no new holder gets such a row. - **Values that no pattern reads [8].** The review's sweep found these `manifest ok` or `family ok`, exit 0, with a trailing line feed, at the head and on main alike: - in `project.yaml`: `name`, a spec or code leg's `path`, `shape.repository` and `shape.digests.digest_algorithm`; - in `family.yaml`: `org`, the family's own `repository`, `tracking_branch`, and the shape's and each member pin's `digest_algorithm`. No `^...$` pattern reads them, so they are not this PR's. A leg's `path` is checked only by `_leg_path_findings`, which asks no pattern, and the family's `tracking_branch` is refused in `project.yaml` (`manifest-tracking-branch`, U+000A) but not in `family.yaml`. `shape.commit` and `tree_sha256` with a line feed are also `manifest ok`; that is #1126's. The first commit's message says "the three readers of an id from a file ask it with `fullmatch` (`manifest-id`, `family-id`, `member-bad-id`)". That is true of those three, but it reads as if there were no others [2]. What the review measured instead: - `adopt-project.py` still reads the id derived from `legs.assembly` (`:6780`) and the plan's `id:` (`:6791`), both `plan-bad-id`, with `.match`; - neither lets a line feed through. A plan `id: "northwind\n"` is `plan-manifest-invalid`, now in `manifest-id`'s words. A plan with no `id:` and `legs.assembly: "NorthWind\n"` is `REFUSED unsafe-value: legs.assembly is 'NorthWind\n'`, exit 2, before the derived id is asked, on main and here alike. So it is the wording alone that overstates. The second commit's `ID_READERS` lists both readers with that reason. The title's pin clause is the narrower one (the pin's name only where the value is no path and no `owner/repo` name), and two inputs change from `unsafe-value` to `update-upstream-unresolvable` (see "Behaviour changed", above). Review: rv1166 and rv1166c, lane 5's independent opus reviewers, at `2593952c` (issuecomment-6352173084); then rc1166b, lane 5's independent sonnet re-checker, which wrote none of #1166: READY at `5102b429`, H0 of this landing (issuecomment-6371408915), with no MUST or SHOULD; its NITs 1 and 2 are corrected in the body. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 5102b42 passed tests, tests-git-bash and SonarCloud; the head 5c8afef is a clean update-branch merge of main 93d2931, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 3c89065 over main 11560eb: the family_init_values, update_shape_unreadable, value_pattern_line_break, repo_hygiene, agents_md_codes, cli_reference sets, 365 passed, 19 skipped. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green". Closes #1139 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b1f45fa is #1144's squash on main. Its own files (scripts/shape_advisory.py, shape-doctor.py, tests/test_doctor_unsearchable_links.py and tests/test_path_guard_hygiene.py) take the branch's side: S's tree is #1144's final head 5b38c3a's tree, and merge 1 already brought that in. The merged tree equals HEAD's tree, c5d0bc7. This is the second of the three merges that restack #1159 after #1144 landed. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…` committed as a symlink, each failing on #1159's head `5370b018`: to its own file, beside it or through a linked folder, at 000 in or beside the repository, and out of the commit; with controls for a link to nothing, a regular leg and a single repository (#1163) Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a committed link through `Tree.text_through`, and `Tree.text` keeps its one argument, so #1159's row that stands in for `Tree.text` with a refusal reads `Refusal` again, not `TypeError` (#1163) `text(rel, follow=True)` made `_leg_by_agents_md` pass a keyword that the stand-in in `tests/test_doctor_unsearchable_links.py`'s `test_a_failure_the_leg_reader_does_not_expect_is_silence_not_advice` (`def text(self, rel)`) does not take, so `read()`'s net named a `TypeError` where the row asks for `Refusal`. The decision is unchanged. 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 6 days and 20 hours by commenting |
9e4aab8 is #1159's final head: update-branch's merge of main 1212295 into 5370b01, the commit this branch was built on. This is the first of the three merges that restack #1163 after #1159 landed as 46fe6ac. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46fe6ac is #1159's squash on main. Its own files (scripts/shape_advisory.py, shape-doctor.py and tests/test_doctor_unsearchable_links.py) take the branch's side: S's tree is #1159's final head 9e4aab8's tree, 863f70c, and merge 1 already brought that in. The merged tree equals HEAD's tree. This is the second of the three merges that restack #1163 after #1159 landed. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Part of #1110: its twelfth site,
scripts/shape_advisory.py_leg_by_agents_md.Was stacked on #1144, which squash-landed on main as
b1f45fa7on 2026-11-08. GitHub retargeted this PR tomain, and the branch is restacked on main by three merges (see## Restacked after #1144 landed).Outside v1.2.0. Brett Heap ruled at 2026-10-21T10:32:04Z: "leave the follow-ups out of v1.2.0". This PR lands after the v1.2.0 tag, on a later word.
66e68f94changes one docstring intests/test_doctor_unsearchable_links.pyand nothing else (r1159b's NIT-B); every figure measured at0ca33611carries.Restacked after #1144 landed
#1144 squash-landed on main as
b1f45fa7. This branch is restacked on it by three merges, with no rebase and no force-push:5b38c3a3, update-branch's merge of mainbd3e9c14into89486f699d93487d66e68f94,5b38c3a3c5d0bc70b1f45fa7, ours for its four filesab31e2e59d93487d,b1f45fa7c5d0bc70, merge 1's0d5704b3(#1169, #1223, #1166, #1133, #1104)5370b018ab31e2e5,0d5704b3fd53e4c0b1f45fa7's own files arescripts/shape_advisory.py,shape-doctor.py,tests/test_doctor_unsearchable_links.pyandtests/test_path_guard_hygiene.py, and its tree is5b38c3a3's, which merge 1 brought in. They take the branch's side, and the merged tree is9d93487d's.scripts/shape_advisory.py: validate-manifest.py, validate-family.py, family.py and update-shape.py: anid:, a date or arepository:ending in a line feed is refused by name, and a pin'ssource_repository:that is no path orowner/reponame is named as the pin's (#1139) #1166 ends itsDATE_REin\Z, away from this PR's lines.+and-lines ofgit diff -U0 89486f69 66e68f94(the reviewed delta) and ofgit diff -U0 0d5704b3 5370b018are the same 707 lines, in the same order and the same three files (674 added, 33 removed). Both have sha2560a7cfc864f721b1f862b807e59cdde700b14e257c5e782c5f0aca5707036fcdf.The gate at
5370b018(3.12, the probe environment, one basetemp per call):tests/test_doctor_unsearchable_links.py,tests/test_shape_advisory.pytests/test_path_guard_hygiene.py,tests/test_no_auto_maintenance.py,tests/test_scaffold_leg_path_collisions.pytests/test_agents_md_codes.py,tests/test_cli_reference.py,tests/test_repo_hygiene.pyThe 2 skips are
tests/test_no_auto_maintenance.py's, at line 222: "the session began with GIT_CONFIG_GLOBAL set, and its file includes that one". The probe environment setsGIT_CONFIG_GLOBAL=/dev/null.What
On #1144's head,
_leg_by_agents_mdread any exception as "no leg's file" (except Exception: return None). The fixtures are clones ofacme/OrbitwhoseAGENTS.mdis the spec leg's or the code leg's file, rendered from its template, in two forms:link: the working-tree file is a link into a folder at mode 000;mode: the file itself is at mode 000.Over either form, the doctor offered
adopt-project.py plan ... --project Orbitinthe way inandsetup.shinthe other way in, and its Triad advisory said "This is a single repository". This held on 3.12 and on a 3.14-like shim, wherePath.is_file,existsand the rest answer False to anyOSError.Base
89486f69against head0ca33611:shape-doctor.py --root <fixture>/Atlas, 32 runs, made at0f9aff05. Re-run at0ca33611, the 16 head reports are byte-identical to0f9aff05's, paths normalised. In--json, the locked fixtures'the way inrow now keepsorigin_org: "acme"where0f9aff05gavenull(4 of 4: spec and code,linkandmode), and nothing else differs.link, lockedwhat is herenamesAGENTS.md: cannot be read (Permission denied).the way ingives NEXTadopt-project.py plan,the other way ingives NEXTsetup.sh, and anadvisory Triadrow says "This is a single repository". Exit 2.what is herenames it, with the cure.the way ingives the reason below and no NEXT. There is nothe other way inrow and no Triad row. Exit 2.mode, lockedwhat is heresaysa git repository, an AGENTS.md. The adopt, scaffold and Triad rows are as above. Exit 2.link, locked.linkandmode)The new
the way inreason:its AGENTS.md cannot be read, so it is not known whether this is a clone of a Triad's leg, which is not adopted or scaffolded on its own, and neither way in is offered until it can be:what is heresays how.At
0f9aff05, every report on 3.12 is byte-identical to its report under the shim (32 of 32). With paths normalised, the code leg's locked reports equal the spec leg's (16 of 16). The readable ones differ only in the leg's name.In process (
leg_cloneandshape_advisory.read(), 3.12 and shim):AGENTS.mdread()isadviseandleg_cloneis Falseread()isnot-answered, withwhyAGENTS.md: cannot be read (Permission denied), so whether this is a leg clone is not known here, and the advisory says nothing, andleg_cloneraisesPermissionErrorleg-clone, Trueleg-clone, Trueadopt-project.py planandcheck: 64 runs at0f9aff05, equal on base and head, 3.12 and shim. Where git cannot give a commit'sAGENTS.md, a folder named as a leg still gets the leg's note, as on base; with no such name,planandcheckprint no advisory, where base printed the advice. No fixture meets this. Adopt reads the commit'sAGENTS.md, never the disk's, so a locked working-tree file changes nothing there.adopt-project.pyis not changed.The whole doctor report: spec leg,
link, locked, 3.12<standard>is the checkout and<fixture>the fixture's folder.Base
89486f69:Head
0ca33611(and0f9aff05, byte for byte):For
mode, locked, base differs only inwhat is here, which saysa git repository, an AGENTS.mdwith no NEXT or WHY. Head's report is the same as forlink.Why
_leg_by_agents_mdfollows EveryPath.is_file()/is_dir()/exists()guard in the shipped tools answers False to any OSError on Python 3.14 (3.12 and 3.13 raise PermissionError), so a directory this user cannot search reads as ABSENT: validate-pins.py skips its workflow@<sha>check, update-shape.py sayscopy-missingandupdate-pin-missing("adopt the project") for files that are there, the doctor says DRIFTED (5 copy-missing), and bump-leg.py's remaining guards, and every other tool's, skip or misreport instead of refusing by name #430's rule: anOSErrorwhose errno says nothing is there answers None, as before. Those errnos are ENOENT, ENOTDIR, EBADF and ELOOP, and Windows' 21, 123 and 1921. Any otherOSErroris raised.repository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385's rule against salvaging text that is not UTF-8; see Known limits.Refusal) ends inread()'s net,not-answered.AGENTS.md,_readturns to_leg_by_namefirst, so a folder named as a leg is stillleg-clone. Without a name, the answer isnot-answered, and the refusedOSErroris kept in the newReading.refused.AGENTS.md._readcatches theRefusalofcat-file blob(inadopt-project.py's reading of a commit) as it catches a refusedOSError. A folder named as a leg isleg-clone, as on base, soplanandcheckprint the leg's note. With no such name, theRefusalgoes on toread()'s net,not-answered.refusedstays None, since noOSErrorwas refused..gitnot-answeredcarriesrefusedtoo.refusedis not inas_dict, soas_dictkeeps its keys.leg_cloneraisesreading.refused.check_the_way_inasksleg_cloneright aftercarries_git. On anOSErrorit givesway_in_unread_row("AGENTS.md", ...), a helper shared with the.gitrow, whose words are unchanged.--json, theAGENTS.mdrow keepsorigin_org, as the leg's own row and the policy row keep it with no command. The.gitrow's stays None, since itsoriginis not read.suggested_projectis None in both, since no--projectis offered.check_the_other_way_inis silent.what is herereads the file through. The newread_throughreads a regularAGENTS.md, so a file at mode 000, whosestatsucceeds, is named there with the cure. It does not raise a read that finds nothing.AGENTS.mdis listed and not read. A read of the directory is refused (Is a directory), and the open of a pipe nobody writes to blocks the doctor for ever.AGENTS.mdalone. Every other name is asked, as before._nothing_there(exc)is now shared with_is_a;_cannot_be_read(name, exc)spells bothwhylines from thestrerror, or the exception's type name where there is none.Tests
tests/test_doctor_unsearchable_links.pygains 24 rows, for 49 in all:doctor_within), over anAGENTS.mdthat is a directory and one that is a named pipe. A run that opens the pipe fails the row instead of hanging the suite. The pipe row skips whereos.mkfifois absent, and nothing opens the pipe in the test's own process;Two of #1144's rows change:
test_the_doctor_reads_the_advisory_as_silentnow expectsPermissionErrorfromleg_cloneover a refused.git, where it said False;test_the_advisory_names_a_git_it_cannot_reach_and_says_nothingalso checkstype(reading.refused) is type(error).89486f69(new file laid over)0ca33611Every base failure is on the row's own assertion:
way-in-scaffoldortriad-advisoryrow (7);DID NOT RAISE(4);'Reading' object has no attribute 'refused'(3);'advise' == 'not-answered'(2);what is heresayingan AGENTS.mdfor a file at mode 000 where there is no repository,'an AGENTS.md' == 'AGENTS.md: cannot be read (Permission denied)'(1).The new controls pass on base:
AGENTS.mdis refused and where git cannot give the commit's;AGENTS.mdthat is not there (dangling, gone before it is read, loop);AGENTS.mdthat is a directory or a named pipe: listed, not read;Makefilewhose read is refused: listed like any other name;Two of this round's changes are pinned by rows that fail without them:
AGENTS.mdfails on0f9aff05(not-answered);origin_orgback to None, the 7 rows that useno_leg_way_infail (7 failed, 42 passed).Mutants: 31, 28 killed. The 3 survivors are those the review accepted as equivalent. Each ran alone, in a clone, over
tests/test_doctor_unsearchable_links.pyon 3.12, and the file's bytes matched its HEAD blob after each one. N2, and M3 over its named-pipe row, ran at0ca33611. The rest ran ate356013b, which differs from0ca33611only by one assertion in N2's row, a row no other mutant fails.scripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyscripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyshape-doctor.pyscripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyscripts/shape_advisory.pyUnicodeDecodeErroris the onlyValueErrortext()can raiseshape-doctor.pyleg_cloneraises a.gitit cannot read ascarries_gitdoes, and is False where there is noneshape-doctor.pyscripts/shape_advisory.pyscripts/shape_advisory.py0f9aff05's_read)scripts/shape_advisory.pyshape-doctor.pyshape-doctor.pyA1-A10 and D1-D8 are the writer's first 18. M3-M19 are the review's (r1), and N2-N5b are this round's. M3 fails both of its rows: the directory row on its assertion, and the named-pipe row on its timeout,
Failed: the doctor did not end within 120 seconds over .../Atlas: something opened its AGENTS.md, after 126 s. The blocked doctor is killed, and no doctor process is left. A9 is caught only by #1144's two rows, which this change extends: no brand-new row catches it.The gate, call by call (3.12 unless named, the probe environment, one basetemp per call, at
0ca33611):tests/test_doctor_unsearchable_links.pytests/test_doctor_unsearchable_links.py, shimtests/test_shape_advisory.py,tests/test_path_guard_hygiene.pytests/test_shape_doctor.py, shard 1 of 4tests/test_shape_doctor.py, shard 2 of 4tests/test_shape_doctor.py, shard 3 of 4tests/test_shape_doctor.py, shard 4 of 4tests/test_doctor_next_is_a_command.py(namescheck_the_way_in)tests/test_adopt_single_repository_record.py,test_bom_reads.py,test_bootstrap_non_utf8_branch.py,test_doctor_gitmodules_not_utf8.py,test_doctor_unreadable_paths.py,test_doctor_validator_verdicts.py,test_repo_hygiene.py(the rest of the files that nameshape_advisory)Known limits
repository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385's rule. One example is a leg'sAGENTS.mdwhose opening is intact ahead of a byte in a legacy code page; another is UTF-16. Over such a file the doctor still offers adoption and the Triad advisory, on base and head alike. Decoding the opening leniently was rejected, because scripts/shape_advisory.pyrepository_namesand shape-doctor.pyorigin_urlandorigin_namereadgit remote get-url originthroughgit_outand catchRefusalandOSErroronly, so an origin URL that is not UTF-8 makesreadanswernot-answered(a single repository loses the Triad advisory, a leg clone its pointer to the assembly root) and endsshape-doctor.py --rootin a UnicodeDecodeError traceback, exit 1, where an ASCII origin gives NOT A SHAPE ROOT, exit 2 #385 forbids salvaging text that is not UTF-8. The rowstest_a_leg_agents_md_that_is_not_utf8_declares_no_leg[a-legacy-byte-after-the-opening]and[utf-16]hold this behaviour, and mutant A4 shows they catch a change to it.leg_clonenow raises over a refused.git, where shape-doctor.py and shape_advisory.py: a.gitor an agent file linked into a folder this user cannot search is said once inwhat is hereoragent files,<name>: cannot be read (<reason>), never absent; a.gitoffers no way in (#1110) #1144's row said False. Both callers askcarries_gitfirst, so no report changes.Not in this PR
AGENTS.md.adopt-project.py's commit reader reads it as its target's path, soplanandchecksay "This is a single repository" over a link to a leg's file, readable or not. Meanwhile the doctor, which reads the disk, calls the same checkout a leg. This is the same on base and head. Filed as shape_advisory.py: a committedAGENTS.mdthat is a symlink is read, inadopt-project.py's commit reading, as the text of its target's path, so a leg cloned with itsAGENTS.mdas a link is told it is a single repository #1163.Lane: openreposhape-1 (openRepoShape-1)
🤖 Generated with Claude Code
Summary by Sourcery
Prevent the doctor from offering adoption or scaffolding when an unreadable
AGENTS.mdleaves a repository's Triad-leg status unknown.Bug Fixes:
AGENTS.mdfiles as unknown rather than as evidence that a repository is not a Triad leg, preventing inappropriate adoption and scaffolding suggestions.AGENTS.mdfiles inwhat is herewith actionable permission details while preserving leg detection by directory name.Enhancements:
AGENTS.mdand preserve existing behavior for directories, named pipes, missing files, and non-UTF-8 content.Tests:
AGENTS.mdfiles, non-UTF-8 content, and ensuring adoption reads committed rather than working-tree metadata.