Repository navigation
The release workflow: a dispatch on the annotated tag v<version> builds the openreposhape wheel twice from git archive of a commit whose own tree passed the tests job, uploads it to TestPyPI, installs it with pipx on three systems and Python 3.9, and uploads the same bytes to PyPI on approval (#563) - #599
Merged
Merged
Conversation
…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>
…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>
…ch as the openRepoShape pipx places for the openreposhape package, places nothing, and names both ways out (#563) pipx places the package's commands in ~/.local/bin as links into the package's own environment, where `--install` places this command. pipx leaves a file it did not place alone, but `--install` copies with `cp`, which writes THROUGH a link: an install over pipx's `openRepoShape` would overwrite the package's command with the bash one and leave the link standing. So `--install` refuses such a target, a dangling link included, before anything is collected, placed or recorded, and names the two exits: keep the package's (`pipx upgrade openreposhape`) or remove the link (`pipx uninstall openreposhape`, or `rm`) and run it again. One loop in install_commands and one test after test_install_refuses_to_also_scaffold, kept apart from the rest of #563 so that it merges on its own. Approved with #563's design by Brett Heap at 2026-10-15T13:32:51Z ("approve all six for #563"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c link, such as the openRepoShape pipx places for the openreposhape package, before it asks the network anything (#563) The launcher's own `--install` now refuses a linked target, but install.sh installs a RELEASE's launcher, which may predate that check and copy through pipx's link into the package's environment. So install.sh checks the target itself, first, and exits 2 with nothing requested, fetched or placed, naming the same two exits: keep the package's (`pipx upgrade openreposhape`) or remove the link and run the line again. Its header's exit 2 lists the case. Kept apart from the launcher's commit and the rest of #563 so that it merges on its own. Approved with #563's design by Brett Heap at 2026-10-15T13:32:51Z ("approve all six for #563"). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…penRepoShape, scaffold-project, adopt-project, update-shape and shape-doctor, each run from a full clone of the standard at the commit the package is stamped with, checked before every run (#563) Brett Heap's approval of #563's design, 2026-10-15T13:32:51Z: "approve all six for #563". This is its first pull request's package, as designed: - THIN. The wheel holds `openreposhape/` (standard library only, in SHIPPED) and the tree's `setup-project.py` byte for byte, beside the modules, so `openRepoShape --preflight` runs before git is installed. - THE STAMP. `src/openreposhape/_release.py` holds the version, tag, commit and digest; the committed one says 0.0.0 and unstamped, and pyproject.toml reads the version from it (`dynamic = ["version"]`). A stamp naming part of a release is refused. - THE CLONE. Every other run uses a full clone at the stamped 40-hex commit, made once per commit in the per-user cache (`$XDG_CACHE_HOME`, `~/Library/Caches`, `%LOCALAPPDATA%`) and kept. Before each run with no override it must be at the commit, carry no change, and digest to the stamp with its own `scripts/repo_shape.py` (`sorted-ls-tree-r-v1`). `$OPENREPOSHAPE_REF` overrides the commit for one run, with no digest checked; `$OPENREPOSHAPE_REPO` names a fork or a mirror. An unstamped build follows main and says so. Beside a checkout (`src/` of one), it uses that checkout as it stands, the launcher's `--doctor` rule. - THE COMMANDS. `openRepoShape` re-states the v1.0.1 launcher's modes, routing, one-mode rule, `--org` handling, usage, ref checks and `--doctor`'s run, and `--version` prints the version, tag, commit, digest and clone. `--install` refuses and names `pipx upgrade openreposhape`. A scaffold hands over to the clone's setup-project.py as setup.sh does (`--into` first, `OPENREPOSHAPE_SELF_BOOTSTRAP=1`), and a typed `--shape-ref` is that clone's ref, as it was setup.sh's. The other four run their file from the clone with every argument unchanged, in the directory typed in. - git-filter-repo==2.47.0 is the one dependency. Every child gets the package environment's scripts directory first on PATH whenever a git-filter-repo is there; no tool changes. `src/`, not the root, because the root holds `openRepoShape`, the launcher, and `openreposhape/` beside it is the same name on a case-insensitive disk (macOS's and Windows' default). The wheel still holds `openreposhape/`. Hatchling 1.27.0 builds it: its `force-include` places setup-project.py, and it is the newest hatchling that runs on Python 3.9. TESTS. tests/test_openreposhape_package.py imports the launcher's own cases from tests/test_openreposhape_command.py, unchanged, and an autouse fixture of its own points that module's `run_cmd` and `USAGE_LINES` at the Python command for each test it collects, so the same cases run against both, on all three OSes, Windows included, where the launcher's module skips. The four launcher cases that run its file (from stdin or by path) are re-stated for the Python command. Its own cases cover the stamp, pyproject, `--version`, the cache (once per commit, reused with the repository gone, nothing left by a failed clone), the refusals of a changed clone, a clone at another commit and a wrong digest, the override, main followed, the four tools' argument passthrough, the scaffold hand-over, `--install`'s refusal, a machine with no git, and the PATH every child gets, in process and through a real venv. tests/test_repo_hygiene.py holds every module of the package in SHIPPED, and the package's copy of openRepoTools' install line to the others. No existing test or skip condition changed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…heel twice on Python 3.9, checks the two are one file holding this commit's bytes, installs it with pipx and runs the five commands, outside pytest (#563) The pytest suite stays network-free, as the header says, and now says which job is the exception: building the wheel needs the backend pyproject.toml pins and installing it needs git-filter-repo, both from PyPI. So a fifth job on ubuntu, with no pytest in it: - builds the wheel twice at the commit's own time (SOURCE_DATE_EPOCH) and `cmp`s the two; - reads every file in the wheel but its metadata against `git show HEAD:`, byte for byte, setup-project.py included; - installs it with pipx into the job's own directories, on Python 3.9, the oldest the package supports; - runs `--version`, `--help`, `--install` (refused, naming pipx upgrade), `--preflight`, and `--doctor --json` over an empty repository against this commit served from a bare repository on disk, and requires the machine row to find the git-filter-repo pipx installed beside the package, with none on PATH; then the four tools' `--help`. actionlint 1.7.12 (the pinned one) passes over the file, with shellcheck. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…[[`, and its test asserts the link and where it points in two assertions (#563) SonarCloud on #590: shelldre:S7688 on the new test in install.sh (bash 3.2 has `[[`), and python:S9073 on the new test's one `and`. Only the lines 8cae45c added change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… loop is one small function per kind of argument, the repeated literals are named once, and the tests change sys.path only through monkeypatch (#563) SonarCloud's findings on #590, none of them in behaviour: - python:S3776: `parse` (cognitive complexity 31) takes each argument through `take` and five helpers, and `main_openreposhape` (22) through `doctor_alone` and `settle_the_names`. The routing is case for case what it was; every refusal keeps its words. - python:S3516: `version` always returned 0; `print_version` returns nothing and the caller exits 0. - python:S1192: `--placement-plan`, `setup-project.py`, `$OPENREPOSHAPE_REF` and `--shape-ref` are constants. - python:S8997 and S9073 in the tests: `package_module` uses `monkeypatch.syspath_prepend` and `monkeypatch.setattr`, and three composite assertions are split. The gate's one failing condition, pythonsecurity:S8705 in `run_child`, is not changed: handing the person's arguments to the standard's own script, unchanged and with no shell, is what the four commands are. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Python 3.9 it installs with, not the runner's pipx with --python (#563) The job's first run on #590 failed in `pipx install --python <3.9>`: the runner's pipx 1.16.7 runs on the image's Python 3.12 and shares one pip with every environment it makes, and that pip cannot run on 3.9 (`TypeError: dataclass() got an unexpected keyword argument 'slots'`). So the job installs pipx into the 3.9 itself, at 1.11.1, the newest release that installs on 3.9, and its shared pip is then one 3.9 runs. Rehearsed locally on Python 3.9.25: the wheel installs, and the doctor's machine row finds the git-filter-repo pipx installed beside the package. The first run also showed what the plan left to check: the ubuntu image ships pipx (1.16.7), actions/setup-python still offers 3.9 (3.9.25), and two builds of the wheel at the commit's time are one file whose every module is this commit's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s only (#563) SonarCloud githubactions:S8541 on #590: a `pip install` without `--only-binary :all:` may run a package's setup script. pipx 1.11.1 and everything it needs on Python 3.9 come as wheels (checked on 3.9.25: argcomplete, click, packaging, platformdirs, tomli, userpath), so the job says so. The wheel this repository builds is still built from its source, which is what the job is for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…is no link, so `resolved` hands it back unchanged (#563) RV590's nit: "symlinks followed" described nothing once the refusal above it made a linked target impossible. The call stays, because it names the record as `read_recorded_ref` does. Comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…py: an annotated tag named in $OPENREPOSHAPE_REF runs its commit, a killed run's half-made clone is swept after an hour, and the clone's fullness, the tag-before-branch order and 128 + the signal are tested (#563) RV590's fix round: - A BUG THE NEW TEST FOUND. `remote_commit` looked for a tag's peeled line (`refs/tags/<ref>^{}`), but `git ls-remote -- <url> <ref>` prints that line only when a pattern names it too, so an annotated tag resolved to its tag object; the clone then checked out the tag's commit and `verify` refused the run ("it is at <commit>"). `remote_commit` now asks for `<ref>` and `<ref>^{}`; a ref is letters, digits and `._/-`, so neither pattern holds a wildcard. Shown with protocol v0 and v2. - SHOULD 1: `test_the_clone_is_full` clones through `file://`, where git honours `--depth`, and checks `--is-shallow-repository` is false and every commit of the synthetic standard is in the clone. - SHOULD 2: the bare-run test runs DETACHED, with the logging `git` first on PATH and the repository nowhere. - SHOULD 3: `PythonCommand.detached` sets `$OPENREPOSHAPE_REPO` nowhere as `__call__` does, and `__call__` runs in the test's directory; the two tests that read the default repository's name set it empty, which is unset (`${VAR:-default}`). - SHOULD 4: the usage says "run from a checkout's src/ (an editable install)". - SHOULD 5: `sweep_incoming` removes an `.incoming-*` directory nothing has touched for an hour before a clone is made, never following a link and never refusing over one it cannot remove; tested with one two hours old (removed) and one fresh (kept). - SHOULD 6: the pyproject comment says only what the package job shows. - Nits: `test_a_ref_that_is_a_tag_and_a_branch_is_the_tag` (a lightweight tag over a branch of its name, and an annotated tag over one) and `test_a_tool_killed_by_a_signal_exits_128_plus_the_signal` (POSIX: SIGTERM is 143). The stub sends itself `$STUB_SIGNAL` where it is set. Each new test fails on its mutation: `--depth 2` (shallow 'true'), the sweep removed (leftover kept), branches before tags, the peeled pattern dropped, and the raw status returned (241). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e ASCII (#563) RV590's nit: the package's `openRepoShape` prints its usage and every refusal to the console the command was typed in, which on a stock Windows is PowerShell 5.1's, so its modules are held to `setup-project.py`'s rule by a new test parametrized over the directory. The existing test is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n annotated tag named in full, `refs/tags/<tag>`, in $OPENREPOSHAPE_REF runs its commit (#563) RV590's re-check of 6179bd7: `remote_commit` looked a full name up as given, never as `<ref>^{}`, so `OPENREPOSHAPE_REF=refs/tags/<annotated>` took the tag object for the commit and every run was refused ("... should be this command's clone of the standard at <tag object>, and it is at <commit>"). The docstring says a full `refs/...` name works, and the bash launcher accepts that spelling. The lookup now tries `<ref>^{}` before `<ref>`; `git ls-remote` already prints that line, because the peeled pattern asks for it. `test_a_ref_that_is_a_tag_and_a_branch_is_the_tag` gains the case `refs/tags/annotated`, which failed before the fix with that refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents.txt: the release's build tools and the install check's pipx are hash locks, hatchling at pyproject.toml's own pin and each of its requirements by digest (#563) The release workflow installs exactly these files with `--only-binary :all: --require-hashes`. The build lock names the `build` frontend and `hatchling==1.27.0`, pyproject.toml's `[build-system]` pin, with hatchling's own requirements: packaging, pathspec, pluggy, trove-classifiers, and tomli below Python 3.11 (RV590, SHOULD 6). The pipx lock is the install check's: pipx 1.11.1 below Python 3.10, the newest that installs on 3.9, and pipx 1.17.8 above it. Both are universal (Python 3.9 and later, Linux, macOS and Windows), resolved by uv 0.12.18 from PyPI as it stood at 2026-10-01T00:00:00Z, and each header carries the one command that regenerates it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ion> builds the openreposhape wheel twice from `git archive` of a commit that passed tests, tests-macos and tests-windows, uploads it to TestPyPI, installs it with pipx on three systems and Python 3.9, and uploads the same bytes to PyPI (#563) Decision 5 of #563's design, approved by Brett Heap at 2026-10-15T13:32:51Z ("approve all six for #563"), adapted from opensoft/openDox-code's release.yml at c94878be. build refuses unless the version is X.Y.Z and not 0.0.0; the dispatched ref is the annotated tag v<version> pointing at the run's commit (read from the forge and from git), or a branch, which is the rehearsal; the checkout is that commit and clean; both environments name a required reviewer and admit only the tags v*; `tests`, `tests-macos` and `tests-windows`, run by tests.yml, passed on the commit itself; and the checkout's own `scripts/repo_shape.py` digests the commit to what `tree_digest_from_gh` reads from the forge. It then installs its tools from the hash lock, extracts the commit twice with `git archive` into two directories, writes the stamp into each, builds a wheel from each at SOURCE_DATE_EPOCH, requires the two to be one file, checks every file but the stamp and the metadata against the commit byte for byte, and records the sha256. testpypi and pypi run only on a v* tag, in their environments, by trusted publishing; they alone get `id-token: write`. Each checks the artifact's digest, the environment's rules and this run's approval at use time, the tag again, and that the index holds no other file of the version; after the upload the index must serve exactly the recorded digest. install runs on ubuntu, macOS and Windows, and on Python 3.9: pipx from its hash lock installs TestPyPI's wheel, taken from TestPyPI's file host by the recorded digest, with git-filter-repo from PyPI alone, then runs `--version`, `--preflight`, `--doctor` over an empty repository (its machine row must find git-filter-repo in the package's environment), `git filter-repo` on the PATH the package gives its children, the four tool commands, and on Windows `openRepoShape` from PowerShell. On a branch, both upload jobs are skipped, the install check runs over the build's own wheel, and both environments' v* limit refuses any copy of this file. The header's "RELEASING TO PyPI" section is what the docs pull request points at: no notes file for v1.1.0 exists yet. Nothing here runs until Brett Heap creates the trusted publishers and the two environments. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ease is where the wheel is shown reproducible, and what the pin alone leaves resolved on the day of a build (#563) RV590, SHOULD 6: one runner's two builds in one checkout show less than the comment claimed. The release workflow is the real check: it builds from `.github/release-build-requirements.txt`, hatchling at this pin and each of its own requirements by digest, twice, from two `git archive` extractions of the tagged commit in two directories, and uploads nothing unless the two are one file. Comment only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
#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>
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>
… package job's comment and the package tests' docstring say the suite runs on Linux since #597 (#563) Comment and docstring only. #597 removed tests-macos and tests-windows, so the package job's comment no longer speaks of "the three jobs above" and "all three runners", and names the three systems the release workflow installs on. The package tests' docstring no longer says its cases run on a Windows job: CI runs the module on Linux, and no CI job runs it on Windows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 15, 2026
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>
…63-pypi-package One conflict, .github/workflows/tests.yml. Main's file is kept whole: #597's Linux-only `tests` job, #593's `concurrency:` group, and #596's header exception and `tests-git-bash` job (on Brett Heap's word "Option 1: keep a small Git Bash job for #596"). 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 995e138. The jobs are `tests`, `tests-git-bash` and `package`. tests/test_install_sh.py and tests/test_openreposhape_command.py merged cleanly: each one's diff from main is exactly this branch's lines (the symlink refusal cases), and its diff from this branch exactly main's. Lane 5's `bash_path` hunks for those cases follow in their own commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…o symlink refusal cases spell paths as Git Bash does, so they pass in tests-git-bash (#563, #585) Lane openRepoShape-5's work, applied as lane 5 wrote it: #596 (#585) landed first, so #590, landing second, carries these hunks. In both cases the refusal is matched against `bash_path(link)`, the launcher's case hands `OPENREPOSHAPE_BIN_DIR` over as `bash_path(bin_dir)`, and a link read back has Windows' `\\?\` prefix stripped before it is compared. `bash_path` is #596's, in tests/conftest.py, and returns the path as it is off Windows. Lane 5's evidence: on Windows (probe run 38769166462, the merge of 01e8c2c with this branch) the four cases failed without the hunks and both suites gave 117 passed with them; on Linux, 117 passed. Here, on Linux: the two suites give 117 passed, with and without Path.write_text writing CRLF. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…b's own, that what it checks needs PyPI, not that it alone reaches PyPI (#563) Comment only, three lines for three. `tests` and `tests-git-bash` both pip-install pytest 8.3.4, and `tests` git-filter-repo too, so "the one job that reaches PyPI" was not true. What is the package job's own is that it builds the wheel and installs it with pipx, which needs the build backend and git-filter-repo from PyPI. The job's own comment below says only that those two come from PyPI and that the suite in `tests` never touches the network, which holds, and is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
main gained #583 (#520), #596 (#585), #614 (#527), #619 (#530), #594 (#526), #617 (#525) and #618 (#418). Three files conflicted: - .github/workflows/tests.yml: main's file (with #596's tests-git-bash job) plus this branch's three additions, byte-identical to theirs: the header paragraph on the `package` job (#590), after main's Git Bash paragraph; #599's actionlint line for release.yml; and #590's `package` job, now after tests-git-bash. actionlint 1.7.12 finds nothing in it. - README.md, the native Windows block's closing paragraph: #596 says the two-liner IS the Windows way in from PowerShell, with no twin of the `openRepoShape` command, and that Git Bash runs that command and the one line, tested in CI but for a typed `yes`; this branch says the two-liner is the way in that installs nothing and pipx's `openRepoShape` runs the same flow. The merge keeps both facts in main's seven lines: the two-liner installs nothing, pipx's command runs the same flow, and Git Bash runs step 3's bash command and its one line, tested in CI but for a typed `yes`. - docs/handbook.html: the same two sentences in the masthead's second-way paragraph and the Windows panel, each combined the same way, and the seven `Starting a project in a new organisation` markers, which now read the merged README (62737be2). No other section's marker moves. README.md is 1477 lines, its cap (1449 on main, +28 for #563). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-release-workflow Brings in #590 at 70955e4: its merge of main (#596's `tests-git-bash` job, #593's concurrency group, #597's Linux-only `tests` job), lane 5's `bash_path` hunks and a header-comment fix. No conflict: git merged tests.yml by itself. The result is #590's file whole (jobs `tests`, `tests-git-bash`, `package`, and its header wording) plus only #599's own change: `.github/workflows/release.yml` in the actionlint step, with its comment and step name. Every other path is #590's, plus #599's own files and its pyproject.toml comment paragraph. The release gate stays ("tests",): `tests-git-bash` is not a release gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
…) into l3-563-docs #599 now merges #590's 70955e4, which carries main 4cf5fb0 (#596's tests-git-bash job), lane 5's `bash_path` spellings in the two symlink refusal tests, and the reworded header paragraph on the `package` job. CI's tests-git-bash job failed #630's b006f16 on those four symlink cases, because this branch still held #590's older test files. Two files conflicted, as RV630's simulation predicted: - .github/workflows/tests.yml, the `package` job's header paragraph only: #590's wording. The file is now 750366b's, which is 70955e4's plus #599's actionlint lines for release.yml. - docs/handbook.html, the seven `Starting a project in a new organisation` markers, from the criss-cross merge base: this branch's side. README.md merged cleanly to b006f16's, and the handbook is b006f16's, byte for byte. tests/test_install_sh.py, tests/test_openreposhape_command.py and tests/test_openreposhape_package.py merged cleanly to 70955e4's. The merged tree differs from 750366b only in this pull request's own ten files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
added a commit
that referenced
this pull request
Oct 16, 2026
…from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) (#590) The first of #563's lane acts, on Brett Heap's ruling 36 ("approve all six for #563"): the `openreposhape` Python package. Refs #563; the workflow PR (#599) and the docs PR (#630) follow, so this does not close the issue. - `pyproject.toml`: hatchling 1.27.0, `requires-python = ">=3.9"`, the version read from the stamp, `dependencies = ["git-filter-repo==2.47.0"]` and five console scripts: `openRepoShape`, `scaffold-project`, `adopt-project`, `update-shape` and `shape-doctor`. - `src/openreposhape/` (standard library only): `_release.py` (the stamp, committed as 0.0.0 and unstamped), `standard.py` (a full clone of this repository at the stamped commit, made once per commit in the per-user cache and checked against the stamped digest before each run) and `cli.py` (the commands). The wheel also holds the tree's `setup-project.py`, so `openRepoShape --preflight` runs with no git. - `openRepoShape` re-states the bash launcher's modes and rules; the packaged `--install` refuses and names `pipx upgrade openreposhape`. The four tool commands run that file from the clone with every argument unchanged and return its exit status. Every child gets the scripts directory first on PATH when a `git-filter-repo` is there. - The bash launcher's `--install` and `install.sh` refuse a target that is a symbolic link (pipx places `~/.local/bin/openRepoShape` as one), dangling links included; the four symlink cases use lane 5's `bash_path` so they also pass under Git Bash in `tests-git-bash`. - `tests.yml` gains a `package` job: it builds the wheel twice and compares the bytes, checks the wheel holds the commit's files byte for byte, installs it with pipx and runs the commands it placed. Lane: openreposhape-3 (openRepoShape-3) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in main at fe237ec: #590's squash, whose tree equals `git merge-tree --write-tree 6f760b7 70955e4` (df113d8), and #626 (6f760b7, scripts/family.py and tests/test_family_refuse_up_front.py). main and 70955e4 differ in #626's two files alone, and those merged cleanly. One conflict, add/add in pyproject.toml. main's blob is 7e1a364, which equals 70955e4's blob. This branch's side, 4791b54, is that blob plus #599's 11-line paragraph, with no line removed; it is kept. .github/workflows/tests.yml merged by itself to this branch's side (eac5664): main's blob 7928afa, which equals 70955e4's, plus #599's release.yml actionlint lines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
brettheap
marked this pull request as ready for review
October 16, 2026 10:45
|
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 15 hours by commenting |
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
main gained #563's own #590 (fe237ec) and #599 (32c22b0), whose trees this branch already held through #599's 750366b (#599's final head, eaa0cda, is 750366b plus a merge of main), and #626, #633, #642, #641, #643, #644, #632, #636, #631, #638, #635, #640, #623, #637 and #622. No conflict: the merged tree is 9be0d0d, the tree `git merge-tree --write-tree` predicted. AGENTS.md, docs/cli.md and tests/test_repo_hygiene.py merged as text; README.md and docs/handbook.html are unchanged by the merge. README.md is 1461 lines, its cap (1449 + 12, tests/readme-cap.d/563.txt), and AGENTS.md 494, its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
brettheap
pushed a commit
that referenced
this pull request
Oct 16, 2026
… tab shows the one-line install for Git Bash, held to the other copies by a test, and the notes are current to main d4a7fab with the fifteen pull requests since 4cf5fb0, the environments, the tag ruleset and the rehearsal (#563, #461) The handbook (#461's line "The handbook's step 3 has no Git Bash path", which lane 3 told lane 5 on #473 that #630 settles): step 3's install block was `data-platform="linux mac wsl"`, so a reader on the Windows tab never saw the one line the Windows panel says Git Bash runs. The Windows tab now has a lead, "In Git Bash, the bash that Git for Windows installs, the one-line install works as on Linux:", the Linux tab's block copied byte for byte as `data-lang="sh"`, and the panel's caveat: the preflight it ends with cannot take a typed `yes` in Git Bash. README.md is unchanged: no test asked for it, and it is at its cap. tests/test_repo_hygiene.py: the test above it finds one copy of the one line per file; `test_every_handbook_copy_of_the_one_line_install_is_the_same` holds every handbook copy (the masthead's and step 3's two) to ONE_LINE_INSTALL, so a copy edited on one tab alone fails. A drifted Windows copy fails it; the page as committed passes. The release notes: - "What changed since v1.0.1" gains one entry per pull request that landed after 4cf5fb0 and is not #563's own, from `git log --first-parent 4cf5fb0..origin/main` at d4a7fab: under update-shape.py and bump-leg.py, #644, #633 and #631; under scaffold, family and the materializer, #622, #643, #640, #641, #623, #638, #635, #626, #637 and #642; under shape-doctor.py, #636 and #632. Each from its squash commit and the issues GitHub says it closes. - The list's placeholder says it is current to main d4a7fab and stays a placeholder for what lands after it; the counts are refreshed inside their placeholder (66 pull requests, 75 of 97 milestone issues closed). - #590 and #599 are cited beside #563, and "Before tagging" says they landed (fe237ec, 32c22b0) and lists the 22 open milestone issues. - "How this release is cut": Brett Heap's word at 2026-10-16T12:06:40Z, the environments testpypi (12:07:31Z) and pypi (12:07:39Z), the tag ruleset 25237399 (12:08:14Z), and the rehearsal, run 38898735926 (12:10:53Z-12:12:00Z, success, both uploads skipped), each read back from the API. The trusted publishers' date stays a placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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-3 (openRepoShape-3)
Claim: #563 (comment)
What
.github/workflows/release.yml, decision 5 of #563's design, which Brett Heap approved at 2026-10-15T13:32:51Z ("approve all six for #563"). It is adapted from opensoft/openDox-code'srelease.ymlatc94878be. The workflow is dispatch only, with aversioninput. Nothing is granted at the top level, and each job states its own permissions.build0.0.0; the ref is the annotated tagv<version>whose tag object names this run's commit (read from the forge and from git), or a branch, which is the rehearsal; the commit is onmainorrelease/v<major>.<minor>(compare, byrefs/heads/); the checkout is that commit and clean, ignored files included;testpypiandpypieach name a required reviewer and admit only the tag rulev*; thetestsjob (whichtests.ymlruns on every trigger since #597) passed in apushorworkflow_dispatchrun oftests.ymlwhose head is the commit, read from that run's own jobs; the checkout's ownscripts/repo_shape.pytree_digestequalstree_digest_from_gh. Then it installs its tools from a hash lock (--only-binary :all: --require-hashes), extracts the commit twice withgit archiveinto two differently named, differently deep directories, writes the stampopenreposhape/_release.pyinto each (the committed file with its four assignments given values), and builds a wheel from each withbuild --wheel --no-isolationatSOURCE_DATE_EPOCH= the commit's time. The two wheels must be one file (cmp). Every file in the wheel except the stamp and.dist-infomust equal the commit's own bytes. Every tracked file undersrc/openreposhape/must be in it, and nothing else. The stamp may differ from the committed file only in its four lines. METADATA, entry points and LICENSE must matchpyproject.tomland the commit, the entry points must declareconsole_scriptsalone, and.dist-info/must hold exactly METADATA, WHEEL, RECORD,entry_points.txtandlicenses/LICENSE. Finally it records the wheel's sha256.testpypiv*tag onlytestpypi;id-token: write. Checks: the artifact is the recorded digest; at use time, the environment still has a reviewer and thev*rule, and this run holds an approval for it; the tag still names this commit; TestPyPI holds no other file of the version. Then it uploads (pypa/gh-action-pypi-publish, pinned by commit, trusted publishing,skip-existing). Afterwards TestPyPI must serve exactly that digest, not yanked.installPIP_INDEX_URL=https://pypi.org/simple/, so git-filter-repo comes from PyPI and never from TestPyPI. git-filter-repo must be absent before the install. pipx must place exactly the five commands,openRepoShapewith its capitals (.exeon Windows), and that is theopenRepoShapeon PATH.--versionmust name the version, tag, commit and digest.--preflightmust run.--doctorover an empty repository must exit 2, and its machine row must find git-filter-repo in the pipx environment.git filter-repo --versionmust work on the PATH from the package's ownstandard.child_environment(on Windows this is git for Windows findinggit-filter-repo.exe). The four tools must answer--help. On Windows, PowerShell must runopenRepoShape --version.pypiv*tag only, aftertestpypiand everyinstalllegpypi, which waits for Brett Heap's approval;id-token: write. The same pre-upload checks astestpypi, then the upload. Afterwards PyPI must serve exactly the verified digest.A dispatch on a branch is the rehearsal. It runs
buildin full and everyinstallleg over the build's own wheel. Both upload jobs are skipped by theirif:, and both environments'v*limit refuses any copy of the file a branch carries. The workflow's header comment says so, and also carries a short RELEASING TO PyPI section for the docs PR to reference, because nodocs/release-notes/v1.1.0.mdexists yet.Also in this PR:
.github/release-build-requirements.txt: a hash lock forbuildandhatchling==1.27.0, with hatchling's own requirementspackaging,pathspec,pluggy,trove-classifiersandtomli(below 3.11). It is universal for Python 3.9 and later and resolved by uv 0.12.18 from PyPI as of 2026-10-01; the header holds the regeneration command..github/release-pipx-requirements.txt: the same for pipx: 1.11.1 below Python 3.10, 1.17.8 above.pyproject.toml: a paragraph under the backend pin saying the release is where the wheel is shown reproducible, and what the pin alone leaves resolved on the day of a build. It is a comment only, and it keeps The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590's own wording above it..github/workflows/tests.yml: its actionlint step readsrelease.ymltoo.tests/test_release_workflow.py: offline. It readsrelease.ymlwithrepo_shape.load_yaml, and it runs three of its steps' own scripts: the stamp, the wheel check, and the main-or-release-branch check.Why
#563 decision 5: TestPyPI, then an install check on three systems, then PyPI, in one dispatched run with each upload held for approval. Also review RV590's SHOULD 6, which the release build now meets:
git archiveextractions in two directories, not two builds in one checkout;Tests
tests/test_release_workflow.py: 56 passed after fix round 3 (54 after round 2, 44 after round 1, 21 at first). Every assertion was checked against a mutatedrelease.yml. In the first round each of these failed exactly one test:id-tokenadded toinstall, the publish action pinned by tag, apushtrigger,--require-hashesdropped, a job dropped from the gate, the gate step deleted, the tag check deleted, the wrong environment, andpypiwithout itsif:. RV599's own mutations are covered under the review sections below.tests/test_repo_hygiene.py,tests/test_openreposhape_package.pyand thetests/test_install_sh.pytest that readstests.yml: 192 passed, on based090f0c5(/usr/bin/python3 -m pytest, stubgh,GH_TOKEN=bogus).tests.ymlpins, sha256-checked), over the four filestests.ymlnow hands it: 0 findings. shellcheck was on PATH, and actionlint ran it.run:scripts, each pulled fromrelease.ymland run underbash -eo pipefailwith GitHub-style env files.ghwas shimmed to read-onlygh api.1.1,01.1.0,1.1.0rc1,0.0.0,1.1.0.post1, a leading space and an embedded newline. The tag step passes the real annotatedv1.0.1at8cda1397. It refuses the wrong tag, the right tag at another commit, a missing tag, and a pull ref; a branch is a rehearsal.37339111713. The gate refuses8cda1397(v1.0.1's commit: notests.ymlrun ever ran on it) andd53080ae(The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590's earlier head, tested only by apull_requestrun); re-checked under the round-2 gate. The digest step agrees with the forge ond53080ae.d090f0c5plus this PR's files (a scratch commit, never pushed), two builds from the two extractions are one file (sha256904d1343…), and the wheel check passes. The wheel check refuses a changed module, a changed stamp line, an extra file, a changedRequires-Distand a missing module.build --no-isolationexits 1 when the backend does not satisfy[build-system] requires.curlshim, and it refuses a file host other than TestPyPI's. The index checks were run against live PyPI JSON and a local fake index: no file, another file held, served, yanked.Review (RV599) and fix round
RV599 found one MUST, five SHOULDs and two nits. All are fixed at
9a632c46.pull_requestrun reports the PR head but tests a merge, and a check run created with an Actions token can land intests.yml's check suite. The gate now reads the jobs oftests.ymlruns whosehead_shais the commit and whose event ispushorworkflow_dispatch(RV599's code), andchecks: readis gone. Read-only against live data:006caf42, unmerged adopt-project.py:checkrecomputes each pin's digest at the plan's commit in the cloneplanread it from, where that clone is still on the machine and is no partial clone, so a commit or a digest edited into the plan no longer checks ok and reaches the root asadoption verifiedover a redvalidate-pins.py(#449) #589's head: exit 1 (only apull_requestrun tested it);bfed143e: exit 0 (a dispatch run, every job green);26c16009: exit 1 (main's push run,testsalone; it passes since round 2, which requirestestsalone).environment:and still publish. A ruleset onv*tags is recommended. openDox-code's accepted limits are carried over: anyone who can push av*tag runs that tag's own copy of the file, the approval is the last line, and lane sessions act as brettheap.compare/<sha>...refs/heads/<branch>to answeridenticaloraheadformainorrelease/v${VERSION%.*}; a branch rehearsal skips it. Live: v1.0.1's8cda1397passes onrelease/v1.0,fb786c11passes on main, and The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590'sd090f0c5refuses.if:s only, nocontinue-on-error,persist-credentials: falseon both checkouts, noPIP_*beyond the install check's two, each upload job's fixed step order with the same scripts in both jobs and exact publish inputs, the wheel check's position and comparisons, and bare success exits only on the rehearsal and the two 404 paths. All 20 of RV599's mutations, plus three on the new gate, now fail a test. One RV599 pattern occurs in both upload jobs, so it was applied to TestPyPI's copy.tests.yml's actionlint step readsrelease.yml, and a test asserts it.entry_points.sections() == ["console_scripts"]and.dist-info/is exactly its five files. The check now runs as a test over a wheel made from HEAD's own blobs, and it passes the real hatchling wheel from the rehearsal.pyproject.tomlnow says "path-independent", and the header no longer calls a second build "a second set of bytes".Review (RV599) round 2 and fix round
RV599's second pass found every round-1 item fixed and its numbers reproduced, and one new MUST and one new SHOULD. Both are fixed at
d2fdc6b7.654c476c),tests.ymlrunstestson every trigger and no longer has the macOS and Windows jobs, so a gate asking fortests-macosandtests-windowsrefused every commit,654c476cincluded. The gate now names("tests",). It still never takes the set from what a run holds, so a job gone missing is a refusal. The step is "the commit carries a passing tests job", its refusal says to dispatchtests.ymlat the commit, and the header's step 1 says main's push run passestestswhile arelease/v<X.Y>branch needs a dispatch. Read-only on live data:654c476c: exit 0 (its push run);006caf42: exit 1 (apull_requestrun alone);bfed143eand26c16009: exit 0.ghthat answers the commit's runs and each run's jobs, in ten cases:testsgreen passes;pull_requestrun alone, a run of another head or another workflow, a skipped job, and a run with notestsjob are refused as not run;running = []andmaxmademin, now fail that test. All 24 applicable mutations are caught:mut2.py's list read as data, three on the gate's filters, and the two survivors. The one skipped mutation targets the check-run filter that MUST 1 removed.l3-563-pypi-packageor main (The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590 takes in ci: run the test suite on Linux only #597 first), and the install check's matrix is unchanged.Review (RV599) round 3 and fix round
RV599's third pass called #599 READY at
d2fdc6b7: MUST 2 and SHOULD 6 were confirmed, and all 14 of its new mutations failed a test. One SHOULD and one nit were left. Both are fixed atb0182e23.SHOULD 7: the release steps named no remedy for a cancelled push run. 11 of the last 20
tests.ymlpush runs on main were cancelled by the next landing, so main's2bf8fd4cis refused with "ended cancelled". The header's step 1 now says main's push run counts only when it finished green, and gives the remedy:tests.ymlon a branch that points at the release commit (arelease/v<X.Y>branch serves while it is still at that commit), and wait for it;--ref maintests main's CURRENT head, not an older commit;gh workflow run tests.yml --ref v<version>), wait, and re-run the release run.The branch route leads, because a tag dispatch running at the tag's commit is GitHub's documented behaviour but not yet seen here. The "has not run" and "ended " refusals name the same commands. Live:
2bf8fd4cstill refuses, now naming them;654c476cpasses, and006caf42refuses with the remedy.Tests and mutations. Two gate cases are new: a cancelled push run refuses, and a dispatch after it passes. Each refusal must name the three remedy pieces. All 26 applicable mutations are caught, including two that drop the remedy from either refusal; the one skipped mutation targets the check-run filter MUST 1 removed.
Nit:
release.yml, the test'sGATEcomment and this body now saytests.yml"runstestson every trigger", not that it holds one job: The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590'spackagejob stays.Checks. The verify set gives 192 passed (the 190 set plus the two new cases), with basetemp
w563b-r3-bt; actionlint 1.7.12 and pyflakes are clean. There was no merge ofl3-563-pypi-package, and the install check's legs are unchanged.Merge of #590's 70955e4
750366bcmerges #590 at70955e4d. That head brings in its merge of main (#596'stests-git-bashjob, #593's concurrency group, #597's Linux-onlytestsjob), lane 5'sbash_pathhunks and a header-comment fix..github/workflows/tests.ymlby itself. The result is The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590's file whole (jobstests,tests-git-bashandpackage, and its header wording) plus only The release workflow: a dispatch on the annotated tag v<version> builds the openreposhape wheel twice from git archive of a commit whose own tree passed the tests job, uploads it to TestPyPI, installs it with pipx on three systems and Python 3.9, and uploads the same bytes to PyPI on approval (#563) #599's change:.github/workflows/release.ymlin the actionlint step, with its comment and step name. Every other path is The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590's, plus The release workflow: a dispatch on the annotated tag v<version> builds the openreposhape wheel twice from git archive of a commit whose own tree passed the tests job, uploads it to TestPyPI, installs it with pipx on three systems and Python 3.9, and uploads the same bytes to PyPI on approval (#563) #599's own files and itspyproject.tomlparagraph.("tests",).tests-git-bashis a small extra job, not a release gate.test_tests_yml_lints_the_release_workflowand the gate tests that readtests.ymlpass.ghrules with basetempw563b-r4-bt:test_release_workflow.py,test_repo_hygiene.py,test_openreposhape_package.py,test_workflow_concurrency.pyandtest_install_sh.py::test_it_installs_the_git_filter_repo_ci_runs: 197 passed;tests.ymllints: 0 findings.Merge of main after #590's squash
eaa0cda1merges main atfe237ec7into750366bc.fe237ec7is #590's squash of70955e4d, plus #626. This PR now targets main.git diff --stat 70955e4d fe237ec7lists only family.py:add,bumpandremoverefuse before their first write a merge, cherry-pick, revert, rebase orgit amin progress, a.gitmodules,members/orfamily.yamlthe sparse checkout leaves out, and a file namedmembers, which each failed after writing and was rolled back, or committed in the wrong place (#612) #626's two files,scripts/family.pyandtests/test_family_refuse_up_front.py. That is the same diffstat as family.py:add,bumpandremoverefuse before their first write a merge, cherry-pick, revert, rebase orgit amin progress, a.gitmodules,members/orfamily.yamlthe sparse checkout leaves out, and a file namedmembers, which each failed after writing and was rolled back, or committed in the wrong place (#612) #626's own commit6f760b7. So every file The openreposhape package: openRepoShape and four tool commands, run from a clone of the standard at the stamped commit and checked against its digest; --install and install.sh refuse a symlinked target (#563) #590 touched has the same blob on main as at70955e4d.pyproject.toml. Main's blob7e1a364bequals70955e4d's. This branch's blob4791b545is that blob plus this PR's 11-line paragraph, with no line removed. The resolution keeps this branch's side..github/workflows/tests.ymlmerged by itself toeac56645. That is main's blob7928afaf, which equals70955e4d's, plus only this PR's actionlint lines.add,bumpandremoverefuse before their first write a merge, cherry-pick, revert, rebase orgit amin progress, a.gitmodules,members/orfamily.yamlthe sparse checkout leaves out, and a file namedmembers, which each failed after writing and was rolled back, or committed in the wrong place (#612) #626 merged cleanly.scripts/family.pycame out as main's blob709e8a92.git merge-tree --write-tree fe237ec7 eaa0cda1is clean and writesa0147411, which is HEAD's own tree.git diff fe237ec7 eaa0cda1touches six files, all this PR's own:.github/workflows/release.yml;.github/release-build-requirements.txtand.github/release-pipx-requirements.txt;tests/test_release_workflow.py;tests.yml's actionlint lines (7 lines);pyproject.toml's paragraph.f1c73e2(theworkflow-unreadablerefusal invalidate-pins.py) touches two files this PR does not.git merge-tree --write-tree f1c73e2 eaa0cda1is clean, and its diff againstf1c73e2is the same six files.ghrules with basetempw563b-r5-bt:test_release_workflow.py56,test_workflow_concurrency.py5,test_repo_hygiene.pyandtest_openreposhape_package.py135, and the CI-pin test 1;tests.ymllints: 0 findings.#590 has landed as
fe237ec7. Nothing is uploaded until Brett Heap creates the trusted publishers on pypi.org and test.pypi.org and the testpypi and pypi environments.Refs #563
🤖 Generated with Claude Code
Summary by Sourcery
Establish a gated, reproducible release pipeline that verifies and promotes one wheel from an annotated release tag through TestPyPI to PyPI.
New Features:
Enhancements:
Build:
CI:
Deployment:
Documentation: