From f050e335af79b2c92e587982fef32d605e5180b0 Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:28:50 +0000 Subject: [PATCH 1/2] shape_materialize.py: `force_stage`'s and `stage_written`'s docstrings state the contract, that a caller writing into a checkout somebody works in calls `force_stage` alone and never after a sweep, instead of counting two callers, so a change to the helper is no longer checked against `update-shape.py` alone with `family.py`'s `add`, `bump` and `remove` commits missing (#369) `force_stage`'s docstring said TWO CALLERS, AND ONLY ONE OF THEM SWEEPS FIRST, and named `update-shape.py apply --add` as the one that calls it alone; `stage_written`'s closing sentence said the same. Since #242 (PR #308) `family.py`'s `_commit_one` (every `add`, `bump` and `remove` commit) and `cmd_bump`'s re-stage call it alone too, for exactly the reason the docstring gave for `update-shape.py`: they write into a holder somebody may be working in, where `-A` would stage their work beside the shape's. A maintainer who read the "ALONE" sentence before changing the helper (adding a sweep, or changing "Empty, this runs nothing") was told one caller depended on it, when two tools do. The decision is to state the contract without a count, which is the second of the three ways the issue offers and the one a third alone-caller cannot make stale again: a caller that writes into a checkout somebody works in calls `force_stage` alone, never after a sweep, and `force_stage` never sweeps itself. The reason the old text gave is kept (`-A` would stage their work beside the shape's; the plain `git add --` that died on an ignored addition after the copies, the pin and the branch were written, #202), and `family.py`'s commits (#242) are named as the second case that put the rule there rather than as a count. `stage_written`'s closing paragraph is rewritten the same way, and drops its own "THREE CALLERS" count: it keeps the list of those that make the tree they stage, and says what a caller that does not make it calls instead. Docstring-only. `ast.dump` of the module with its docstrings stripped is identical before and after, so no behaviour moved. The net the issue names, `tests/test_family_add_excludes.py` and `tests/test_update_shape_add.py`, was run over the changed bytes. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 --- scripts/shape_materialize.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/scripts/shape_materialize.py b/scripts/shape_materialize.py index e6f41ead..a408247a 100644 --- a/scripts/shape_materialize.py +++ b/scripts/shape_materialize.py @@ -851,13 +851,19 @@ def force_stage(work: Path, paths=()) -> None: No copy-list entry holds a glob character today; the day one does, it stages itself and no neighbour. - TWO CALLERS, AND ONLY ONE OF THEM SWEEPS FIRST. `stage_written` runs - `git add -A` before it, because it stages a tree this tool has just - made. `update-shape.py apply --add` calls this ALONE (#202): it writes - into a checkout somebody may be working in, where `-A` would stage their - work beside the shape's, and its plain `git add --` died on an ignored - addition after the copies, the pin and the branch were already written. - Empty, this runs nothing. + A CALLER THAT WRITES INTO A CHECKOUT SOMEBODY WORKS IN CALLS THIS ALONE, + NEVER AFTER A SWEEP. `stage_written` runs `git add -A` before it, and is + for a caller that stages a tree this tool has just made, where every path + is its own. In a checkout somebody may be working in, `-A` would stage + their work beside the shape's, so such a caller names its paths and + forces those and nothing else, and this function never sweeps: a caller + that wants a sweep makes it first, as `stage_written` does. Two cases put + the rule here. `update-shape.py apply --add` (#202): its plain `git add + --` died on an ignored addition after the copies, the pin and the branch + were already written. And `family.py`'s `add`, `bump` and `remove` + commits (#242), which stage a gitlink and `family.yaml` in a holder + somebody may be working in. The next caller of that kind is bound by the + same rule without being named here. Empty, this runs nothing. """ paths = list(paths) if paths: @@ -883,10 +889,12 @@ def stage_written(work: Path, written=()) -> None: landed, in `root_key`'s POSIX spelling, which `git` takes as a pathspec on Windows too. Empty, this is `git add -A -- .` and nothing else. - ONE HELPER, THREE CALLERS: `git_init_commit` (the scaffold's two legs and - `adopt-project.py`'s seeded leg), the scaffold's assembly root and - `family.py init`'s holder. The forced half is `force_stage`, which - `update-shape.py apply --add` calls on its own (#202). + A CALLER THAT HAS JUST MADE THE TREE IT STAGES CALLS THIS, as + `git_init_commit` does (the scaffold's two legs and `adopt-project.py`'s + seeded leg), and the scaffold's assembly root and `family.py init`'s + holder. A caller that writes into a checkout somebody works in does not: + `-A` would stage their work beside the shape's, so it calls the forced + half, `force_stage`, on its own (#202, #242). """ run(["git", "add", "-A", "--", "."], cwd=work) force_stage(work, written) From 080baddcfde50713bebdf427f75c0d6564d82c9b Mon Sep 17 00:00:00 2001 From: Brett Heap <1513478+brettheap@users.noreply.github.com> Date: Mon, 12 Oct 2026 16:01:55 +0000 Subject: [PATCH 2/2] shape_materialize.py: `force_stage`'s docstring tells the history without a number and says exactly what each `family.py` verb stages, and its opening list of sharers names `family.py`'s `add`, `bump` and `remove` commits, so the contract is no longer stated with a count or with a stage list that is wrong for `remove` (#369) The review of f050e33 found two things wrong in the new paragraph and three small ones. "Two cases put the rule here" was a count, which the issue's decision 2 asks the text not to carry, and it overclaimed: #202 (150c41d) created the function, the call-alone shape and the old "TWO CALLERS" text, and #242 only ADOPTED the rule for `family.py`'s commits. The paragraph now says what happened without a number: the rule began with `update-shape.py apply --add` (#202), and `family.py`'s commits took it up (#242). "which stage a gitlink and `family.yaml`" was inexact. `add` stages the gitlink, `.gitmodules` and `family.yaml`; `bump` the gitlink and `family.yaml`; `remove` hands `force_stage` `.gitmodules` and `family.yaml` only, the gitlink's deletion already staged by its `git rm`. The paragraph says each. The opening sentence's list of tools that share the helper omitted `family.py`'s `add`, `bump` and `remove` commits, the same gap the issue is about; they are named beside `init`. In `stage_written`'s closing paragraph the trailing list ("and the scaffold's assembly root and `family.py init`'s holder") gets a verb of its own, and no code span is broken across a line. Docstring-only, as before: `ast.dump` of the module with its docstrings stripped is identical to origin/main's, to 3ae0109's and to f050e33's. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 --- scripts/shape_materialize.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/scripts/shape_materialize.py b/scripts/shape_materialize.py index a408247a..e7bcf134 100644 --- a/scripts/shape_materialize.py +++ b/scripts/shape_materialize.py @@ -832,9 +832,10 @@ def env_commit(work: Path, message: str) -> None: def force_stage(work: Path, paths=()) -> None: """Stage exactly `paths`, FORCED past any ignore rule, and nothing else. - THE FORCED ADD THE SCAFFOLD, `family.py init`, `adopt-project.py`'S - SEEDED LEG AND `update-shape.py` SHARE; the seeded leg reaches it - through `git_init_commit`, as `stage_written` says. A file the shape + THE FORCED ADD THE SCAFFOLD, `family.py` (`init`, and every `add`, `bump` + and `remove` commit), `adopt-project.py`'S SEEDED LEG AND + `update-shape.py` SHARE; the seeded leg reaches it through + `git_init_commit`, as `stage_written` says. A file the shape wrote and pinned has to reach the commit whatever the operator's own excludes say — a global `core.excludesFile`, `.git/info/exclude`, a `.gitignore` the project keeps — because a pin row naming a file the @@ -857,13 +858,15 @@ def force_stage(work: Path, paths=()) -> None: is its own. In a checkout somebody may be working in, `-A` would stage their work beside the shape's, so such a caller names its paths and forces those and nothing else, and this function never sweeps: a caller - that wants a sweep makes it first, as `stage_written` does. Two cases put - the rule here. `update-shape.py apply --add` (#202): its plain `git add - --` died on an ignored addition after the copies, the pin and the branch - were already written. And `family.py`'s `add`, `bump` and `remove` - commits (#242), which stage a gitlink and `family.yaml` in a holder - somebody may be working in. The next caller of that kind is bound by the - same rule without being named here. Empty, this runs nothing. + that wants a sweep makes it first, as `stage_written` does. The rule + began with `update-shape.py apply --add` (#202), whose plain `git add --` + died on an ignored addition after the copies, the pin and the branch were + already written. `family.py`'s commits took it up (#242), each in a + holder somebody may be working in: `add` stages the gitlink, + `.gitmodules` and `family.yaml`; `bump` the gitlink and `family.yaml`; + `remove` `.gitmodules` and `family.yaml`, the gitlink's deletion already + staged by its `git rm`. The next caller of that kind is bound by the same + rule without being named here. Empty, this runs nothing. """ paths = list(paths) if paths: @@ -889,9 +892,9 @@ def stage_written(work: Path, written=()) -> None: landed, in `root_key`'s POSIX spelling, which `git` takes as a pathspec on Windows too. Empty, this is `git add -A -- .` and nothing else. - A CALLER THAT HAS JUST MADE THE TREE IT STAGES CALLS THIS, as + A CALLER THAT HAS JUST MADE THE TREE IT STAGES CALLS THIS. `git_init_commit` does (the scaffold's two legs and `adopt-project.py`'s - seeded leg), and the scaffold's assembly root and `family.py init`'s + seeded leg), and so do the scaffold's assembly root and `family.py init`'s holder. A caller that writes into a checkout somebody works in does not: `-A` would stage their work beside the shape's, so it calls the forced half, `force_stage`, on its own (#202, #242).