Repository navigation
shape-doctor.py: the pins row keeps its gate where update-shape.py check says REFUSAL AHEAD beside a merged-away row, so it no longer says that the apply check prints drops the row when that apply is refused, and the shape currency counts name every state check counts (#676) - #751
Conversation
…standard, the `shape currency` row asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy called `both` (#683) Measured again on main de92614 with git 2.43: run from a `--filter=blob:none` clone of the standard, the doctor made five lazy fetches, one under each `git show` of a copy the standard changed since the pin (missing objects at the pin 1255 -> 1250), and with the clone's remote out of reach it said `DRIFTED (5 both)` of copies nobody edited; `update-shape.py check` printed the same five `both` rows. shape-doctor.py: - `partial_clone_row`: where `partial_clone` holds, `objects_lacking` asks `git rev-list --objects --no-walk --missing=print` of the pinned commit and of HEAD, before `prepare` or any `ls-tree` reads them. Where either lacks an object, the row is CANNOT ANSWER (`environment`, exit 3, error `shape-doctor-partial-clone`), and its NEXT is `git -C <shape> fetch --refetch --no-filter <promisor remote>`. - `promisor_remotes`: git 2.43's own reading of a promisor remote. `promisor = false` alone is no longer partial (git fetches nothing there), a `partialclonefilter` is (git fetches there whatever `promisor` says), and `extensions.partialclone` names one. - `check_shape_currency`: a `prepare` refusal of a read that failed (`update-upstream-unreadable`) gets a `why` of its own and no `fetch --all`, which would bring nothing to read it. - `as_the_standard_wrote_it` takes that refusal as "not proved", as it took a None. update-shape.py: - `Upstream.blob` reads with `git cat-file blob`, and where that fails, `files_at` (one `ls-tree -r` per revision) says whether the revision lists a file at the path: None where it does not, a directory included, and `update-upstream-unreadable` where it does, so `check` and `apply` refuse rather than print `both`. tests/test_doctor_partial_clone.py: new; nothing is fetched where `rev-list --missing=print` lists the same objects before and after. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One conflict, in shape-doctor.py's EXIT CODES list, exit 3: this branch added a partial clone lacking what the comparison reads (#683), and main added a validator that crashed or cannot be given what it needs (#278, #410) and a naming policy that does not load (#678). All are kept, in main's form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… check` says REFUSAL AHEAD beside a merged-away row, so it no longer says that the `apply` `check` prints drops the row when that `apply` is refused, and the `shape currency` counts name every state `check` counts (#676) The `pins` row: `with_merged_away_rows` named `check`, and said the `apply` it prints drops each merged-away row, wherever the gate's only findings were merged-away rows and neither the pin nor the manifest is a link. A pinned COPY kept as a link is `copy-is-link` to `check`, which says REFUSAL AHEAD over it, and its `apply` exits 2 writing nothing; so does `both`, `upstream-removed`, `unmapped`, and a file `apply` would rewrite that has a hard link. `refusals_ahead` now asks `check`'s own two questions over the rows `prepare` classifies (`Row.is_conflict`, and `hard_linked_rewrites`). Where either names a file, the row keeps the gate as its NEXT, names what `check` reports, and leaves out the sentence about `apply`. Where the doctor cannot ask -- the pin does not read, the checkout is a partial clone short of what the comparison reads (`partial_clone_row`, #683), or `prepare` refuses -- it keeps the gate too, so it never fetches. The `shape currency` row: the reason printed the five original states and `merged-away` only, so a `copy-is-link`, `copy-missing`, `upstream-removed`, `unmapped` or `already-at-target` row stood under no count. Every non-zero state of `update-shape.py`'s `ORDER` now follows the five, in that order, so a root with none of them reads as before. Stacked on #742 (#683): `refusals_ahead` uses its `partial_clone_row`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR corrects merged-away pin guidance by modeling update-shape.py's refusal conditions, expands shape-currency counts to every nonzero check state, and adds defensive partial-clone handling so the doctor never fetches or reports false drift when comparison objects are unavailable. Flow diagram for partial-clone safety checksflowchart TD
A[check_shape_currency] --> B[read_pin]
B --> C{partial_clone}
C -- no --> D[prepare]
C -- yes --> E[objects_lacking]
E --> F{objects missing}
F -- yes --> G[partial_clone_row]
G --> H[CANNOT ANSWER; person runs fetch --refetch --no-filter]
F -- no --> D
D --> I{upstream readable}
I -- no --> J[update-upstream-unreadable]
I -- yes --> K[classify rows and count states]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
|
(lane openRepoShape-3, 2026-10-16T23:33:01Z) Lane 3's independent review of #751 at #676's claims hold on main and on #742's head:
On the head, the row agrees with
Other checks
Lane: openreposhape-3 (openRepoShape-3) |
|
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 1 day and 1 hour by commenting |
main gained 25 pull requests since de92614: #634, #699, #671, #719, #701, #717, #700, #702, #722, #720, #725, #721, #728, #705, #727, #718, #744, #743, #749, #740, #737, #734, #742, #751 and #736. No conflict: AGENTS.md, adopt-project.py and docs/cli.md merged as text, and README.md and docs/handbook.html are unchanged by the merge. README.md is 1473 lines, its cap; AGENTS.md 494, its cap; every handbook marker is current; `render-cli-reference.py --check` is ok. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…es every reason THIS checkout cannot answer, and the notes are current to main bf1f099 with the 25 pull requests since de92614 (#563, #683) - W683 and RV-f's N4 on #742: README's exit-3 list (Keeping a project's shape current, :1208-1220) and its handbook twin (`doctor`) named only a pin whose commit this checkout does not carry. They now say, as shape-doctor.py's EXIT CODES does: a pin's commit it lacks, a partial clone missing what is compared (#683), a read that fails (`update-upstream-unreadable`), a crashed validator of its own (#278, #410), or its own naming policy not loading (#678). +1 line, with tests/readme-cap.d/683.txt; README.md is 1474, its cap (1422 + 2 + 5 + 12 + 22 + 10 + 1). The two `Keeping a project's shape current` markers move: `doctor` text and marker, `current` marker only. - The notes, "What changed since v1.0.1", one entry per pull request from `git log --first-parent de92614..bf1f099`, each from its squash commit and the issues GitHub says it closes: - adopt-project.py: #699, #719, #749, #727, #717, #728 (with #621's hint), #743 + #720 + #718, #722, #721; - update-shape.py, bump-leg.py and a project's own scripts: #702 (with the pinned validators), #634; - scaffold, setup, family and the shared readers: #725, #671, #744; - shape-doctor.py: #742, #701, #737, #751, #736, #734, #700 + #705, #740; - install, CI and the docs: the exit-3 sentence above. - Counts (99 pull requests since bfed143; 109 of 135 milestone issues closed at 10:28Z), the "current to main" sha and the open list (26) are at main bf1f099, still placeholders for tag time. Left for round B: lane 5's eight lines (#630 issuecomment-6227129879), whose pull requests #726, #747, #682, #724, #746, #745 and #738 are open drafts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #676 (comment)
Closes #676.
Stacked on #742 (#683); lands after it. This branch starts at #742's head
3027937d, so until #742 lands this PR's diff shows #742's commits too. Two reasons:refusals_aheadasks shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742'spartial_clone_rowbefore it callsprepare.check_shape_currency.Re-measured on main
66b230ed(with #742, which does not touch either row)The issue's two lines still hold. The root is
tests/test_update_shape_merged_away.py's adopted root, withshape/Makefilemerged away andAGENTS-shape.mdkept as a committed symlink to the same bytes.update-shape.py checksaysmerged-awayandcopy-is-link, andREFUSAL AHEAD: \apply` will refuse while these are unresolved — AGENTS-shape.md`.pinsrow: NEXTupdate-shape.py check. Its reason ends "…reports it `merged-away`, and the `apply` it prints drops its pin row in the re-pin commit: the step the `shape currency` row names too". Norefusal_aheadin the detail.shape currencyrow:9 unchanged / 0 upstream-changed / 0 locally-modified / 0 both / 0 upstream-added / 1 merged-away … : shape/Makefile, AGENTS-shape.md. That is 10 counted over 11 rows, with the link's path under no count.AGENTS-shape.mdrather than the issue's.gitignorebecause git does not follow an in-tree.gitignoreor.gitattributeskept as a symlink. A copy nothing reads at run time keeps every gate's answer the same.What changed and why
shape-doctor.pywith_merged_away_rows(shape-doctor.py:2662; new docstring paragraph at:2693; call at:2714). After thelinked_recordcheck, it asksrefusals_ahead.why. The reason says whatcheckreports: "…so `update-shape.py check` reports it `merged-away`, but it also says REFUSAL AHEAD over AGENTS-shape.md (copy-is-link), and the `apply` it prints refuses while that is unresolved, so it drops no pin row yet". The detail gainsmerged_awayandrefusal_ahead({path: why}).check, and the sentence about theapplydropping the row.refusals_ahead(:2745, new) askscheck's own two REFUSAL AHEAD questions over the rowsprepareclassifies:Row.is_conflict:both,upstream-removed,unmapped,copy-missing,copy-is-link, nevermerged-away), by its state;applywould rewrite that has another name (hard_linked_rewrites, update-shape.pyapplyoverwrites a pinned copy,contracts/shape-pin.yamland the manifest in place (Rollback.writeopens the pathr+b), so one kept as a HARD link is rewritten through its other name, a file outside the root (or acp -alsnapshot of the whole root) that nocheckshowed and no pin covers, while the README, the one path guarded withst_nlink > 1, is refused #422), ashard-link.checkprints REFUSAL AHEAD for it too, andapplyrefuses it (update-rewrite-hardlink), so the old sentence was false there as well.preparewould fetch (shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742'spartial_clone_row); orpreparerefuses. Theshape currencyrow says which, and thepinsrow keeps its gate. So the doctor still fetches nothing.prepareper doctor run, only where everypinsfinding is a merged-away row.check_shape_currency's counts (:4631-4634). The five states print always, as before. Every other state inupdate-shape.py'sORDERwith a non-zero count follows them, inORDER's order:merged-away,already-at-target,upstream-removed,unmapped,copy-missing,copy-is-link.merged-awaystill comes straight after the five, so a root with none of the new states reads byte for byte as before.detail.countsalready held them all.Tested
The tests are in
tests/test_update_shape_merged_away.py, which owns #502's merged-away root and its helpers.pinsrow beside a copy kept as a link (:450, through thelinked_beside_mergedfixture at:420):refusal_ahead == {"AGENTS-shape.md": "copy-is-link"}, NEXT withoutupdate-shape.py, no "the `apply` it prints drops", and the REFUSAL AHEAD clause named. The fixture first asserts thatcheckitself saysmerged-away,copy-is-linkand REFUSAL AHEAD over the link.:461): the summary ends/ 1 merged-away / 1 copy-is-link, and its counts add up todetail.counts. The old merged-away counts test now shares that helper,assert_the_counts_sum_to_the_rows(:403).pinsrow beside a hard-linked pin (:472):refusal_ahead == {"contracts/shape-pin.yaml": "hard-link"}, and the gate is kept.checkreads (:486): nothing is fetched (rev-list --missing=print, as shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742's tests ask), and the gate is kept. It imports shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742'smoved,partial_clone_ofanddoctored(:48).shape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742's head, without this change, the four new tests fail; the old counts test passes.refusals_ahead's partial-clone guard removed, the fourth fails on "the doctor fetched".Command, run in three groups at once (stub
ghfirst on PATH,GH_TOKEN=bogus, emptyGH_CONFIG_DIR):Results, all on
b7312258:tests/test_shape_doctor.py:428 passed, 3 skipped, 1 xfailed in 356.07s (0:05:56).tests/test_update_shape_merged_away.py,tests/test_update_shape_hardlink.py,tests/test_doctor_partial_clone.py,tests/test_doctor_gitlink_repairs.pyandtests/test_doctor_pin_restore_merges.py, together:98 passed in 135.01s (0:02:15).tests/test_doctor_next_is_a_command.py,tests/test_doctor_legs_members_next.py,tests/test_pin_source_env_collisions.py,tests/test_shape_pin_files_not_a_list.py,tests/test_update_shape_next_quoting.py,tests/test_repo_hygiene.pyandtests/test_cli_reference.py, together:496 passed in 254.38s (0:04:14).Pinned copies
None.
shape-doctor.pyis not copied into projects.Docs
No doc states the
pinsrow's merged-away sentence or theshape currencyrow's list of counted states, so README.md, AGENTS.md, the handbook anddocs/cli.mdare unchanged.Lands after
shape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742 (shape-doctor.py: run from a partial clone of the standard, theshape currencyrow fetches what the clone lacks, and with its remote out of reach calls each copy the standard changed since the pinboth, so a doctor that fetches nothing fetches, and offline says DRIFTED of copies nobody edited #683). This PR is stacked on it.→ #676and ticked when this closes. That is not done here.🤖 Generated with Claude Code
Summary by Sourcery
Make shape diagnosis refuse safely when upstream data is unavailable and accurately reflect update-shape refusal behavior and state counts.
New Features:
Bug Fixes:
Enhancements:
Tests: