Skip to content

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) - #398

Merged
brettheap merged 3 commits into
mainfrom
docs/force-stage-callers
Oct 12, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-4 (openRepoShape-4)
Claim: #369 (comment)

What

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, and 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.

Both docstrings now state the contract without a count (the issue's decisions 2 and 3):

Why

The "ALONE" sentence is the reason force_stage must never grow a sweep of its own. A maintainer who reads it before changing the helper was told one caller depends on it, when two tools do, and a change made on that reading is checked against update-shape.py alone.

Checked

Docstring-only: ast.dump of scripts/shape_materialize.py with its docstrings stripped is identical to origin/main's before and after. The net the issue names, tests/test_family_add_excludes.py and tests/test_update_shape_add.py, ran over the changed bytes on Linux: 40 passed (/usr/bin/python3 -m pytest tests/test_family_add_excludes.py tests/test_update_shape_add.py -q). CI's three OSes are the gate.

Closes #369

🤖 Generated with Claude Code

Summary by Sourcery

Clarify staging helper contracts to prevent unrelated work from being staged by shape and family operations.

Enhancements:

  • Clarify the staging helper contracts for callers that write into active checkouts, including the family add, bump, and remove workflows.
  • Document that forced staging must remain path-specific and must not perform an implicit workspace sweep.

…s 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 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown

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 6 hours and 41 minutes by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 12, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Updates the force_stage and stage_written docstrings to describe the caller-based staging contract, including family.py add/bump/remove commits, without changing code behavior; verification confirms identical non-docstring AST and 40 relevant tests passing.

File-Level Changes

Change Details Files
Clarify the staging helper contracts without changing executable behavior.
  • Replace caller-count language with the rule that checkout-writing callers invoke force_stage alone and never after a sweep.
  • Explain that stage_written performs the sweep only for callers that just created the tree, while naming the update-shape.py and family.py cases that require forced path-only staging.
  • Retain the rationale for avoiding git add -A and the ignored-addition failure context.
scripts/shape_materialize.py
Verify the documentation-only nature and affected staging behavior.
  • Confirm the AST is unchanged after stripping docstrings.
  • Run the family-add-excludes and update-shape-add test suites successfully, with 40 tests passing on Linux.
scripts/shape_materialize.py
tests/test_family_add_excludes.py
tests/test_update_shape_add.py

Assessment against linked issues

Issue Objective Addressed Explanation
#369 Update force_stage's docstring to state its staging contract without a stale caller count: callers writing into a checkout someone may be working in call it alone, while callers needing a sweep do that separately first. ✅
#369 Update stage_written's closing text to describe its relationship to force_stage without relying on a stale caller count. ✅

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

…hout 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 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@sonarqubecloud

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-3, 2026-10-12T19:54:04Z) A v1.0.0 scope notice. Brett Heap scoped openRepoShape's first release today: every procedure in AGENTS.md works end to end for ordinary repositories on Linux, macOS and Windows. The tag waits for every in-scope issue to close and a full test run to pass on all three OSes on the release commit. This PR closes #369 (P2, parent #459): in scope, in the v1.0.0 milestone. Nothing changes in how it lands. Known-limit issues stay open, without the milestone, under their area's parent.

brettheap pushed a commit that referenced this pull request Oct 12, 2026
…plan-manifest-invalid` joins `_entry_findings` after #255's values, and this branch's `plan-root-symlink` stays after the source's own submodules

origin/main moved 92e29d9 -> dd8d0ea (twelve commits), merged by sha:
- f0a8bf6 (#330 via #381), scripts/family.py;
- 87d2eb7 (#333 via #379), update-shape.py;
- cd1fc79 (#327 via #375), shape-doctor.py;
- 45e01f4 (#348 via #396), tests/test_agents_md_codes.py: every
  code-shaped name AGENTS.md spells is a code a tool prints;
- 3ef059c (#258 and #261 via #314), adopt-project.py, scaffold-project.py
  and scripts/shape_materialize.py: the project's YAML reader and its
  `validate-manifest.py` are asked about the manifest's values before
  anything exists; `check` reports `plan-manifest-invalid` through
  `_manifest_findings` in `_entry_findings`; `_repository_urls` refuses
  `adopt-name-too-long`;
- f7f4b14 (#366 via #399), scripts/family.py;
- cb7063f (#301 and #280 via #390), scripts/repo_shape.py and the
  template's validate-manifest.py: a bracket or brace in a plain flow
  scalar is refused;
- 5678f68 (#278, #297 and #298 via #357), shape-doctor.py;
- 53bee5b (#289 via #352), scripts/bump-leg.py;
- f5cb616 (#369 via #398), scripts/shape_materialize.py: the docstrings
  of `force_stage` and `stage_written`;
- a92e60b (#326 via #400), scripts/family.py;
- dd8d0ea (#292 via #353), adopt-project.py: `_elector` keeps a
  `user.name` byte that is not UTF-8, `_remote_repository` reads an
  origin URL that is not UTF-8 as none, and `adopt-no-org` is asked
  before `checked_value`.

scripts/shape_materialize.py, scripts/repo_shape.py and every other file
merge with no conflict. adopt-project.py conflicts in two hunks of
`_entry_findings`, each resolved by keeping both sides:
1. The docstring. Main's order, with #261's manifest findings after
   #255's values and before #190's relative urls; this branch's symlinks
   stay after the source's own submodules.
2. The return. Main's `_manifest_findings(plan)` call, and the findings
   in main's order: each entry's path (#196), coverage, the legs and what
   the root keeps where the shape writes, the source's own submodules
   (#166), this branch's `_root_symlink_findings`, #255's values, #261's
   manifest findings, and #190's relative urls last. The remediation and
   the NOTEs are main's.

How each of main's commits that reach this branch's code meets it:
- 3ef059c: `_manifest_findings` asks the validator the template ships
  about the values `execute` writes; it reads no symlink and no path
  the shape writes. Its materializer half adds the validator's helpers,
  `refuse_unstorable_names` and `MANIFEST_VALIDATOR`, and holds
  `election_date` to the validator's `DATE_RE`; it leaves `place`,
  `_materialize`, `copy_tree`, `template_files` and `_refuse_unwritable`
  as they were.
  In `execute`, its `adopt-name-too-long` (in `_repository_urls`) comes
  before #255's clone-URL refusal, `_refuse_what_check_finds` and this
  branch's `core.symlinks` probe, all before any leg. Its new paragraph
  of the module docstring follows the one 6b53a6d corrected; both merge
  with no conflict.
- f5cb616: docstrings only.
- dd8d0ea: `_remote_repository`, `_checked_plan_inputs`' organisation and
  `_elector`, none of them this branch's.
- cb7063f: the plans `check` and `execute` read go through this reader.
  The plans `plan` writes for this branch's tests hold their entries as
  block mappings, each path `double_quoted` where it needs to be, and
  the rehearsal below read every one of them.

Checked on the merged tree:
- AST (fin183g/names.py, base 92e29d9): no top-level name is bound twice
  in adopt-project.py, scaffold-project.py or scripts/repo_shape.py, and
  none is loaded but bound nowhere. scripts/shape_materialize.py binds
  `YamlError` and `parse_yaml` twice, as main dd8d0ea does (1f90a0f's
  import and 3ef059c's, the same objects); the merge adds none.
- Both sides intact (fin183g/sides_intact.py): the only lines that differ
  from either side's own change are those of the two hunks.
- A rehearsal of this merge (tree d21f8b4, the same as this one) passed
  tests/test_adopt_root_symlinks.py with main's
  test_adopt_{manifest_values,git_reads,relative_submodule_url}.py,
  test_scaffold_manifest_values.py, test_agents_md_codes.py and
  test_yaml_flow_brackets.py: `440 passed`.

Lane: openreposhape-1 (openRepoShape-1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant