Skip to content

install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one line curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bash installs 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

Merged
brettheap merged 17 commits into
mainfrom
l3-564-carry-to-main
Oct 15, 2026

Conversation

@brettheap

@brettheap brettheap commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Lane: openreposhape-3 (openRepoShape-3)
Claim: #564 (comment)

Refs #564. That issue is in milestone v1.0.1 and closes when v1.0.1 is released, so this PR does not close it.

What

This carries #577, v1.0.1's one-line installer, into main for v1.1.0, as Brett Heap's ruling 35 (2026-10-14T22:54:54Z) asks: "the same change carried into main afterwards". #577 was written on bfed143 (v1.0.0) for release/v1.0. This branch merges #577's head 868c9bf8 into main 64fc9a86, then main dd3f23aa (#580), and adds three commits of its own.

Tests

Every file ran on its own on this branch, with a stub gh first on PATH, GH_TOKEN=bogus and an empty GH_CONFIG_DIR. The stub was never called.

File Result
test_install_sh 45 passed
test_openreposhape_command 66 passed
test_repo_hygiene 65 passed
test_readme_sections 76 passed
test_agents_md_codes 47 passed
test_cli_reference 22 passed
test_git_output_decoding 67 passed
test_stdout_encoding 47 passed, 1 skipped
test_setup_by_hand_commands 5 passed
test_update_add_codes_documented 6 passed
test_family_tracking_branch 17 passed
test_family_init_landing 9 passed
test_setup_project_py 116 passed
test_shape_doctor 382 passed, 3 skipped
test_adopt_github_exits 19 passed
test_adopt_late_failure 86 passed

The six files the last commit touches re-ran on its head c1e6b82f; the other ten ran on its parent, a155945a.

Review (RV587, of c1e6b82f), and the fix round 5191f0eb

An independent review found no MUST and two SHOULD items.

Main moved to f5fc7056 (#560) after this PR opened. #560 changes only update-shape.py and its tests, and git merge-tree is clean.

Merge of #597 (b23b245)

Main 654c476c (#597, CI on Linux only, with #565 and #555 before it) is merged in as b23b245. Three conflicts:

  • .github/workflows/tests.yml: main's file, whole. This PR's /bin/bash -n install.sh and its comment lived in the macOS job that ci: run the test suite on Linux only #597 removed. No bash 3.2 step is added anywhere, as ci: run the test suite on Linux only #597 decided, and this PR no longer touches tests.yml.
  • README.md: both sides kept. The macOS sentence keeps install.sh in the list and takes ci: run the test suite on Linux only #597's wording: "setup.sh, install.sh and the openRepoShape command are written to use no syntax newer than that, though CI runs on Linux only and no run of it checks them against bash 3.2". The next sentence keeps "all three entry points". It is still four lines, so README stays at 1449 of its 1449 cap and tests/readme-cap.d/564.txt is unchanged.
  • docs/handbook.html.
    • The macOS panel says what the merged README says.
    • The seven § Starting markers (masthead, start, partway, ci, legs, doublepin, park) move to a0e0f23c, the value the hygiene check prints. The macOS sentence is the only part of that section the merge changed, and nothing else in those seven sections speaks of CI's runners or bash 3.2.
    • The footer is the same on both sides.

test_install_sh.py's pin check still holds: the Linux job installs git-filter-repo==2.47.0, install.sh's pin. The tests' dated history that names the macOS job stays as #597 left it, and docs/release-notes/v1.0.1.md is not edited. Sourcery's summary below still lists a macOS CI step; this merge removed it.

On b23b245, with the stub gh: test_repo_hygiene 65 passed, test_readme_sections 76, test_install_sh 45 and test_openreposhape_command 66. git merge-tree --write-tree origin/main HEAD is clean, and its tree equals the head's (6f23930d). install.sh, tests/test_install_sh.py and docs/release-notes/v1.0.1.md still match #577's head and the v1.0.1 tag byte for byte.

Merge of main bcfa3645 (#588, #569) as f9b07c9: the seven § Starting markers conflicted (a0e0f23c here, 95f06438 from #588) and move to the merged section's d77a1e59, as the hygiene check prints; both sides' prose merged on its own. README stays 1449 (cap 1449) and AGENTS.md 492 (cap 492, raised by #566). test_repo_hygiene 65, test_readme_sections 76, test_install_sh 45, test_openreposhape_command 66, test_readme_windows_quoting_sentence 3, test_setup_by_hand_commands 5 and test_cli_reference 22 passed, and the squash tree equals the head's (5d63da26).

🤖 Generated with Claude Code

Summary by Sourcery

Provide a secure one-line installer that installs and pins openRepoShape releases, prepares git-filter-repo when possible, and finishes with the preflight check.

New Features:

  • Add a one-line release installer that installs the latest or a named openRepoShape release without requiring GitHub CLI authentication.
  • Pin installed commands to their release tag and propagate that pin through setup and doctor operations.
  • Install git-filter-repo with pipx when available, otherwise provide a platform-appropriate installation command, before running preflight.

Enhancements:

  • Document the release installer, ref recording behavior, consent rules, platform support, and installation workflow across the README, CLI reference, handbook, and release notes.
  • Extend command installation to optionally record, preserve, override, or remove a release reference beside the installed executable.
  • Update repository hygiene rules and handbook markers for the new installer and documentation changes.
  • Add guidance preventing AI assistants from running the one-line installer on a user's behalf.

Documentation:

  • Add v1.0.1 release notes covering the one-line installer, release pinning, installation behavior, and release publication procedure.

Tests:

  • Add comprehensive offline coverage for installer release discovery, validation, secure downloads, pinning, dependency handling, preflight outcomes, upgrades, and interrupted downloads.
  • Extend command tests to cover recorded references, ref precedence, symlinked installations, and pinned setup and doctor execution.
  • Include the installer in shipped Bash parsing and repository hygiene checks.

Brett Heap and others added 11 commits October 14, 2026 23:34
…pe.ref` beside the command it places, and the installed command then fetches setup.sh at that ref, hands setup.sh `--shape-ref` for it and checks the doctor's clone out at it, so a command installed from a release stays on that release while `$OPENREPOSHAPE_REF` still overrides it for one run (#564)

An installed command read `$OPENREPOSHAPE_REF` at every run and fell back
to main, so from v1.0.0 on, with main carrying v1.1.0 work, a command
installed from a release drifted off it within hours. The ref is now NAMED
by the variable or by the record, and both places that honoured only the
exported variable (the doctor's checkout and setup.sh's `--shape-ref`)
honour either.

The record is a sidecar file, never an edit to the command: the placed
command stays byte for byte the file it was installed from, so the
installer's own `(unchanged)` verdict and any copy vendored and checked by
sha256 still compare bytes and never see the record. An install with no
ref records nothing and removes any record already there, which is the
command exactly as before. The ref is checked before anything is placed,
and the record is checked when it is read, naming the file and the two
ways out. `--version` prints the ref in force. docs/cli.md is regenerated
for the `--help` paragraph that says so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oShape/releases/latest/download/install.sh | bash`, installs the newest release's openRepoShape pinned to that release, installs git-filter-repo with pipx where pipx is and it is not or prints the one command that would, and ends with `openRepoShape --preflight`; README, the handbook and the v1.0.1 release notes lead with it (#564)

Brett Heap, 2026-10-14: "we need it to be more simple to install ... now
and after, we need a simple install process", and "v1.0.1 soon".

install.sh is attached to every release, so `releases/latest/download/`
is always the newest. It reads the newest tag from github.com's
`releases/latest` redirect with no gh and no login, asking `gh api` only
when the redirect names none; takes a named release as its one argument;
fetches that release's `openRepoShape`, checks it is the command, and runs
its `--install` with `$OPENREPOSHAPE_REF` set to the release, then asks the
installed command's `--version` that it stayed there. Every step says what
it did in one line. It never runs sudo. Exit 0 installed and ready, 1
installed but the preflight or the pin needs attention, 2 nothing
installed; git-filter-repo never moves it. Everything runs from a `main`
called on the last line, so a cut-off download runs nothing.

README.md: step 3 and the worked example lead with the one line, the gh
api and raw-URL lines stay as the by-hand way at main, and Layout gains
install.sh; the cap moves 1422 -> 1450, with the reason in
test_readme_is_short_enough_to_be_read. docs/handbook.html says the same,
names the new README blob and adds the (#564) clause. install.sh joins
SHIPPED_BASH and the macOS job's `/bin/bash -n` step, and one test holds
the one line byte for byte in every file that carries it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rds a ref only with `OPENREPOSHAPE_RECORD_REF=1`, which install.sh sets, so a script that exports `$OPENREPOSHAPE_REF` around it gets v1.0.0's install; every curl that runs is HTTPS only, redirects included, at TLS 1.2 or newer; a link to the command reads its record; `--help` answers past a broken record; and install.sh says which failure it met (#564)

The review on #577 (RV577, FIX-ROUND) and SonarCloud shell:S6506.

openRepoShape:
- The ref is recorded only when asked, or when it came from the command's
  own record (a re-install of itself stays pinned). workBenches exports
  `OPENREPOSHAPE_REF=pinned-by-workBenches-no-fetch` around `--install`;
  that no longer becomes a record every later run would fail on. Any other
  install records nothing and removes a record already there.
- The raw-URL fallback curl carries `--proto '=https' --proto-redir
  '=https' --tlsv1.2`, written on the line.
- The record is found beside the FILE, symlinks followed with a portable
  `readlink` loop (no `-f`, at most 40 links).
- A broken record is kept as a fault and refused after the arguments are
  read, so `--help` still answers; `--version` and every run refuse. A
  record that is a directory is refused in the usual form, by a run and by
  an install, before anything is placed.

install.sh:
- Both curls HTTPS only, redirects included, TLS 1.2 or newer.
- Sets `OPENREPOSHAPE_RECORD_REF=1` for the install.
- A fetch the server refused (curl exit 22) asks whether the release
  exists; no answer at all points at the network instead. A preflight that
  exits 1 names what is missing; any other status says it did not run to
  the end.
- With no pipx, `apt-get` machines are told `sudo apt-get install -y pipx
  && pipx install git-filter-repo==2.47.0` (printed, never run), because
  Debian 12 and Ubuntu 23.04+ refuse `pip install --user`.
- A command that did not record the tag gets its preflight run with the tag
  named, not main's setup.sh; the shell-profile notes say so.
- The last line is `{ main ${1+"$@"}; }`: a download cut inside it runs
  nothing. install.sh itself never runs sudo; the wording now says the
  preflight's offers, some of them sudo, each wait for a typed yes.

Tests: HTTPS_ONLY in the call asserts and a guard on every curl that runs
in both scripts; the opt-in, links, a directory record and `--help` past a
bad record; an upper-case and a relative `location`; a non-default
OPENREPOSHAPE_BIN_DIR; the apt-get branch; the network failure; the
preflight's two failures; three truncated downloads; S9073 composite
asserts split. docs/cli.md regenerated for the `--help` change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, the fix round on #577: install.sh itself never runs sudo while the preflight it ends with offers what is missing behind a typed yes, the command is pinned to the release tag only when asked, step 3 folds the pin into one paragraph six lines shorter, AGENTS.md says the one-line install is the human's to run, and the notes gain the steps that attach install.sh to the release (#564)

- README.md: step 3's paragraph now carries what "pinned" means and the
  corrected sudo sentence, and leaves the exit statuses to `install.sh
  --help` and the release notes; the worked example's lead-in is one line.
  The cap moves to 1444 (from 1450 in d0cdbf4), with the reason updated in
  test_readme_is_short_enough_to_be_read.
- docs/handbook.html: the same paragraph, the same rule in "For AI
  assistants", the #564 footer clause updated, and the new README blob.
- AGENTS.md: "The one-line install (`install.sh`) is theirs to run: relay
  it, never run it." under "Never answer an install prompt", paid for by
  tightening the `--allow-upstream-org` paragraph's middle clause; still
  490 lines, every rule kept.
- docs/release-notes/v1.0.1.md: the corrected sudo sentence, the opt-in
  (`OPENREPOSHAPE_RECORD_REF=1`) and what a script that exports
  `$OPENREPOSHAPE_REF` now gets, HTTPS only, Git Bash supported but its
  tests skip on the Windows job (a known limit, followed up in v1.1.0), and
  a "Before tagging (remove at tag time)" section: upload install.sh to
  v1.0.1, check the `releases/latest/download/install.sh` redirect, never
  attach it to v1.0.0, and publish a later patch on an older line with
  `--latest=false`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…follow in order (land, prove the squash tree or dispatch the three-OS run on it, tag it annotated, create the release with install.sh attached and --verify-tag, check the asset by redirect and by bytes), Git Bash is said to be meant to work but untested until #585, and only install.sh's own downloads are said to be HTTPS only (#564)

The second fix round on #577 (RV577). The old checklist published before
attaching install.sh, could tag main through `gh release create` with no
tag, uploaded an unchecked working-tree file and never tested the release
commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-564-carry-to-main

Conflicts, both resolved to main's side, because main retired the
mechanism #577's side used (#519, #551):
	tests/test_repo_hygiene.py: README.md's cap stays README_CAP_BASE plus
	  tests/readme-cap.d/; #577's inline 1422 -> 1444 raise is not taken here
	  and becomes a ledger file of its own in the next commit.
	docs/handbook.html: the footer keeps main's frozen history and its
	  closing sentence; #577's "added (#564)" clause and whole-README blob
	  are the retired convention, and the sections' markers carry it instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… the one-line install in a ledger file of its own, as main raises it, rather than #577's inline 1422 -> 1444 on release/v1.0 (#564)

README.md on main is 1427 lines at its cap (1422 + 5, #550); #564's lines
take it to 1449. The reason is #577's, carried with its accounting:
ten lines for the two install.sh blocks, nine for step 3's paragraph,
two for the by-hand ways kept at main, one for the Layout row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…S is a typed yes and the one-line install asks nothing, placing the command and git-filter-repo through pipx without sudo, and the eight sections that read README.md § Starting and § Layout record #564's README after a re-read (#564)

What changed in README.md since the markers were set is #564 alone:
`git diff f6eaab7 HEAD -- README.md` equals `git diff origin/main HEAD --
README.md` (the § Starting markers were set by #579, f6eaab7), and § Layout
moved only by the install.sh row since #551 (5e59e3b) set its marker.

Re-read against the merged README, each section in full:
- masthead: its fastest path is #577's install.sh line with an accurate
  summary (newest release, pinned, git-filter-repo where pipx can, ends in
  the preflight). No change.
- start: carries #577's install step, the by-hand ways at main and "Each way
  installs". One statement was made false: "Every action that changes the
  machine or the account is a typed yes" stood a few lines below install.sh
  installing git-filter-repo with pipx unasked. It now scopes the rule to
  what the tool offers and says what the one-line install does, every fact
  quoted from README step 3. The machine: block paragraph, the preflight
  step, Run it and What setup.sh actually runs stay true as written.
- partway (the WARNING), ci (private legs), legs (the --explain line),
  doublepin (the hand-edited pin), park (openRepoTools' line, which
  --install still prints when install.sh calls it): nothing #564 touched.
  No change.
- layout: carries #577's install.sh row, in README's order; "standard
  library only" stays true (install.sh is bash, git-filter-repo a tool).
  No change.

Markers: § Starting f0693d8a -> cc78d254 in masthead, start, partway, ci,
legs, doublepin, park; § Layout c8be0af4 -> 94701efc in layout; both values
as the hygiene check printed them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #580 (#546), dd3f23a. One conflict:
	tests/test_repo_hygiene.py: #564's one-line-install test and #580's
	  DOCTOR_PASSAGES test were added at the same place; both are kept,
	  #564's first.
The launcher, its test, README.md, AGENTS.md, docs/handbook.html and
docs/cli.md merged cleanly, each holding both sides' hunks unchanged.
README.md stays 1449 lines (cap 1449) and AGENTS.md 490 (cap 490). Every
handbook marker of § Starting, § Layout and § Keeping equals its segment's
fingerprint in the merged README after a re-read, so none moves.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yped yes, beside step 3's one-line install, which installs git-filter-repo with pipx where it can (#564)

"Nothing is installed without that typed `yes`" became "The preflight
installs nothing without that typed `yes`", and the rest of the sentence is
kept. The longer clause re-wraps the NOTE's last eight lines into eight, so
README.md stays at 1449, its ledger cap, and every line stays within 80
columns. `py setup-project.py --preflight` stays on one line, because
test_the_windows_commands_name_files_that_exist reads `py <file>` across
whitespace.

Re-read against the new wording, the seven sections that read
§ Starting: start's consent rules ("Every action the tool offers ... is a
typed yes", "The one-line install asks nothing ...", "no offer at all") and
its four "The tool offers the rest" panels already put the typed yes on the
preflight's offers; its `machine:` block line is about --install, and
partway's "Nothing is created before the typed yes" quotes the WARNING, not
the NOTE; masthead, ci, legs, doublepin and park render nothing from the
NOTE. No page text changes. The markers move as the hygiene check printed:
cc78d254 -> c9aebc08 in masthead, start, partway, ci, legs, doublepin, park.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026

Copy link
Copy Markdown

Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 2 days and 12 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Oct 15, 2026

Copy link
Copy Markdown

Reviewer's Guide

Carries the v1.0.1 installer into main by introducing a hardened release-aware install.sh, extending openRepoShape with persistent release pinning, and updating tests, CI, documentation, and release procedures to describe and validate the new one-line installation flow.

Sequence diagram for the release-pinned one-line installation

sequenceDiagram
    actor User
    participant Install as install.sh
    participant GitHub as GitHub Releases
    participant Launcher as openRepoShape
    participant Pipx as pipx
    participant Preflight as openRepoShape --preflight

    User->>Install: curl .../releases/latest/download/install.sh | bash
    Install->>GitHub: Resolve releases/latest redirect
    GitHub-->>Install: Release tag
    Install->>GitHub: Fetch openRepoShape at release tag
    GitHub-->>Install: Launcher script
    Install->>Launcher: --install with OPENREPOSHAPE_REF and OPENREPOSHAPE_RECORD_REF=1
    Launcher-->>Launcher: Write command and openRepoShape.ref
    opt git-filter-repo missing and pipx available
        Install->>Pipx: Install git-filter-repo
    end
    Install->>Preflight: Run --preflight
    Preflight-->>User: Offer missing actions with typed yes
Loading

File-Level Changes

Change Details Files
Adds a release-backed one-line installer that fetches the newest or named release, installs the matching launcher pinned to its tag, optionally installs git-filter-repo, and finishes with preflight validation.
  • Resolve the latest release via HTTPS redirect, validate release/repository inputs, fetch and verify the release launcher, and install it with ref recording enabled.
  • Support pipx installation of git-filter-repo, detect existing installations, or print platform-appropriate fallback commands without running sudo directly.
  • Run the installed command's preflight last and distinguish successful, not-ready, unpinned, and refused outcomes.
  • Harden Bash 3.2 compatibility, HTTPS-only curl usage, temporary-file cleanup, partial-download behavior, and input validation.
  • Add extensive offline tests covering release resolution, pinning, failures, upgrades, dependency-install branches, preflight status, symlinks, forks, and interrupted downloads.
install.sh
tests/test_install_sh.py
Extends the launcher installation model to persist and honor a release ref beside installed commands while preserving main-following behavior for ordinary installs.
  • Add opt-in OPENREPOSHAPE_RECORD_REF handling to write, update, remove, and validate openRepoShape.ref.
  • Make recorded refs control setup.sh fetches, standard clone pins, doctor behavior, and version output, with an explicit environment override.
  • Keep installed command bytes unchanged and support ref behavior through symlinks and reinstallation.
  • Expand command tests and test environment handling for installed copies and recorded refs.
openRepoShape
tests/test_openreposhape_command.py
docs/cli.md
Documents and propagates the release installer across user-facing guidance, release procedures, and repository metadata.
  • Replace the primary installation path with the release asset one-liner while retaining manual main-based installation as an alternative.
  • Explain release pinning, git-filter-repo handling, preflight consent, upgrade behavior, platform limits, and release publication/verification steps.
  • Update the generated handbook and repository layout references, AI-agent guidance, release notes, and README section markers.
  • Include the installer in Bash parsing and shipped-script hygiene checks and add the associated README cap ledger.
README.md
docs/handbook.html
docs/release-notes/v1.0.1.md
AGENTS.md
.github/workflows/tests.yml
tests/test_repo_hygiene.py
tests/readme-cap.d/564.txt

Possibly linked issues


Tips and commands

Interacting with Sourcery

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

Customizing Your Experience

Access your dashboard to:

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

Getting Help

…ore its own two acts, and the preflight it ends with asks before each offer (#564)

"The one-line install asks nothing" was not true as written: install.sh
ends with `openRepoShape --preflight`, which asks `Type yes to continue:`
before each offer, as step 3 above it and README.md:239-241 say. Only
install.sh's own two acts, placing the command and installing
git-filter-repo with pipx, go unasked. Found in #587's review.

Handbook text only, in the same five lines: README.md is unchanged, so no
marker moves, and the hygiene check asks for none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 15, 2026
…3-pypi-package

Brings in #587's refresh onto main fb786c1 (#568, #581), 25b2aee. No
conflict: the refresh touches none of this branch's files, and every hunk
of #563 stays as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #597 (654c476, CI on Linux only), with #565 and #555 before it.
Three conflicts:
	.github/workflows/tests.yml: main's file whole. #587's
	  `/bin/bash -n install.sh` and its comment lived in the macOS job #597
	  removed; no bash 3.2 step is added anywhere, as #597 decided, and this
	  pull request no longer touches tests.yml.
	README.md: the macOS sentence keeps #587's `install.sh` in the list and
	  takes #597's wording: "`setup.sh`, `install.sh` and the `openRepoShape`
	  command are written to use no syntax newer than that, though CI runs on
	  Linux only and no run of it checks them against bash 3.2", with "all
	  three entry points" after it. Four lines, as both sides had: README.md
	  stays 1449 lines, its cap with tests/readme-cap.d/564.txt (+22).
	docs/handbook.html: the macOS panel's sentence says what the merged
	  README says. The seven markers that read README.md § Starting a
	  project in a new organisation (masthead, start, partway, ci, legs,
	  doublepin, park) move to a0e0f23c306cebdf627d90225e33c277f05de64b, as
	  the hygiene check printed; the macOS sentence is the only part of that
	  section the merge changed, and no other part of the seven speaks of
	  CI's runners or bash 3.2. The footer is the same on both sides.
test_install_sh.py's pin check still holds: the Linux job installs
git-filter-repo==2.47.0, install.sh's pin. The tests' dated history that
names the macOS job stays, as #597 left it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 15, 2026
…I) into fix/585-git-bash-windows: tests.yml is the base's, without #596's guard step in the gone tests-windows job, and the handbook keeps #597's macOS panel beside #596's Git Bash text, its seven markers moved to the merged README (#585)

The base, lane 3's #587 branch, merged main 654c476, which holds #597 ("ci:
run the test suite on Linux only", merged by Brett Heap 2026-10-15T16:54:43Z).
#597 removed the tests-windows and tests-macos jobs, made README's macOS
sentence and the handbook's macOS panel say CI runs on Linux only, and moved
the seven handbook markers for README.md section "Starting a project in a new
organisation". Two files conflicted.

.github/workflows/tests.yml: the base's file, byte for byte (blob b2de8af).
Dropped from this branch:
- the step "Git Bash is the bash the launcher and installer suites run",
  which sat in tests-windows;
- the tests-macos comment's clause that the windows job runs the command
  only under Git Bash.
Both jobs are gone, so both hunks went with them. At this commit alone no CI
job runs the two suites under Git Bash, so README's "tested in CI" is not yet
true here. The next commit adds the small tests-git-bash job, on Brett Heap's
word at 2026-10-15T18:20:35Z: "Option 1: keep a small Git Bash job for #596".

docs/handbook.html: the base's text, with #597's macOS panel, and this
branch's Git Bash text (the masthead, the Windows panel, the consent rules),
which merged as text. The seven conflicts were the seven markers for README.md
section "Starting a project in a new organisation": this branch's 26629e7a
against the base's a0e0f23c. Each now records a88ac2d0315c9c8ed6e962ab6ae34b856346ecc9,
the merged section's hash, computed with tests/test_repo_hygiene.py's own
readme_segments and readme_fingerprint.

README.md merged cleanly as text: #597's macOS sentence and this branch's
Windows paragraph and step-3 clause are in different paragraphs. It is 1449
lines, the cap (1422 + 5 for 550.txt + 22 for 564.txt).

Every other file is the base's. Against the base, this merge differs only in
this branch's five files: README.md, docs/handbook.html, tests/conftest.py,
tests/test_install_sh.py and tests/test_openreposhape_command.py.
tests/test_repo_hygiene.py and tests/test_readme_sections.py: 141 passed on
the merged tree.

Lane: openreposhape-5 (openRepoShape-5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap and others added 3 commits October 15, 2026 14:56
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>
@sonarqubecloud

Copy link
Copy Markdown

@brettheap
brettheap merged commit 22eacee into main Oct 15, 2026
3 checks passed
@brettheap
brettheap deleted the l3-564-carry-to-main branch October 15, 2026 20:27
brettheap pushed a commit that referenced this pull request Oct 15, 2026
#587 landed on main as 22eacee (squash), so this branch takes its final
head first, then main.

One conflict, .github/workflows/tests.yml. Main's file is kept whole: #597's
Linux-only header and the removal of tests-macos and tests-windows, and
#593's `concurrency:` group with its comment. This branch adds to it only
its own two parts, unchanged: the header paragraph on the `package` job,
after main's, and the `package` job at the end, byte for byte as at
d090f0c. tests/test_repo_hygiene.py merged cleanly; its diff from #587's
head is exactly this branch's lines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 15, 2026
22eacee's tree equals `git merge-tree --write-tree 2b6dbb7 3d8fa05`, and
main differs from #587's final head, merged just before this, only in
#584's two files (adopt-project.py, tests/test_adopt_root_symlinks.py),
which merge cleanly.

Three files #587 added conflicted as add/add, because the merge base
(62ef256) has none of them: install.sh, openRepoShape and
tests/test_install_sh.py. For each, main's blob IS #587's final head's
(453507b, 2ab98fb, 34c7de5), and this branch's file is that blob plus
exactly this branch's own hunks, so main brings nothing this side lacks.
Each keeps this branch's side: taking main's would drop the launcher's and
install.sh's symlink refusals and their tests.

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
…fter it) into fix/585-git-bash-windows: the four files that conflict take this branch's copy, because main's copy of each is byte for byte #587's final head 3d8fa05, which the merge before this one already holds (#585)

#587 squash-landed on main as 22eacee, whose tree is #587's final head
3d8fa05 merged onto main 2b6dbb7. The previous commit merged 3d8fa05, so
this branch already holds #587's every change; main merges the commits
3d8fa05 did not have: 2b6dbb7 (#584, adopt-project.py's symlink finding) and
77bb5f0 (#615, bootstrap.py and its templates, landed after 22eacee).

The squash is not an ancestor of 3d8fa05, so the merge base is 62ef256 and
four files conflict although both sides hold the same #587 text:
README.md, docs/handbook.html, tests/test_install_sh.py (add/add) and
tests/test_openreposhape_command.py. For each, `git show 3d8fa05:<file>` and
`git show origin/main:<file>` are the same blob, and 77bb5f0 does not touch
it:
- README.md                            d164131
- docs/handbook.html                   59ec34d
- tests/test_install_sh.py             34c7de5
- tests/test_openreposhape_command.py  5e49547
So main's copy is 3d8fa05's copy, and ours is 3d8fa05's plus this branch's
change: each takes ours (`git checkout --ours`). No file was merged by hand.

Every other file merged as text. Against main this merge differs only in this
branch's six files: .github/workflows/tests.yml, README.md,
docs/handbook.html, tests/conftest.py, tests/test_install_sh.py and
tests/test_openreposhape_command.py (430+, 141-), and
`git merge-tree --write-tree HEAD origin/main` gives this commit's own 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
…e-doctor.py and test lines, a clean merge

Main moved from 62ef256 to 55faad2 under this branch, four commits: #587 (22eacee), #615 (77bb5f0), #584 (2b6dbb7) and #591 (55faad2, for #521 and #427). #591 changed `shape-doctor.py` (the `origin` reading, `:363-369` and `:7339+`) and `tests/test_shape_doctor.py` (`:9122+`), the two files this branch changes, so ruling 27 asks for this merge before it lands. The other three touch neither file.

No conflict: this branch's hunks are in `members_verdict` (`shape-doctor.py:6975-6992` and `:7163-7183`) and after `test_a_sibling_with_this_members_id_but_another_origin_is_not_counted` in the tests, and #591's are far from both. Nothing was resolved by hand.

Lane: openreposhape-4 (openRepoShape-4)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
brettheap pushed a commit that referenced this pull request Oct 15, 2026
…his PR's head carries the newest main before rv540c's fix round (#540, #451, #450)

#591 is the only commit main gained since d62a03c (GitHub's update-branch
merge of main 77bb5f0, #574, #584, #587, #615): adopt-project.py and
shape-doctor.py read `origin` one way (#521, #427). It touches this PR's
adopt-project.py, in the `_checked_plan_inputs` neighbourhood and elsewhere,
and no other file this PR changes: git merge-tree --write-tree says the merge
is clean, so this commit holds the textual resolution only (none was needed)
and every change of behaviour comes after it. #589 (#449) and #582 stay as
f540n merged them.

Lane: openreposhape-2 (openRepoShape-2)
Co-Authored-By: Claude Sonnet 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 pushed a commit that referenced this pull request Oct 15, 2026
…files keep this branch's side, proved byte-equal to main's first (#595)

main's merge base with this branch is 62ef256, from before install.sh, so
four files #587 added or changed conflicted:
- README.md, docs/handbook.html (content);
- install.sh, tests/test_install_sh.py (add/add).

For each, main's blob was proved equal to #587's final head (3d8fa05),
merged in the commit before this one. This branch's side is that head plus
this PR's own changes, so it is kept. Taking main's side would have dropped
the PR.

22eacee's tree is `git merge-tree --write-tree 2b6dbb7 3d8fa05`, and it
differs from 3d8fa05's only in adopt-project.py and
tests/test_adopt_root_symlinks.py, which a main commit after 2b6dbb7
changed. Both come in here as main's blobs.

The result differs from the commit before it in those two files alone, and
from main only in this PR's five: README.md, docs/handbook.html,
install.sh, tests/test_install_sh.py and tests/test_install_sh_filter_repo.py.

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>
brettheap added a commit that referenced this pull request Oct 18, 2026
…e-line install is held in the notes of every release under `docs/release-notes/` but v1.0.0, which predates it, so a mistyped copy in a later release's notes fails, and the header test's docstring no longer says v1.1.0.md does not exist (#885) (#887)

`tests/test_repo_hygiene.py` held the one-line install byte for byte in `docs/release-notes/v1.0.1.md` alone (`ONE_LINE_INSTALL_IN`), so a mistyped copy in `v1.1.0.md` passed (#885; RV883's mutant M1, in its review of #883).

Now:

- **The notes are found, not named.** `ONE_LINE_INSTALL_IN` takes every `.md` in `docs/release-notes/` when the tests are collected, so each release's notes are held the day they are added, with no edit to the test. `v1.1.0.md` and `v1.1.1.md` are held.
- **One exclusion.** `RELEASE_NOTES_WITHOUT_THE_ONE_LINE_INSTALL = {"v1.0.0.md"}` names the notes that predate the line, which #587 (#564) brought in after v1.0.0's notes were written.
- **The glob is `*.md`.** A `.md` there that is not a release's notes fails until it is named in the set, and its failure says so.
- **A second test** holds that `v1.0.1.md` and `v1.1.0.md` were found, so a moved directory or a pattern that stops matching fails. It also holds every `v*.md` but `v1.0.0.md`, so naming a release's notes in the set fails.
- **`tests/test_release_workflow.py`'s header test** no longer says `v1.1.0.md` does not exist. Both notes point at `release.yml`'s header for the release steps.

Mutants M1, M2, M3, M5, M6, M8 and M9 are caught. M7 (a mistyped first copy in `v1.0.1.md`) and M10 (an extra flag after the line) survive, as they did before. They go to #931, with lane 5's three NITs.

Review: lane 1's independent reviewer found it PR-READY at `b566a95c`. Lane 5's cross-lane review found it READY there with one SHOULD. Round 2 (`96422b98`) took it, after a plain merge of main `5017830`, and lane 5's re-check found `96422b98` READY.

It lands by fast refresh (Brett Heap, 2026-10-15T22:48:35Z). Its reviewed head `96422b98` is green on Linux and SonarCloud. The branch merged main twice and was never rebased; the last merge is update-branch's, clean, giving 38daf71, which is green on SonarCloud and Git Bash. Lane 1's gate passed on its tree: the 5 test files that read the release notes or the workflows (`test_repo_hygiene.py`, `test_release_workflow.py`, `test_workflow_concurrency.py`, `test_agents_md_codes.py` and `test_cli_reference.py`), 221 passed and 0 skipped.

Closes #885

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant