Repository navigation
shape-doctor.py: manifest kinds counts a registered kind validated only on a kind of root that runs its check, so a contracts/family.yaml in an assembly root, or a contracts/project.yaml in a family holder, is a FINDING naming the git rm that takes it out instead of ok and COMPLIANT (#327) - #375
Conversation
…` only on a kind of root that runs its check, so a `contracts/family.yaml` in an assembly root, or a `contracts/project.yaml` in a family holder, is a FINDING naming the `git rm` that takes it out instead of `ok` and COMPLIANT (#327) `check_manifest_kinds` sent every `kind:`-bearing file except #211's top-level `family.yaml` to `MANIFEST_VALIDATORS.get(kind)` and counted it `validated` whenever the kind had an entry, whatever the root. The gate registered for `family-manifest`, `validate-family.py`, runs in a family holder only, and the one for `project-manifest`, `validate-manifest.py`, in an assembly root only. So those files were checked by nothing while the row said "every declared kind has a registered validator". - `ManifestKind.covered_by` is now the ids of the checks that validate the kind, one for each kind of root, and `ManifestKind.runs_in()` reads each check's `applies_to` out of `CHECKS`: the registry alone says where a check runs. `pinned_contract_manifest` and `repository-naming-policy` name `family` beside `pins` and `naming`, because a holder's `validate-family.py` reads `contracts/shape-pin.yaml` and loads the naming policy itself. - A file whose registered kind is checked nowhere on this kind of root goes under `detail.registered_elsewhere` (manifest, kind, validator, runs_in), not `validated`, and the row is a FINDING: "no validator that runs in <an assembly root | a family holder> is registered for kind <path> (<kind>: <validator> runs only in <root>)". The verdict is `INVALID (manifest-kinds)`, exit 1. - A FINDING, not a note. The row already calls a declared kind that nothing validates a FINDING (an unregistered kind), the same bytes as `family.yaml` at the top of the root are one (#211), and a note is a difference nothing asserts, whereas a file that declares `kind:` asserts what it is. - The next command is `git -C <root> rm -- <path>`, quoted for the reader's shell, committed on a branch and landed as a pull request. The other exit, as the human decides, is the kind's `home`: `family.py init` then `family.py add` for a family manifest; `setup.sh` or `adopt-project.py plan`, then `family.py add`, for a project manifest. The line also rules out renaming the file to `project.yaml` or `family.yaml` in this root. Unchanged: a root with no such file gets the same `manifest kinds` row. On an untouched assembly root and family holder, its `--json` row is byte-identical between the two heads, because the key is added only when there is one, as `family_beside` is. #211's top-level case still runs first and still checks the members. The unregistered and unreadable cases are untouched. Every test in tests/test_shape_doctor.py that passed before still passes. Left out: a registered kind at a path its check never reads, in a root where that check does run, is still counted `validated`. Examples are a `contracts/project.yaml` in an assembly root, a `contracts/family.yaml` in a holder, and a `contracts/<x>-pin.yaml` that no leg or declared descent names. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-12T14:44:49Z) @codex review |
|
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 8 hours and 45 minutes by commenting |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Reviewer's GuideUpdates Sequence diagram for misplaced manifest findingsequenceDiagram
participant Doctor as shape-doctor.py
participant Registry as MANIFEST_VALIDATORS
participant Checks as CHECKS
participant Report as manifest kinds row
Doctor->>Registry: Look up manifest kind
Registry-->>Doctor: ManifestKind.covered_by
Doctor->>Checks: Read check.applies_to via runs_in()
Checks-->>Doctor: Root kinds where validator runs
Doctor->>Report: Add registered_elsewhere detail
Doctor->>Report: Emit FINDING and git rm NEXT
Flow diagram for manifest kind validation by root typeflowchart TD
A["Manifest with kind"] --> B{"Kind registered?"}
B -->|No| C["FINDING: unregistered"]
B -->|Yes| D["ManifestKind.runs_in()"]
D --> E{"Validator runs in this root?"}
E -->|Yes| F["validated"]
E -->|No| G["FINDING: registered_elsewhere"]
G --> H["NEXT: git rm, or move to the kind's home"]
File-Level Changes
Assessment against linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
… file, so a `contracts/family.yaml` nobody committed is to be deleted instead of handed to a `git rm` that says "did not match any files", one with uncommitted changes is told to commit or drop them first, and a project manifest's other exit names `setup-project.py` for Windows: the fix round on the review of 4710487 (#327) The review of 4710487 found that the NEXT named `git -C <root> rm -- <path>` for every file, while the doctor reads the disk. Measured with the suite's own fixtures, that command exits 128 ("did not match any files") for a file that was copied in and never added, or that the root's excludes ignore. It exits 1 ("has local modifications") for a committed file edited since. The "committed on a branch" clause described nothing for either. - `git_states` asks ONE `git --no-optional-locks --literal-pathspecs status --porcelain=v1 -z --untracked-files=all --ignored` about the files the row names. A path it does not list is `committed`: HEAD, the index and the work tree agree on it, and that is the one state `git rm` takes. `??` alone is `untracked`, `!!` alone is `ignored`, and anything else is `changed`: an edit, a staged change, a file staged and never committed, or a removal staged while the file is still on disk. `--no-optional-locks` keeps `status` from writing the refreshed index back, because the doctor writes nothing into the repository. - `removal_ways` gives each state its own exit: - `committed`: the `git rm`, committed on a branch and landed as a pull request; - `untracked` or `ignored`: delete it, with nothing to commit; - `changed`: commit or drop the changes first, with the `git status` that shows them. `git rm` will not discard them, and the next run names the `git rm`. A row whose files come out different ways names each file in its own clause. Each file's state is `detail.registered_elsewhere[].git`. - `git_states` declines a directory that is not its own repository (`not_its_own_repository`, as for a leg). There, `status` would answer about the enclosing repository, with paths relative to its top, and every file would read as `committed`. The NEXT then carries no parenthesis and reads like #211's: take it out of this root. - The project manifest's other exit names `setup-project.py` on Windows beside `setup.sh`, which is a bash script. It is one sentence on every platform, so it names both. Unchanged: a committed, clean file gets the NEXT it got at 4710487, byte for byte. The two earlier tests' NEXT assertions are untouched, and only their `detail` gains `"git": "committed"`. Tests: 6 new. Untracked, ignored, committed-then-edited and staged-never- committed (each FINDING, then followed to COMPLIANT), three files that come out three ways with a stat-dirty index left byte for byte, and `git_states` in a directory inside another repository. The first five fail on 4710487 on the NEXT; the last, on the missing helper. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-3, 2026-10-12T15:38:33Z) fix round 1 (independent review, codex is out of quota): The head is now 0bf6a9f, a fast-forward from 4710487 with one new commit. 1. should-fix: the NEXT's
2. nit: Tests. Six new tests in
Evidence:
Unchanged. A committed, clean file gets 4710487's NEXT byte for byte (measured above). The two earlier tests' NEXT assertions are untouched; their The squash message now describes the final state. |
|
…tor-manifest-kinds-unreadable-pin: `manifest kinds` keeps #291's restore beside #375's registered-elsewhere finding (#291, #327) #267's branch, this PR's base, now carries main up to 5678f68, #375 and #357 among it. Merged as text, shape-doctor.py conflicts only in `check_manifest_kinds`: the four regions main's #205 and #211 already caused, now with #375's `ungated` in them. #357 does not touch that function, and `pin_restore` and `pin_reads_at` merge clean, because main left them as #267 has them. The resolution keeps every side. In the loop, #205's skip of the root's own manifest comes first, then #291's `load_yaml` and `refusals`, then #211's `beside`, then #375's split of a registered kind by `ManifestKind.runs_in` into `validated` and `ungated`. `registered_elsewhere` joins `detail` only when there is one. The reason names every fault. The NEXT is `"; ".join(fixes)` as on main, with the unregistered kind's sentence for an unregistered kind only, never for a file that does not load. A row with a file that does not load prints the restore alone: joined by `;` to #375's "take ... out of this root" or to #211's sentence, it would be a second command in a pasted line (#277). The next run names the rest. The #291 docstring paragraph now names those faults. Two compose-only tests, each beside a conflicted shape pin: a `family.yaml` beside `project.yaml` (#211), kept from the earlier composition, and a `contracts/family.yaml` in an assembly root (#327), new. Each NEXT is the restore alone and equals the `shape currency` row's. Run, the row is the other fault's alone, and #327's own `git rm`, run in turn, leaves the root COMPLIANT. On the merged tree: tests/test_shape_doctor.py 312 passed, 3 skipped (the Windows-only PowerShell cases); tests/test_doctor_validator_rows.py 28 passed; tests/test_cli_reference.py and tests/test_repo_hygiene.py 63 passed; tests/test_windows_paths.py and tests/test_shape_advisory.py 79 passed; scripts/render-cli-reference.py --check ok. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…plan-manifest-invalid` joins `_entry_findings` after #255's values, and this branch's `plan-root-symlink` stays after the source's own submodules origin/main moved 92e29d9 -> dd8d0ea (twelve commits), merged by sha: - f0a8bf6 (#330 via #381), scripts/family.py; - 87d2eb7 (#333 via #379), update-shape.py; - cd1fc79 (#327 via #375), shape-doctor.py; - 45e01f4 (#348 via #396), tests/test_agents_md_codes.py: every code-shaped name AGENTS.md spells is a code a tool prints; - 3ef059c (#258 and #261 via #314), adopt-project.py, scaffold-project.py and scripts/shape_materialize.py: the project's YAML reader and its `validate-manifest.py` are asked about the manifest's values before anything exists; `check` reports `plan-manifest-invalid` through `_manifest_findings` in `_entry_findings`; `_repository_urls` refuses `adopt-name-too-long`; - f7f4b14 (#366 via #399), scripts/family.py; - cb7063f (#301 and #280 via #390), scripts/repo_shape.py and the template's validate-manifest.py: a bracket or brace in a plain flow scalar is refused; - 5678f68 (#278, #297 and #298 via #357), shape-doctor.py; - 53bee5b (#289 via #352), scripts/bump-leg.py; - f5cb616 (#369 via #398), scripts/shape_materialize.py: the docstrings of `force_stage` and `stage_written`; - a92e60b (#326 via #400), scripts/family.py; - dd8d0ea (#292 via #353), adopt-project.py: `_elector` keeps a `user.name` byte that is not UTF-8, `_remote_repository` reads an origin URL that is not UTF-8 as none, and `adopt-no-org` is asked before `checked_value`. scripts/shape_materialize.py, scripts/repo_shape.py and every other file merge with no conflict. adopt-project.py conflicts in two hunks of `_entry_findings`, each resolved by keeping both sides: 1. The docstring. Main's order, with #261's manifest findings after #255's values and before #190's relative urls; this branch's symlinks stay after the source's own submodules. 2. The return. Main's `_manifest_findings(plan)` call, and the findings in main's order: each entry's path (#196), coverage, the legs and what the root keeps where the shape writes, the source's own submodules (#166), this branch's `_root_symlink_findings`, #255's values, #261's manifest findings, and #190's relative urls last. The remediation and the NOTEs are main's. How each of main's commits that reach this branch's code meets it: - 3ef059c: `_manifest_findings` asks the validator the template ships about the values `execute` writes; it reads no symlink and no path the shape writes. Its materializer half adds the validator's helpers, `refuse_unstorable_names` and `MANIFEST_VALIDATOR`, and holds `election_date` to the validator's `DATE_RE`; it leaves `place`, `_materialize`, `copy_tree`, `template_files` and `_refuse_unwritable` as they were. In `execute`, its `adopt-name-too-long` (in `_repository_urls`) comes before #255's clone-URL refusal, `_refuse_what_check_finds` and this branch's `core.symlinks` probe, all before any leg. Its new paragraph of the module docstring follows the one 6b53a6d corrected; both merge with no conflict. - f5cb616: docstrings only. - dd8d0ea: `_remote_repository`, `_checked_plan_inputs`' organisation and `_elector`, none of them this branch's. - cb7063f: the plans `check` and `execute` read go through this reader. The plans `plan` writes for this branch's tests hold their entries as block mappings, each path `double_quoted` where it needs to be, and the rehearsal below read every one of them. Checked on the merged tree: - AST (fin183g/names.py, base 92e29d9): no top-level name is bound twice in adopt-project.py, scaffold-project.py or scripts/repo_shape.py, and none is loaded but bound nowhere. scripts/shape_materialize.py binds `YamlError` and `parse_yaml` twice, as main dd8d0ea does (1f90a0f's import and 3ef059c's, the same objects); the merge adds none. - Both sides intact (fin183g/sides_intact.py): the only lines that differ from either side's own change are those of the two hunks. - A rehearsal of this merge (tree d21f8b4, the same as this one) passed tests/test_adopt_root_symlinks.py with main's test_adopt_{manifest_values,git_reads,relative_submodule_url}.py, test_scaffold_manifest_values.py, test_agents_md_codes.py and test_yaml_flow_brackets.py: `440 passed`. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t: `check_pin` takes the root its `update-pin-row` remediation reads, so #290's refusal survives #267's split instead of a NameError (#290) Main 580d7df carries #267 (decff87), which split update-shape.py's `read_pin(root)` into the read and `check_pin(pin, pin_path)`, so that shape-doctor.py can check the pins in a root's history in memory. The `update-pin-row` raises moved into `check_pin`, and on this branch they call `pin_rows_remediation(root)`, #290's `git -C <root> log` advice, where `root` is not a name. The textual merge is clean and the tree dies at run time: `NameError: name 'root' is not defined` for a `files:` of `5`, `true` or `1.5` in `check`, `apply` and shape-doctor.py, which then prints no report at all. This commit takes the planned #267 composition, the PR's two attachments fix1-compose-267-resolution-v2.patch and root-pin-faults-after-267.patch, applied unchanged; main moved their hunks by offset only (shape-doctor.py 526 lines, tests/test_shape_doctor.py 521, update-shape.py 1): - update-shape.py: `check_pin(pin, pin_path, root)`, `read_pin` passes its root, and the docstring says `root` is the repository whose history a refusal's `git log` reads. - shape-doctor.py `pin_reads_at` passes `ctx.root`. Its docstring says `check_pin` refuses `files: 5` as `update-pin-row`, and `TypeError` leaves its `except` tuple: no pin's bytes raise one now, so one means a caller out of step with `check_pin`, and the doctor fails loudly rather than answer that no commit holds a pin to put back. - tests/test_shape_doctor.py: #267's ROOT_PIN_FAULTS gains `files: 5`, `true` and `1.5`, each `update-pin-row`, so each is INVALID, exit 1, with `checkout HEAD` as its NEXT, which run as printed leaves the root COMPLIANT. Main's other changes to this PR's files merge as text away from these lines: #446 (245d775) in validate-pins.py, #406 (ec8e09a) in validate-family.py, #379 (87d2eb7) in update-shape.py, and #375 (cd1fc79) and #357 (5678f68) in shape-doctor.py. Neither module has changed on main since decff87. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #327 (comment)
What
manifest kindscounts a registered kindvalidatedonly on a kind of root where a check registered for that kind runs.ManifestKind.covered_byis now the ids of the checks that validate the kind, one for each kind of root.ManifestKind.runs_in()reads each check'sapplies_toout ofCHECKS, so the registry alone says where a check runs and there is no second list of root kinds to drift from it.pinned_contract_manifestandrepository-naming-policynow namefamilybesidepinsandnaming. A holder'svalidate-family.pyreadscontracts/shape-pin.yamland loads the naming policy itself (templates/family-root/scripts/validate-family.py, the shape-pin reader andNamingPolicy.load(root / "contracts" / "repository-naming.yaml")). Without these two entries every family holder would turn INVALID.A file whose registered kind is checked by nothing on this kind of root goes under
detail.registered_elsewhere(manifest,kind,validator,runs_in), not undervalidated. The row is a FINDING and the verdict isINVALID (manifest-kinds), exit 1. Theregistered_elsewherekey appears only when there is such a file, asfamily_besidedoes.The next command is
git -C <root> rm -- <path>, quoted for the reader's shell byquote_arg, committed on a branch and landed as a pull request. The other exit, as the human decides, is the kind'shome:family.py init, thenfamily.py add;setup.shoradopt-project.py plan, thenfamily.py add.The line also rules out renaming the file to
project.yamlorfamily.yamlin this root, the one move that looks like a fix. In a holder it makes the root read as a project. In an assembly root it swaps this finding for shape-doctor.py reads onlyproject.yamlat a root that also carries afamily.yaml, so an assembly root whosefamily.yamldeclares two unmounted members is COMPLIANT, and itsmanifest kindsrow says that kind has a registered validator, which is never run #211's.shape-doctor.py reads only
project.yamlat a root that also carries afamily.yaml, so an assembly root whosefamily.yamldeclares two unmounted members is COMPLIANT, and itsmanifest kindsrow says that kind has a registered validator, which is never run #211's top-levelfamily.yamlcase still runs first and still checks the members. The unregistered and unreadable cases are unchanged.A finding, not a note
The deciding text is AGENTS.md, "Checking a repository's compliance":
noteis "a difference nothing asserts" and never moves the verdict.A file that declares
kind:is a manifest asserting what it is. This row already makes a declared kind that nothing validates a FINDING: an unregistered kind gets "no validator registered for kind ...". #211 also made these same bytes a FINDING when they sit at the top of the root. A note would rankkind: family-manifestundercontracts/belowkind: service-inventoryin the same directory. It would also make one file a FINDING at one path and a note one directory down. The new sentence therefore opens the way the unregistered kind's does.Why
Measured by scaffolding an assembly root (
Atlas) and a family holder (InkRouter, one member,IRRS) from each head withscaffold-project.pyandfamily.py init/add, then committing one file into a copy of each:ok"5 manifest(s), every declared kind has a registered validator"; COMPLIANT, exit 0manifest kindsrow in--jsonis byte-identical (cmp)ok"3 manifest(s), ..."; COMPLIANT, exit 0cmp)contracts/family.yaml(family-manifest)ok"6 manifest(s), every declared kind has a registered validator"; COMPLIANT, exit 0FINDING(below);INVALID (manifest-kinds), exit 1contracts/project.yaml(project-manifest)ok"4 manifest(s), ..."; COMPLIANT, exit 0FINDING(below);INVALID (manifest-kinds), exit 1family.yamlat the top (#211)FINDING;INVALID (manifest-kinds), exit 1contracts/spec-pin.yaml(pinned_contract_manifest)ok; COMPLIANT, exit 0contracts/project.yaml(project-manifest)ok; COMPLIANT, exit 0contracts/family.yaml(family-manifest)ok; COMPLIANT, exit 0This head, with the root's path shown as
<root>:The new tests run that
git rm, commit it, and run the doctor again: both roots are back to COMPLIANT, exit 0.Issue check. The issue's measurements reproduced exactly on 71b58a1, and again on ab623dd after the rebase: 6 and 4 manifests,
ok, COMPLIANT, exit 0. Nothing in it turned out wrong, and this PR takes its first direction. One thing it did not say: the rule as worded there ("itscovered_bycheck applies to the root's kind") would have made every family holder INVALID if applied to the old one-check table. A holder's owncontracts/shape-pin.yamlandcontracts/repository-naming.yamlwere registered only topinsandnaming, and neither check runs in a holder. Bite (b) below shows it.Tests
All on this head, 4710487, rebased on main ab623dd. Each run used
--basetempwithgit-filter-repo2.47.0 first on PATH:tests/test_shape_doctor.pytests/test_family.py tests/test_shape_advisory.py tests/test_commit_trailers.py tests/test_openreposhape_command.pyplus the four guardstests/test_repo_hygiene.py tests/test_cli_reference.py tests/test_git_output_decoding.py tests/test_stdout_encoding.pytests/test_gh_guard.py(it runs the doctor against the sessionghguard)Main ab623dd collects 257 tests in
tests/test_shape_doctor.pyand this head collects 261. The four new ones are:test_a_contracts_family_yaml_in_an_assembly_root_is_a_finding;test_a_contracts_project_yaml_in_a_family_holder_is_a_finding;test_a_contracts_family_yaml_and_one_beside_project_yaml_say_both;test_a_registered_kind_is_answered_only_where_its_check_runs.This diff adds tests and changes none, so every test that passes on main passes here.
On main ab623dd, the four new tests: 4 failed.
Bites, each on this head with the fix broken one way, running the four new tests and the three holder and shape-doctor.py reads only
project.yamlat a root that also carries afamily.yaml, so an assembly root whosefamily.yamldeclares two unmounted members is COMPLIANT, and itsmanifest kindsrow says that kind has a registered validator, which is never run #211 tests they lean on:validatedwherever it sits: 3 failed;pinned_contract_manifestwithoutfamily: 4 failed, two of them existing holder tests (test_a_family_holder_gets_the_family_rows,test_a_root_with_no_family_yaml_beside_gets_the_row_it_always_got);git rm --cached, a removal that leaves the file on disk: 2 failed;Skips, all Windows-only, and CI's Windows job runs them:
tests/test_shape_doctor.py: "the PowerShell line is printed, and powershell.exe exists, only on a Windows host";tests/test_stdout_encoding.py: "the default this is about is Windows' own".No new test is skipped on any OS.
shlex.splitreads thegit rmline back only off Windows; on Windows the line is held to the doctor's ownquote_arg. The removal itself runs as an argv list on every OS.The untouched assembly root and family holder: the
manifest kindsrow in--jsonis byte-identical between main ab623dd and this head (cmp). The full--jsondiffers only in commit shas: this standard's own, and the scaffolded legs' and member's.Composes with
Each trial merge is
git merge-tree --write-tree --name-only <branch> <this head>, against this head 4710487 and against origin/main ab623dd alone. The table lists every OPEN pull request whose diff touchesshape-doctor.pyortests/test_shape_doctor.py:fix/doctor-manifest-kinds-unreadable-pin@ ebffb08shape-doctor.pyshape-doctor.pyfix/doctor-root-shape-pin-unreadable@ 8badae0fix/doctor-legs-members-next@ 604e2d7fix/doctor-validator-cannot-run@ 8ad856ffix/update-shape-pinned-copy-symlink@ e965ab0fix/pin-files-not-a-list@ 039530bfix/adopt-relative-submodule-url@ a0272b2ManifestKind.runs_in()reads the registry:applies_to,CHECKSandMANIFEST_VALIDATORS. None of theshape-doctor.pyPRs above changes it. The diffs of #267, #343, #347, #354 and #357 do not name it at all, and #358's namesMANIFEST_VALIDATORSonly inside the "register the kind" sentence it moves.#358 and this PR both change the
manifest kindsrow#358's conflict is with main, not with this PR. #358 is based on 39ac59c, before main's #211 (eb6df75) and #205 (d94d155) rewrote
check_manifest_kinds. It conflicts inshape-doctor.pywith origin/main alone. #267's head is an ancestor of #358's, so the merged tree below carries #267 too.The two rules compose in behaviour. For the test, I resolved this head merged with ebffb08 by hand in a scratch worktree. The resolution kept every rule from both sides:
project.yamldoes not parse (a merge conflict) NOT A SHAPE ROOT, exit 2, and its NEXT line tells the reader to adopt that assembly root in place while the Triad row calls it a single repository #205's skip of a root manifest that does not load;project.yamlat a root that also carries afamily.yaml, so an assembly root whosefamily.yamldeclares two unmounted members is COMPLIANT, and itsmanifest kindsrow says that kind has a registered validator, which is never run #211'sfamily_beside;registered_elsewhere;manifest kindsrow puts back a shape or leg pin undercontracts/that does not load with the git commandpin_restorepicks from its own state, shows a copy the shape pin has a row for or a neutral product pin with a read-onlygit diff, and leaves any other file to a person whatever it is called, so its NEXT no longer tells the reader to delete the pin (#291) #358'sload_yamlrefusals andunreadable_next.On that tree:
tests/test_shape_doctor.py: 294 passed, 3 skipped (297 collected = main's 257 + 4 here + 36 of shape-doctor.py: themanifest kindsrow puts back a shape or leg pin undercontracts/that does not load with the git commandpin_restorepicks from its own state, shows a copy the shape pin has a row for or a neutral product pin with a read-onlygit diff, and leaves any other file to a person whatever it is called, so its NEXT no longer tells the reader to delete the pin (#291) #358's). No test of either PR broke.contracts/family.yaml: this PR's FINDING, unchanged,INVALID (manifest-kinds), exit 1.contracts/project.yaml: this PR's FINDING, unchanged, exit 1.contracts/spec-pin.yamlholds a committed conflict marker: shape-doctor.py: themanifest kindsrow puts back a shape or leg pin undercontracts/that does not load with the git commandpin_restorepicks from its own state, shows a copy the shape pin has a row for or a neutral product pin with a read-onlygit diff, and leaves any other file to a person whatever it is called, so its NEXT no longer tells the reader to delete the pin (#291) #358's row, unchanged. The reason isunreadable as YAML: contracts/spec-pin.yaml (yaml-unparsable: ...), the NEXT isgit -C <root> checkout '<commit>~1' -- contracts/spec-pin.yaml # the pin as it was before ..., and the detail isrestore: committed.detailcarriesregistered_elsewhereANDrestoreandrefusals.One decision is left for whichever of the two lands second: the NEXT of a row that holds both. My scratch resolution joined the fixes with
;, as main's #211 already does forfamily_beside. #358's restore command ends in a#shell comment, so this PR'stake ... out of this root (...)clause lands inside that comment. Pasted, the line runs only the restore. #358's own docstring states the rule for this case: "where a row has both it names the restore first ... The next run names the other", with one command per row. Under that rule the NEXT is the restore alone while a file does not load, and that is what I would pick. The same question already stands between #358 and #211'sfamily_besideon main.Left out
validated. Examples:contracts/project.yamlin an assembly root,contracts/family.yamlin a holder, and acontracts/<x>-pin.yamlthat no leg or declared descent names; all three measured above. Separating them means knowing which paths each validator reads.pinsrow waits on it (shape-doctor.py calls an assembly root whoseproject.yamldoes not parse (a merge conflict) NOT A SHAPE ROOT, exit 2, and its NEXT line tells the reader to adopt that assembly root in place while the Triad row calls it a single repository #205), but the row still lists the pin files asvalidated. Only an aside notes that the manifest row reports the load failure.Closes #327
🤖 Generated with Claude Code
Summary by Sourcery
Report registered manifests as invalid when their validators do not run in the current root type, and provide safe removal or relocation guidance.
New Features:
Bug Fixes:
Enhancements:
Tests: