Repository navigation
bump-leg.py and update-shape.py quote the paste lines that named a root or a path bare, the doctor's quote_arg quotes a leading = for zsh, and README says the printed commands are for a POSIX shell (#532) - #588
Merged
Conversation
…ot or a path bare, the doctor's `quote_arg` quotes a leading `=` for zsh, and README says the printed commands are for a POSIX shell (#532) Brett Heap ruled on 2026-10-14 ("README; doctor exempt") that the tools print POSIX-quoted commands, for Git Bash or WSL2, and that README says so; the doctor's own PowerShell quoting stays. The lines still concatenating a root or a path were the ones that did not hold that rule: under `sp ace/` they pasted as two arguments, under `it's/` as a quotation nothing closes, and under `d$x/` the shell expanded `$x` before git saw it. bump-leg.py. `_switch_command` spells `git -C <root> switch -c bump/<role>-<id>` once, with `shlex.join`, for both refusals (`bump-leg-root-detached`, `bump-leg-on-tracking-branch`), which put `str(root)` in bare, and for the dry run's hint (#477), which already was quoted, so the three keep naming the same branch the same way. The closing `NEXT git -C <root> push -u origin <branch>` is `shlex.join` too. update-shape.py. Each path is `shlex.quote`d in `check`'s ADDED UPSTREAM list (`--add <path>`), in `apply`'s `pass --add <path>` note, in the `update-local-drift` remedy (`--accept-local <path>`), and in the two sentences `check` appends to a `both` and a `locally-modified` row, which name `apply --accept-local <path>` (display only, but the flag is typed). The `update-branch-exists` remedy's `git -C <root> checkout <branch>` goes through `_by_hand`, the file's one quoter for a line a person pastes. A name that needs no quoting prints as it always did, so every existing assertion that pins a plain path stands. shape-doctor.py. `quote_arg` quotes a word that begins with `=` in `sh`'s dialect, as `repo_shape.shell_quoted` does: zsh, macOS's login shell, expands a bare `=word` to a command's path (`zsh -f -c 'echo =ls'` prints `/usr/bin/ls`). PowerShell's dialect is unchanged, because PowerShell expands no `=word`; so the family test that holds the two quoters to one answer keeps its leading-`=` exception for `nt` alone. README.md and docs/handbook.html. One sentence at the end of the "Windows, natively" section and of the Windows panel's last paragraph: printed commands are quoted for a POSIX shell, so paste them into Git Bash or WSL2, and only `shape-doctor.py`'s lines, `family.py`'s `NEXT` lines and `setup-project.py`'s family lines are quoted for PowerShell, on Windows. It names more than the doctor because `family.py` (`repo_shape.shell_line`, #507) and `setup-project.py` (`shell_word`, #486) spell those lines for the host's shell too; `templates/family-root/scripts/siblings.py` does not (`shlex.join`, by design) and is not named. README.md stays at 1427 lines, the cap with `tests/readme-cap.d/550.txt`: two paragraphs of the section are tightened to make the room, and no cap file is added. The seven markers that record README.md § Starting a project in a new organisation (#519) move to its new blob; the footer's history is frozen, so it gains no clause. Tests. New `tests/test_bump_leg_paste_lines.py` and `tests/test_update_shape_paste_lines.py` split each printed line with `shlex.split` under roots and paths named `sp ace`, `it's` and `d$x`, and paste the runnable ones into `sh` (the `--add` into `apply`, the checkout and the `switch -c` into a root, the `NEXT` into a push). `tests/test_family_printed_quoting.py` holds `quote_arg("=ls")` and the rest of its `LEADING_EQUALS` to the POSIX dialect and reads them back through `sh`, `bash` and `zsh -f`. The byte-for-byte refusal in `tests/test_bump_leg_dry_run_tracking.py` spells its `switch -c` with `shlex`, as the dry run's hint does there. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Reviewer's GuideThe PR makes printed remediation, NEXT, and path-bearing commands safely pasteable by applying POSIX-aware quoting, closes shape-doctor's leading-equals gap for zsh, and documents the resulting shell-specific command conventions in the README and handbook, with extensive regression and integration coverage. Sequence diagram for quoting and pasting a remediation commandsequenceDiagram
actor User
participant Tool as bump-leg.py or update-shape.py
participant Shell as POSIX shell
participant Git
User->>Tool: Run check or bump command
Tool->>Tool: shlex.join or shlex.quote
Tool-->>User: Print quoted remediation or NEXT command
User->>Shell: Paste printed command
Shell->>Git: Execute command with preserved root, path, and branch arguments
Git-->>User: Perform checkout, switch, push, or apply
Flow diagram for safely pasteable printed commandsflowchart LR
Tool[bump-leg.py or update-shape.py] --> Quote[POSIX quoting]
Quote --> Command[Printed remediation or NEXT command]
Command --> Shell[Git Bash or WSL2]
Shell --> Args[Original paths and roots preserved as single arguments]
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
…rShell and says Git Bash, bump-leg.py quotes the leg's `git -C <leg> status` line, and tests pin the handbook to README and the named tools to the code (#532) R588's review of the first commit found the README sentence untrue of `family.py`, which prints more than its `NEXT` lines in the host's dialect (`repo_shape.shell_line`/`shell_quoted`): the dry run's `branch` line and the `switch -c` remedy of `family-holder-detached` and `family-on-tracking-branch` (`_switch_command`), the `add` remedy of `family-holder-already-here` (`_add_command`), and the `git rm -r --cached` of `member-path-taken`. On Windows that dialect leaves a plain root bare, backslashes included, so a reader who followed the sentence and pasted one of them into Git Bash got `git -C C:workOrbitOrbit ...`. Brett Heap's ruling ("README; doctor exempt", 2026-10-14T23:54:30Z) stands: the tools print POSIX-quoted commands and README says so, and the doctor's PowerShell quoting stays. README.md and docs/handbook.html. The sentence is now: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash. Only `shape-doctor.py`'s lines, `setup-project.py`'s family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `git rm -r --cached` lines are quoted for PowerShell, on Windows." - It names exactly the PowerShell-quoted lines, found by reading every call of `quote_arg`, `shell_quoted`, `shell_line` and `shell_word` at this head. The dry run's `branch` line is a `switch -c` line (`_branch_said` prints `_switch_command`), so it needs no words of its own. `git rm -r --cached` is spelled in full because `family.py`'s by-hand rollback prints `git rm -q --cached` in POSIX, as it does `rm -rf`, `git config --unset-all` and the lock's `rm -f`. - "Git Bash" and no longer "Git Bash or WSL2": the sentence sits in "Windows, natively", where every printed root and interpreter is a Windows path (`'C:\work\Atlas'`), which a WSL2 shell cannot open. WSL2 has its own section. - README.md stays at 1427 lines: the paragraph that says the Microsoft Store `python` has no launcher is joined to the "Two commands rather than one pipe" paragraph after it, which saves its blank line. The seven markers for § Starting a project in a new organisation move to the new blob. New tests/test_readme_windows_quoting_sentence.py. The handbook's Windows panel, with its tags stripped, must say README's sentence word for word once whitespace is one space (a README-only change with the markers re-hashed passed every test before it), and the shipped files that call a quoter that follows the host must be exactly `shape-doctor.py`, `scripts/family.py` and `setup-project.py`, read with `ast`, so a fourth is a fault until the sentence names it. scripts/bump-leg.py. `_restore_leg_if_moved`'s `LegNotAsFound` told a person to read `git -C <leg> status` with the leg's path bare, the last paste line in the file that was: it is `shlex.join` now, inside the backticks. tests/test_bump_leg_rollback.py asserts that spelling, built with `shlex` so it holds on a Windows root, where `shlex` quotes every backslash path; and tests/test_bump_leg_paste_lines.py calls `_restore_leg_if_moved` on a leg under `sp ace/`, `it's/` and `d$x/` and requires the line to split into the intended argv and, pasted into `sh`, to read that leg. tests/test_bump_leg_paste_lines.py. The closing `NEXT` is also held for a branch named `bump/it's`, `wip$x` and `fix/a;b`, which git accepts: its last word must be the branch, and, pasted, the line pushes that branch to the root's remote. tests/test_update_shape_paste_lines.py builds the row before `pytest.raises`, so only `_refuse_local_drift` is inside the block (Sonar S5778). Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…arkers of docs/handbook.html set to the merged README.md's section blob, the only conflict Merges main 70331aa, which holds #597 (Linux-only CI) and lane 4's #586 (shape-doctor.py, #547), so that #588 (#532) lands on a head whose merge base is a main that has touched none of its files since (ruling 27). README.md, shape-doctor.py, update-shape.py and tests/test_family_printed_quoting.py auto-merged; docs/handbook.html is the one conflict. The conflict is seven one-line hunks, each the marker at the top of a section that reads README.md § Starting a project in a new organisation. Main's side carried blob acdf65726ad6433d1d70386eb17f21b015673da0 and #588's side blob 20dfa48fe4008a2ebf77a4cf1aee232714072f27. Neither names the merged README.md's section, which now holds both changes, so all seven are set to 95f06438cf8088e29bba6f56991898f304405f3b: `readme_fingerprint` of that segment, imported from tests/test_repo_hygiene.py and applied to the `readme_segments` cut of the merged README.md. The same call over main's README.md and over #588's reproduces acdf657... and 20dfa48..., so it is the function the page's check reads. Every other line of the page is as git merged it, and the page was written as bytes. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
|
brettheap
marked this pull request as ready for review
October 15, 2026 18:58
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 1 day and 4 hours by commenting |
This was referenced Oct 15, 2026
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
Brings in main bcfa364: #588 (709eec1, POSIX-quoted paste lines and README's Windows sentence) and #569 (bcfa364, setup-project.py's paste lines). One conflict: docs/handbook.html: the seven markers that read README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park) conflicted, this branch's a0e0f23c against #588's 95f06438. Both moved to the merged section's blob, d77a1e59fe4951aa2a6f96808725d0506d4ce357, as the hygiene check printed. Both sides' prose merged on its own: #588's POSIX-quoting sentence in the Windows panel beside #587's install step, NOTE and macOS sentence. Re-read: nothing in the seven sections is made false by the other side. README.md and docs/cli.md merged cleanly with both sides' hunks, and `render-cli-reference.py --check` passes. README.md is 1449 lines (cap 1449) and AGENTS.md 492 (cap 492, raised by #566 on main). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…-shape.py lines, a clean merge Main at 709eec1 is #588's squash (#532), which changed update-shape.py in `accept_local_exit`, `keep_local_exits`, `cmd_check`, `commit_on_branch`, `_refuse_local_drift` and `_note_untaken_additions`, none of which this branch touches. Under ruling 27 a pull request whose file main changed after its merge base needs this refresh before it lands. The merge is clean: git auto-merged update-shape.py, and nothing needed resolving. Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
… bump-leg.py lines, a clean merge Lane: openreposhape-4 (openRepoShape-4) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
12 tasks done
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…n-source-unread Merges origin/main 66267f0 so the PR's CI merge ref and the fix that follows run on current main. The fourteen landings since 2bf8fd4: #597, #578, #593, #589 (#449), #586, #573 (#537), #575 (#539), #566 (#528), #571, #588, #569 (#538), #582 (#523), #616 and #576 (#441, #442). Those that touch this PR's files: - adopt-project.py: #589 (`check` recomputes each pin's digest in the clone `plan` read it from: LOCAL_CLONE_RE, _recorded_clone, _why_not_rechecked, _recomputed_pin_finding, _pin_digest_findings), #582 (what `plan`, `execute` and AGENTS.md say around a run) and #566 (a source's own `single-repository.yaml`). - scaffold-project.py and docs/cli.md: #573, #569 and #576 (the `_declaration` docstring: `--pin-source` verifies the first chain link alone, which this PR's N3 wording already says). - tests/test_adopt_pins.py: #589's thirteen cases. - tests/test_scaffold_leg_path_collisions.py: #573. This commit holds the textual resolution only. One hunk conflicted, the `from shape_materialize import (...)` list at adopt-project.py:261. It is resolved as the union of both sides' changes to the list: main's added `pin_digest_from_source` (#589), and this PR's added `DIGEST_FROM_CLONE`, `DIGEST_FROM_GH`, `pin_source_path` and `refuse_an_unread_pin_source` and its removal of `one_printable_line`, whose last caller this PR deleted and to which main added none (keeping it is pyflakes' "imported but unused"). pyflakes adopt-project.py is clean on the merge. Every other file merged without a marker, and `scripts/render-cli-reference.py --check` is ok. The merge alone does not keep #589 working: this PR's `plan` records a bare `local-clone`, which names no clone for #589's `check` to read, and `check` refuses `local-clone (<path>)` as `plan-pin-malformed`. The next commit reconciles the two. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…on main as 22eacee) into fix/585-git-bash-windows: README's "Windows, natively" and the handbook's Windows panel keep #588's POSIX-quoting sentence after this branch's Git Bash sentences, README stays at its 1449-line cap, and the seven markers move to the merged README (#585) #587 squash-landed on main as 22eacee at 2026-10-15T20:27:52Z, and GitHub retargeted #596 to main. 22eacee's tree (831e71d) is `git merge-tree --write-tree 2b6dbb7 3d8fa05`, so main holds exactly #587's final head. This merge takes that head first; the next commit merges main. Since b23b245, the last base this branch merged, 3d8fa05 had merged main three times. Among what came in is lane 4's #588 (709eec1, #532): one sentence at the end of README's "Windows, natively" and of the handbook's Windows panel saying printed commands are quoted for a POSIX shell, so paste them into Git Bash, and naming the lines quoted for PowerShell on Windows; it also tightened the two paragraphs above it and moved the seven markers for README.md section "Starting a project in a new organisation". Two files conflicted. README.md, one region: the paragraph's last lines. Kept from this branch: "That two-liner IS the Windows way in from PowerShell, which has no twin of ... `openRepoShape` command. Git Bash runs that command and step 3's one line, tested in CI but for a typed `yes`, which its preflight cannot take there." Kept from #588, word for word: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash. Only `shape-doctor.py`'s lines, `setup-project.py`'s family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `git rm -r --cached` lines are quoted for PowerShell, on Windows." Dropped from #588's side: "the `openRepoShape` command in step 3 below is a macOS, Linux and WSL2 convenience with no Windows twin, because on Windows there is nothing to install", which this branch replaced because Git Bash runs that command. To fit the cap, "no twin of the `openRepoShape` command in step 3 below" became "no twin of step 3's `openRepoShape` command" (step 3 is "Install the `openRepoShape` command"), and the six lines rewrap. README is 1449 lines, its cap (1422 + 5 for 550.txt + 22 for 564.txt); #588's tighter paragraphs above merged as text. docs/handbook.html, eight regions. The Windows panel keeps this branch's "There is no openRepoShape command to install in PowerShell ... none of its offers can be accepted there." and then #588's sentence, word for word, so tests/test_readme_windows_quoting_sentence.py still finds README's sentence in the panel once. The seven marker regions (this branch's 34723d9a against #588's d77a1e59) each record 278a19479524fe8aae292daaca84492902b140ed, the merged section's hash from tests/test_repo_hygiene.py's own readme_segments and readme_fingerprint; all 23 markers match the merged README. Everything else merged as text. Against 3d8fa05 this merge differs only in this branch's six files: .github/workflows/tests.yml (the tests-git-bash job and the header's exception sentence, beside main's #593 concurrency block), README.md, docs/handbook.html, tests/conftest.py, tests/test_install_sh.py and tests/test_openreposhape_command.py. tests/test_repo_hygiene.py, tests/test_readme_sections.py and tests/test_readme_windows_quoting_sentence.py: 144 passed on the merged tree. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 15, 2026
…book.html's seven § Starting markers move to the merged README's blob (#595) #587 landed on main as 22eacee, and its branch, this PR's base, is gone. This merges its final head first, as the stacked-PR-after-squash procedure says. The one conflict was docs/handbook.html: the seven markers for README.md § Starting a project in a new organisation (masthead, start, partway, ci, legs, doublepin, park). Both sides changed that segment: - this branch, step 3's install.sh paragraph; - #587, the macOS bash 3.2 sentence (#597) and the Windows paste-quoting sentence (#588). All seven move to the merged README's blob, d1666fa6. § The three legs changed on #587's side alone, and its markers already carry the merged blob. Both sides' prose merged with no conflict. This branch's step 3 and consent rule are where they were, and #587's two new sentences sit elsewhere in `start`. handbook_marker_problems reports nothing. README.md is 1449 lines, at its cap of 1422 + 5 + 22. AGENTS.md is 492, at its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 15, 2026
…es in Git for Windows' own bash, so README and the handbook say Git Bash runs the one-line install, tested in CI but for a typed yes, which it cannot take there (#585) (#596) **Fix round 7: main after #598 (3fb21d9) (head 52524fe).** Lane 3's #598 (#595) landed on main as 3fb21d9. It rewrote README step 3's `install.sh` paragraph and the handbook's masthead and `start` for `uv`, and for the two readinesses install.sh now ends with. It also changed `install.sh` and `tests/test_install_sh.py`, and added `tests/test_install_sh_filter_repo.py`. One merge commit, **52524fe3**, merges main 396c0ff: 55faad2 (#591), fbbbc8d (#572), 7fdec0a (#624), 3fb21d9 (#598) and 396c0ff (#570). No other change. - **Conflicts.** README.md had 1 region (step 3's `install.sh` paragraph). docs/handbook.html had 8: the consent rules' `install.sh` item and the seven § Starting markers. `tests/test_install_sh.py` merged as text, and so did the handbook's masthead and `start`. - **The head holds all of main.** `git merge-tree --write-tree 52524fe origin/main` gives 52524fe's own tree, 6d8ffea. `git diff --stat origin/main 52524fe` names only this PR's six files (436+, 147-). **The README and handbook resolution with #598 (52524fe).** - **README, before.** This PR (4c3b72a) had added "; in Git Bash it can take none" after step 3's "each behind a typed `yes` at your terminal". #598 rewrote the same paragraph: "It installs `git-filter-repo` with `pipx`, or `uv`, where it can, or prints the command, runs `openRepoShape --preflight`, and says last if the machine is ready to adopt or only to scaffold. `install.sh` never runs `sudo`; the preflight offers what is missing, each behind a typed `yes` at your terminal." - **Fitting the cap.** Keeping both made nine lines where #598 has eight, and README was at its cap. The cap was not raised. Two rewordings fit it, and no fact is dropped: - "`install.sh` is attached to every release and installs the newest, with no `gh` and no login." became "`install.sh`, attached to every release, installs the newest with no `gh` and no login." - #598's "`install.sh` never runs `sudo`;" became "It never runs `sudo` itself;". "It" is install.sh, the subject of the sentence before it, and "itself" is #564's own word for the contrast with the preflight. - **README, after** (8 lines, at most 80 characters, as 78 README lines on main already are): ``` `install.sh`, attached to every release, installs the newest with no `gh` and no login. Its command is PINNED to the release tag, recorded in `openRepoShape.ref` beside it: `setup.sh` and the standard are fetched at that tag, not at main. It installs `git-filter-repo` with `pipx`, or `uv`, where it can, or prints the command, runs `openRepoShape --preflight`, and says last if the machine is ready to adopt or only to scaffold. It never runs `sudo` itself; the preflight offers what is missing, each behind a typed `yes` at your terminal; in Git Bash it can take none. `| bash -s -- v1.0.1` names a release; the same line again upgrades. ``` #598's `pipx`/`uv` sentence and this PR's clause are word for word. README is **1449** lines, its cap (1422 + 5 for `550.txt` + 22 for `564.txt`). - **The Git Bash clause is still true after #598.** Under `curl … | bash`, `[ -t 0 ] || exec </dev/null` (install.sh:342; #598 moved it down from :277 and left its text alone) leaves the preflight no terminal on stdin. In Git Bash, setup-project.py runs on native Windows Python, where `open_terminal()` returns None (`os.name == "nt"`, setup-project.py:1372). So `can_ask()` is False, and `offer()` takes no `yes`. #598 changes what install.sh does before and after the preflight, not how the preflight reads an answer. - **Handbook, consent rules.** The `install.sh` item keeps #598's text, "installs `git-filter-repo` with `pipx` or `uv` where one is there, and itself never runs `sudo` or installs either of them", and then this PR's clause, word for word: "; in Git Bash it can take no `yes`, so it installs nothing." - **Handbook, masthead and `start`.** They merged as text: this PR's "macOS, Linux, WSL2, Git Bash" heading and PowerShell line, beside #598's "where `pipx` or `uv` can install it … and whether the machine is ready to adopt or only to scaffold". The `start` step-3 paragraph is #598's. It is `data-platform="linux mac wsl"`, as before, so it carries no Git Bash clause. - **The seven § "Starting a project in a new organisation" markers.** This PR's 278a1947 conflicted with #598's d1666fa6. All seven now record `badbb6b273215042ba0e8ae167fd760dc25430ee`, computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`. That driver reproduces both parents' hashes (main d1666fa6, 4c3b72a 278a1947). All 23 markers match the merged README. - **`tests/test_install_sh.py`.** #598's hunks are `no_pipx` and `no_pipx_but_apt`'s printed commands, the test's docstring, and the line before the printed command, which now starts "there is neither pipx nor uv". They sit beside this PR's `bash_path` in `present_on_path` and in the same test's preflight line. Both sides' tests keep their meaning. **Fix round 6: retarget to main after #587 (22eacee) (head 4c3b72a).** #587 squash-landed on main as 22eacee at 2026-10-15T20:27:52Z, and GitHub retargeted this PR to `main`. 22eacee's tree, 831e71d, is `git merge-tree --write-tree 2b6dbb7 3d8fa05`: main holds exactly #587's final head 3d8fa05. So this round adds two merge commits and no other change: - **3080ac0d** merges #587's final head, 3d8fa05. It conflicted only in README.md and docs/handbook.html, with lane 4's #588 (709eec1), which 3d8fa05 had merged from main. The resolution keeps both sides' text (below). - **4c3b72ae** merges main, 77bb5f0: 22eacee, plus 2b6dbb7 (#584), which the squash carried but 3d8fa05 lacked, and #615's 77bb5f0, which landed after it. Four files conflicted, because the squash is not an ancestor of 3d8fa05 (merge base 62ef256). For each of them, main's copy is the same blob as 3d8fa05's, and 77bb5f0 does not touch it. Each therefore takes this branch's copy, which is 3d8fa05's copy plus this PR's change: | File | `3d8fa05e:` = `origin/main:` (77bb5f0) = `22eaceed:` | |---|---| | README.md | d164131 | | docs/handbook.html | 59ec34d | | tests/test_install_sh.py (add/add) | 34c7de5 | | tests/test_openreposhape_command.py | 5e49547 | No file was merged by hand. `git merge-tree --write-tree 4c3b72a origin/main` gives 4c3b72a's own tree, 97843ec, so the head holds all of main. `git diff --stat origin/main 4c3b72a` names only this PR's six files: `.github/workflows/tests.yml`, README.md, docs/handbook.html, `tests/conftest.py` and the two suites (430+, 141-). **The README and handbook resolution with #588 (3080ac0).** #588 ends README's "Windows, natively" and the handbook's Windows panel with one sentence: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash. Only `shape-doctor.py`'s lines, `setup-project.py`'s family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `git rm -r --cached` lines are quoted for PowerShell, on Windows." That sentence now follows this PR's Git Bash sentences, word for word. `tests/test_readme_windows_quoting_sentence.py` holds the handbook to README's words, and it passes. - **README**, before (3d8fa05): "That two-liner IS the Windows way in; the `openRepoShape` command in step 3 below is a macOS, Linux and WSL2 convenience with no Windows twin, because on Windows there is nothing to install. Printed commands are quoted …" - **README**, after: "That two-liner IS the Windows way in from PowerShell, which has no twin of step 3's `openRepoShape` command. Git Bash runs that command and step 3's one line, tested in CI but for a typed `yes`, which its preflight cannot take there. Printed commands are quoted …" - **Fitting the cap.** The two texts side by side came to seven lines where #588 had six, and README was at its cap. So "no twin of the `openRepoShape` command in step 3 below" became "no twin of step 3's `openRepoShape` command". Step 3 is "Install the `openRepoShape` command", so the meaning is the same. The six lines rewrap, and README stays at **1449** lines, its cap (1422 + 5 for `550.txt` + 22 for `564.txt`). No raise. - **#588's other README hunk**, the tighter `python setup-project.py` and "Two commands rather than one pipe" paragraphs, merged as text. - **Handbook Windows panel.** It keeps this PR's "There is no `openRepoShape` command to install in PowerShell … so none of its offers can be accepted there." and then #588's sentence, unchanged. - **The seven § "Starting a project in a new organisation" markers** (this PR's 34723d9a against #588's d77a1e59) now record `278a19479524fe8aae292daaca84492902b140ed`, the merged section's hash. It was computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`, and all 23 handbook markers match the merged README. - **Consistency.** Every Windows, Git Bash, PowerShell and WSL2 sentence was read again in the merged text. #588's sentence is true in Git Bash too: the named tools run under native Windows Python there, where `os.name` is `nt`, so their lines are quoted for PowerShell, "on Windows", as it says. See checklist candidate 7. **Fix round 5 (head fe58d66).** One comments-only commit to `tests.yml`, for the re-review's N1 and N3. The header says the FULL suite runs on Linux only, so read alone it stays true beside the exception. The job's comment gives the measured "about a minute and a half (86 s on the first run, 50 s of it pytest)" where it said about three minutes. **Fix round 4 (head 5f001de).** Brett Heap merged #597 ("ci: run the test suite on Linux only") at 2026-10-15T16:54:43Z, which removed the Windows job these suites ran in. His word at 2026-10-15T18:20:35Z: "Option 1: keep a small Git Bash job for #596". That round added two commits: - **f6552862** merged lane 3's `l3-564-carry-to-main` b23b245 (#587's branch, this PR's base until fix round 6), which held main 654c476 and so #597. `tests.yml` became that branch's, byte for byte: the guard step and the macOS comment edit went with the two jobs #597 removed. The handbook keeps #597's macOS panel beside this PR's Git Bash text, and its seven markers were moved. - **5f001de1** adds the `tests-git-bash` job, and fixes the macOS sentence that #597 made say "CI runs on Linux only". v1.0.1's one-line install (#564) is meant to work in Git Bash on Windows, but nothing ran it there: `tests/test_openreposhape_command.py` and `tests/test_install_sh.py` wore `WINDOWS_SKIP`. They now run in the `tests-git-bash` job, under Git Bash. **Which bash.** `BASH` in `tests/conftest.py` is Git for Windows' `usr\bin\bash.exe`, found from `git --exec-path`, never from PATH. Probe 1 on `windows-latest` showed why: - A bare `bash` through `subprocess` is System32's WSL launcher. It prints "Windows Subsystem for Linux has no installed distributions." in UTF-16. CreateProcess searches System32 before PATH. - `shutil.which("bash")` finds `Git\bin\bash.EXE`. That launcher puts Git's `mingw64\bin` and `usr\bin` first on PATH, so Git's real `curl` came before the suite's fake one. The old install.sh suite, run that way, reached github.com and printed the real newest release: `release: v1.0.1, the newest release of opensoft/openRepoShape (https://github.com/opensoft/openRepoShape/releases/latest)`. A `git_bash_session` fixture puts Git's `mingw64\bin` (or `clangarm64\bin` on ARM64), `usr\bin` and `MSYSTEM` where Git Bash puts them, behind any fake a test puts first. `BASH_SKIP` replaces `WINDOWS_SKIP` in these two files only. **What the tests now do on Windows.** - **Paths.** Every path handed to bash, or expected in what it prints, is spelled the way Git Bash spells it (`/d/a/...`). The conversion uses Git's own `cygpath` (`bash_path`, `native_path`). Git Bash converts `HOME`, `TMPDIR` and `PATH` itself. - **Line endings.** Every script a test writes for bash is written as LF bytes. The fake's redirect headers and its `gh` tag are written as bytes: Python on Windows prints `\r\n`, and `$(...)` keeps the `\r`. - **The executable bit.** Git Bash's drives are mounted `noacl`, so `chmod 755` changes nothing. A file that starts with `#!` runs anyway. `--install`'s output is checked by mode 755 on POSIX, and by bash's own `test -x` on Windows. - **A test's own PATH.** On Windows it holds `#!/bin/sh` wrappers. A symlink to a Git Bash program cannot load its DLL: with symlinks, `mktemp` failed and install.sh refused `could not make a temporary directory`. - **An argument holding a newline** reaches Git Bash through the environment. On the Windows command line it was split in two: `v1\nforged` arrived as "two versions given". In Git Bash, bash hands bash the argument whole, so the test now does the same. - **`ln -s`.** A new test makes a link with Git Bash's own `ln -s`, after dropping any `$MSYS` the caller set. By default that is a copy, which has no `openRepoShape.ref` beside it and so follows main. With `MSYS=winsymlinks:nativestrict` it is a real link, which reads the record. - **Two anchors (fix round 2).** - The fetched-setup.sh test now also asserts that the parent of the stub's directory exists. Its "the directory is gone" line therefore cannot pass on a path Python cannot open. Both asserts read one `ran_dir = native_path(ran_in)` (fix round 3), so the existence check cannot be reverted to `Path(ran_in)` alone. - The `gh` shim test already could not pass with the shim unreachable: it asserts that the shim's own log holds exactly `auth status`, and only the shim writes that log. Probe 4 reverted its `os.pathsep` and it still passed: the shim stayed reachable either way, most likely through MSYS's conversion of PATH, which the log does not show. **The workflow (fix round 4).** - **The header** keeps #597's text and adds one sentence: "One exception, on Brett Heap's word at 2026-10-15T18:20:35Z, "Option 1: keep a small Git Bash job for #596": the `tests-git-bash` job below runs the launcher's and the installer's suites, and nothing else, on Windows under Git Bash." - **`tests-git-bash`**: `runs-on: windows-latest`, `timeout-minutes: 15`, and no `if:`, so it runs on every trigger the workflow has: a push to main, a pull request, a manual run. Its steps: 1. checkout and setup-python, at the ubuntu job's two commits, with `fetch-depth: 0`; 2. `python -m pip install --disable-pip-version-check pytest==8.3.4`; 3. the git identity, appended to `GITHUB_ENV` in PowerShell, as the old Windows job did; 4. "Git Bash is the bash the launcher and installer suites run": fails the job when conftest finds no Git Bash, and prints the bash the suites will run; 5. `python -m pytest -q -rA tests/test_openreposhape_command.py tests/test_install_sh.py --basetemp=${{ runner.temp }}\t`. - **Its env** is the old Windows job's: `PYTHONUTF8=1`, `core.autocrlf=false` and `core.longpaths=true` through `GIT_CONFIG_COUNT`. - **Its comment** says why it exists (#585, the one-line install on Windows), why it runs two suites only (#597), why its bash is Git's `usr\bin\bash.exe` (pointing to conftest), and why it runs on a push to main. **The docs.** - **README § "Windows, natively" and the handbook's Windows panel** say: PowerShell has no twin of the command; Git Bash runs it and the one-line install, tested in CI but for a typed `yes`, which the preflight the one-line install ends with cannot take there. #588's sentence about POSIX-quoted printed commands follows (fix round 6). - Why: install.sh:342 (:277 before #598) runs `exec </dev/null` under `curl … | bash`, and setup-project.py's `open_terminal()` has no Windows path. - Nothing installs without a typed `yes`, so consent holds. - Fix round 4: this sentence was left with nothing behind it once #597 removed the Windows job. `tests-git-bash` makes it true again, so its text is unchanged. - **README's macOS sentence and the handbook's macOS panel (fix round 4).** #597 made them say "though CI runs on Linux only and no run of it checks them against bash 3.2". With this job that is false. They now say "though no CI run, on Linux or in Git Bash on Windows, checks them against bash 3.2". README rewraps inside the same four lines. - **Step 3's sentence about the preflight's offers** (README) and **the consent rules** (handbook) add that in Git Bash it can take no `yes`. Since fix round 7, both clauses follow #598's `pipx`-or-`uv` text, and README's paragraph is reworded to fit (above). - The handbook's own step-3 install paragraph is `data-platform="linux mac wsl"`, so it is already scoped away from Windows, and is left. - **The WSL2 sentence** says WSL2 "runs every bash entry point in Linux itself", instead of being "still how you run" them. - **The handbook's masthead** lists Git Bash beside macOS, Linux and WSL2. - **README keeps its line count**: 1449, the cap (1422 + 5 for `550.txt` + 22 for `564.txt`). - **The seven handbook markers** for README.md § "Starting a project in a new organisation" record its new hash, `badbb6b273215042ba0e8ae167fd760dc25430ee` (fix round 7, after #598). Fix round 4's merge set them to `a88ac2d0…`, 5f001de moved them to `34723d9a…` for the macOS sentence, and fix round 6 to `278a1947…` after #588. Every hash was computed with `tests/test_repo_hygiene.py`'s own `readme_segments` and `readme_fingerprint`. - **A separate small job, on Brett Heap's word.** At 2026-10-15T18:20:35Z he chose "Option 1: keep a small Git Bash job for #596". The option read: "A small Windows job for just these two suites. The probe runs took 2-3 minutes end to end, so it finishes well inside the 34-36 minute Linux job and adds no wall-clock time. The README claim stays true. It is an exception to #597's decision, so it needs your word." So the job runs these two suites and nothing else. It is not a matrix entry on the ubuntu job, whose actionlint download is a linux-amd64 binary. - **Every trigger, a push to main included.** A squash lands a tree no pull-request run tested (#122), and these two suites are the only run of the one-line install on Windows. At about a minute and a half beside the ubuntu job (86 s on its first run), it adds no wall-clock time. - **What the job keeps from the old Windows job, and why.** - `PYTHONUTF8=1`: the suites decode bash's output with `text=True`, which uses the locale's code page (cp1252) outside UTF-8 mode, and the launcher's `--help` prints em dashes (openRepoShape:108, :119-120). - `core.autocrlf=false` and `core.longpaths=true`: every Windows run of these suites had both, #585's four probes included, and no run without them exists. - `fetch-depth: 0`: `test_a_ref_the_doctor_would_check_out_is_validated` sets `OPENREPOSHAPE_REPO` to this checkout, so the launcher clones it, and every Windows run of it cloned a full one. - `--basetemp=${{ runner.temp }}\t`: the old job's short temp root, for MAX_PATH. - **What it leaves out.** - **git-filter-repo.** These suites never reach a real one. The install suite's PATH holds only its own fakes, `git-filter-repo` and `pipx` among them. The launcher's machine report gets a fake `git-filter-repo` on the PATH `machine_path` builds. On Linux, with every PATH directory that held a `git-filter-repo` removed (`filter-repo=none` in the run log), the two suites gave **113 passed**. `test_it_installs_the_git_filter_repo_ci_runs` still finds the ubuntu job's pin. - **actionlint.** The ubuntu job lints this file. - **`usr\bin\bash.exe`, not `bin\bash.exe`, and not PATH.** The probe-1 lines quoted above. - **`git_bash_session` is needed, though the runner's own PATH hides it.** - The runner's PATH already holds Git's `bin`, `usr\bin` and `mingw64\bin`, with `bin` before System32 (probe-1 PATH dump). So reverting the fixture changes nothing there (probe 2: 113 passed). - A stock Git for Windows install puts only `Git\cmd` on PATH. Probe 3 ran the two files on exactly that PATH, where `which mktemp` is `None` and `which bash` is `C:\Windows\system32\bash.EXE`: - with the fixture: **113 passed**; - with it reverted: **59 failed, 15 passed, 39 errors**, for example `bash: line 199: mktemp: command not found` and `this machine has no mktemp, which the install needs`. - **`bash -s` runs still take their script through a text-mode pipe.** That pipe hands bash CRLF on Windows, but Git Bash's bash drops a carriage return before a newline: a CRLF script piped to it printed `crlf-ok` and exited 0 (probe 1). Sending bytes instead changed no result (mutation `text-stdin`, 113 passed), so that change was dropped. - **Python can make symlinks on the runner** (`os.symlink: ok`), so the three existing symlink tests run there unchanged. - **The macOS sentence names Git Bash without a line.** "No CI run, on Linux or in Git Bash on Windows, checks them against bash 3.2" is true with this job, and fits README's four lines. A clause naming the two suites would have needed a fifth line and a cap raise. - **Not claimed: that setup's own `yes` is asked in Git Bash.** Typed at a Git Bash prompt, `openRepoShape <Project>` reads its `yes` through native Windows Python. Whether that Python sees a console depends on Git Bash's pseudo-console support, and nobody has confirmed it on a Windows machine. - **Linux, on the head 52524fe** (fix round 7, `dirty=0`): - the two suites: **113 passed** in 41.23s; - `tests/test_install_sh_filter_repo.py` (#598's, unchanged): **18 passed** in 7.32s; - `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py`, `tests/test_setup_by_hand_commands.py` and `tests/test_readme_windows_quoting_sentence.py`: **171 passed** in 25.43s. - `git grep -l 'install.sh' tests/` finds `conftest.py`, `readme-cap.d/564.txt`, `test_install_sh.py`, `test_install_sh_filter_repo.py` and `test_repo_hygiene.py`. Every test file among them was in these runs. No skips. - The merge, before its commit: `test_repo_hygiene.py`, `test_readme_sections.py` and `test_readme_windows_quoting_sentence.py`, 144 passed. - **actionlint 1.7.12 on 52524fe**, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. `tests.yml` is the same blob as at 4c3b72a (5fc46de); #598 does not touch it. - **Linux, on the head 4c3b72a** (fix round 6, `dirty=0`): - the two suites: **113 passed** in 28.57s; - `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py`, `tests/test_setup_by_hand_commands.py` and `tests/test_readme_windows_quoting_sentence.py`: **171 passed** in 17.09s. - Those cover every `tests/test_install_sh*.py` (there is one) and every test file `git grep -l 'install.sh' tests/` finds (`test_install_sh.py`, `test_repo_hygiene.py`; the other two hits are `conftest.py` and `readme-cap.d/564.txt`). - 3080ac0's merge, before its commit: `test_repo_hygiene.py`, `test_readme_sections.py` and `test_readme_windows_quoting_sentence.py`, 144 passed. - **actionlint 1.7.12 on 4c3b72a**, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. The workflow keeps main's #593 `concurrency` block byte for byte (this PR's two `tests.yml` hunks are the header and the new job), the header's "The FULL suite runs on LINUX ONLY" with the exception sentence, and `tests-git-bash`. - **Linux, on the head 5f001de** (`dirty=0`): the two suites, `tests/test_repo_hygiene.py`, `tests/test_readme_sections.py`, `tests/test_cli_reference.py` and `tests/test_setup_by_hand_commands.py`: **281 passed** in 61.24s. - **Linux, fix round 4, before its commit:** - the two suites: 113 passed; - the two suites with no `git-filter-repo` on PATH: **113 passed**; - the four docs and hygiene files: 168 passed; - the merge alone, before its commit: `test_repo_hygiene.py` and `test_readme_sections.py`, 141 passed. - **actionlint 1.7.12**, fetched and checksum-checked as the ubuntu job does, on `tests.yml` and the two template workflows: exit 0, with shellcheck on PATH. - **The YAML parse.** Triggers: `push` to `main`, `pull_request`, `workflow_dispatch`. Jobs: `tests` (ubuntu-latest) and `tests-git-bash` (windows-latest, `timeout-minutes: 15`). Neither has an `if:`, so both run on every trigger. - **Linux, earlier rounds:** on 01e8c2c, the same five files without `test_setup_by_hand_commands.py`, 276 passed. Lane 5's re-review got the same 276 there. On 649b089, with `tests/test_setup_by_hand_commands.py`, `tests/test_agents_md_codes.py` and `tests/test_gh_guard.py` as well, 334 passed. - **Windows, `windows-latest`, through probe branches that ran only these suites:** - Probe 1 (run 38752845751): 109 passed, 2 failed, 0 skipped. The two failures were the newline arguments, fixed since. - Probe 2 (run 38754067543, tree ebba347): **113 passed, 0 skipped**, in 68.83s. - Probe 3 (run 38757083883, on 649b089): **113 passed** in 50.97s. On a stock Git for Windows PATH (only `Git\cmd`): 113 passed. - Probe 4 (`l5-585-probe-4`, on 01e8c2c): **113 passed** in 77.92s (run 38769166462). - **The same two files as they were at 5191f0e, with only `WINDOWS_SKIP` lifted** (probe 1): 88 failed, 23 passed. Two causes: the WSL launcher, and the real network through `Git\bin\bash.EXE`. - **Windows on 5f001de, the PR's own run of `tests-git-bash`:** **113 passed** in 50.28s, with the whole job taking 1 min 26 s (18:37:00Z to 18:38:26Z, check run 116601758252). - The guard step printed `C:\Program Files\Git\usr\bin\bash.exe`. - Its steps and env are probe 4's, less the git-filter-repo install, and with `-q -rA` and a 15-minute job timeout. Probe 2 ran these on **ebba347f**, the tree before the squash, which still had a `run_piped` helper; the `text-stdin` row is that helper. The squashed head 649b089 was then re-measured whole by probe 3 (113 passed), and fix round 2's head 01e8c2c by probe 4. Each row reverts one Windows hunk and runs the two files again. | Hunk reverted | Result | |---|---| | `BASH` is the bare word `bash` | 108 failed | | paths keep Windows' spelling | 25 failed | | scripts written with `write_text` (CRLF) | 1 failed: `test_a_release_whose_command_records_no_ref_exits_one`, whose launcher's first line became `#!/usr/bin/env bash\r` and was refused as not the command | | the fake prints its headers and tag in text mode | 22 failed | | mode 755 checked on Windows too | 2 failed | | symlinks on a test's own PATH | 28 failed | | newline arguments on the command line | 2 failed | | no `git_bash_session` | 113 passed on the runner's PATH; on a stock Git for Windows PATH (probe 3) 59 failed and 39 errors, against 113 passed with it | | `bash -s` script sent as bytes (`run_piped`) | 113 passed either way; this hunk was dropped | Fix round 2's anchors (probe 4, on 01e8c2c): | Run | Result | |---|---| | the fetched-setup.sh test, `native_path(ran_in)` reverted to `Path(ran_in)` in both asserts | exit 1: 1 failed. The new parent-directory anchor catches it. Since fix round 3 both asserts read one `ran_dir`, so this is the only way to revert it. | | the `gh` shim test, its PATH joined with `:` instead of `os.pathsep` | exit 0: 1 passed. The shim stays reachable, so the hunk is not load-bearing on this runner. The log shows only that outcome; MSYS's conversion of PATH is the likely reason. The test is still not vacuous: it asserts that the shim's own log holds exactly `auth status`, and only the shim writes that log, so an unreachable shim would fail it. | | both `ln -s` cases with `MSYS=winsymlinks:nativestrict` exported, head | exit 0: 2 passed | | the same, with the `MSYS` drop reverted | exit 1: 1 failed (`ln-default` made a link), 1 passed | #590 adds four cases to these two suites, written while they skipped on Windows: - `test_a_command_that_is_a_symlink_is_refused_before_any_request[live|dangling]`; - `test_install_refuses_a_target_that_is_a_symlink[live|dangling]`. Each compares a path bash printed with `str(path)`. Each also compares `os.readlink`'s answer with the target, and on Windows that answer typically starts `\\?\` (Python's `os.readlink` documentation, since 3.8; the probe's failures stop at the first assert, so no log shows it). Linux passes on the merged tree (117 passed), but under Git Bash those four cases fail, and `tests-git-bash` runs them. **Whichever of #596 and #590 lands second applies these hunks** to the merged tree. Probe 4 (run 38769166462) applied exactly these. ```diff --- a/tests/test_install_sh.py +++ b/tests/test_install_sh.py @@ -668,12 +668,14 @@ def test_a_command_that_is_a_symlink_is_refused_before_any_request( result = net.run() assert result.returncode == 2, result.stdout + result.stderr said = " ".join(result.stderr.split()) - assert f"{link} is a symbolic link, so nothing was installed" in said, said + assert f"{bash_path(link)} is a symbolic link, so nothing was installed" \ + in said, said assert "pipx upgrade openreposhape" in said assert "pipx uninstall openreposhape" in said assert net.calls() == [], net.calls() assert link.is_symlink() - assert link.readlink() == packaged + # Windows reads a link back with `\\?\` ahead of its target. + assert str(link.readlink()).removeprefix("\\\\?\\") == str(packaged) if not dangling: assert packaged.read_bytes() == original assert sorted(path.name for path in net.bin.iterdir()) == \ --- a/tests/test_openreposhape_command.py +++ b/tests/test_openreposhape_command.py @@ -407,17 +407,18 @@ def test_install_refuses_a_target_that_is_a_symlink(tmp_path, dangling): link = bin_dir / "openRepoShape" link.symlink_to(packaged) result = run_cmd("--install", home=tmp_path, - env={"OPENREPOSHAPE_BIN_DIR": str(bin_dir), + env={"OPENREPOSHAPE_BIN_DIR": bash_path(bin_dir), "OPENREPOSHAPE_REF": "v9.9.9", "OPENREPOSHAPE_RECORD_REF": "1"}) assert result.returncode == 2, result.stdout + result.stderr said = " ".join(result.stderr.split()) - assert f"{link} is a symbolic link, so --install will not copy this " \ - "command through it" in said, said + assert f"{bash_path(link)} is a symbolic link, so --install will not " \ + "copy this command through it" in said, said assert "pipx upgrade openreposhape" in said assert "pipx uninstall openreposhape" in said assert link.is_symlink() - assert os.readlink(link) == str(packaged) + # Windows reads a link back with `\\?\` ahead of its target. + assert os.readlink(link).removeprefix("\\\\?\\") == str(packaged) if not dangling: assert packaged.read_bytes() == original assert not packaged.with_name("openRepoShape.ref").exists() ``` Both files already import `bash_path` once #596 is in. On Linux, the merge of 649b089 with d090f0c, with these hunks: **117 passed**. Fix round 7, on the merge of 52524fe with #590's head 1c8a708, with fix round 6's resolved `tests.yml` (both inputs unchanged since; the result is blob 266c62c): the hunks apply (`git apply --check` exit 0). The two suites (117 by collection), `tests/test_install_sh_filter_repo.py` (18), `tests/test_openreposhape_package.py` and `tests/test_repo_hygiene.py` give **270 passed** in 48.69s. Fix round 6: on the merge of 4c3b72a with #590's current head 1c8a708, with `tests.yml` resolved as below, the hunks apply (`git apply --check` exit 0) and the two suites give **117 passed**; with `tests/test_openreposhape_package.py` and `tests/test_repo_hygiene.py`, 252 passed. Probe 4 runs the merge of 01e8c2c with `l3-563-pypi-package` on Windows: the four cases without the hunks, then both suites with them. In run 38769166462, on Windows: - **Without the hunks**: exit 1, `4 failed, 113 deselected`. These are exactly the four cases. The launcher pair failed on `REFUSED: D:\a\_temp\…\bin/openRepoShape is a symbol…`, which confirms rv585's inferred launcher failures; the install pair failed on `REFUSED: /d/a/_temp/…/.local/bin/openRepoShape …`. - **With the hunks**: they apply (exit 0), and both suites give **117 passed** in 75.13s. **`tests.yml` conflicts with #590 (fix round 6; unchanged in fix round 7, where #590's head is still 1c8a708 and this PR's `tests.yml` is the same blob).** #590 now targets `main` too: at fix round 6 its merge ref's first parent was main 77bb5f0. Its head 1c8a708 has merged #587's final head and main, so it holds #597, and it merges with main cleanly. Both PRs add to the same two places in `tests.yml`, so against 4c3b72a there are three regions: - **The header.** Keep this PR's "The FULL suite runs on LINUX ONLY" and its exception sentence, then #590's four lines about the `package` job. - **The end of the file, two regions.** git splits the two new jobs where they share the checkout and setup-python lines. Keep both jobs whole: `tests-git-bash`, then #590's `package`. - **Checked.** I built that file: this PR's `tests.yml` with #590's header paragraph and its `package` job added. Against #590's file it differs only in this PR's two hunks, and against this PR's file only in #590's two. `yaml.safe_load` gives the jobs `['tests', 'tests-git-bash', 'package']`, and actionlint 1.7.12 exits 0 on it. #590's `package` comment now says "the other jobs" and "on Linux like the rest of the suite since #597", which stay true beside `tests-git-bash`. Fix round 4's advice to drop `tests-windows` and `tests-macos` and to reword "three jobs" no longer applies: #590 has done both. The open pull requests are the 17 with a `refs/pull/<n>/merge` ref in `git ls-remote origin 'refs/pull/*'`: 583, 590, 594, 596, 599, 614, 617-623 and 625-628. Each head was merged with 52524fe and, separately, with main 396c0ff. The ones not in the table merge cleanly with this head and touch none of its six files: #583, #614, #617, #618, #619, #621, #622, #625, #626 and #628. | Against | Result | |---|---| | origin/main 396c0ff (#591, #572, #624, #598 3fb21d9, #570) | **clean**. The merge gives this head's own tree, 6d8ffea: the head holds all of main. | | #590 1c8a708 (`l3-563-pypi-package`) | **conflicts** in `.github/workflows/tests.yml`, 3 regions: the header and the end of the jobs. #590 against main alone is clean, so this is the two PRs' own overlap. It also touches `tests/test_install_sh.py` and `tests/test_openreposhape_command.py`, which merge as text. See the #590 section above for the resolution, the four test cases and fix round 7's 270 passed. | | #599 b0182e2 (lane 3, stacked on #590's old head d090f0c) | **conflicts** in `.github/workflows/tests.yml`, 2 regions. #599 also conflicts with main alone, in six files (`tests.yml`, README.md, the handbook, `install.sh`, `openRepoShape`, `tests/test_install_sh.py`). It is #597's conflict, through #599's stale base, not this job's. | | #627 fc0656b (`fix/606-readme-followups`, #606; new since fix round 6) | **conflicts** in docs/handbook.html only: 7 regions, each one § Starting marker line (this PR's badbb6b2 against #627's 51702a2b). Its README hunks (the second-run refusal in § Starting, and `CONTRACTS_DIR` and the `single-repository.yaml` question in § Adopting) merge as text with this PR's. #627 against main alone is clean. **Whoever lands second** sets the seven markers to the merged README's hash. On this head plus fc0656b that is `fe0d932b031e4a08fc885f93d6cd978df0d5a25c`. README is then 1456 lines: 1449 plus #627's own `readme-cap.d/606.txt` (+7). | | #594 be808a7, #623 1e30fa7 | **clean**. Both touch `tests/conftest.py`, which merges as text. | | #620 4c049a8 | **conflicts** in `adopt-project.py`, and so does #620 against main alone: its own conflict with main, not an overlap with this PR. | #598 landed as 3fb21d9 and is in the main row; fix round 7 is that merge. Every lane-5 PR and every other PR that has landed is in the main row. - **`tests/test_setup_sh.py`, and `test_repo_hygiene.py`'s `bash -n` test, still skip on Windows,** and since #597 nothing runs them on Windows at all. #585 names the launcher's and the installer's suites, and Brett Heap's word keeps a job for those two only. The same `BASH` would serve both. - **Any typed `yes`.** CI has no terminal. - **`tests/test_install_sh_filter_repo.py` in `tests-git-bash` (fix round 7).** #598's file is not added to the job; that is the coordinator's call. See checklist candidate 8. - **`docs/release-notes/v1.0.0.md` and `docs/release-notes/v1.0.1.md`** are records of those releases, and #597 left them unedited too. v1.0.1's notes say #585 runs the suites under Git Bash; that stays true. The v1.1.0 text is below. - **A required check.** This PR does not make `tests-git-bash` a required status check. That is a ruleset setting, and Brett Heap's call. No v1.1.0 notes file exists on main yet, so this PR writes none. Proposed text, a section of its own: > **Git Bash on Windows (#585).** Git Bash, the bash that Git for Windows installs, runs the one-line install and the `openRepoShape` command. CI's `tests-git-bash` job runs their suites under Git for Windows' own bash, with the PATH Git Bash makes, and fails if it cannot find that bash. That job is an exception to #597's Linux-only CI, made on Brett Heap's word. In PowerShell the way in is unchanged: `py setup-project.py`. Two things differ from macOS and Linux. `ln -s` in Git Bash makes a COPY unless `MSYS=winsymlinks:nativestrict` is set; a copy has no `openRepoShape.ref` beside it, so it follows main, and `openRepoShape --version` says so. Link it with that setting (Windows lets a non-administrator make a symlink only in Developer Mode), or put `~/.local/bin` on `PATH` instead. And `chmod` changes nothing on Git Bash's drives; a file that starts with `#!` runs as a command anyway. Not tested there: `setup.sh` run on its own (its suite still skips on Windows), and any typed `yes`, for which CI has no terminal. In Git Bash the preflight that the one-line install ends with cannot take a `yes` at all, so it installs nothing for you. 1. **The machine report's `python3` line on Windows.** `--install`'s `machine:` block probes only `python3`. Where Python came from python.org, Git Bash has `python` and no `python3`, or only the Microsoft Store stub. The block then says `python3 MISSING`, or "present" for a stub, while `--preflight` passes with `python`. 2. **A drive-letter `$OPENREPOSHAPE_REPO`.** `shape_clone_url` treats `C:/src/openRepoShape` as an owner/name and builds `https://github.com/C:/src/openRepoShape.git` from it. Git Bash and git itself both accept the drive-letter spelling. 3. **A Windows-spelled `$OPENREPOSHAPE_BIN_DIR`.** `--install` then prints `export PATH="D:\...:$PATH"`, which bash cannot use: the drive's colon splits it. 4. **A stale docstring.** `test_a_ref_the_doctor_would_check_out_is_validated` says the refusal arrives before any clone. `shape_checkout` clones first, then checks the ref. 5. **On Windows, the preflight a piped install ends with cannot take a typed `yes`.** - Under `curl … | bash`, install.sh:342 (:277 before #598) hands its children /dev/null for stdin. - setup-project.py's `open_terminal()` returns None when `os.name == "nt"`, so it has no Windows path to the person's terminal. Opening `CONIN$`, where POSIX opens `/dev/tty`, might be the analogue. - Found by rv585; it is the root of this PR's README wording. 6. **The handbook's step 3 has no Git Bash path.** Its install block and paragraph are `data-platform="linux mac wsl"` (docs/handbook.html:775-784 on 01e8c2c), so a reader on the Windows tab never sees the one-line install that the Windows panel says Git Bash runs. Adding it to the Windows tab is a docs decision. 7. **In Git Bash, the host-quoted lines are still quoted for PowerShell.** `openRepoShape --doctor`, a family `setup-project.py` run and `family.py`, started from Git Bash, run under native Windows Python, where `os.name` is `nt`. So `quote_arg`, `shell_quoted`, `shell_line` and `shell_word` (for example setup-project.py:2331, `if os.name == "nt" and not posix`) quote for PowerShell, even though the person is at a bash prompt. #588's sentence stays true ("on Windows"). But someone who follows "Git Bash runs that command" and pastes such a line back into Git Bash gets the failure that `tests/test_readme_windows_quoting_sentence.py`'s docstring names: `git -C C:\work\Orbit` reads as `C:workOrbit`. Choosing `sh`'s dialect under Git Bash (for example on `MSYSTEM`) would be a #532 decision. 8. **#598's installer cases do not run under Git Bash.** `tests-git-bash` runs `tests/test_install_sh.py`, and since fix round 7 that suite reaches #598's code in two ways: every run now prints `adoption:` and `done:`, and `no_pipx` expects uv's printed command. But the uv install, the readinesses' wording and the pasted next steps are tested in `tests/test_install_sh_filter_repo.py` only, on Linux. README says Git Bash runs step 3's one line, "tested in CI". - **What the file uses.** It inherits `test_install_sh.pytestmark` (`BASH_SKIP` and `git_bash_session`), and drives every run through `Net`: `fake` (LF bytes through `write_script`), `run` (`BASH`, `bash_path(INSTALLER)`), `serve`, `installed` and `calls`. Its own `uv` fake writes LF bytes, like `Net.fake`, and names the interpreter as `shlex.quote(sys.executable)`, as `Net.fake` does in the job today. No assert compares a path bash printed with a Python path; each checks a line's start or end. - **Under Git Bash it would likely pass.** Its programs see `PATH=net.path` alone, so the runner's own pipx or uv cannot leak in. The launcher's `--install` probes `python3` with `command -v` and never runs it, so the logging `python3`/`python` fakes stay silent. Its `paste()` sends a script through a text-mode pipe, as `Net.run(piped=True)` already does in the job. - **Not run on Windows.** On Linux: 18 passed in 7.32s. Adding it to the job is one more path on the job's pytest line. 9. **README still names `pip install git-filter-repo` for adopting.** README:768 (§ Adopting) says "`git filter-repo` is a **hard dependency** — `pip install git-filter-repo`, or the same name from apt or brew." #595 found that `pip install` is refused on an externally managed Python (Debian 12, Ubuntu 23.04, Arch; PEP 668), and #598's install.sh no longer prints it. Neither #598 nor this PR touches that line. Review: rv585 found 0 blockers, 2 should-fix and 8 nits on 649b089 (CHANGES NEEDED), fixed or answered in 01e8c2c; the re-review rv585b found 5 nits on 01e8c2c (APPROVE WITH NITS), fixed or answered in 3095a9c; rv585c found 4 nits on 5f001de (APPROVE WITH NITS), fixed or answered in fe58d66. CI (Brett Heap's ruling 27, Linux-only since #597): H0 is the head 52524fe, a hand-resolved merge of main 396c0ff, green on tests (Linux), tests-git-bash and SonarCloud; main since then (#625 8f453d0, #583 de428f5) touched none of this PR's files, and the local gate passed on the predicted squash over de428f5: the launcher's, the installer's and install.sh's filter-repo suites, 131 passed; and the hygiene, README-section, CLI-reference, setup-by-hand and Windows-quoting sets, 171 passed. The tests-git-bash job is on Brett Heap's word of 2026-10-15T18:20:35Z, "Option 1: keep a small Git Bash job for #596". Landed on Brett Heap's word of 2026-10-15T22:24:36Z, "Merge #596 when its turn comes". Closes #585 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Lane: openreposhape-4 (openRepoShape-4)
Claim: #532 (comment)
What changed and why
Brett Heap ruled that the tools print POSIX-quoted commands and that README says so. These are the lines that still concatenated a root or a path, so they pasted as two arguments under
sp ace/, as a quotation nothing closes underit's/, and with$xexpanded away underd$x/. Measured on 45e01f4,--add notes/shape notes.mdpasted as printed was refusedunrecognized arguments: notes.md. Two commits:9d8ee915is the change,c2e19022is the fix round after R588's review (the README sentence was not true offamily.py).scripts/bump-leg.py(4 lines)_switch_command(:1123) spellsgit -C <root> switch -c bump/<role>-<id>once, withshlex.join, forbump-leg-root-detached(:1161) andbump-leg-on-tracking-branch(:1171), which putstr(root)in bare, and for the dry run's hint (:1664, bump-leg.py--dry-run, step 1 of advancing a leg, is refusedbump-leg-on-tracking-branchon every fresh project, before theold -> newline the human is to read #477), which already was quoted. One function, so the three keep naming the same branch the same way.NEXT git -C <root> push -u origin <branch>(:1696) isshlex.join._restore_leg_if_moved'sLegNotAsFoundtold a person to read`git -C <leg> status`with the leg's path bare, the last paste line in the file that was (:1381). It isshlex.joinnow, inside the backticks.update-shape.py(each pathshlex.quoted, the checkout through_by_hand, the file's own rule)cmd_check's ADDED UPSTREAM--add <path>(:2134).commit_on_branch'supdate-branch-existsremedy,git -C <root> checkout <branch>(:2468)._refuse_local_drift's--accept-local <path>(:3158) and_note_untaken_additions'pass --add <path>(:3177).accept_local_exit(:1921) andkeep_local_exits(:1939).shape-doctor.pyquote_arg(ride-along, :720): a word that begins with=is quoted insh's dialect, asrepo_shape.shell_quoteddoes. zsh, macOS's login shell, expands a bare=wordto a command's path (zsh -f -c 'echo =ls'prints/usr/bin/ls,=xstops withx not found). PowerShell's dialect is unchanged: it expands no=word(R588 read PowerShell 7.4.6's tokenizer to confirm; nopwshon the host). The family test that holds the two quoters to one answer keeps its leading-=exception forntalone.README.md and docs/handbook.html: one sentence at the end of "Windows, natively" (README.md:155-157) and of the Windows panel's last paragraph (docs/handbook.html:726-730):
family.pyalso prints, in the host's dialect (repo_shape.shell_line/shell_quoted): theswitch -cremedy offamily-holder-detachedandfamily-on-tracking-branchand the dry run'sbranchline (_switch_command, :672-676, used at :690 and :724), theaddremedy offamily-holder-already-here(_add_command, :911-919, used at :1014 and ininit's NEXT STEPS), andmember-path-taken'sgit rm -r --cached(:2006-2016). On Windows that dialect leaves a plain root bare, so pasting one into Git Bash gavegit -C C:workOrbitOrbit ...for any Windows root. The dry run'sbranchline is aswitch -cline, so it needs no words of its own.shlex, or'posix'forced):family.py's by-hand rollback lines (:3175-3195, :3545-3606, includinggit rm -q --cached, which is why the sentence spellsgit rm -r --cachedin full), the module-storerm -rf(:2112),git config --unset-all(:2071, :4449) and the lock'srm -f(:4502); and all ofsiblings.py(shlex.join, on purpose, :777-785).'C:\work\Atlas'), which a WSL2 shell cannot open. WSL2 has its own section. Brett's chosen option said "Git Bash or WSL"; the coordinator tells him.tests/readme-cap.d/550.txt(1422 + 5). No cap file is added. To make the room: the Microsoft Storepythonparagraph went from 3 lines to 2 (round 0), the stdin paragraph from 8 to 7 (round 0), and the Store paragraph is joined to the "Two commands rather than one pipe" paragraph after it, which saves its blank line (fix round). Meaning unchanged.<!-- reads README.md § Starting a project in a new organisation: blob ... -->markers (masthead,start,partway,ci,legs,doublepin,park) move to the section's new blob,20dfa48f...; the README cap holds at 1427; the footer's history is frozen (HANDBOOK_HISTORY_CLAUSES) andREADME.md blobis retired, so no clause or hash is added there. AGENTS.md is unchanged (490 lines): no line in it says the opposite.docs/cli.mdis unchanged: no--helptext moved.Brett's ruling
The sentence names more than the doctor, because the doctor is not the only tool that quotes for PowerShell on Windows on main. A sentence that named only the doctor would be false of two more, and the coordinator tells Brett.
scripts/family.pyprints itsNEXTlines, itsswitch -c,addandgit rm -r --cachedremedies and its dry run'sbranchline throughrepo_shape.shell_line/shell_quoted(:158, family.py:initrefuses to land in the standard's checkout and names a runnablescripts/family.py, printed commands quote paths, andadd,bumpandremoverefuse the tracking branch and print their branch's push (#488, #486, #487) #507), which spell PowerShell's way whenos.name == "nt"(repo_shape.py:3009).setup-project.pyspells the family lines of itsNext:block with its ownshell_word(:2102-2141, family.py and siblings.py print commands with unquoted paths, so under a folder with a space themember-git-dir-cachedremedy'srm -rfwould delete the wrong directory, andadd's push line fails #486), andtests/test_family_printed_quoting.pyholds it to the same answer.templates/family-root/scripts/siblings.pyis not named, although the first brief expected it: it prints withshlex.join, on purpose, so a holder with an olderrepo_shape.pystill runs (:777-785), and says "in a POSIX shell (Git Bash on Windows)" (:762).Tests
Background runs, one basetemp, test identities,
frvenvfirst on PATH,-q -p no:cacheprovider -o tmp_path_retention_policy=failed -rfs.Round 0 (
9d8ee915): the command is in the first PR body; 1339 passed, 4 skipped in 15:42, thentests/test_repo_hygiene.py tests/test_readme_sections.pyover the final README and handbook, 129 passed.Fix round (
c2e19022), once, over the files this round touches and the ones that pin what it touches:524 passed, 1 skipped in 2:04 (
test_bump_leg.py:693: needs a case-insensitive disk). zsh, dash and bash are on the host. Not run on Windows or macOS.tests/test_readme_windows_quoting_sentence.py(3): README's sentence is in its "Windows, natively" section once; the handbook'spanel-windows, with its tags stripped and its whitespace one space, says it word for word (a README-only change with the markers re-hashed passed every test before this); and the shipped.pyfiles that call a quoter that follows the host (quote_arg,shell_quoted,shell_line,shell_word, none forced to'posix'), read withast, are exactlyshape-doctor.py,scripts/family.pyandsetup-project.py. A fourth is a fault until the sentence names it. Not brittle to setup-project.py: paste lines quote each path, the scaffold's NEXT STEPS wait for step (7), step (3) refuses a file above the clone,-- --dry-runends at the plan, step (4) hears passed pins, and the upstream guard ignores case (#538) #569 or family.py:addrefuses, before the member is read and on every disk, any path under another spelling ofmembers/, in its index, in HEAD or on disk, and names a remedy per entry that never moves the members' checkouts (#362, #363, #364) #574: a call that passes'posix'orposix=Trueis not counted.tests/test_bump_leg_paste_lines.py(33 now): the closingNEXTfor a branch namedbump/it's,wip$xandfix/a;b(its last word is the branch; pasted, the line pushes that branch to the root's own remote); and_restore_leg_if_movedcalled in-process on a leg undersp ace/,it's/andd$x/(the backticked command splits intogit -C <leg> status, and pasted intoshit reads that leg).tests/test_bump_leg_rollback.pyasserts the same spelling, built withshlex, so it holds on a Windows root.tests/test_update_shape_paste_lines.py:388: the row is built beforepytest.raises, so only_refuse_local_driftis inside the block (Sonar S5778).tests/test_bump_leg_dry_run_tracking.py(the byte-for-byte refusal, round 0),tests/test_family_printed_quoting.py::test_it_is_the_doctors_rule_on_any_ascii_value(round 0), andtests/test_bump_leg_rollback.py:466(this round)._switch_commandunquotedNEXTbare--addbare--accept-localin the drift refusal bareupdate-branch-existscheckout barepass --addbarebothsentence barelocally-modifiedsentence barequote_arg's leading-=rule droppedNEXTquotes the root, leaves the branch barefamily.py, markers re-hashedstatusline baregit rm -r --cachedbump-leg.pycallsshell_line(a fourth host-dialect tool)Out of scope
shlex.quote("=x")returns=xbare, so update-shape.py, bump-leg.py and adopt-project.py keep the zsh=gap, andscripts/repo_shape.py:3005-3006still saysquote_arg"still leaves it bare": stale forshnow, butrepo_shape.pyis a copied shape file, so a docstring edit re-pins every project. Both go on Tracking: shape-doctor.py, reporting a repository's compliance #460's checklist.validate-pins.pyandvalidate-family.py, and adopt-project.py's lines: the documentation sentence answers them, and they do not change.setup-project.py'scd <clone>(:2169) is printed bare; setup-project.py: paste lines quote each path, the scaffold's NEXT STEPS wait for step (7), step (3) refuses a file above the clone,-- --dry-runends at the plan, step (4) hears passed pins, and the upstream guard ignores case (#538) #569 already quotes it (shell_word(clone, posix=True)).Shared files
git merge-tree --write-treeof this head (c2e1902), each head re-read withgit ls-remotefirst:f5fc7056(includes update-shape.py:applyrefuses to rewrite a file that has a second, hard-linked name, and a rollback that cannot put a byte back names it with the line that finishes it and puts back the rest (#422, #428) #560 and openRepoShape --doctor says it writes nothing into the repository but a --placement-plan file you name, keeps its clone for the NEXT lines that name it, shows its synopsis description again, and refuses a second --placement-plan or a second path (#546) #580): clean, tree927d96ee.FINDINGorREFUSEDand any other non-zero exit is a crash, so a gate killed by a signal, ended byos._exit(1), holding conflict markers or failing argparse no longer "reports a finding", a crash of the standard's own naming script is CANNOT ANSWER instead ofINVALID (naming), and a pass that printedWARNINGornotelines is anotequoting them while a red row quotes its finding and never a warning (#410, #448) #55765b11898: clean,46d20a69.legsandmembersNEXT names every detached commit it would move, and sends a mount with no.gitof its own, an unborn HEAD, a mount git finds no url for, and a leg with its own lockstep fact to a line that cures it or to none (#413, #414) #55966a93539: clean,1e00a89d.2ca4044f: clean,993d9c84.manifest kindsprints one command for its NEXT, thegit rm,git cleanorgit statusits files need as git has them, where it printed prose joined by;whose backticks ranfamily.pyin zsh, and counts a manifestvalidatedonly at a path its gate reads, so acontracts/<x>-pin.yamlno leg or product names is a FINDING (#548, #439) #555c04ff8ad: clean,ec5e6939.--link-sourceis the first chain link's tree alone, and a source that names no path is refused (#441, #442) #576a707d4ba: clean,e5ced435.make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #5788d3d1cce: clean,8bb2de52.checkfinds, andexecuterefuses before any leg, the branch namesexecutefailed on, soadopt_branch: a..b, a source branchAdoptor one hidden byhideRefsno longer passcheckand fail after both legs exist (#520, #420) #583 (adopt-project.py), which conflicts with main itself and not with this PR (merge-treeof main with it conflicts inadopt-project.pytoo).curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #5875191f0eb(lane 3,l3-564-carry-to-main) conflicts indocs/handbook.htmlat the seven § Starting markers, and in nothing else. README merges clean (1449 lines, install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587's cap564.txt). Whichever of install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587 and this lands second makes a marker-only merge: for these two heads the merged blob ise3d9e463...; recompute it at landing.curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bash, installs the newest release pinned to it, with git-filter-repo, and ends with the preflight, and the installed command stays on its release (#564) #577 targetsrelease/v1.0, not main (lane 3's v1.0.1 patch). It conflicts with main itself indocs/handbook.htmlandtests/test_repo_hygiene.py, and touches none of this PR's hunks.Closes #532
🤖 Generated with Claude Code
Summary by Sourcery
Make generated command guidance safe to paste across POSIX shells and accurately document its Windows shell behavior.
Bug Fixes:
Enhancements:
Documentation:
Tests: