Skip to content

adopt-project.py, shape_materialize.py and the assembly-root template: a kept GNUmakefile/ is a directory GNU make stops at, python3 scripts/bootstrap.py names python on Windows, and the template says what to run where make reads another file - #1207

Draft
brettheap wants to merge 7 commits into
mainfrom
fix/1132-template-bootstrap

Conversation

@brettheap

@brettheap brettheap commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Goal

#1132, the parts that hold on main: the assembly-root template says what to run where GNU make reads a kept GNUmakefile or makefile first; a kept DIRECTORY of either name is named as one GNU make stops at, in the CONTRACTS_DIR line and in execute's NEXT; every sentence that names python3 scripts/bootstrap.py for a person to run names python on Windows too; and two follow-up texts no longer say more than is true (RV1105b's NIT-2 and NIT-3a).

This PR was built on main 961640c6. Lane 4's #1121 (#1047) landed as 0ed3e57f8 at 21:37Z, and this round merged it in (6fa1edd56): NEXT now words a kept file (#1121's) and a kept directory (this PR's) through one lookup and one clause. #1105 landed as 12122952b on 2026-11-08, and this PR merges it in (see "Main merge after #1105"). The items that live only in #1105's code, and the parts of #1121's that this merge does not do, are in their own issue, #1240 (see "Left"). Item 5 is #1206.

Main merge after #1105

Lane 4's #1105 (#1048) landed as 12122952b at 16:45Z. This round merges main into the branch with git merge origin/main, never a rebase, and does it twice:

The first merge conflicted in scripts/shape_materialize.py only: 3 hunks, all in collision_follow_up, as "Landing order" said. adopt-project.py and templates/assembly-root/AGENTS-shape.md merged with no conflict.

Both sides' behaviour stays: #1105's second GNU_MAKE_READS, its _predict_collisions lookup and its collision line, and this PR's _make_reads_first, _make_reads_first_clause and NEXT. None of #1240's unification is done here (one tuple, one lookup).

The three hunks

hunk this PR (6fa1edd56) main (46fe6ac19, #1105) resolution
1. The docstring's NIT-4 sentence "This said "both callers say it", and execute calls it nowhere" "Materialized.follow_ups() would say this of them, and nothing calls it" Main's. Both sides rewrote the same sentence for the same finding. Main's also names Materialized.follow_ups(), which #1240's item G changes. So this PR no longer touches that sentence, and the last scripts/shape_materialize.py bullet under "What changed" is #1105's text now.
2. The docstring's Makefile paragraphs the BOOTSTRAP_COMMAND sentence and NIT-2's tail the heading "WHERE make READS THE MERGED FILE", and the "BUT GNU MAKE READS" paragraph Both. Main's heading and both paragraphs. This PR's sentences on BOOTSTRAP_COMMAND and on NIT-2's tail go in the first paragraph, and one new sentence goes in the second: "Both name that command as BOOTSTRAP_COMMAND does, python on Windows as well (#1132)."
3. The returns two returns, each one sentence arrive, joint, meanwhile and until set per branch, then one pair of returns #1105's structure, with a per-branch then in place of until. With no reads_first, the line is this PR's, byte for byte. With one, it is #1105's, byte for byte, except that the command a person runs is BOOTSTRAP_COMMAND, both in meanwhile and in "and then ... runs it".

#1105's test pins

tests/test_adopt_collision_make_reads.py is #1105's file, and it pins the texts this PR changes, as "Landing order" expected.

  • Four pins move. MERGE_IS_ENOUGH and BOTH_MERGES (no reads_first) move to this PR's words. MEANWHILE and SCRIPT_MERGED (with one) move to python on Windows. All four go through a new COMMAND.

  • One comment changes. The comment on the Makefile+scripts row now says what NIT-2's tail says.

  • Measured: before the move, the six adopt and pin files gave 11 failed and 242 passed, and the 11 are exactly the rows that use those four pins. After the move, 253 passed.

  • On main's code: main 46fe6ac19's four production files (adopt-project.py, scripts/shape_materialize.py, templates/assembly-root/AGENTS-shape.md and README.md) were swapped into the mutant worktree at e5e077940. 64 of the three files' 106 rows fail:

    • in this file, exactly those 11 rows, and the other 20 pass;
    • in the new file, 34 of 35, as before (NIT-4's row passes);
    • in tests/test_adopt_next_make_reads_first.py, 19 of 40, as before.

    The four files were then restored, each matched the head by sha256 (4 of 4), and the tree was clean.

NIT-A

tests/test_adopt_next_make_reads_first.py:64 called #480's NOTE "unchanged", but the merge of #1121 moved that pin to python on Windows. The comment now says the NOTE "names python on Windows too since #1132". The change is a comment only, in its own commit.

The second merge, after #1123

Lane 1's #1118 and #1123 landed while the first merge was being gated: 9ec0dc79c at 17:30Z and 9729315f1 at 17:47Z. Both merge with e5e077940 with no conflict in the text. The predicted merge with 9ec0dc79c passed (gate P below). The predicted merge with 9729315f1 failed 1 row of 254 (gate P2):

id mutant rows failing result
D1 _make_reads_first drops a directory's /, so the line is the one #1123's row pinned 1 of 34, the row killed
D2 #1123's R10: twins reads the entry with its slash 1 of 34, the row, with IndexError killed

Mutants on the resolution

H3a-H3g and U1-U3b are the first merge's. All ran in the mutant worktree at e5e077940.

  • Method: each mutant was applied alone, then restored with git checkout. Each restore matched by sha256 (shape_materialize.py b18f09d7c396, adopt-project.py 2055a81acdc6), and the tree was clean after each.
  • Rows: H3a-H3g, U3 and U3b ran against 93 rows: tests/test_adopt_collision_make_reads.py (31), the new file (35) and tests/test_adopt_e2e.py (27). U1 ran against the new file, tests/test_adopt_make_reads_first.py (33) and tests/test_adopt_collision_make_reads.py, 99 rows. U2 ran against the new file and tests/test_adopt_next_make_reads_first.py (40), 75 rows.
  • Unmutated, every row passes (gate A below).
id mutant rows failing result
H3a hunk 3, no reads_first: meanwhile back to main's plain python3 4 of 93 killed
H3b hunk 3, no reads_first: the script tail back to main's "before both merges are made" 4 of 93 killed
H3c hunk 3, with reads_first: meanwhile without BOOTSTRAP_COMMAND (main's) 5 of 93 killed
H3d hunk 3, with reads_first: "and then ... runs it" without BOOTSTRAP_COMMAND (main's) 3 of 93 killed
H3e hunk 3, with reads_first: this PR's tail in place of #1105's 3 of 93 killed
H3f hunk 3: reads_first ignored, so the resolution is this PR's side whole 15 of 93 killed
H3g hunk 3, with reads_first: #1105's joint ("; and") lost 3 of 93 killed
U1 _makefile_follow_up drops _make_reads_first (first = None) 31 of 99 killed
U2 _next_steps drops _make_reads_first (first = None) 35 of 75 killed
U3 _predict_collisions asks _make_reads_first in place of #1105's lookup 0 of 93 survived
U3b _predict_collisions passes no reads_first, so #1105's lookup is lost in the merge 15 of 93 killed

Runs

Each run went through the lane's runner, with git-filter-repo 2.47.0 (CI's pin) on PATH, on a commit with a clean tree.

run git files passed failed skipped
gate A2 2.43.0 01e93b6d1, the head: the 12 files below 1096 0 3: the PowerShell rows (test_shape_doctor.py:10516), which run only on a Windows host
gate B2 2.47.1 01e93b6d1: the same 12 files 1096 0 3: the same
gate A 2.43.0 e5e077940, after the first merge: the 12 files below 1095 0 3: the same
gate B 2.47.1 e5e077940: the same 12 files 1095 0 3: the same
gate P 2.43.0 the predicted merge of e5e077940 with main 9ec0dc79c (#1118; tree ba3f3c1f3, a local commit object, not pushed): the six adopt and pin files, plus #1118's tests/test_adopt_root_shape_paths.py and tests/test_adopt_symlink_target_bytes.py 509 0 0
gate P2 2.43.0 the predicted merge of e5e077940 with main 9729315f1 (#1123; tree 0eb290ae2, a local commit object): the six adopt and pin files 253 1: #1123's makefile-directory row, which the second merge moves 0
gate P4 2.43.0 the predicted merge of 01e93b6d1 with main 801a7d6d0 (#1106; tree 25bc27d39, a local commit object): the six adopt and pin files, plus #1106's tests/test_adopt_one_byte_locale.py and tests/test_adopt_unenterable_source_and_locale.py 277 0 0

The files are the six adopt and pin files (tests/test_adopt_collision_make_reads.py, tests/test_pinned_copy_text.py, the new file, tests/test_adopt_next_make_reads_first.py, tests/test_adopt_e2e.py and tests/test_adopt_make_reads_first.py, 253 rows), plus tests/test_shape_doctor.py and tests/test_scaffold_leg_path_collisions.py, which adopt changes need, plus the hygiene five (test_path_guard_hygiene.py, test_repo_hygiene.py, test_agents_md_codes.py, test_cli_reference.py and test_pinned_copy_text.py). That is 1098 rows in one run at e5e077940, and 1099 at 01e93b6d1, where #1123 adds one row.

What held on main, before the change

All of this was measured on main 961640c68 with GNU make 4.3, offline.

item on main
1, the template AGENTS-shape.md (the "Work from here" paragraph and the five-commands table) and README.md (Get started, and the parking steps) give make bootstrap with no caveat. With the template's own Makefile beside a plain GNUmakefile or makefile, make -n bootstrap exits 2, No rule to make target 'bootstrap'.
2, a kept directory With GNUmakefile/ or makefile/ beside Makefile, make bootstrap exits 2 with make: *** GNUmakefile: Is a directory. Stop., or the same with makefile. It does this for every target, and for each directory alone too. Main's CONTRACTS_DIR line says "make there exports CONTRACTS_DIR only once GNUmakefile does". NEXT prints make bootstrap for a root that keeps only GNUmakefile/. Beside a kept Makefile, it prints #480's NOTE that the targets "arrive only once the follow-up merges the two". Rendered by the lane's probe_texts.py on main.
3, python3 The template's scripts/bootstrap.py says python3 scripts/bootstrap.py (python on Windows). #480's NOTE and the Makefile collision follow-up's "until then" name python3 alone.
NIT-2 The no-reads_first script-aside tail says "neither make bootstrap nor python3 scripts/bootstrap.py runs the shape's bootstrap before both merges are made". In a root that keeps its own Makefile and scripts/bootstrap.py, merging the script alone makes python3 scripts/bootstrap.py print the shape's SHAPE-BOOT, while make bootstrap still exits 2. Measured again by a row below.
NIT-3a _predict_collisions says the wording comes from "the same function execute reports its actual collisions through", and collision_follow_up says "Both callers say it". execute never calls collision_follow_up: it carries the plan's stored follow_ups: into the split message.

What changed

adopt-project.py:

scripts/shape_materialize.py:

templates/assembly-root/:

  • AGENTS-shape.md: the "Work from here" paragraph adds "; where the root keeps a GNUmakefile or a makefile, which GNU make reads before Makefile, run python3 scripts/bootstrap.py (python on Windows), which is what the shape's make bootstrap runs." A sentence after the five-commands table says they are the shape's Makefile's targets, which make at the root reaches only where the file it reads first holds them, or includes a Makefile that does.
  • README.md: one sentence after Get started covers make bootstrap "here and below". A second says make park and make resume below work there only where that file holds them, or includes a Makefile that does.
  • This is a template byte change, so every project re-pins. No digest of either file is stored in this repository. tests/ was grepped for every changed line, and only this PR's own rows pin them (see "Template pins").

Tests:

This round (#1207's review, issuecomment-6374516382)

finding what changed
SHOULD-1 A row pins NEXT where only scripts/bootstrap.py went aside beside a kept GNUmakefile/ (test_next_after_the_script_merge_names_python3_where_make_stops). It fails under the review's mutant X1.
SHOULD-2 The "Left" list is one issue, #1240.
SHOULD-3 "Landing order", below. #1121 landed while this round ran, and is merged and resolved here.
NIT-1 AGENTS-shape.md's sentence after the table loses "(above)": nothing above names a Makefile that includes another.
NIT-2 README.md names make park and make resume.
NIT-3 Beside a kept Makefile, the directory NOTE names the merge as well as the directory. Its row is parametrized over GNUmakefile/ and makefile/.
NIT-4 An adoption sends GNUmakefile/ to the code leg, and NEXT names make bootstrap. It fails under the review's mutant X6.
NIT-5 The notes for the squash message at landing, below.

Rows that fail on main

Measured at the merge head 6fa1edd56, in a scratch worktree:

  • main 0ed3e57f8's bytes of the four production files (adopt-project.py, scripts/shape_materialize.py, templates/assembly-root/AGENTS-shape.md, templates/assembly-root/README.md) were checked out over the head;
  • the new file (35 rows) and tests/test_adopt_next_make_reads_first.py (40 rows) ran through the lane's runner;
  • the four files were restored with git checkout, and each matched the head by sha256 (c94bd19b, 12555f39, 8660bafb, b3f9409b).
file fail on main pass on main
the new file 34 1: test_an_adoption_sending_a_gnumakefile_directory_to_a_leg
tests/test_adopt_next_make_reads_first.py 19 21

Main now has #1121, so its _next_steps takes surviving=. The NEXT rows for a kept directory therefore fail on their words, not on a TypeError. Main reads GNUmakefile/ as a file: "then run python3 scripts/bootstrap.py; GNU make at the root reads the source's GNUmakefile, ...". This is the case for the SHOULD-1 row, the after-the-merges row, both directory rows, and NIT-3's makefile/ row.

The new file's other rows fail on main for these reasons:

  • the template rows: main's AGENTS-shape.md and README.md lack the sentences;
  • _make_reads_first: main has no such helper;
  • the CONTRACTS_DIR line: "only once GNUmakefile does";
  • the NOTE and collision rows: plain python3, and NIT-2's old tail;
  • the two adoptions that keep the directory.

The one new row that passes on main is NIT-4's. It sends GNUmakefile/ to the code leg, and there main's NEXT is already make bootstrap. It guards what main has: under the review's mutant X6, which hands NEXT every leg's paths, it is the row that fails.

The 19 rows of #1121's file that fail on main are exactly the rows that use the three pins this merge moved (OWN_MAKEFILE_NOTE, RUNS, script_aside). The 21 that pass use none of them.

Earlier heads, on main 961640c68 and with the same method: 1c7ca5c6d failed 29 of 35 rows, and 8b499842f failed 34 of 35 (the new file plus the three tests/test_adopt_e2e.py rows the mutants use).

Mutants

This round, at 75c90b6db, in a scratch worktree. Each mutant was applied alone and restored, and each file's sha256 was checked against the commit (4/4 every time). Each ran against 95 rows: the new file (35), tests/test_adopt_e2e.py (27) and tests/test_adopt_make_reads_first.py (33). Unmutated, all 95 pass.

id mutant rows failing (of 95) result
X1 the review's: the script-only NOTE names make bootstrap after the merge (if stops and makefile) 1 killed
X6 the review's: cmd_execute passes every leg's paths 1 killed
N1 NIT-3 undone: beside a kept Makefile, no merge named 2 killed
N2 the merge named where the script went aside too 1 killed
N3 the merge named where no Makefile was kept 4 killed
T1 NIT-1 undone: "(above)" back 1 killed
T2 NIT-2 undone: README's make park / make resume sentence removed 1 killed

The merge with #1121 rewrote the clause code that N1-N3 mutate (stopped became reads, and the merge condition gained stops). The mutants were not run again at 6fa1edd56.

Earlier, 18 mutants at 1c7ca5c6d against 43 rows, each applied alone in a scratch worktree and restored with its sha256 checked:

id mutant rows failing (of 43) result
M1 _make_reads_first drops the / of a directory 14 killed
M2 the clause's directory branch never taken 10 killed
M3 NEXT ignores a kept directory (stops = None) 6 killed
M4 NEXT's command stays make bootstrap for a directory 4 killed
M5 cmd_execute passes surviving=() 2 (the two adoptions) killed
M6 BOOTSTRAP_COMMAND without (python on Windows) 12 killed
M7 NIT-2's tail back to "neither ... before both merges are made" 2 killed
M8 AGENTS-shape.md's "Work from here" caveat removed 1 killed
M9 the sentence after the five-commands table removed 1 killed
M10 README.md's sentence removed 1 killed
M11 #480's NOTE back to plain python3 1 killed
M12 the collision follow-up's "until then" back to plain python3 2 killed
M13 after the merges, NEXT says make bootstrap where a directory stops make 1 killed
M14 the lookup takes the LAST kept name (reversed) 4 killed
M15 the directory NOTE's lead ignores a kept Makefile 1 killed
M16 the lookup folds case (fold=True) 4 killed
M17 the clause drops GNU make's own words (GNUmakefile: Is a directory.) 10 killed
M18 the file clause says does whatever has is 0 survived

M18 was equivalent before the merge: the file clause's only caller was the CONTRACTS_DIR line. Since the merge, #1121's NOTE calls it with has them, and tests/test_adopt_next_make_reads_first.py pins that wording. M18 was not run again.

Runs

Each run went through the lane's runner: gh stubbed first on PATH with a bogus token, GIT_SSH_COMMAND=false, GIT_CONFIG_GLOBAL=/dev/null, EMAIL set, -p no:cacheprovider, and tmp_path_retention_policy=failed. Each run is on a commit, with a clean tree.

run commit git files passed failed
gate M 6fa1edd56, the merge 2.43.0 the new file, test_adopt_next_make_reads_first.py, test_adopt_e2e.py, test_adopt_make_reads_first.py, test_path_guard_hygiene.py, test_repo_hygiene.py, test_agents_md_codes.py, test_cli_reference.py, test_pinned_copy_text.py, test_scaffold_leg_path_collisions.py 618 0
gate N 6fa1edd56 2.47.1 the new file and test_adopt_next_make_reads_first.py (75), then test_adopt_e2e.py (27), run one after the other 102 0
gate H 6fa1edd56 2.47.1 the hygiene five 379 0
gate A2 75c90b6db 2.43.0 the new file, test_adopt_e2e.py, the hygiene five (test_path_guard_hygiene.py, test_repo_hygiene.py, test_agents_md_codes.py, test_cli_reference.py, test_pinned_copy_text.py), test_adopt_make_reads_first.py, test_adopt_contracts_dir.py, test_adopt_printed_sentences.py, and the template's readers: test_scaffold_e2e.py, test_scaffold_free_text_values.py, test_free_plan_hint.py, test_template_quoted_values.py, test_template_values_single_pass.py, test_template_text_placeholders.py, test_bootstrap_placement_texts.py, test_park_resume_targets.py, test_update_shape_hardlink.py 1128 0
gate A 1c7ca5c6d 2.43.0 17 files (the new file, test_adopt_e2e.py, the hygiene five, test_scaffold_leg_path_collisions.py, test_adopt_make_reads_first.py, test_adopt_contracts_dir.py, test_adopt_printed_sentences.py, test_adopt_attributes_eol_block.py, test_adopt_leg_path_collisions.py, test_scaffold_e2e.py, test_bootstrap_placement_texts.py, test_park_resume_targets.py, test_template_text_placeholders.py) 966 0
gate B 1c7ca5c6d 2.47.1 the new file, test_adopt_e2e.py, test_path_guard_hygiene.py, test_repo_hygiene.py 209 0
doctor 1c7ca5c6d 2.43.0 test_shape_doctor.py, test_scaffold_leg_path_collisions.py 549 (3 skipped: the PowerShell rows, which run only on a Windows host) 0

Template pins

tests/ was grepped for every line this PR changes in templates/assembly-root/ ("They are the shape", "reads first holds", "includes a Makefile that", "(above)", "here and below", "Where this root keeps", "it is what the shape", "before Makefile", "python on Windows"). Only tests/test_adopt_make_bootstrap_followups.py pins them. Four other files read the README as a whole, and each passes in gate A2:

  • test_scaffold_free_text_values.py counts its lines against the template;
  • test_free_plan_hint.py looks for "Free plan" and --repo;
  • test_template_quoted_values.py and test_template_values_single_pass.py render it.

Merges

git merge-tree --write-tree of the head 01e93b6d1, on git 2.43.0, at 18:11Z.

Landing order

The conflicts are the same in every order, measured offline both ways round and with predicted squashes:

So the order decides only who resolves them. Both have landed, and lane 5 resolved both on this branch:

Main was merged in once more after lane 1's #1123 (merge 01e93b6d1): one of #1123's rows pinned the line this PR changes for a kept makefile/ directory, and it moved (see "The second merge, after #1123").

No other PR has to land first now. 6fa1edd56 was re-checked READY (rv1207b, rv1207c and rv1207d, issuecomment-6722602375). The head, 01e93b6d1, needs a check of its delta since then: the two merges' resolutions, the four moved pins in #1105's file, #1123's moved row, and NIT-A.

Left

#1240 tracks what this PR leaves:

The local branch fix/1132-template-bootstrap-stacked (9eb5cce0b, never pushed and never tested) is a model of the end state on #1105 + #1121 + main.

For the squash message at landing

Limits

Closes #1132

Lane: openreposhape-5 (openRepoShape-5)

🤖 Generated with Claude Code

Summary by Sourcery

Clarify bootstrap and GNU make behavior across assembly-root templates and adoption workflows, including kept GNUmakefile/makefile directories and Windows command guidance.

New Features:

  • Document the correct bootstrap command and make-target behavior for assembly roots containing earlier GNU make file names or directories.
  • Detect kept GNUmakefile/ and makefile/ directories and provide accurate adoption follow-up and NEXT guidance.

Bug Fixes:

  • Correct bootstrap guidance for Windows by identifying python as the interpreter alternative.
  • Fix collision follow-ups describing when the shape bootstrap becomes runnable after script and Makefile merges.
  • Correct collision-plan documentation to reflect how follow-ups are stored and reported.

Enhancements:

  • Share GNU make precedence and bootstrap command wording across adoption planning, execution guidance, and materialization follow-ups.

Documentation:

  • Update assembly-root AGENTS and README templates with caveats for GNU make precedence and Windows bootstrap invocation.

Tests:

  • Add comprehensive coverage for template guidance, GNU make file and directory precedence, adoption follow-ups, NEXT output, Windows wording, and real directory-collision adoptions.
  • Update existing expectations for unified bootstrap command wording and directory handling.

…: a kept `GNUmakefile/` is a directory GNU make stops at, `python3 scripts/bootstrap.py` names `python` on Windows, and the template says what to run where `make` reads another file

RV1105's and RV1121's follow-ups (#1132) that hold on main, measured on
main 961640c and with GNU make 4.3:

1. The template's AGENTS-shape.md ("Work from here" and the five-commands
   table) and README.md (Get started, and the parking steps below it) gave
   `make bootstrap` with no caveat. At the root of an adoption that kept a
   `GNUmakefile` or a `makefile`, GNU make reads that file first and finds
   no `bootstrap` rule. Each now says to run `python3 scripts/bootstrap.py`
   (`python` on Windows) there, which is what the shape's `make bootstrap`
   runs.
2. A kept DIRECTORY `GNUmakefile/` or `makefile/` stops GNU make for every
   target ("GNUmakefile: Is a directory.  Stop.", exit 2). The
   `CONTRACTS_DIR` line said `make` exports the variable "only once
   `GNUmakefile` does", and NEXT printed `make bootstrap` (or, beside a kept
   `Makefile`, #480's NOTE that the merge brings the targets). One lookup,
   `_make_reads_first`, now gives the name with a `/` for a directory, and
   `_make_reads_first_clause` says GNU make stops there; NEXT takes the
   plan's root paths (`surviving=`, as #1121 passes them) and names `python3
   scripts/bootstrap.py` there. A kept FILE in NEXT stays #1047's (#1121).
3. Every sentence that names `python3 scripts/bootstrap.py` for a person to
   run now names `python` on Windows, as the template's own
   `scripts/bootstrap.py` does (`BOOTSTRAP_COMMAND`): the collision
   follow-up's "until then" and #480's NOTE. Pasted command lines stay as
   they were.
NIT-2 (#1105's second review): where `scripts/bootstrap.py` went aside too,
   the Makefile's follow-up said neither command runs the shape's bootstrap
   "before both merges are made"; the script's merge alone lets `python3
   scripts/bootstrap.py` run it. It now says that command runs it once the
   script is merged, and `make bootstrap` once both merges are made.
NIT-3a: `_predict_collisions` said `execute` reports its collisions through
   `collision_follow_up`, and `collision_follow_up` said "both callers say
   it"; `execute` never calls it.

Left, as #1132's body names: item 4 (`GNU_MAKE_READS` twice, three
lookups), NIT-1, NIT-3b, NIT-3c, NIT-4, NIT-5 and the parents' own
instances of items 1-3, until #1105 and #1121 land; item 5 (follow-ups
stale after a hand edit of the plan) in its own issue.

Closes #1132

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Implementation

Define the shared bootstrap command and update collision follow-ups to describe accurately which command works after each merge, including the script-only case.

  • scripts/shape_materialize.py L435-440, L1036-1048, L1138-1146 (+2 small edits)

Detect the first GNU make precedence name retained at the root, distinguish directories from files, and use that distinction in the CONTRACTS_DIR follow-up.

  • adopt-project.py L318-321, L3806-3852, L3879-3884 (+1 small edits)

Correct the collision-prediction comment to describe how planned follow-ups are stored and carried into the split commit.

  • adopt-project.py L3911-3913, L6600-6607, L16285

UI

Make NEXT choose a runnable bootstrap command and explain GNU make’s behavior when a retained precedence name is a file or directory.

  • adopt-project.py L16900-16927, L16943-16947, L16957-16964 (+1 small edits)

Documentation

Explain in the assembly-root guidance when to run the bootstrap script instead of make, and when the shape’s make targets are reachable.

  • templates/assembly-root/AGENTS-shape.md L19-21, L108-112
  • templates/assembly-root/README.md L20-25

Tests

Update existing adoption expectations for the revised follow-up wording and Windows interpreter guidance.

  • tests/test_adopt_collision_make_reads.py L85-97 (+3 small edits)
  • tests/test_adopt_e2e.py L297-300, L441-445 (+2 small edits)
  • tests/test_adopt_make_reads_first.py L103-112, L195-198 (+2 small edits)
  • tests/test_adopt_next_make_reads_first.py L64-70, L79-80, L119

Add coverage for template instructions, GNU make file and directory precedence, NEXT and collision follow-ups, Windows command wording, and real adoption scenarios.

  • tests/test_adopt_make_bootstrap_followups.py L1-562

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

This was referenced Oct 23, 2026
…ure GNU make against the template's `Makefile` each assert the sentence they back, so they fail on main too, and #480's NOTE row asks `_next_steps` as main's signature can

Five rows of the new file measured GNU make 4.3 against the template's own
`Makefile`, and nothing tied them to the words they back, so they passed on
main 961640c as they pass here (#1132):

- the four `test_make_reads_the_template_makefile_only_where_nothing_comes_first`
  rows now assert AGENTS-shape.md's "Work from here" caveat, which their
  measurement backs;
- `test_the_script_merge_alone_lets_python3_run_the_shapes_bootstrap` now
  asserts the Makefile collision follow-up's tail where the script went
  aside too (NIT-2), which its measurement backs.

`test_the_480_note_names_python_on_windows` called `_next_steps` with
`surviving=`, which main's signature does not take, so on main it failed
with a TypeError and not on the NOTE's words. `next_said` now leaves
`surviving=` out when it is None, and the row asks the NOTE both ways:
through main's signature, and with the root's paths as `cmd_execute`
passes them.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap brettheap added this to the v1.2.1 milestone Oct 23, 2026
@brettheap

Copy link
Copy Markdown
Contributor Author

Independent review of #1207 (#1132) for lane openRepoShape-5

NOT READY

There is no MUST. On every path I measured, the code does what the title and body say. Three SHOULDs need action before landing. SHOULD-1 needs one new test row, so the head moves. SHOULD-2 is a body or issue edit. SHOULD-3 is a landing-order question. With those done, and CI green, I would call it READY.

Findings

SHOULD-1: the script-only-aside branch with a kept directory has no row (mutant X1 survives)

  • Where: adopt-project.py:16178-16179, the if stops inside the if script: NOTE.
  • What I changed: in my own worktree, mutant X1 turned if stops else " first, and then run \make bootstrap`."intoif stops and makefile else ...`.
  • Result: all 92 rows still pass (92 passed in 104.43s).
  • What the mutant breaks: a root that keeps only its own scripts/bootstrap.py plus GNUmakefile/ is told "merge shape/scripts/bootstrap.py into scripts/bootstrap.py first, and then run make bootstrap." GNU make 4.3 stops at that directory: make: *** GNUmakefile: Is a directory. Stop., rc 2.
  • What the head prints now (correct): "NOTE the root keeps its own scripts/bootstrap.py, and the shape's is shape/scripts/bootstrap.py, so neither make bootstrap nor python3 scripts/bootstrap.py runs the shape's bootstrap yet: merge shape/scripts/bootstrap.py into scripts/bootstrap.py first, and then run python3 scripts/bootstrap.py (python on Windows); GNU make at the root stops at the source's directory GNUmakefile/, ... only once the root keeps no directory of that name."
  • Why it matters: the body says "where the script went aside too, it names that command after the merges". Only the case where both files went aside is pinned (test_next_after_the_merges_names_python3_where_a_directory_stops_make, :280-295).
  • Fix: add a row, or a parametrize of that row:
    said = next_said(adopter, capsys,
                     [("scripts/bootstrap.py", "shape/scripts/bootstrap.py")],
                     ["scripts/", "GNUmakefile/"])
    assert said == (f"{HEAD}NOTE the root keeps its own scripts/bootstrap.py, and the "
        "shape's is shape/scripts/bootstrap.py, so neither `make bootstrap` nor "
        "`python3 scripts/bootstrap.py` runs the shape's bootstrap yet: merge "
        "shape/scripts/bootstrap.py into scripts/bootstrap.py first, and then run "
        f"{COMMAND}; {stops('GNUmakefile', RUN_THERE)}.\n")

SHOULD-2: Closes #1132 closes the issue while the body's "Left" items have no open issue

SHOULD-3: the landing order against #1105 and #1121 is not stated, and it conflicts both ways

NIT-1: "(above)" points at nothing above

  • Where: templates/assembly-root/AGENTS-shape.md:102-105.
  • Measured: "...only where the file it reads first holds them, or includes a Makefile that does (above)". Nothing above says "includes a Makefile". "Work from here" (:19-21) names the files but not the include.
  • Fix: drop "(above)" or point it at "Work from here". Do it in this PR, which already makes every project re-pin. A later fix makes them re-pin again.

NIT-2: the README caveat covers make bootstrap but not the parking targets

  • Where: templates/assembly-root/README.md:20-22.
  • Measured: "instead, here and below" covers the two make bootstrap lines. The same parking block has make park (:35) and make resume (:38), which fail the same way at such a root. The README says nothing about them, while AGENTS-shape.md:102-105 does.
  • Fix: add one clause saying those targets work only where the file GNU make reads first holds them, or includes Makefile. README.md has no shape-pin row, so this costs no re-pin.

NIT-3: beside a kept Makefile, the directory NOTE no longer mentions the merge

  • Where: adopt-project.py:16180-16185.
  • Measured by my probe: "NOTE the root keeps its own Makefile and the shape's is shape/Makefile, and GNU make at the root stops at the source's directory makefile/, ... so make bootstrap and the shape's other make targets run there only once the root keeps no directory of that name; ..."
  • Why it matters: removing the directory is not enough. The source's Makefile still has no bootstrap rule until the two are merged. The CONTRACTS_DIR line at the same root does say both.
  • Fix: where makefile is set, pass a reach like "make bootstrap and the shape's other make targets run there once the follow-up merges the two, and".

NIT-4: no row shows that NEXT reads only the root's paths (mutant X6 survives)

  • Where: adopt-project.py:15530.
  • Measured: mutant X6, surviving=paths_for["root"] + paths_for["code"] + paths_for["spec"], leaves all 92 rows passing.
  • Fix (optional): an adoption row where GNUmakefile/ goes to a leg and NEXT still prints make bootstrap.

NIT-5: two descriptions of what is left are slightly off

Checked and true

The PR and its body

  • The live body equals $Q/v124/n1132/body-post2.md up to its Lane line (diff gave no difference).
  • The head is 8b499842f, a draft on milestone v1.2.1, and it closes [1132].
  • I read the whole diff of all 6 files.
  • git diff --stat 1c7ca5c6d HEAD touches only tests/test_adopt_make_bootstrap_followups.py.
  • At the head, the sha256 of the four production files match the body: 400730ad, 660962cb, 9ff37f19, 4ece14bb.

The code

GNU make 4.3, measured with the template's Makefile:

  • GNUmakefile/ and makefile/ each give rc 2 and "Is a directory. Stop.".
  • GNUmakefile/ plus a makefile file gives "GNUmakefile: Is a directory."
  • makefile/ plus a GNUmakefile file gives "No rule".
  • A GNUmakefile or makefile file alone gives "No rule".
  • makefile/ plus GNUmakefile -> Makefile gives rc 0.
  • Each result agrees with _make_reads_first.

Rows on main

Gates (run9.sh; clean tree at 8b499842f):

run git files result
g243 2.43.0 the two changed test files 59 passed
g247 2.47.1 ($Q/v115/w764/git247/bin first on PATH) the two changed test files 59 passed
doc 2.43.0 test_shape_doctor.py, test_scaffold_leg_path_collisions.py 549 passed, 3 skipped (the PowerShell rows, Windows host only)
hyg 2.43.0 11 files (below) 766 passed
tpl 2.43.0 4 files (below) 197 passed
  • hyg: test_repo_hygiene.py, test_agents_md_codes.py, test_cli_reference.py, test_template_text_placeholders.py, test_pinned_copy_text.py, test_bootstrap_placement_texts.py, test_park_resume_targets.py, test_adopt_make_reads_first.py, test_adopt_contracts_dir.py, test_template_quoted_values.py, test_template_values_single_pass.py.
  • tpl: test_scaffold_e2e.py, test_scaffold_free_text_values.py, test_free_plan_hint.py, test_update_shape_hardlink.py.

My mutants (in wtm, each alone, against 92 rows: the new file, test_adopt_e2e.py and test_adopt_make_reads_first.py; unmutated, all 92 pass; each restored, sha256 4/4 OK):

id mutant result
X1 script-only-aside NOTE says make bootstrap SURVIVED (SHOULD-1)
X6 NEXT gets every leg's paths SURVIVED (NIT-4)
X7 stops = first, so a kept FILE takes the directory path SURVIVED; expected, since that case is #1121's and main's
X8 the after-table sentence loses (python on Windows) killed, 1 row
X9 README loses "here and below" killed, 1 row
X10 the directory clause drops "the first of ... it finds" killed, 10 rows

Template pins

  • I grepped tests/ for every changed line, and for nearby phrases ("Work from here", "run make bootstrap", "They are the shape", "here and below", "includes a Makefile that", "Where this root keeps", the five-commands table). Only the PR's own rows pin the changed lines.
  • No test or file stores a digest or byte size of either file. I grepped for the sha prefixes of both files at main and at the head, and nothing matched.
  • test_scaffold_e2e.py's rule list still finds "make bootstrap".
  • The README line-count check in test_scaffold_free_text_values.py counts against the template, and it passes.

Merge-tree

Not checked

(Posted by lane openRepoShape-5's coordinator. rv1207 is lane 5's independent opus reviewer; it wrote none of #1207. Lane 5 takes the three SHOULDs as a fix round: SHOULD-1's row, SHOULD-2's open issue for the Left items, and SHOULD-3's landing order with lane 4 for #1105 and #1121. #1207 lands only after a re-check is READY.)

Lane: openreposhape-5 (openRepoShape-5)

Brett Heap and others added 2 commits October 23, 2026 21:32
… kept `Makefile` and `GNUmakefile/` says the merge comes first too, README.md names `make park` and `make resume`, and rows pin the script-only aside and a directory sent to a leg

#1207's independent review (issuecomment-6374516382), the code items:

- SHOULD-1: no row pinned NEXT where only `scripts/bootstrap.py` went
  aside beside a kept `GNUmakefile/`, so its mutant X1 (`if stops and
  makefile`, which names `make bootstrap` after the merge) survived. A
  row now asserts the whole NOTE there.
- NIT-1: `AGENTS-shape.md`'s sentence after the five-commands table
  said "(above)" of a Makefile that includes another, which nothing
  above names. The pointer is gone.
- NIT-2: README.md's caveat covered `make bootstrap` only. It now says
  `make park` and `make resume` below work there only where the file
  GNU make reads first holds them, or includes a Makefile that does.
- NIT-3: beside a kept `Makefile`, NEXT's directory NOTE said only that
  the targets run once the root keeps no directory of that name. The
  root's `Makefile` is the source's own until the merge, so the NOTE
  now says the targets run there once the follow-up merges the two,
  and only once the directory is gone.
- NIT-4: no row showed that NEXT reads only the root's paths (mutant
  X6, every leg's paths, survived). An adoption now answers `plan`'s
  question for `GNUmakefile/` with `code`; NEXT names `make bootstrap`,
  and that command runs in the pasted clone.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…teps` asks `_make_reads_first` once for a kept file or directory, words both through `_make_reads_first_clause`, and #1121's NOTEs name `python` on Windows

#1121 (#1047) and this branch both changed `_next_steps`, and git
stopped on four hunks of adopt-project.py (84 lines). The resolution:

- execute's comment names both: a kept `GNUmakefile` or `makefile` is
  what GNU make reads first (#1047), and a directory of either name
  stops it (#1132).
- The docstring keeps #1121's paragraph and this branch's two.
- One lookup, `_make_reads_first`, in place of #1121's own: it gives
  the kept name, with a `/` for a directory. The command line is
  `python3 scripts/bootstrap.py` where a `Makefile` went aside or
  `first` is set, as #1121 had it.
- One clause, `_make_reads_first_clause(first, ..., "has them")`. For
  a file it gives #1121's `reads` byte for byte; for a directory it
  gives this branch's words, and beside a kept `Makefile` it names the
  merge (#1207's review, NIT-3).
- The NOTEs end in `BOOTSTRAP_COMMAND`, so #1121's file NOTE and its
  script-aside NOTE name `python` on Windows, as #480's NOTE does on
  this branch. tests/test_adopt_next_make_reads_first.py moves three
  pins to that: OWN_MAKEFILE_NOTE, RUNS and script_aside. Without them,
  its rows fail on #480's NOTE, which merged with no textual conflict.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@brettheap

Copy link
Copy Markdown
Contributor Author

Independent review of #1207 (#1132) for lane openRepoShape-5, by rv1207b (opus), at 6fa1edd: READY on what is measured so far, PARTIAL

This review is partial because the coordinator sent LAST STEP at 22:06Z. So far there is no MUST and no SHOULD. Two gates are still running (see "Left").

What I checked and measured

Setup. I made two detached worktrees at 6fa1edd56735027367e2685213f8f7245b93bb9a:

  • /workspace/projects/openRepoShape-worktrees/rv1207b-review, which stays clean, for the gates;
  • /workspace/projects/openRepoShape-worktrees/rv1207b-mut, for the mutants.

The notes are in $Q/v130/rv1207b/STATE.md. Main is still 0ed3e57f8. #1105's head is still b28f40de0.

1. The first review's findings are fixed as claimed

2. The merge resolution of #1121

I wrote a probe that renders NEXT and the CONTRACTS_DIR line for 25 roots. Its output is $Q/v130/rv1207b/probe/compare.out. I ran it against three trees: the head, main 0ed3e57f8 (a git archive copy), and 75c90b6db (also a copy).

3. Mutants at 6fa1edd56

The log is $Q/v130/rv1207b/mut/summary.txt. Each mutant was applied alone and then restored, and every restore checked 4/4 sha256 OK with a clean tree.

  • Fast set: 61 rows from the two NEXT files, without the adoption rows. Unmutated, all 61 pass.
  • Adoption set: 5 rows, all passing unmutated.
id mutant rows failing
X1a script-only NOTE names make bootstrap unless a Makefile went aside 5 of 61
X1b the same, for a directory only 1 (SHOULD-1's row)
N1 merged = "" 2
N2 merge named even where the script went aside 1
N3 merge named without a kept Makefile 2
K1 merge named for a kept FILE too (75c's condition) 3
K2 clause given only for a directory (75c's gate) 18
K3 command test makefile or stops (75c's) 3
K4 the elif first NOTE back to plain python3 10
K5 the script-aside NOTE back to plain python3 5
K6 M18: the file clause says does 9
K7 _make_reads_first drops the / of a directory 14
T1 "(above)" put back 1
T2 the README's park/resume sentence removed 1
X6 NEXT is handed every leg's paths 2 of 5

All 15 mutants are killed. M18 (K6) is no longer equivalent, which confirms #1240's item J.

4. Gates, all through run9.sh

5. Template pins

I grepped tests/ for 18 phrases from the changed lines. Only tests/test_adopt_make_bootstrap_followups.py pins the changed text. The other hits are unrelated:

  • a sentence in the doctor test;
  • a comment in test_ruleset_hint_tracking_branch.py;
  • park/resume text in the family-root tests;
  • os.name text in test_setup_project_py.py.

6. The body

7. Merge-trees

Findings

MUST: none so far.

SHOULD: none so far.

NIT-A: a stale comment.

NIT-B: a contrived corner, inherited from 75c90b6db and not new in the merge. I did not measure it with make; it follows from GNU make's order.

Left (not done because of LAST STEP)

  • Two gates still running (the gates.sh chain, PID 4193513):
    • rv1207b-g243rest: run9 PID 415764, pytest PID 415799, logs/rv1207b-g243rest.log;
    • then rv1207b-g247rest: logs/rv1207b-g247rest.log.
    • Together they cover test_shape_doctor.py, test_scaffold_leg_path_collisions.py and the hygiene five, on 2.43 and on 2.47.1.
    • Results land in $Q/v130/rv1207b/logs/gates.summary. READY depends on both ending with rc=0.
  • The template's scripts/bootstrap.py wording, checked against BOOTSTRAP_COMMAND.
  • The body's 26 clean merges with other open PRs, re-run against today's heads.
  • A grep of tests/ for a stored digest of either template file.
  • NIT-B measured with GNU make.

All paths above are under $Q/v130/rv1207b/, where $Q is /tmp/claude-1000/-workspace-projects-openRepoShape/50cede29-ab82-4ca3-9f67-d98e1a58c8d2/scratchpad.

Lane: openreposhape-5 (openRepoShape-5)

@brettheap

Copy link
Copy Markdown
Contributor Author

Independent review of #1207 (#1132) for lane openRepoShape-5, by rv1207b and rv1207c (opus), at 6fa1edd: READY on everything measured, PARTIAL (the two rest gates are still running)

This review is PARTIAL because the coordinator sent LAST STEP at 71%. No MUST and no SHOULD were found. One gate pair is still running (see "Left"). The verdict becomes READY outright if both of those gates end with rc=0.

Head and setup

rv1207b's findings (issuecomment-6375894847), kept as measured

  1. The first review's findings are fixed:
  2. The merge of adopt-project.py: execute's NEXT prints python3 scripts/bootstrap.py where GNU make at the root reads a kept GNUmakefile or makefile, and its NOTE names that file and what it takes for make bootstrap to run there (#1047) #1121 is right:
  3. Mutants: all 15 are killed: X1a, X1b, N1-N3, K1-K7, T1, T2 and X6. Every restore was checked 4/4 sha256 OK. M18 (K6) is killed by 9 rows, which confirms adopt-project.py, shape_materialize.py and the assembly-root template: after #1121 and #1105, one GNU_MAKE_READS and one lookup, their NEXT NOTE and collision line as true as #1207's, and the NITs 1, 3b, 3c, 4 and 5 of #1105's second review #1240's item J.
  4. Gates: the PR's test files plus test_adopt_make_reads_first.py, 135 rows, all passed on git 2.43.0 and on 2.47.1.
  5. Template pins: of 18 phrases grepped from the changed lines, only tests/test_adopt_make_bootstrap_followups.py pins changed text.
  6. The body: the counts (35 and 40 rows, 34/1 and 19/21) are true. Closes #1132 is the only closing keyword, body-guard.py exits 0, and sq.sh's cut keeps the Closes and Lane lines.
  7. shape_materialize.py and the pinned AGENTS-shape.md: where the root keeps a GNUmakefile or a makefile, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file says make reads the first of the three and the block reaches it only once that file holds it (#1048) #1105 still conflicts: in scripts/shape_materialize.py only, in both orders, 3 hunks of 65 lines.

rv1207c's checks (the items rv1207b left)

  1. The template's scripts/bootstrap.py wording matches BOOTSTRAP_COMMAND.
    • templates/assembly-root/scripts/bootstrap.py:7 reads make bootstrap # or: python3 scripts/bootstrap.py (python on Windows). templates/family-root's line 7 is the same.
    • BOOTSTRAP_COMMAND is "python3 scripts/bootstrap.py (python on Windows)": the same pair of names.
    • The PR does not change bootstrap.py or the template Makefile.
    • GNU make 4.3, run with -n on the template Makefile, prints python3 scripts/bootstrap.py for bootstrap, with or without OS=Windows_NT. It prints python ... only with PYTHON=python. See NIT-C.
  2. The body's "26 clean merges" still hold.
  3. No test stores a digest of either template file.
    • I took the sha256 and blob id of templates/assembly-root/AGENTS-shape.md and README.md at 0ed3e57f8 (the same blobs as main 50869f384) and at 6fa1edd56. None appears anywhere in the tree, in full or as a 12-character prefix.
    • AGENTS-shape.md is COPIED_VERBATIM: each project's shape pin digests it when the project is scaffolded. README.md is TEMPLATED and has no pin.
    • Other tests that read these files still hold:
      • test_adopt_contracts_dir.py asserts ADOPTED_SENTENCES, which comes from test_pinned_copy_text.py, and that file is in the running gate.
      • test_scaffold_free_text_values.py:318 compares line counts with the live template.
      • test_scaffold_e2e.py:781 checks for rules such as "make bootstrap", which are still present.
    • Hygiene of the added lines: every added template line is at most 77 columns. The one non-ASCII byte in the diff is an em dash carried over from main's own docstring line. No assert joins conditions with and/or (S9073).
  4. NIT-B is measured with GNU make 4.3 and confirmed. See below.
  5. The head has not moved since 6fa1edd56.

Findings

MUST: none.

SHOULD: none.

NIT-A (rv1207b): a stale comment. tests/test_adopt_next_make_reads_first.py:64 says "#480's NOTE, for a root that keeps only its own Makefile: unchanged." The merge moved that pin to add (python on Windows), so "unchanged" is no longer true. Drop the word, or say the text changed for #1132.

NIT-B (rv1207b, measured by rv1207c): a second name hidden behind the directory. This comes from the PR's own directory clause and is byte-identical at 75c90b6db. #1240 does not list it.

  • The roots: G/+m (a GNUmakefile/ directory and a makefile file) and M+G/+m (the same, plus a kept Makefile).
  • What the head prints:
    • For G/+m, NEXT's NOTE says the targets "run there only once the root keeps no directory of that name".
    • For M+G/+m, it says they run "once the follow-up merges the two, and only once the root keeps no directory of that name".
    • Both CONTRACTS_DIR lines say "only once the root keeps no directory of that name".
  • What GNU make 4.3 does:
    • With GNUmakefile/ present, make bootstrap and make contracts-dir exit 2 with "GNUmakefile: Is a directory. Stop."
    • After GNUmakefile/ is moved aside, with the merged Makefile in M+G/+m, both still exit 2: "No rule to make target 'bootstrap'", because make now reads the source's makefile.
    • Only once makefile is moved aside as well do they work: SHAPE-BOOT, rc 0.
  • In short: the condition the text names is necessary but not enough, and the text never names makefile. These roots are contrived. Add it to adopt-project.py, shape_materialize.py and the assembly-root template: after #1121 and #1105, one GNU_MAKE_READS and one lookup, their NEXT NOTE and collision line as true as #1207's, and the NITs 1, 3b, 3c, 4 and 5 of #1105's second review #1240 as a new item; it does not need a fix round.
  • Files: probe $Q/v130/rv1207c/probe-make/nitb.sh, output nitb.out.

NIT-C (rv1207c, optional): "is what make bootstrap runs".

Left (not done because of LAST STEP)

  • The rest gates at 6fa1edd56. They cover test_shape_doctor.py, test_scaffold_leg_path_collisions.py and the hygiene five, on git 2.43.0 and then 2.47.1, about 1035 rows each.
    • They are still running and were not killed: chain $Q/v130/rv1207c/gates.sh, PID 2089124.
    • 2.43 run rv1207c-g243rest: run9 PID 2089168, pytest PID 2089283, log $Q/v130/rv1207c/logs/rv1207c-g243rest.log. At 14:40:55Z it was at about 48% with no failure and 3 skips.
    • 2.47.1 run rv1207c-g247rest follows, with log $Q/v130/rv1207c/logs/rv1207c-g247rest.log.
    • Results land in $Q/v130/rv1207c/logs/gates.summary as <name> rc=N <time>. READY outright depends on both lines reading rc=0.
  • Everything else in R2's list is done.

Notes are in $Q/v130/rv1207c/STATE.md, with a CHECKPOINT dated 2026-10-24T14:41Z. Other files in that folder: mt.txt (merge-trees), prlist.json, digests.txt, probe-make/ and rv1207b-posted-review.md. $Q = /tmp/claude-1000/-workspace-projects-openRepoShape/50cede29-ab82-4ca3-9f67-d98e1a58c8d2/scratchpad.

Lane: openreposhape-5 (openRepoShape-5)

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T14:18:07Z) Independent review of #1207 (#1132) for lane openRepoShape-5, by rv1207b, rv1207c and rv1207d, at 6fa1edd: READY

No MUST and no SHOULD. Both rest gates ended rc=0 and the merge-tree with main is clean. I wrote nothing: no commit, push or GitHub write, and the review worktree stayed clean at 6fa1edd56.

Earlier findings (rv1207b and rv1207c)

Their posted review is issuecomment-6385544168. It found no MUST and no SHOULD, with three NITs. Everything it measured was READY.

Step 1: head and main

  • Head: still 6fa1edd56735027367e2685213f8f7245b93bb9a, read by gh pr view and ls-remote at 13:33Z and again at 14:18Z. The PR is OPEN, a draft, MERGEABLE, on milestone v1.2.1, and closingIssuesReferences is [1132]. rv1207b-review has an empty git status --porcelain.
  • Main moved: it was bd3e9c148 when I started and is 93d293141 now (2 commits).
  • Overlap: from 0ed3e57f8 to bd3e9c148, 8 files changed and one is a PR file, adopt-project.py. The two commits since add adopt-project.py again. No other PR file changed. The hunks are far from the PR's, and the merge-trees are clean.

Step 2: the rest gates at 6fa1edd56

The 7 files are test_shape_doctor.py, test_scaffold_leg_path_collisions.py and the hygiene five. They collect 932 rows: 449+104+121+101+47+23+87. One run at a time, EMAIL exported, through run10.sh.

Gate git Result
rv1207d-g243 2.43.0 929 passed, 3 skipped, 13m17s, rc=0
rv1207d-g247 2.47.1 929 passed, 3 skipped, 13m22s, rc=0
  • Skips: the same 3 on both legs, test_shape_doctor.py:10516 ("powershell.exe exists, only on a Windows host").
  • git per leg: each log prints git --version, and the 2.47.1 leg resolves tools/git247/bin/git.
  • filter-repo: both runs used 2.45.0, because they started at 13:34Z and 13:47Z, before run10.sh changed at 14:09Z.
  • filter-repo rows re-run on 2.47.0: only 4 rows in these 7 files execute git filter-repo, the needs_filter_repo rows in test_shape_doctor.py. I re-ran them on 2.47.0 (CI's pin), and also the PR's adoption files (test_adopt_e2e.py, test_adopt_make_bootstrap_followups.py, test_adopt_next_make_reads_first.py, test_adopt_make_reads_first.py), at the head.
Gate git filter-repo Result
rv1207d-fr247-A-243 2.43.0 2.47.0 4 passed
rv1207d-fr247-A-247 2.47.1 2.47.0 4 passed
rv1207d-fr247-B-243 2.43.0 2.47.0 135 passed
rv1207d-fr247-B-247 2.47.1 2.47.0 135 passed

The earlier review's "about 1035 rows" was loose; the real count is 932.

Step 3: merge-tree with main

  • With bd3e9c148: git merge-tree --write-tree bd3e9c148 6fa1edd56 is clean (rc=0) on both 2.43.0 and 2.47.1, tree 84d7b62560c2377289d3b49940cef51a56c38a38. It differs from the head only in main's 8 files.
  • With 93d293141, re-run after main moved: clean (rc=0) on both versions, tree 42ccfd3e191fb2d2d81f2c95b2f76ff258bf9abc.

Extra: the merged trees, gated in scratch repos

I extracted each predicted merge into its own repo under L/work/rv1207d/ (merged-main/, merged-main2/). Nothing was written to the shared repo.

Merged tree git Files Result filter-repo
84d7b6256 (main bd3e9c148 + head) 2.43.0 test_adopt_e2e.py, test_adopt_make_bootstrap_followups.py, test_adopt_next_make_reads_first.py, test_adopt_make_reads_first.py, test_adopt_partial_clone_source.py, test_path_guard_hygiene.py 294 passed 2.45.0
84d7b6256 2.47.1 the same 6 294 passed 2.45.0
84d7b6256 2.43.0 the other 6 of the 7 rest-gate files 808 passed, 3 skipped (the same PowerShell skips) 2.45.0
42ccfd3e1 (main 93d293141 + head) 2.43.0 the 6 above plus main's test_adopt_check_throwaway_repos.py, test_adopt_nested_attributes.py, test_adopt_reencoded_files.py, test_command_scopes_built_at_the_test.py 573 passed, 1 skipped 2.45.0
  • The one skip: test_adopt_check_throwaway_repos.py:482, which needs a case-insensitive disk.
  • Not run on the newest merge: the full 7-file gate on 2.47.1, or any of these on filter-repo 2.47.0. Landing should gate the head against current main as usual.

Verdict

READY. No finding changes the earlier review's three NITs, and nothing in the gates or the merges contradicts it. For the coordinator at landing: re-check the head against main (now 93d293141), and add NIT-B to #1240.

Logs and scripts are in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1207d/, with notes in STATE.md there.

  • Logs (logs/): rv1207d-g243.log, rv1207d-g247.log, rv1207d-fr247-{A,B}-{243,247}.log, rv1207d-merged243.log, rv1207d-merged243b.log, rv1207d-merged247.log, rv1207d-merged2-243.log, mt-main.txt, mt-main2.txt, collect.txt, gates.summary.
  • Scripts (the folder itself): gates.sh, merged-gate.sh, merged-gate2.sh, merged-gate3.sh, fr247-gate.sh.

Lane: openreposhape-5 (openRepoShape-5)

Brett Heap and others added 3 commits November 8, 2026 17:20
…on_follow_up` keeps #1105's `reads_first` lines and #1207's script-aside tail, each naming `python` on Windows, and four pins in #1105's test file move with them

Lane 4's #1105 (#1048) landed as 1212295, and its `collision_follow_up`
conflicted with this branch's, as the body's "Landing order" said it would.
`adopt-project.py` and `templates/assembly-root/AGENTS-shape.md` merged with
no conflict. The resolution keeps both sides' behaviour and does none of
#1240's unification: #1105's second `GNU_MAKE_READS`, its `_predict_collisions`
lookup and its collision line stay, and so do this branch's
`_make_reads_first`, `_make_reads_first_clause` and NEXT.

scripts/shape_materialize.py, three hunks, all in `collision_follow_up`:
- Docstring, the NIT-4 sentence: both sides rewrote the same sentence for
  the same finding. Main's is kept, since it also names
  `Materialized.follow_ups()`, which #1240 item G changes.
- Docstring, the Makefile paragraphs: main's heading and its "BUT GNU MAKE
  READS" paragraph, with this branch's `BOOTSTRAP_COMMAND` and NIT-2
  sentences in the first, and one sentence in the second saying both of its
  commands are named as `BOOTSTRAP_COMMAND` names them.
- The returns: #1105's per-branch `arrive`, `joint` and `meanwhile` stay,
  and its `until` becomes a per-branch `then`. With no `reads_first`, the
  line is this branch's, byte for byte. With one, it is #1105's, byte for
  byte, but the command a person runs is `BOOTSTRAP_COMMAND` in `meanwhile`
  and in "and then ... runs it" (#1240 item C, for #1105's line).

tests/test_adopt_collision_make_reads.py (#1105's): `MERGE_IS_ENOUGH`,
`MEANWHILE`, `BOTH_MERGES` and `SCRIPT_MERGED` move to those words, through a
new `COMMAND`. Before the move exactly the 11 rows that use them failed, and
the other 242 rows of the six adopt and pin files passed.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LE_NOTE` no longer calls #480's NOTE unchanged, since #1132 adds `python` on Windows to it (rv1207b's NIT-A)

The merge of #1121 moved that pin to "(`python` on Windows)", and the
comment above it still said "unchanged" (#1207's review, NIT-A, at
`tests/test_adopt_next_make_reads_first.py:64`). Comment only.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…`makefile-directory` row in `tests/test_adopt_make_reads_first.py` expects the line that says GNU make stops at a kept `makefile/`, and still guards `twins`

Lane 1's #1123 (#1061) landed after this branch merged main 46fe6ac, and
the merge with it is clean by text. By meaning it is not: #1123 adds a row,
`test_a_name_only_a_disk_that_ignores_case_takes_for_makefile[makefile-directory]`,
that expects the `CONTRACTS_DIR` line a kept `makefile` FILE gets for a kept
`makefile/` DIRECTORY ("only once `makefile` does"). #1123's own message says
that line is not true for a directory, since GNU make 4.3 stops there with
`makefile: Is a directory.`, and that the row works as a crash guard for
`twins` in `_makefile_follow_up`. This branch changes exactly that line
(#1132, RV1121's NIT-1): a kept directory is one GNU make stops at. On the
predicted merge the row was the one failure in 254 rows.

The row now expects `SHAPES + written_as("makefile")`, as #1123 wrote it,
which is what guards `twins`, followed by this branch's directory clause,
through a new `stops_at` helper beside `reads`. The comment above `ANSWERED`
and the row's docstring say so. Nothing else changes, and #1118's merge
(9ec0dc7, also in this main) is clean by text and by its rows.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Nov 8, 2026

Copy link
Copy Markdown

@brettheap

Copy link
Copy Markdown
Contributor Author

(lane openRepoShape-5, 2026-11-08T19:27Z) Delta check of #1207's main merges, 6fa1edd5..01e93b6d, by rv1207e, lane 5's opus reviewer, which wrote none of it: READY. Its three NITs are answered in the body: NIT-2's line number is corrected, NIT-3's Merges note names #1143 and #1117, and NIT-1's docstring sentence is listed under "Left" beside #1240's items F and G. The report follows as the reviewer wrote it.

Delta check of #1207 (#1132) for lane openRepoShape-5, by rv1207e (opus), 6fa1edd..01e93b6: READY

The delta is clean. Neither merge carries a hidden change (no "evil merge"): each differs from git's own merge only where the body says. Hunk 3 renders as promised on every branch. The four moved pins and #1123's moved row change only what they should, and all gates pass. I found no MUST and no SHOULD, and three NITs. CI's Linux tests job was still running at 19:25Z; SonarCloud, package and tests-git-bash pass.

PR head 01e93b6d1, read back at 19:25Z. Main is now 9252ef208: #1117 landed at 19:15:56Z and touches only README, the handbook and scripts/family.py.

Findings

NIT-1: one docstring sentence kept from #1105 is now untrue. In scripts/shape_materialize.py:1067-1068, in hunk 2's second paragraph, it reads "With none, the Makefile's follow-up is what it was, byte for byte".

NIT-2: one line number in the body is off by one. "Main merge after #1105" says hunk 2 sits at lines 1033-1070. It starts at 1032, the heading line, which is main's side of the conflict.

NIT-3: the body's "Merges" section is one landing behind. It stops at main 801a7d6d0. #1143 landed as 81eaf757c at 18:42:21Z and touches adopt-project.py, and #1117 followed. Both merge cleanly, and gate P below covers them. Current numbers, if the coordinator wants to refresh the section: 48 open PRs, 24 touching #1207's files, 23 clean.

Checks, by measurement

  1. No evil merge.

    • Merge 1: merge-tree --write-tree 6fa1edd56 46fe6ac19 conflicts only in scripts/shape_materialize.py (tree 7f725522a). Against 7b0192bde, exactly two files differ: that file and tests/test_adopt_collision_make_reads.py.
      • Hunks 1 and 2 are docstring only, inside the conflict markers.
      • Hunk 3's resolution also rewrites main's auto-merged meanwhile/until lines just above the markers (lines 1143-1156 of git's merged file, conflict markers included). The body's table declares this as "a per-branch then in place of until".
    • Merge 2: merge-tree e5e077940 9729315f1 is clean (tree 0eb290ae2). Against 01e93b6d1, only tests/test_adopt_make_reads_first.py differs.
  2. Hunk 3 renders as promised. I loaded collision_follow_up from each commit and rendered 9 branches (reads_first none, GNUmakefile, makefile; script aside or not).

    • With no reads_first, the head equals 6fa1edd56 byte for byte (3 of 3).
    • With reads_first set, the head with BOOTSTRAP_COMMAND put back to plain python3 equals main 46fe6ac19 byte for byte (6 of 6).
  3. The moved pins hold that change and nothing else. I evaluated the constants:

    id mutant rows failing result
    K1 no reads_first: meanwhile back to plain python3 3 of 66 killed
    K2 with reads_first: meanwhile back to plain python3 5 of 66, all through MEANWHILE killed
    K3 with reads_first: "and then ... runs it" back to plain python3 3 of 66, through SCRIPT_MERGED killed
    K4 with reads_first: "neither ... nor" also takes BOOTSTRAP_COMMAND 3 of 66 killed

    K4 shows the pins keep shape_materialize.py and the pinned AGENTS-shape.md: where the root keeps a GNUmakefile or a makefile, which GNU make reads first, the Makefile collision follow-up no longer says the shape's make targets arrive with the merge, and the pinned file says make reads the first of the three and the block reaches it only once that file holds it (#1048) #1105's plain words there. That matches NEXT's own convention at adopt-project.py:16951-16958, so the "in the words execute's NEXT uses" sentence (adopt-project.py, shape_materialize.py and the assembly-root template: after #1121 and #1105, one GNU_MAKE_READS and one lookup, their NEXT NOTE and collision line as true as #1207's, and the NITs 1, 3b, 3c, 4 and 5 of #1105's second review #1240's item F) now holds for the meanwhile part.

  4. README.md, adopt-project.py and the handbook: a kept directory entry makefile/ is tested, README says make at the root reads a kept GNUmakefile or makefile instead, and a docstring says the shape's Makefile alone holds CONTRACTS_DIR (#1061) #1123's row.

    • GNU make 4.3 at /usr/bin/make, with makefile/ alone and beside Makefile, prints make: *** makefile: Is a directory. Stop. and exits 2 for both contracts-dir and bootstrap. So the new stops_at expectation is true.
    • The twins crash guard still bites. Each mutant was applied alone and restored, with adopt-project.py's sha256 3c2cb5f5bea5 matched every time:
    id mutant rows failing (of 34) result
    R10 both rstrip("/") dropped 1, [makefile-directory], IndexError killed
    R10f filter only 1, [makefile-directory], IndexError killed
    R10e element only 1, [makefile-directory], assert killed
    D1 _make_reads_first drops the / 1, [makefile-directory] killed
  5. Survivor U3 differs only for a kept directory.

  6. NIT-A and the body edits.

    • NIT-A (e5e077940) is a comment-only change, true of the pin below it.
    • The live body differs from the writer's file at exactly lines 5, 129 and 281, the coordinator's three edits. Each is true: 12122952b is 16:45:04Z on 2026-11-08 and is in the head. The rest of the difference is the Sourcery section, which its bot rewrites.
    • The body guard exits 0, and the only closing keyword is Closes #1132.
  7. Gates (through run10.sh, git-filter-repo 2.47.0 from venv247, clean trees):

    gate commit git files passed skipped failed
    A head 01e93b6d1 2.43.0 the writer's 12 1096 3 0
    B head 01e93b6d1 2.47.1 (tools/git247) the writer's 12 1096 3 0
    P predicted merge with main 9252ef208 (fcd7f5846, tree 279f8c6ce, never pushed) 2.43.0 the six adopt/pin files, plus adopt-project.py: under a one-byte locale such as Latin-1, check reads a plan written in UTF-8 whose source is there, asking the plan's UTF-8 bytes where the spelling as written names nothing, rather than refusing plan-source-gone (#1078) #1106's tests/test_adopt_one_byte_locale.py and tests/test_adopt_unenterable_source_and_locale.py, plus adopt-project.py: a --work-dir link into a loop that Python 3.12 reads past a link that does not loop names the loop, Windows' winerror 1921 counts as one, and a path read as the loop's link says so, with tests for r1065's mutants (#1074) #1143's tests/test_adopt_work_dir_refusal.py 359 0 0
    p1189 predicted merge with adopt-project.py: check names the case twins the plan keeps in one repository as plan-case-twins on every disk, a one-blob set as a NOTE in a leg and, in the root, two paths in one folder, and execute refuses the finding before any leg (#1161) #1189 (ab7818f8d) 2.43.0 the three test files both PRs edit 62 + 38 0 0
    • The 3 skips are the PowerShell rows at tests/test_shape_doctor.py:10516, which run only on a Windows host.
    • No other main commit since the head's base touches adopt-project.py or scripts/shape_materialize.py.
  8. Merge-trees (git 2.43.0, 18:56Z; PR list re-read at 19:22Z with no head moved):

Both worktrees are clean and detached at 01e93b6d1. No runs are in flight, and nothing was committed, pushed or posted. The only new git objects are ref-less scratch commits (12914b6b6, fcd7f5846, ab7818f8d).

Files are in /workspace/projects/.lane-worktrees/openRepoShape-5/work/rv1207e/:

  • STATE.md
  • logs: logs/gA243.log, logs/gB247.log, logs/gP243.log, logs/p1189.log, logs/mt-all.txt
  • render and pin evidence: logs/render-compare.txt, logs/pins-check.txt, logs/with-none-check.txt, logs/u3-exhaustive.txt, m1/conflicted-vs-resolved.diff
  • mutant logs: mut/*.log

Lane: openreposhape-5 (openRepoShape-5)

brettheap added a commit that referenced this pull request Nov 8, 2026
…says a full name that is no pattern takes the one ref git completes it to, which may be a branch, where it said any other source names no branch (#1238) (#1244)

#1238 is a wording NIT that the review of #1235 (#1230) found in the code: NIT 4 of rv1235's first review, and NIT 2 of its re-check. The docstring of `gives_a_branch_another_name` in `scripts/shape_materialize.py` ended "ANY OTHER SOURCE NAMES NO BRANCH", but git completes a source that is a full name and no pattern, and completes `refs/h` to the branch `refs/heads/refs/h` where the remote has one. The sentence now says what git does with such a source, as an open list ("such as"), and where the clash is. A reader of the docstring is no longer told that a source outside `refs/heads/` is never a branch.

Wording only, in one docstring of one file: 9 lines added, 3 removed. No code, no row, no other file. The file's syntax tree with every docstring dropped is the same as main's.

The sentence is at `scripts/shape_materialize.py:1720-1723`, the end of the docstring of `gives_a_branch_another_name` (line 1671):

```
1720:     BRANCH REACHES). A shorter start, such as `refs/*` or `*`, can name a
1721:     tag, and that clause has left it out first. ANY OTHER SOURCE NAMES NO
1722:     BRANCH, though a refspec from one can still put a ref on a branch's
1723:     name: `origin_fetch_refspecs` says where (WHAT CAN STILL CLASH).
```

It is true of a source in another namespace such as `refs/pull/7/head`. It is not true of every other source. A source that is a full name and no pattern takes the one ref git completes the name to on the remote: for `refs/h`, `refs/h` itself, the branch `refs/heads/refs/h`, the tag `refs/tags/refs/h`, `refs/refs/h` or `refs/remotes/refs/h`, and where several exist git takes the exact ref over a tag over a branch. Where the remote has the branch, the source is a branch. The code is right: `gives_a_branch_another_name("refs/h", ...)` is False, because the source has no `*` and does not start with `refs/heads/`, so the refspec is carried. What fails afterwards is the clash the docstring's own next clause names. Only the words were untrue.

Measured on this branch's base, with git 2.43.0 (`/usr/bin/git`) and git 2.47.1, which gave the same answers on every row. Local bare remotes, `GIT_CONFIG_GLOBAL=/dev/null`, `GIT_CONFIG_NOSYSTEM=1`, `GIT_SSH_COMMAND=false`. rv1235's probe scripts were run again here (copied unchanged, new output folders), and their logs are identical to rv1235's. The fetch was `+refs/heads/*:refs/remotes/origin/*` plus the refspec of the row, and the remote had `main` and `hy`:

```
the remote has                    refspec                          result
branch refs/heads/refs/h          +refs/h:refs/remotes/origin/hy   128 Cannot fetch both refs/heads/hy and refs/heads/refs/h to refs/remotes/origin/hy
tag refs/tags/refs/h              +refs/h:refs/remotes/origin/hy   128 Cannot fetch both refs/heads/hy and refs/tags/refs/h to refs/remotes/origin/hy
exact ref refs/h (update-ref)     +refs/h:refs/remotes/origin/hy   128 Cannot fetch both refs/heads/hy and refs/h to refs/remotes/origin/hy
refs/refs/h or refs/remotes/refs/h  +refs/h:refs/remotes/origin/hy 128, the same message naming that ref
each of those five refs, alone    onto origin/not-a-branch         0, holds that ref's commit
branch refs/heads/refs/h          onto origin/refs/h               0 (the branch's own name: no clash)
refs/pull/7/head                  onto origin/main                 128 Cannot fetch both refs/heads/main and refs/pull/7/head to refs/remotes/origin/main
branch refs/heads/main            onto origin/main                 0
```

So a name with no `*` can be a branch that its spelling does not show, and the clash is a ref that is not that branch put on a branch's name, which is what the second half of the sentence has to say. "ANY OTHER" was wrong in a second way: a carried refspec from a source that IS the branch does not clash onto that branch's own name.

The docstring's last sentence, now lines 1721-1729:

```
    tag, and that clause has left it out first. ANY OTHER SOURCE TAKES NO
    BRANCH BY A PATTERN, and a full name that is no pattern, such as
    `refs/h`, takes the one ref git completes it to on the remote: that ref
    itself, or one such as the branch `refs/heads/refs/h` or the tag
    `refs/tags/refs/h` (measured, git 2.43 and 2.47.1). Such a name can
    stand for a branch its spelling does not show, so it is not left out
    here. A refspec from any such source can still put a ref on a branch's
    name, if the ref is not that branch itself: `origin_fetch_refspecs`
    says where (WHAT CAN STILL CLASH).
```

- **"Takes no branch by a pattern"** holds for every source this sentence covers. Git matches a pattern against each ref's whole name, and a ref under `refs/heads/` starts with the part before the `*` only where that part is a start of `refs/heads/` or is under it, the two cases the function reads before this sentence.
- **The list is open** ("one such as"): it names the exact ref, the branch and the tag, and the rows above show two more completions (`refs/refs/h`, `refs/remotes/refs/h`), so no reader takes the three as all.
- **"So it is not left out here"** is what the code does: `gives_a_branch_another_name` is False for `refs/h` onto `origin/hy`, onto `origin/refs/h`, for `refs/pull/7/head` onto `origin/main`, and for the pattern `refs/pull/*/head`, and `origin_fetch_refspecs(["+refs/h:refs/remotes/origin/hy"])` returns it carried. It is True for `refs/heads/main` onto `origin/hy`, as before.
- **"If the ref is not that branch itself"** is the condition the rows show: the clash is two different refs on one name, a branch's.

The checks the issue's Verify lists:

- **The diff is only that docstring.** The diff has one hunk, inside the docstring, which spans lines 1672-1724 on main and 1672-1730 here. The file's tree with every docstring dropped (module, class and function: 168 each side) is equal on main (`50869f384`) and at this head under `python3 -I`, `ast.dump`: 380120 characters of dump on both sides. A control that renames `gives_a_branch_another_name` on the head side gives 380121 and DIFFERENT, and the same trees with the docstrings kept differ (520956 against 521366), so the check can fail. Python 3.12.3. The check compares two dumps made by one interpreter and asserts no text that depends on the version.
- **`git grep -n 'ANY OTHER SOURCE NAMES NO$' -- scripts/`** finds nothing at this head. On main it finds `shape_materialize.py:1721`. With any case and no line end, `git grep -n -i 'other source names no'` also finds nothing here, and nothing but that line on main.
- **No added line is over 79 columns or holds a non-ASCII character.** 9 lines are added, the longest is 75 columns, and none has a tab or trailing space. The file's own width is unchanged: 20 lines over 79 columns (longest 118) and 90 lines with a non-ASCII character, as on main, none of them mine.
- **No test, `README.md`, `docs/` page or `AGENTS.md` line pins the sentence.** The issue's grep holds at this head.

The issue asks whether `shape_materialize.py:1874` (now 1880, in the docstring of `origin_fetch_refspecs`) and `bump-leg.py:1165` (in `_leg_fetch_refspecs`) want the same change. Both paragraphs were read, and both are left as they are:

```
shape_materialize.py:1880:     WHAT CAN STILL CLASH is a refspec carried whose source names no branch,
bump-leg.py:1165:     whose source names no branch, onto a name a branch of the remote has
```

1. **They define a shape by its examples and say nothing about what git completes a name to.** Each goes on at once to the pull requests (`origin/pr/` where a branch `pr/7` is, and `+refs/pull/7/head:refs/remotes/origin/main` beside a branch `main`), and neither names `refs/h`. They were not the sentence that told a reader a source is never a branch.
2. **"Names no branch" is spelling there, as it is in the sentence the function's docstring opens with.** "A SOURCE UNDER `refs/heads/` NAMES BRANCHES" is true by spelling too: `can_name_a_tag`'s docstring measures that `+refs/heads/zz` took the tag `refs/tags/refs/heads/zz` where the remote had no branch `zz`. The sentence changed here now says outright that the spelling of a name with no `*` does not show what it stands for, so the docstring that defines the class carries the caveat the two borrowers need.
3. **The mechanism they state is the one that fails `refs/h`:** "wherever a name it writes is a branch's". The `origin_fetch_refspecs` paragraph ends with why such a refspec is carried at all: "Which names the remote's branches have is not known from its refspecs, and left out, what such a refspec keeps would be pruned on every run". That holds for `refs/h` word for word.
4. **The cost of leaving them is one reading:** a reader who takes "names no branch" in git's sense meets the single case `refs/h` beside a remote branch `refs/heads/refs/h`. If the review wants a word there, it is a few words in each place, and no open PR touches either paragraph (merge-trees below), so it can be a fix round.

None of either. No row is added or changed and no behaviour moved: the syntax tree outside the docstring is main's, so there is nothing for main to fail and nothing to mutate. `gives_a_branch_another_name` returns what it returned for every case in the table above (called in process at this head; the syntax tree outside the docstrings is main's).

All through the lane's runner `run9.sh` (stub `gh` first on PATH, `GIT_SSH_COMMAND=false`, `GIT_CONFIG_GLOBAL=/dev/null`), on the committed head `36ec589a6` with the tree clean (`dirty=0`), as one run of seven files at a time: `tests/test_pinned_copy_text.py`, `tests/test_path_guard_hygiene.py`, `tests/test_repo_hygiene.py`, `tests/test_agents_md_codes.py`, `tests/test_cli_reference.py` (the hygiene five), `tests/test_family_bump_fetch_refspecs.py` and `tests/test_bump_leg_fetch_refspecs.py`.

- **The seven files, git 2.43.0** (`/usr/bin/git`): 504 passed in 469.44s (47 `test_agents_md_codes.py`, 68 `test_bump_leg_fetch_refspecs.py`, 23 `test_cli_reference.py`, 57 `test_family_bump_fetch_refspecs.py`, 121 `test_path_guard_hygiene.py`, 87 `test_pinned_copy_text.py`, 101 `test_repo_hygiene.py`; the counts are from `--collect-only -q` on the same head and add up to 504). Log `v131/n1238/gate-g243.log`.
- **The seven files, git 2.47.1** (first on PATH, `git --version` printed into the log): 504 passed in 714.00s. Log `v131/n1238/gate-g247.log`.
- **`-k carried-refs-h-not-a-pattern`** over `tests/test_family_bump_fetch_refspecs.py` and `tests/test_bump_leg_fetch_refspecs.py`: 2 passed, 123 deselected, on 2.43.0 and on 2.47.1 (`run-k-g243.log`, `run-k-g247.log`).
- Both runs start with `dirty=0`. Python 3.12.3 (CI runs 3.14); nothing asserts text that depends on the version.

The measured git behaviour above comes from the probe scripts, not the test files. `-k carried-refs-h-not-a-pattern` over the two fetch-refspec files is part of those runs.

`git merge-tree --write-tree origin/main HEAD` exits 0, with tree `9d874bcf2caf`, equal to this head's tree, against main `50869f384` (the merge base). Against the head of each open PR that touches `scripts/shape_materialize.py` or `scripts/bump-leg.py` (read from `gh pr list --state open --limit 100 --json number,headRefName,headRefOid,files`, 52 open PRs at the last read, 7 of them touch one of the two files), `git merge-tree --write-tree HEAD <head>` exits 0 and the merged tree still holds the new sentence and not the old one:

```
#1223 f50b472  exit 0   #1207 6fa1edd  exit 0   #1160 0068f5a  exit 0   #1131 55c30f2  exit 0
#1118 2307036  exit 0   #1105 b28f40d  exit 0   #1104 de89aa8  exit 0
```

Main moved once while this was gated, to `4829418f0` (`adopt-project.py`, `docs/cli.md` and a new test file: none of the files this change or its runs touch), and `git merge-tree --write-tree origin/main HEAD` against it also exits 0.

None of their hunks is within 100 lines of the sentence changed here.

Review: rv1244 and rv1244b, lane 5's independent sonnet reviewers, which wrote none of #1244: READY at `36ec589a6`, H0 of this landing (issuecomment-6723280477), with no MUST or SHOULD; its four NITs are optional (two wordings, a body note and a follow-up). CI (Brett Heap's ruling 27): H0 is the head 473959f itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 9729315; and the local gate passed on its predicted squash tree fb798f3 over main 9252ef2: the repo_hygiene, agents_md_codes, cli_reference sets, 171 passed. Landed on Brett Heap's word of 2026-10-23T13:10:26Z, relayed to every lane by lane 3 (#473 issuecomment-6365697123): "merge v1.2.1 PRs once reviewed and green".

Closes #1238

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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