Repository navigation
shape-doctor.py: manifest kinds prints one command for its NEXT, the git rm, git clean or git status its files need as git has them, where it printed prose joined by ; whose backticks ran family.py in zsh, and counts a manifest validated only at a path its gate reads, so a contracts/<x>-pin.yaml no leg or product names is a FINDING (#548, #439) - #555
Merged
Merged
Conversation
…e `git rm`, `git clean` or `git status` its files need as git has them, where it printed prose joined by `; ` whose backticks ran `family.py` in zsh, and counts a manifest `validated` only at a path its gate reads, so a `contracts/<x>-pin.yaml` no leg or product names is a FINDING (#548, #439) #548. The rule across the doctor is that a NEXT is one pasteable command or nothing, and prose is the row's `why` (#277). `manifest kinds` broke it: each fault's sentence was the NEXT, joined by "; ", and #211's FAMILY_BESIDE_FIX held `family.py init` and `family.py add` in backticks, which zsh, interactive or `-c`, runs where the line is pasted. Now every fault but a file that does not load is a file to take out of the root, so the NEXT is the one command `removal_next` names for them as git has them: `git rm` (committed), `git clean -f` (`-x` where one is ignored), or the `git status` that shows changes `git rm` will not discard. The `why` says what it does, names files the next run handles, and gives each fault's other way out as its own sentence (register the kind, move what it describes to its home, move the membership to a holder, declare the pin), as the human decides. A name git would read as a pathspec pattern is passed after `--literal-pathspecs`: `git rm -- 'contracts/shape-pin[.]yaml'` took the shape pin out with it. A refusal's remedy reaches no NEXT either (#408): `run_checks`, for a check that lets a `Refusal` out, and `placement_policy`, for a standard whose path policy does not load, print no NEXT and put the remedy, less its `Remediation:` label, in the `why`. The #286 linked-record row's NEXT was the one other prose NEXT left in the doctor; it is now that row's `why`. #439. Brett Heap ruled on 2026-10-12: "#439 (registered kind at a path no check reads): FINDING". `ManifestKind.read_at` names where the check that runs on this root reads each kind: `project.yaml`, `family.yaml`, `contracts/repository-naming.yaml`, and for pins the shape pin plus, in an assembly root, `declared_pins` (a pin per leg and per `neutral_product_pins:` entry). A file of a kind whose check runs here, at any other path, is listed under `unread` with the path, the kind and where that kind is read, and is a FINDING whose NEXT takes it out as git has it. Only a pin in an assembly root is offered the human declaring it, and the row never declares one. A file is read by any name it has (`samefile`), so a case-folded or hard-linked leg pin stays `validated`; while `project.yaml` does not load, no pin is called unread. `path-classification-policy` is not decided by path, as before. An untouched root's row is byte for byte what it was. Tests: `rows_of` holds every row the suite reads to "no backtick outside a quoted value, no `Remediation:`" (the guard #408 asked for), shown to fail on the lines main printed; the #277 property covers five new manifest kinds fixtures; a zsh paste shows the old #211 line running `family.py` and the new NEXT running none; #439's five files each run to COMPLIANT by their NEXT; refusal rows from `run_checks` and `placement_policy` carry no NEXT. 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 |
Reviewer's GuideThe PR makes doctor remediation machine-safe and actionable by ensuring every Sequence diagram for path-aware manifest validationsequenceDiagram
participant Doctor
participant Registry
participant Gate
participant Git
Doctor->>Registry: ManifestKind.reads(ctx)
Registry-->>Doctor: gate-readable paths
Doctor->>Gate: Check manifest at declared path
Gate-->>Doctor: validated or not read
alt Same file as a readable path
Doctor->>Doctor: read_here via samefile
Doctor-->>Doctor: validated
else Gate does not read path
Doctor->>Git: git_states(path)
Git-->>Doctor: committed, untracked, ignored, or changed
Doctor-->>Doctor: FINDING with removal command
end
Flow diagram for machine-safe doctor remediationflowchart TD
A[Doctor detects finding] --> B{File can be removed?}
B -->|Yes| C{Git state}
C -->|Committed| D[git rm]
C -->|Untracked or ignored| E[git clean -f]
C -->|Ignored| F[git clean -f -x]
C -->|Changed| G[git status]
D --> H[One pasteable next command]
E --> H
F --> H
G --> H
B -->|No| I[No next command]
A --> J[Alternative remedies in why]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…read` or takes it out, whatever the fault, so a misspelt leg role or a wrong `kind:` in the naming policy no longer gets a NEXT that removes the spec pin or the policy, and its NEXT shows a file git does not track instead of deleting it (#548, #439, review of #555) Fix round for the independent review of 557a259. A file a gate reads is that gate's to judge. `read_by_a_gate` is every root path a gate that runs here reads, as any kind: each kind's `read_at` for the kinds whose check runs on this root, plus `own_pins`, the pins the shape itself writes (the shape pin, and in an assembly root `contracts/spec-pin.yaml` and `contracts/code-pin.yaml`, whatever the legs declare). `pins_read_here` counts `own_pins` too. On a scaffolded root with one committed edit, main says `manifest kinds ok` and 557a259 said: - `- role: specs`: "no gate here reads contracts/spec-pin.yaml (... only at ... contracts/specs-pin.yaml)", NEXT `git rm -- contracts/spec-pin.yaml`, beside the `manifest` row's FINDING manifest-roles; - `contracts/repository-naming.yaml` declaring `kind: pinned_contract_manifest`: "no gate here reads contracts/repository-naming.yaml", NEXT `git rm` of it, beside the `manifest` row's REFUSED policy-wrong-kind. Both now say `ok manifest kinds 5 manifest(s)`, as main does. Beyond the review's two cases, `taking_out` keeps every such path out of the NEXT for any fault: an unregistered kind, or one whose check runs elsewhere, at a gated path is still this row's finding, as on main, but the NEXT never takes the file out, and the `why` says the gate's own row judges it. A file no commit holds is shown, never deleted. `git clean` cannot be undone, and AGENTS.md tells an agent to run the NEXT, so for an untracked or ignored file the NEXT is `git -C <root> status --ignored -- <files>`, and the `why` names the `git clean -f` (`-x` where ignored) for a person to run, as `pin_restore` shows a file a person may have edited. A committed file keeps `git rm`, which history can undo; a changed one keeps `git status`. Tests: `tests/test_update_shape_merged_away.py`, which skips without git-filter-repo, asserted the linked-record row's NEXT as prose; it now asserts no NEXT and the sentence in `why`. The guard reads a NEXT with `outside_single_quotes`, moved beside `rows_of`, which reads `quote_arg`'s `'\''` where the regular expression did not. The pathspec test goes through the row with `contracts/*-pin.yaml` (POSIX) and `contracts/[s]hape-pin.yaml`, and checks the `git rm` stages that file alone. New: both review cases through the doctor, and the gated-path NEXT. The #205 paragraph of `check_manifest_kinds` no longer describes main's NEXT. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
4 tasks
…nly NEXT that shows the commit or change that set it, and a `why` naming the kind a gate reads that path as and the row that really judges it, so the shape pin declaring `pinned_contract_manifes` or `family-manifest` is no longer a FINDING with no NEXT whose `why` points at a row that says `ok` (#548, #439, review of #555) Round 2 of the review of PR #555. What was wrong. Round 1 kept a file a gate reads out of every NEXT and said "its own row judges it". For the shape pin and a declared neutral product pin no row does: validate-pins.py's _check_shape_pin_header reads their fields and never their kind. So `kind: pinned_contract_manifes` (E1) or `kind: family-manifest` (E2) in contracts/shape-pin.yaml was INVALID (manifest-kinds) with no NEXT, a `why` sending the reader to a row that said ok, and an offer to register the kind or move the pin to a family holder. A leg pin of the wrong kind (E3), which the pins row does refuse (pin-wrong-kind), was still offered "Or register the kind". What changes. - kinds_read_here(ctx) maps each path a gate here reads to the kind it reads it as; kind_checked_by(ctx) names the paths whose gate refuses another kind, and its row: the naming policy (manifest, or family in a holder) and each declared leg's pin (pins). Nothing else, and nothing while the root's own manifest does not load. - A file at such a path declaring another kind is `judged`. The NEXT is read-only (showing_the_kind): `git -C <root> diff HEAD -- <file>` for a change no commit holds, else `git -C <root> log -p -1 -- <file>`, or `git status --ignored` where git does not track it. The `why` names what that shows, the declared kind, the kind a gate here reads the path as, which row judges it ("the <row> row's gate finds that too" only where kind_checked_by says so; otherwise "no gate here checks the kind it declares, so this row is the one that says so"), and to put the kind line back. No register, move-to-a-holder or rename way is offered for a judged file; files beside it to take out are named for the next run. - A registered kind whose check runs here, at a gated path read as another kind that no gate checks (the shape pin declaring `repository-naming-policy`), was `validated`, COMPLIANT. It is now a FINDING of this row, listed under the new `misdeclared` detail key, present only when there is one. - The untracked file's `why` spells the deleting `git clean` after a colon, with no backticks a pasted line would run, and the changed group's sentence says git rm without backticks too. Tests. tests/test_shape_doctor.py: E1 committed and uncommitted, E2 and E4 on the shape pin through the whole doctor, E3 on the spec leg's pin, and a declared neutral pin in process; each NEXT is run and shown to change nothing and to print the kind line, and the shape pin, put back, is COMPLIANT. run_read_only takes a `log` NEXT too. The naming policy's round-1 test now expects the `diff HEAD` NEXT and the manifest row as its judge. tests/test_doctor_next_is_a_command.py holds both new NEXTs to the one-command property and the new untracked sentence. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts: # tests/test_doctor_next_is_a_command.py
This was referenced Oct 15, 2026
|
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
… merge for ruling 27 A clean merge of main 2bf8fd4 (#555's squash, on 26c1600), with no hand resolution: git auto-merged shape-doctor.py and no path was conflicted. The merge's tree, f9f6e6f, equals the tree `git merge-tree --write-tree 2ca4044 origin/main` prints (T), and it is the tree the targeted set passed on in a scratch merge of the predicted #555 squash before #555 landed: 1106 passed, 4 skipped (three PowerShell tests that need a Windows host and one that needs a case-insensitive disk), over the PR's own file, the doctor, bump-leg, update-shape and hygiene files. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
Brings in #597 (654c476, CI on Linux only), with #565 and #555 before it. Three conflicts: .github/workflows/tests.yml: main's file whole. #587's `/bin/bash -n install.sh` and its comment lived in the macOS job #597 removed; no bash 3.2 step is added anywhere, as #597 decided, and this pull request no longer touches tests.yml. README.md: the macOS sentence keeps #587's `install.sh` in the list and takes #597's wording: "`setup.sh`, `install.sh` and the `openRepoShape` command are written to use no syntax newer than that, though CI runs on Linux only and no run of it checks them against bash 3.2", with "all three entry points" after it. Four lines, as both sides had: README.md stays 1449 lines, its cap with tests/readme-cap.d/564.txt (+22). docs/handbook.html: the macOS panel's sentence says what the merged README says. The seven markers that read README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park) move to a0e0f23c306cebdf627d90225e33c277f05de64b, as the hygiene check printed; the macOS sentence is the only part of that section the merge changed, and no other part of the seven speaks of CI's runners or bash 3.2. The footer is the same on both sides. test_install_sh.py's pin check still holds: the Linux job installs git-filter-repo==2.47.0, install.sh's pin. The tests' dated history that names the macOS job stays, as #597 left it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…lean merge after #555 and #597 A merge of main 3f768a6. The brief named 654c476 (#597); main has since moved one commit, to siblings.py and family.py, which touches AGENTS.md among this branch's files. No hand resolution: git auto-merged AGENTS.md, adopt-project.py, shape-doctor.py, tests/test_doctor_next_is_a_command.py and tests/test_shape_doctor.py, and no path was conflicted, although #555 (2bf8fd4) rewrote shape-doctor.py and both of those tests. - The merge's tree, 6ad6724, is the tree `git merge-tree --write-tree b640d34 origin/main` prints. - This branch's own changed lines against main are the same 1,289 `+`/`-` lines they were against its base 2247dbf, in the same 8 files. - #555's `placement_policy` refusal (no NEXT, its remedy in the `why`) is as main has it; this branch's moved NEXT in `check_placement` stays a command, the doctor again, as `unreadable_next`'s is. - AGENTS.md stays at 490 of its 490 lines. Lane: openreposhape-1 (openRepoShape-1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
… fix/540-pin-source-unread This merges origin/main 2bf8fd4 as the shared store held it at 2026-10-15T16:50Z, thirteen landings past the 7d9bec9 this branch last merged: #555 (shape-doctor's manifest kinds), #565 (scaffold's `scaffold-remote-exists`), #581 (adopt's `check` of a kept pin path and an unwritable work directory), #568, #559, #557, #560, #580, #556, #558, #567, #579 and #562. It is done so that the PR's CI merge ref and this round's rewording run on current main. The merge was clean, with no conflict hunk. `adopt-project.py`, `scaffold-project.py`, `scripts/shape_materialize.py`, `docs/cli.md` and `tests/test_adopt_root_symlinks.py` took main's changes beside this PR's by the textual merge, and `scripts/render-cli-reference.py --check` passes on the result. Main: 2bf8fd4. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-3 (openRepoShape-3)
Claim: #548 (comment) (#548), #439 (comment) (#439)
What
manifest kindsprints one command for its NEXT, or none (shape-doctor.py: no NEXT holds prose or a backtick (manifest kindsjoins prose NEXTs with "; ", and zsh runs FAMILY_BESIDE_FIX's backticks) #548). Every fault except a file that does not load is a file to take out of the root. So the NEXT is the one commandremoval_nextnames for all of them, chosen by how git has them:git -C <root> rm -- <files>;git -C <root> status --ignored -- <files>, which shows them. The deletion is never the NEXT: no commit holds such a file, so it cannot be undone. Thewhynames thegit clean -f(-xadded when one is ignored) for a person to run, after a colon and in no backticks (fix rounds 1 and 2);git -C <root> status -- <files>, which shows the changesgit rmwill not discard.A file at a path a gate here reads is never taken out, whatever the fault (fix round 1). Where it declares a kind other than the one a gate reads that path as, the NEXT shows read-only how it came to (
git diff HEADorgit log -p -1). Thewhynames both kinds and the row that judges it, and says to put the kind line back (fix round 2). Files that come out another way are named in thewhyfor the next run. Thewhyalso gives each fault's other way out, except for a file a gate reads, as its own sentence, as the human decides: register the kind, move what it describes to itshome, move the membership to a holder, or declare the pin. A file that does not load still getsunreadable_next's restore first, as before.FAMILY_BESIDE_FIXis nowFAMILY_BESIDE_WAY, a sentence in thewhy.A name git reads as a pathspec pattern gets
--literal-pathspecs. Measured:git rm -- 'contracts/shape-pin[.]yaml'also takes outcontracts/shape-pin.yaml. Names without a pattern character are spelled as before.shape-doctor.py:
manifest kindscounts a file that declares a registered kindvalidatedwherever it sits in a root that runs that kind's check, though each check reads only fixed paths, so acontracts/project.yamlin an assembly root, acontracts/family.yamlorcontracts/spec-pin.yamlin a family holder, and acontracts/<x>-pin.yamlthat no leg or declared descent names areokand COMPLIANT though nothing reads them #439: a registered kind at a path no gate reads is a FINDING.ManifestKind.read_at(ctx)says where the check that runs on this root reads each kind:project-manifestproject.yamlmanifest kindscounts acontracts/*.yamlthat declares a registered kind as validated though that kind's validator never runs on this kind of root, so acontracts/family.yamlin an assembly root, or acontracts/project.yamlin a family holder, isokand COMPLIANT where the same bytes at the root are a FINDING #327)family-manifestproject.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/shape-doctor.py:manifest kindscounts acontracts/*.yamlthat declares a registered kind as validated though that kind's validator never runs on this kind of root, so acontracts/family.yamlin an assembly root, or acontracts/project.yamlin a family holder, isokand COMPLIANT where the same bytes at the root are a FINDING #327)family.yamlpinned_contract_manifestdeclared_pins: the shape pin,contracts/<role>-pin.yamlper leg,contracts/<product lowercased>-pin.yamlperneutral_product_pins:entrycontracts/shape-pin.yamlrepository-naming-policycontracts/repository-naming.yaml(validate-manifest.pyreads it)contracts/repository-naming.yamlpath-classification-policymanifest kindscounts a file that declares a registered kindvalidatedwherever it sits in a root that runs that kind's check, though each check reads only fixed paths, so acontracts/project.yamlin an assembly root, acontracts/family.yamlorcontracts/spec-pin.yamlin a family holder, and acontracts/<x>-pin.yamlthat no leg or declared descent names areokand COMPLIANT though nothing reads them #439)A file of a kind whose check runs here, sitting at any other path (and at no path any gate here reads, as any kind: fix round 1), goes under
detail.unread(manifest,kind,read_at,git). It is named in the reason asno gate here reads <path> (<kind>, which a gate here reads only at <paths>), and its NEXT takes it out as git has it. Only a pin in an assembly root is offered the other way out, the human naming the product underneutral_product_pins:. The row never declares one, and thewhysays never to rename the file onto a path a gate reads.samefile) as one at a read path counts as read: a leg pin spelled in another case on a case-folding filesystem, or a hard link. Without this, the NEXT wouldgit rma pin a gate does read.project.yamldoes not load. It declares nothing this run can read, so every pin counts as before and themanifestrow names the file.validated, and the row,--jsondetail included, is byte for byte what it was (measured below). 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's skip, 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'sfamily_besideand shape-doctor.py:manifest kindscounts acontracts/*.yamlthat declares a registered kind as validated though that kind's validator never runs on this kind of root, so acontracts/family.yamlin an assembly root, or acontracts/project.yamlin a family holder, isokand COMPLIANT where the same bytes at the root are a FINDING #327'sregistered_elsewhereclassification and detail are unchanged.A refusal's remedy reaches no NEXT (shape-doctor.py puts a
Refusal's remediation, a paragraph of prose that opensRemediation:, in a row's NEXT (placement_policyandrun_checks), so a standard whose owncontracts/path-classification.yamldoes not load hands the project a NEXT about its legs and leg pins, whichshandbashreject as a syntax error and zsh, macOS's default shell, runs, executinggit submodule update --init --recursiveandpython3 scripts/validate-pins.pyin the reader's directory #408). This coversrun_checks, for a check that lets aRefusalout, andplacement_policy, for a standard whose own path policy does not load. Neither prints a NEXT now. The remedy, less itsRemediation:label, goes in the row'swhy(remedy_as_why).The one other prose NEXT left in the doctor. It was update-shape.py
applyrewrites a pinned copy,contracts/shape-pin.yamlor the manifest THROUGH a symlink the root keeps at that path (Rollback.writeopens itr+b), so the standard's bytes land at the link's end, outside the root if that is where it points, with exit 0, and--branchreports a commit holding a file that commit does not contain #286's linked-recordshape currencyrow, "replace with the file or directory it points at, ..., and run this doctor again". An AST scan of everyRow(...)/placement_row(...)call found no other literal prose NEXT. That row now has no NEXT, and the sentence is itswhy. Its docstring already said "Said, not spelled as a command".Why
Brett Heap's ruling 11 (2026-10-12): "#439 (registered kind at a path no check reads): FINDING". The ruling comment on #439 adds: "The verdict is INVALID, exit 1, and the NEXT takes the file out as git holds it." The rule across the doctor is that a NEXT is one pasteable command or nothing, and prose belongs in the row's
why(#277).manifest kindswas the row #277 left out.The defect: pasting main's #211 NEXT into zsh runs the commands in its backticks, measured with a logging
family.pyfirst onPATH:The same happens in
zsh -f -i. The new testtest_the_family_beside_next_pasted_into_zsh_runs_no_family_pypastes both lines through a pty.Measured on main 5ed3e5d (before) and head (after). The roots were scaffolded into local bare remotes, and the holder was made with
family.py initandadd. Everything ran offline: aghstub that exits 97,GH_TOKEN=bogus, an emptyGH_CONFIG_DIRand an empty global git config. Each file was committed by itself unless marked otherwise. Root paths are shortened to their names.Untouched roots. Both runs print
ok manifest kinds 5 manifest(s), every declared kind has a registered validator(holder:3 manifest(s)), COMPLIANT, exit 0. Thestatus,reasonanddetailin--jsoncompare byte-identical (cmp) for the assembly root, the holder, and the #327contracts/family.yamlroot.#439's five files. Main says
ok, 6 (holder 4) manifests, COMPLIANT exit 0 for each. Head:Each verdict is
INVALID (manifest-kinds), exit 1. Run as printed and committed, each root is COMPLIANT again (tested).#548's rows. Main:
Head, as of fix round 2 (the reasons are unchanged):
#408's path. Measured in process with a check patched to raise
Refusal("probe-refused", ..., REMEDIATION)andPathPolicy.loadpatched to raise the same:"next"isRemediation: run `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`.and"why"is null;"next"is null and"why"isrun `git submodule update --init --recursive`, then `python3 scripts/validate-pins.py`.Tests
The guard.
tests/test_shape_doctor.py::rows_ofnow asksassert_next_holds_no_proseof every row a--jsonrun returns. The check is no backtick outside a single-quoted value, and noRemediation:. Every row the suite reads, from any builder, is therefore a case. This is the "property over the rows the suite builds" #408 asked for: a remedy arrives at run time, from another module, where no scan of the doctor's strings can see it.test_the_rows_guard_finds_a_backtick_or_a_remediation_in_a_nextshows it failing on main's #211 NEXT, onREMEDIATIONand on a bareRemediation:line, and passing a quoted path that holds a backtick.New tests:
tests/test_doctor_next_is_a_command.py:# <comment>, which a stock interactive zsh (macOS's default shell) does not read as a comment, so pasting the line hands the comment's words to the command as arguments, runs whatever follows a;, and runs themake bootstrapthelegsandmemberscomments put in backticks, then exits 127 #277's property (no comment, backtick, live operator or$(; the sentence is thewhy) now covers sevenmanifest kindsfixtures: committed, untracked, changed, 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 file, every fault at once, and (fix round 2) a pin of the wrong kind, committed (log -p -1), and the naming policy's changed (diff HEAD).test_a_refusal_a_check_lets_out_has_no_next_and_keeps_its_remedy, which checksrun_checksandplacement_policywith no remedy and withREMEDIATION.tests/test_shape_doctor.py:manifest kindscounts a file that declares a registered kindvalidatedwherever it sits in a root that runs that kind's check, though each check reads only fixed paths, so acontracts/project.yamlin an assembly root, acontracts/family.yamlorcontracts/spec-pin.yamlin a family holder, and acontracts/<x>-pin.yamlthat no leg or declared descent names areokand COMPLIANT though nothing reads them #439's five files (test_a_registered_kind_at_a_path_no_gate_reads_is_a_finding, parametrized): FINDING,unreaddetail, the NEXT, then COMPLIANT after running it.test_each_kind_is_read_at_the_paths_its_gate_opens).test_a_pin_a_gate_reads_stays_validated_by_any_name_it_has: a hard-linked leg pin; a case-folded leg pin (validated where the filesystem folds case,unreadwhere it does not); a neutral pin validated while declared andunreadwith the declare sentence once undeclared; nounreadwhileproject.yamldoes not load.test_a_name_git_reads_as_a_pattern_is_passed_literally, through the row:contracts/*-pin.yaml(POSIX) andcontracts/[s]hape-pin.yaml; run as printed, thegit rmstages that file alone.test_a_file_a_gate_reads_is_never_unread_when_the_fault_is_elsewhere(the review's two roots) andtest_the_next_never_takes_out_a_file_a_gate_reads.Updated tests. Every #211/#327 test now asserts the bare command and the
why. The untracked and ignored cases run the NEXT (git status --ignored; the file stays), then thegit clean -fthewhynames (-xfor ignored), to COMPLIANT. The edited and staged cases run thegit status, then thegit rmit leads to. The #211-plus-unregistered case runs both NEXTs in turn, then thewhy'sgit clean, to COMPLIANT. The linked-record test, andtests/test_update_shape_merged_away.py's (fix round 1), assert no NEXT and the sentence inwhy.Runs. Fix round 2, head f112441, the same 13 files on Linux with the git-filter-repo wrapper and the gh stub:
848 passed, 3 skipped in 562.19s, exit 0, the same 3 Windows-only skips. Fix round 1, head abbce43, Linux, with a git-filter-repo 2.47.0 wrapper first on PATH as well, so the files that skip without it run: the same 13 files,840 passed, 3 skipped in 630.97s, exit 0. The 3 skips are the PowerShell tests that need a Windows host. The first round's runs follow.First round, head 557a259, Linux,
/usr/bin/python3 -m pytest <files> -q --basetemp=… -o tmp_path_retention_policy=failed -p no:cacheprovider, with the gh stub above:tests/test_shape_doctor.py,test_doctor_next_is_a_command.py,test_doctor_gitlink_repairs.py,test_doctor_legs_members_next.py,test_doctor_unfinished_merge.py,test_doctor_validator_rows.py,test_family.py,test_shape_pin_files_not_a_list.py,test_update_shape_merged_away.py,test_refusal_default_remediation.py,test_repo_hygiene.py,test_agents_md_codes.pyandtest_cli_reference.py(every file that reads rows throughrows_of, plus the guards):822 passed, 17 skipped in 523.00s, exit 0. The skips are existing ones; none of the new tests skips on Linux.--jsonspells the rootas_posix()), the 14 tests that use it:14 passed.Windows and macOS are CI's to show. The case-folding branch and
os.linkrun there for the first time.Fix round 1 (independent review of 557a259)
One commit on top, abbce43.
SHOULD-FIX-1, CI red.
tests/test_update_shape_merged_away.py:342still asserted the linked-recordshape currencyNEXT as prose. That file skips without git-filter-repo, which is why the first round's run missed it. It now asserts no NEXT and the sentence inwhy. Every file that both reads doctor rows and skips without git-filter-repo (test_shape_doctor.pyandtest_update_shape_merged_away.py) ran with git-filter-repo 2.47.0 on PATH. Before the fix, that file gave3 failed, 7 passed; after it,10 passed.SHOULD-FIX-2,
unreadtook out a file a gate reads.read_by_a_gate(ctx)is every root path a gate that runs here reads, as any kind. A registered kind at one of those paths is neverunread.own_pinsare the pins the shape itself writes: the shape pin, pluscontracts/spec-pin.yamlandcontracts/code-pin.yamlin an assembly root, whatever the legs declare.pins_read_herecounts them as read. Measured on a scaffolded root with one committed edit (manifestrow unchanged throughout):One step past the review. An unregistered kind, or one whose check runs elsewhere (shape-doctor.py:
manifest kindscounts acontracts/*.yamlthat declares a registered kind as validated though that kind's validator never runs on this kind of root, so acontracts/family.yamlin an assembly root, or acontracts/project.yamlin a family holder, isokand COMPLIANT where the same bytes at the root are a FINDING #327), sitting at a gated path is still this row's finding, as on main. Buttaking_outnever puts that file in the NEXT, and thewhysays the gate's own row judges it. Otherwise akind: service-inventoryin the naming policy, orkind: family-manifestin the shape pin, would get a NEXT ofgit rmon the file the gate reads. On main thatgit rmwas prose; shape-doctor.py: no NEXT holds prose or a backtick (manifest kindsjoins prose NEXTs with "; ", and zsh runs FAMILY_BESIDE_FIX's backticks) #548 made it runnable. (Fix round 2 replaces thatwhy. No gate checks the shape pin's kind, so such a file now gets a read-only NEXT, and the row names its real judge.)SHOULD-FIX-3, no irreversible deletion in a NEXT. For untracked or ignored files the NEXT is now
git -C <root> status --ignored -- <files>. Thewhynames thegit clean -f(-xwhere ignored) for a person to run, and gives the alternative. Committed files keepgit rm; changed files keepgit status. No test expectsgit cleanin a NEXT, and AGENTS.md is untouched.nit-4. The pathspec test now goes through the row with
contracts/*-pin.yaml(POSIX; Windows takes no*in a name) andcontracts/[s]hape-pin.yaml.git ls-filesshows each pattern matching more than one file. The row's NEXT isgit -C <root> --literal-pathspecs rm -- '<name>', and run as printed it stages that file alone.nit-5. The guard reads a NEXT with
outside_single_quotes, moved intotests/test_shape_doctor.pybesiderows_ofand imported back bytests/test_doctor_next_is_a_command.py; importing it the other way would be circular. The guard test now passesgit -C '/w/it'\''s `here`' status, which the regular expression had misread.nit-6. The shape-doctor.py calls an assembly root whose
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 paragraph ofcheck_manifest_kindsno longer describes main's NEXT as current.Fix round 2 (re-review of abbce43)
One commit on top, f112441.
SHOULD-FIX, a wrong
kind:in a pin was a dead end. Round 1 kept a file a gate reads out of every NEXT and said "its own row judges it". For the shape pin and a declared neutral product pin no row does:validate-pins.py's_check_shape_pin_headerreads their fields, never their kind. Now:kinds_read_here(ctx)maps each path a gate here reads to the kind it reads it as.kind_checked_by(ctx)names the paths whose gate refuses another kind, and that gate's row: the naming policy (manifest;familyin a holder, both viaNamingPolicy.load,policy-wrong-kind) and each declared leg's pin (pins,pin-wrong-kind). Nothing else, and nothing while the root's own manifest does not load.showing_the_kind):git -C <root> diff HEAD -- <file>for a change no commit holds, elsegit -C <root> log -p -1 -- <file>.whysays what that shows, then the declared kind, the kind a gate here reads the path as, and who judges it. "The row's gate finds that too" is said only wherekind_checked_bysays so; otherwise it is "no gate here checks the kind it declares, so this row is the one that says so". It ends "put its kind line back to ".Measured on a scaffolded root, one edit each, committed unless marked. The only other FINDING in any of them is the
pinsrow in E3:Head's verdicts: E1, E2 and E4
INVALID (manifest-kinds), E3INVALID (pins, manifest-kinds), exit 1 each.One step past the review: E4. A registered kind whose check runs here, at a gated path read as another kind that no gate checks, was
validated, COMPLIANT. It is now this row's FINDING, listed under a newdetail.misdeclared(manifest,kind,read_as,git), which is present only when there is one. Where a gate does check the kind, the file staysvalidatedand that gate's row is the finding, as round 1 measured: the naming policy declaringpinned_contract_manifeststill readsok manifest kindsbeside themanifestrow'spolicy-wrong-kind.NIT E3. The leg pin of the wrong kind no longer gets "Or register the kind"; covered by the E3 test.
NIT, the untracked
why. The deleting command now follows a colon, with no backticks a pasted sentence would run: "... never this row's, with nothing to commit after. A person deletes it with: git -C clean -f -- ". The changed group's "whichgit rmwill not discard" lost its backticks too.Tests:
tests/test_shape_doctor.py::test_a_wrong_kind_in_the_shape_pin_is_shown_never_a_dead_endcovers E1 committed, E1 not committed, E2 and E4, through the whole doctor. It checks the reason, thedetailkeys, the NEXT and the wholewhy. It runs the NEXT, which changes nothing and prints the+kind:line. With the line put back, the root is COMPLIANT.test_a_leg_pin_of_the_wrong_kind_is_offered_no_registrationcovers E3.test_a_declared_neutral_pin_of_the_wrong_kind_is_this_rows_to_sayruns in process, because thepinsrow would askgh.run_read_onlyaccepts alogNEXT.diff HEADNEXT and themanifestrow as the judge.tests/test_doctor_next_is_a_command.pyaddstaking_out-committedandtaking_out-changedtoCASES, and the untracked entry opens with the new sentence.Composes with
Trial merges (
git merge-tree --write-tree --name-only) of f112441 (fix round 2; abbce43 and 557a259 gave the same) against the open branches touching these files:main(b8b7062)l3-549-doctor-machine-row(9362501)l3-545-doctor-way-in(e64f47e)tests/test_doctor_next_is_a_command.pyThe #553 conflict is add/add at the same two anchors: both PRs append fixture builders after
_way_inand entries at the end ofCASES. Keeping both sides resolves it. Whichever lands second does that.Left out
contracts/*.yamlnothing validates, or afamily.yamlbesideproject.yaml, is a finding by name") still reads true. A follow-up could add "or one at a path no gate reads".docs/cli.mdcarries only--help, whose first line is unchanged. AGENTS.md's rule 3 ("A FINDING names its fix, and you run THAT") does not mention theWHYline. That line is where this row now says another way out is the human's to choose (e.g.git rmvs registering a kind). AGENTS.md is at its 490-line cap, so that wording is left for a follow-up decision.path-classification-policyis not decided by path, as shape-doctor.py:manifest kindscounts a file that declares a registered kindvalidatedwherever it sits in a root that runs that kind's check, though each check reads only fixed paths, so acontracts/project.yamlin an assembly root, acontracts/family.yamlorcontracts/spec-pin.yamlin a family holder, and acontracts/<x>-pin.yamlthat no leg or declared descent names areokand COMPLIANT though nothing reads them #439 scoped it out.Closes #548. Closes #439.
Lands after the v1.0.0 tag (v1.1.0).
🤖 Generated with Claude Code
Summary by Sourcery
Make shape-doctor manifest findings produce safe, actionable commands while validating manifests against the paths their gates actually read.
New Features:
manifest kindsidentify registered manifests placed at paths that no applicable gate reads as findings.Bug Fixes:
Enhancements:
Tests: