Repository navigation
shape-doctor.py: a project.yaml or family.yaml that is there and does not load is INVALID (manifest) or INVALID (family), exit 1, quoting load_yaml's own refusal and naming the project's own validator, instead of NOT A SHAPE ROOT, exit 2, with an adopt-project.py plan NEXT and a Triad row calling an assembly root a single repository (#205) - #232
Conversation
…ere and does not load is INVALID (manifest) or INVALID (family), exit 1, quoting load_yaml's own refusal and naming the project's own validator, instead of NOT A SHAPE ROOT, exit 2, with an adopt-project.py plan NEXT and a Triad row calling an assembly root a single repository (#205) Context.__init__ read each manifest through _read, which turned every load_yaml Refusal (yaml-unparsable, yaml-unreadable) into None, so a manifest that was there and did not load (a merge conflict, a tab, a non-UTF-8 byte) read exactly like a missing one. Now each manifest is loaded once through _load, which keeps the refusal. `kind:` still decides first, so no root that declared a kind reads differently. Only where no file that loads declares one does the filename decide: the root is PROJECT (or FAMILY), its manifest is None, and manifest_refusal holds the refusal. The manifest (family) row is then a FINDING that quotes `REFUSED <code>: <detail>` verbatim and untruncated. That is the line the project's own validator prints about the same file through the same loader. The row does not repeat the refusal's default remediation, which points at git submodule update. Its NEXT is the gate validator_for picks (the project's own copy first) plus the file to fix. Which side of a conflict wins is left to the person. naming, pins (validate-pins.py reads project.yaml before any pin), legs, leg shape files, placement and members wait on that row as n/a with detail.waits_on. Before, they answered questions they could not ask: "declares no non-assembly leg", and "no members yet" as an ok. manifest kinds no longer lists the same file a second time under "remove the file from the root". The kind line says the manifest does not load. --placement-plan refuses by name, exit 3, and writes nothing. --help is unchanged, so docs/cli.md is too. WIP: the code and the tests are complete and the 14 new tests pass (11 of them fail against 20e13f0's doctor). The brief's targeted run (test_shape_doctor.py whole, test_shape_advisory.py, test_manifest_validator.py, test_load_yaml_refusals.py, test_cli_reference.py) has not run yet. Rename the subject once it passes. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…to all three breakages an assembly root is, in text and in --json, no report of an unloadable manifest names adopt-project.py anywhere but the machine row's own git-filter-repo fact, that row is the row the root had before it broke, and an absent manifest is still NOT A SHAPE ROOT, exit 2, in the text report too (#205) 9f2e6c0 made the doctor read a project.yaml or family.yaml that is there and does not load as the kind its filename names, INVALID, exit 1. Its tests broke project.yaml three ways (a merge conflict, a tab, a non-UTF-8 byte) and family.yaml one way. This commit adds tests only; shape-doctor.py is unchanged. family.yaml now breaks all three ways (FAMILY_BREAKAGES). The conflict is in the last commit: line, a member's pin. The byte goes into the family's name. Each case checks the same verdict and rows as --json in the text report: every reason and every NEXT verbatim, the kind line naming the family row, and none of NOT A SHAPE ROOT, single repository, the way in, what is here, remove the file or git submodule. no_adoption_offered is strict now. Before, it looked for the phrase "adopt-project.py plan" and skipped the machine row. Now the name appears nowhere: not in the text report outside the machine row's own line, not in any row's reason, next or detail, and not in the payload's top-level fields. The machine row may name it only while git-filter-repo is missing. That clause is a fact about the workstation, printed in a COMPLIANT report as well, and the Windows CI job has no git-filter-repo by design. Holding the machine row to it as well would turn that job red over a sentence #205 must leave alone. machine_row_unchanged holds that row to the row the same root had before its manifest broke, in the project and family --json tests. The id, label, status, preflight path and git-filter-repo path must match. When the preflight's exit and report lines also match, the whole row must match. The preflight asks gh, which answers from its host, so two runs it answered differently are compared only on what does not come from it. The absent-manifest case also checks family_yaml false and no assembly-root row. It runs the text report too: kind line, NOT A SHAPE ROOT (exit 2), the way-in NEXT. Its two cases name both ways in, adopt-project.py plan for a git repository and setup.sh (setup-project.py on Windows) for a plain directory, each where it applies. Tested with git-filter-repo 2.47.0 on PATH: the brief's targeted run (test_shape_doctor.py, test_shape_advisory.py, test_manifest_validator.py, test_load_yaml_refusals.py, test_cli_reference.py) was 298 passed on 9f2e6c0 and 300 passed (one pytest warning: a garbage temp directory it could not delete) on this commit. The 16 #205 tests also pass without git-filter-repo on PATH. Against 20e13f0's doctor 13 of them fail; the 3 that pass (absent twice, kind-outranks) guard what must not change. --help is unchanged, so docs/cli.md is too. Lane: openreposhape-4 (openRepoShape-4) 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 2 days and 6 hours by commenting |
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Reviewer's GuideThe doctor now distinguishes an absent manifest from a present manifest that load_yaml refuses: such roots remain classified by filename, report a single INVALID finding with the loader’s refusal and the owning validator as the next step, suppress dependent cascades and adoption guidance, and are covered by extensive project/family regression tests. Sequence diagram for unloadable manifest reportingsequenceDiagram
participant Doctor as shape-doctor.py
participant Loader as load_yaml
participant Validator as project validator
participant Report as report
Doctor->>Loader: load_yaml(manifest_path)
Loader-->>Doctor: Refusal(code, detail)
Doctor->>Doctor: store manifest_refusal
Doctor->>Validator: validator_for(ctx, own, template)
Validator-->>Doctor: owning validator path
Doctor->>Report: unloaded_manifest_row(ctx, check_id, label, own, template)
Report-->>Report: INVALID finding with REFUSED code and detail
Report-->>Report: dependent rows are n/a with waits_on manifest or family
Report-->>Report: suppress adoption and Triad guidance
Flow diagram for manifest refusal classificationflowchart TD
A[Context loads project.yaml and family.yaml once] --> B{Valid kind declaration found?}
B -->|Yes| C[Classify by declared kind]
B -->|No| D{Manifest refusal found?}
D -->|Yes| E[Classify by filename kind]
E --> F[Store manifest_refusal]
D -->|No| G[NOT A SHAPE ROOT]
C --> H[Normal validation]
F --> I[INVALID manifest or family]
I --> J[Manifest row quotes REFUSED code and detail]
I --> K[Dependent rows are n/a and wait on manifest row]
I --> L[Placement plan refuses without writing]
G --> M[Exit 2]
I --> N[Exit 1]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…REFUSED line from Refusal.__str__ instead of spelling it a second time, the tests hold their NEXT to the root's own validator as a path and not as a substring, and the `conflicted` docstring claims only the one-conflicted-manifest case it builds (#205) The review of 50aa967 (MERGEABLE WITH NITS) left three nits for this branch; this commit is all three, and the report text is byte-identical. N1. The NEXT tests asserted that `scripts/validate-manifest.py --root` (`validate-family.py --root`) appears in the command, and the standard's template copy contains that string too: a NEXT that named the template passed. Mutant M5 (`used = ctx.shape / template`) was killed only by accident, through the template's `ModuleNotFoundError: No module named 'repo_shape'` when the command was pasted, and would have survived once that is fixed. `assert_next_runs_the_roots_own` compares the words as paths, both sides resolved so a symlinked temporary directory (macOS) or a `\` spelling (Windows) is one path: the NEXT's validator is `<root>/scripts/<script>`, its `--root` argument is the root, and `detail.validator` is the same file. It stands in the three places that checked the substring (the `--json` report, the pasted-command test, the family holder), and M5 now fails each of the nine cases on that assertion, before any command runs. N2. shape-doctor.py spelled `REFUSED {code}: {detail}` a second time, next to `Refusal.__str__`, which is where the loader and every validator print it. `refusal_line` takes it from there: `str(refusal)` less the `\n<remediation>` trailer, so the quoted line has one definition. It is not the text up to the first newline, which would cut a refusal whose `detail` holds one: `detail` opens with the path the loader was given, and a root directory with a newline in its name would be quoted short, with no error to say so. A new test pins it for the loader's default remedy, a one-line remedy, a several-line remedy, and a path with a newline in it; the first-line and whole-`str()` mutants both fail it. The doctor's text and `--json` output on a project and a family holder broken two ways each (a merge conflict, a non-UTF-8 byte) is identical before and after, byte for byte. N3. The `conflicted` docstring named "two shape updates in an assembly root" as the case it builds. That case also conflicts `contracts/shape-pin.yaml` and reads CANNOT ANSWER, not INVALID (review S1, a defect on main, filed apart). It now says what it does build: one conflicted manifest and nothing else in the tree, and says nothing about a tree where other files conflict too. Run, over these bytes (sha256 recorded first): tests/test_shape_doctor.py tests/test_load_yaml_refusals.py tests/test_cli_reference.py, with git-filter-repo 2.47.0 on PATH: 222 passed, no skips, exit 0. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
(lane openRepoShape-4, 2026-10-11T19:24:40Z) Review round 1 is in: 1c9282e, one commit on 50aa967, answering the adversarial review's three nits.
|
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
(lane openRepoShape-4, 2026-10-11T19:28:19Z) The two pre-existing defects this description says follow-up issues will carry are now filed and re-verified on |
…b6df75) and this branch both edit check_manifest_kinds, resolved as their union, so a family.yaml beside an assembly root's project.yaml and a project.yaml that does not load each keep their own verdict (#205) #220 (#211) landed on main as eb6df75 and #205 (this branch) both edit `check_manifest_kinds` in shape-doctor.py, and git conflicts in four hunks there: the docstring, the locals, the detail/condition block and the FINDING return. Nothing else conflicts; tests/test_shape_doctor.py auto-merges with no top-level name defined twice. The resolution is the union of both sides: - the docstring keeps both paragraphs, #220's first, because its "IS THAT CASE TOO" points back at the base paragraph just above it and would otherwise sit two paragraphs from its antecedent; - both locals stay: this branch's `elsewhere` (the root's own manifest, when it does not load) and #220's `beside` (a family.yaml declaring `family-manifest` in an assembly root); - this branch's `elsewhere` skip is kept in the loop, and its `aside` block comes before #220's `beside` detail, with #220's widened condition `unknown or unreadable or beside is not None`; - the FINDING return is `"; ".join(parts) + aside, "; ".join(fixes), detail`, so the `aside` reaches the row on the finding path as it does on the OK one. The two never fire on the same root. `elsewhere` needs the root's own manifest to be one that does not load, and `beside` needs the root to be a PROJECT with a family.yaml that loads and declares `family-manifest`; a root whose project.yaml does not load beside a family.yaml reads as a FAMILY root under #205's filename rule, so `beside` cannot fire, and a root whose project.yaml loads has no `elsewhere`. Each feature keeps its own verdict. The resolution was made by resolve_lane3.py, the union review205-50aa967.md reviewed against #220 at 7fe2c56 and #205 at 50aa967, with one line changed so the docstring paragraphs come in the order above (the script as written emits this branch's paragraph first). #220's change to check_manifest_kinds is byte-identical at 7fe2c56 and in the landed eb6df75. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
|
…, #272 via #274, #205 via #232) into fix/adopt-record-submodule-placement Brings in 23e16ae (#251), 60588d5 (#206), 018ce0d (#207), 67a398a (#272) and d94d155 (#205) on top of 5751460. No rebase. The first four change none of this branch's files. d94d155 changes shape-doctor.py and tests/test_shape_doctor.py, as this branch does. One conflict, in shape-doctor.py's module docstring, under EXIT CODES. Both sides added a clause to exit 1's list of findings: #205 "a manifest is there and does not load", and #189 "or, for a submodule, a leg other than the one `project.yaml` records for it". Both are kept: #205's in its place in the list, and #189's after the path-policy clause it qualifies. Only the docstring's first line reaches `--help`, so docs/cli.md is unchanged. Merged without a conflict, and checked: - `check_placement`: #205 adds its `waiting_row` gate after the family-holder return and before `ctx.legs()`. The merged function is this branch's plus exactly those eight lines. So a `project.yaml` that does not load makes the placement row `n/a`, waiting on the `manifest` row, before the record is read. The record is read from `ctx.manifest`, which #205 now loads once, with the same `load_yaml`, so a manifest that loads gives the record it always gave. - #205 removes `Context._declares`, and nothing on this branch names it. This branch leaves `placement_row` unchanged, and #205's gate calls it as (NA, reason, None, detail). - No top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py, scripts/shape_materialize.py, shape-doctor.py, validate-manifest.py or update-shape.py. Each merged module's names are the base's plus what either side added. Every definition only one side changed is that side's text. The two that both changed are the docstring above and `check_placement`. - These commits leave adopt-project.py, scripts/repo_shape.py and scripts/shape_materialize.py untouched. So the record's writer and reader are the ones the round trip and the revert table ran on at a01ab02, and the record is still spelled by `double_quoted`. - AGENTS.md is untouched: 472 lines, under the cap of 472. - The branch's diff against main is still #189 alone: 12 files, +1670 -157. The one deletion more than against 5751460 is the EXIT CODES sentence above, which now rewraps two of #205's lines. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, #205, #215, #241 and #249/#250 merge with no conflict, and `_entry_findings` still hands `_leg_findings` the tree and ends with the root-symlink findings origin/main moved 60588d5 -> b30595c while the targeted runs of 8f624aa went: 018ce0d (#207 via #222), 67a398a (#272 via #274), d94d155 (#205 via #232), c2ff2b0 (#215 via #240), c3a9645 (#241 via #256) and b30595c (#249 and #250 via #284). It is merged again because c2ff2b0 changes adopt-project.py and b30595c changes scripts/shape_materialize.py's `render`. Conflicts: none. `git merge-tree --write-tree` gave tree be093a5, and the merge stages the same tree. Checked on that tree: - `_entry_findings` still calls `_leg_findings(plan, tree)` and ends with `_root_symlink_findings(plan, source, tree)`. - No top-level name is bound twice in adopt-project.py, scaffold-project.py, scripts/repo_shape.py or scripts/shape_materialize.py, none is loaded but bound nowhere (AST), and main changed the signature of no function this branch calls. - Both sides are intact by +/- line sets: each side's change against the merge base 60588d5 reappears, line for line, against the other side. - c2ff2b0 adds helpers to tests/conftest.py (`planned`, `GITLINK`, `findings` and others) and no fixture. tests/test_adopt_root_symlinks.py imports its conftest names one by one and none of these, so its own `planned` and `GITLINK` are unaffected. - b30595c's `render` spells a double-quoted YAML value with `double_quoted`; this branch's walker reads `assembly_root_paths()` and never renders. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t read names the git command that puts back the last pin a tool wrote (`checkout --ours`, `checkout HEAD` or `checkout <commit>~1`, chosen from the pin's own git state), so the row's NEXT fixes it instead of refusing it again (#237) The review of PR #267 at 092a5f9 found the new row's NEXT a dead end (F1). `update-shape.py check` reads the pin with the same `read_pin`, so run as printed it exited 2 with the same refusal every time, and the remediation it then printed was wrong for the case: `git submodule update` for a conflict, "adopt it" for a deleted pin, "re-save the file" (a hand edit of a pinned file) for a stray byte. An operator who follows AGENTS.md ("Run the one the row names ... never edit a pinned file") was stuck, and README's "names the exact command that fixes it" was false for this row. `pin_restore` now picks the command from the pin's state, read-only: - unmerged (porcelain UU, AA, ...): `checkout --ours` (or `--theirs` where only that side holds a pin, read off the index stages, not the letters) of every conflicted path the shape writes: the pin, the manifest, README.md and the copies, never a file of the project's own; - uncommitted, where HEAD holds a pin: `checkout HEAD -- <pin>`; - otherwise, where the last commit to change the pin has a parent that holds one: `checkout <that commit>~1 -- <pin>`; - none of these: the old `update-shape.py check` NEXT, saying that whether the root was ever scaffolded or adopted is a person's call. `--json` records which, as `detail["restore"]`. Status is read as bytes with `--no-optional-locks`, so the doctor still writes nothing. N2: no note carries a `;`, an apostrophe or a backtick, so a pasted line is one command (#277). N1: the docstring names #232's `unloaded_manifest_row`, which is not on main or this branch. Tests run each printed command and ask the doctor again: the nine uncommitted faults and two committed ones (a `git rm` and a `commit: "zzzz"`, each followed by an unrelated commit) end COMPLIANT, exit 0; the issue's own merge, with `Makefile` and a file of the project's own also conflicted, names only `Makefile` and the pin and then reads the pin; a squashed root commit keeps the check NEXT. At 092a5f9 each of these fails, and head's NEXT run as printed exits 2. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es and leaves a conflicted `project.yaml` or `README.md` to a person, and every pin it puts back out of history is read first, so the other side's own edits survive it and two breaking commits in a row, a sparse checkout or assume-unchanged no longer name a wrong rollback (#237) Fix round 2 on PR #267, answering lane openRepoShape-2's re-verify of 9f64bd0 (V1, V2). V1 (P2). The unmerged NEXT named the manifest and README.md beside the pin and the copies, and `checkout --ours` writes stage 2 whole. `apply` writes only two lines of the manifest's `shape:` block and README's `Shape:` line; every other line is the project's own, so the other side's edits to those files went, hunks git had merged cleanly included, and the doctor then said COMPLIANT. Now only the files the shape writes whole are taken: the pin, each `copy_source` key and its collision copy. A conflicted manifest or README.md is named in the note as a person's merge and listed in `detail["left_for_a_person"]`, as #232's manifest row already says of the same file. The review's optional refinement (naming either file where the dropped side changed only tool-written lines) is not taken. V2 (P3). The committed class named `<last>~1` whenever it held a pin. It never asked whether HEAD's pin was the broken one, or whether the pin it put back read. A sparse checkout or assume-unchanged over a good re-pin got a rollback of that re-pin, and two commits in a row that each broke the pin cycled between `checkout <last>~1` and `checkout HEAD`. - `update-shape.py`: `read_pin`'s checks move into `check_pin(pin, pin_path)`, which `read_pin` calls, so the doctor checks a pin out of git with the same code. - `pin_reads_at` reads `<rev>:contracts/shape-pin.yaml` with `cat-file blob` in memory (no temporary file: the doctor writes nothing), decodes and parses it as `load_yaml` does, and runs `check_pin`. - `uncommitted` needs HEAD's pin to read (the review's optional step 4, so a dirty copy over a broken HEAD goes straight to history). - New class `hidden`: HEAD's pin reads and status is clean. The NEXT is `git ls-files -v`, which shows the S or h, and no rollback is named. - `committed` walks `rev-list -n 50 HEAD -- <pin>` newest first and names the first commit whose parent's pin reads. `pin_restore(ctx)` still answers `(restore, command)`; it gains an optional `detail` it writes `left_for_a_person` into, so existing callers are unchanged. Tests: the README case and the issue's own two-`apply` merge (real re-pins from a copy of the standard with two more commits; shape/a also renames the project and adds a README paragraph), two breaking commits, a dirty copy over a committed break, assume-unchanged and a sparse checkout. All six fail at 9f64bd0 for the review's reasons. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-4 (openRepoShape-4)
Claim: #205 (comment)
Fixes #205.
What
shape-doctor.pynow keeps theRefusalthatload_yamlraises for a manifest that is there and does not load, instead of reading it as "no manifest". Aproject.yamlorfamily.yamlthat fails to load makes the root the kind its filename names:INVALID (manifest)orINVALID (family), exit 1;manifest(orfamily) row is a FINDING that quotes the loader's ownREFUSED <code>: <detail>line and whose NEXT names the project's own validator on that file;n/aand waits on it (waits_onin--json), so the report names one fault, not a cascade;--placement-planrefuses with exit 3 and writes nothing;adopt-project.pyadvice appears anywhere except themachinerow's own git-filter-repo fact, and there is no Triad row calling the root a single repository.A directory with neither file is still
NOT A SHAPE ROOT, exit 2, naming both ways in.--helpis unchanged, sodocs/cli.mdis too.The branch has two commits: 9f2e6c0 (the doctor and 14 tests) and 50aa967 (tests only). 9f2e6c0's subject starts
WIP (#205):, and the no-force-push ruleset keeps it that way; this PR's title is the squash subject.Why (repro on
main)A merge conflict, a tab or a non-UTF-8 byte in an assembly root's
project.yamlmade the doctor reportNOT A SHAPE ROOT, exit 2, with a NEXT telling the reader toadopt-project.py planthe assembly root in place, and a Triad row saying "This is a single repository." Meanwhile the project's ownvalidate-manifest.pynamed the real fault. The issue has the full repro; the review below re-ran it againstmain(NOT A SHAPE ROOT (exit 2)) and this head (INVALID (manifest) (exit 1), quotingREFUSED yaml-unparsable: …/project.yaml: line 77: …).Tests
tests/test_shape_doctor.pygains 16 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 tests: project.yaml broken three ways (conflict marker, tab, non-UTF-8 byte) in the text report, in--jsonand through the NEXT command; family.yaml broken the same three ways; the absent-manifest case for a git repository and for a plain directory;--placement-plan's refusal; and a manifest that declares its kind outranking one that does not load. Against 20e13f0's doctor, 13 of the 16 fail; the 3 that pass guard what must not change.test_shape_doctor.py test_shape_advisory.py test_manifest_validator.py test_load_yaml_refusals.py test_cli_reference.py) was 300 passed on this head, and 300 passed again on this head merged withmaina5b09c3. The 16 tests also pass with git-filter-repo off PATH, as on the Windows job.macOS and Windows have not run these tests yet; this PR's CI is the first time.
Not changed here (found by the review, both already on
main)contracts/shape-pin.yamlas well asproject.yamlstill readsCANNOT ANSWER, exit 3:check_shape_currencytakes the project's own unparsable pin file for a fault of this checkout, and that verdict outranks INVALID. The rows are right; the verdict is not. Onmainthe same tree readsNOT A SHAPE ROOT. A follow-up issue will carry it.ModuleNotFoundErrorif pasted.main's validator row does the same through the sharedvalidator_for, so the fix belongs there. A follow-up issue will carry it.Landing notes
manifest kindsFINDING, INVALID (manifest-kinds), naming each member it pins with no gitlink, off its pin or with no 40-hex pin and each gitlink under members/ no row names, so a root its family gate refuses is no longer COMPLIANT, exit 0, with that file counted as validated (#211) #220 (lane 3, 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) edits the same function:git merge-treeconflicts in four hunks, all insidecheck_manifest_kinds. The resolution is a pure union (keep both locals, keep shape-doctor.py: a family.yaml beside an assembly root's project.yaml is amanifest kindsFINDING, INVALID (manifest-kinds), naming each member it pins with no gitlink, off its pin or with no 40-hex pin and each gitlink under members/ no row names, so a root its family gate refuses is no longer COMPLIANT, exit 0, with that file counted as validated (#211) #220's condition with this branch'saside, return both), and the two features never fire on the same root. Applied in scratch, the union passedtests/test_shape_doctor.py(206 passed against shape-doctor.py: a family.yaml beside an assembly root's project.yaml is amanifest kindsFINDING, INVALID (manifest-kinds), naming each member it pins with no gitlink, off its pin or with no 40-hex pin and each gitlink under members/ no row names, so a root its family gate refuses is no longer COMPLIANT, exit 0, with that file counted as validated (#211) #220's 7fe2c56). Suggested order: shape-doctor.py: a family.yaml beside an assembly root's project.yaml is amanifest kindsFINDING, INVALID (manifest-kinds), naming each member it pins with no gitlink, off its pin or with no 40-hex pin and each gitlink under members/ no row names, so a root its family gate refuses is no longer COMPLIANT, exit 0, with that file counted as validated (#211) #220 first; this branch then mergesmainwith that union and pushes before it lands. If this lands first, the mirror-image resolution is ready for lane 3.fix/adopt-record-submodule-placement(no PR) conflicts in one EXIT CODES docstring hunk; that merge is mechanical.🤖 Generated with Claude Code
Summary by Sourcery
Treat present but unloadable manifests as invalid roots, report the loader's refusal and correct validator, and prevent dependent checks from producing misleading results.
New Features:
Bug Fixes:
Enhancements:
Documentation:
Tests: