Repository navigation
README, AGENTS.md, docs/cli.md and the handbook: pipx install openreposhape is the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a bare pip install, and a first draft of the v1.1.0 release notes (#563) - #630
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>
…s and #817's entries, #642's upgrade note for a holder's `cd` line, and v1.0.0's `--into` limit kept open, current to main 64ec47c (#563) RV630's pre-review of the B2 stage (lane 3's session): - SHOULD-1: the CI bullet said every test that clones a just-pushed local remote clones it safely (#554), but five plain-path `git submodule update --init` runs are left on main (tests/test_doctor_legs_members_next.py:631, :987, :1054; tests/test_family_bump_remove_rollback.py:337, :908). It now says "runs `git clone` on", which is what #554 changed. - NIT-1: #812's READMEs quote a local clone URL "that needs it". - NIT-2: #682's first sentence no longer repeats the lead ("there:"). Landed since 40e678e, each entry checked against its squash: - #747 (#681), 6626a3c, whose tree equals `merge-tree 40e678e ff7c59b`: a new family bullet, with RV630's NIT ("`member-gitlink-absent`'s remedy puts the gitlink back by hand"); v1.0.0's limits at v1.0.0.md:466 and :467 are said closed. - #817 (#708), 64ec47c, whose tree equals `merge-tree 6626a3c d63a267`, and whose own lines equal those of 8fd2fa6, the head RV630 read: RV630's text, appended to the YAML reader bullet. #738 (#695) is v1.1.1 (lane 5, #473 issuecomment-6236564004), so its docs entry is not taken, and v1.0.0's `--into` limit (v1.0.0.md:492) is listed as still open under Known limits. Its upgrade note is true of main since #642 (0ed1b84), so it goes in now, under "Install and upgrade": lane 5's sentence verbatim, and the README's `Y/Y` layout. Counts and the open list at main 64ec47c: 113 pull requests since bfed143; 124 of 135 milestone issues closed at 16:42Z; 11 open, each with a pull request open, #681 and #708 out, #695 marked as planned for v1.1.1. All still placeholders. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efusals and `family-operation-in-progress` name a path as `shown_source` names it, so an ESC or a line break in a path no longer reaches the terminal (#692) (#731) #622's folder refusals printed a path as it was. `family.py init --into <a file named wd-ESC[31mRED> --dry-run` gave `REFUSED family-folder-cannot-be-made: the family folder .../wd-^[[31mRED/Orbit cannot be made: ...` through `cat -v`. A name with a line break and then `WARNING forged-line: pwned` printed that line as a line of its own. R612's `family-operation-in-progress` printed the mark's path across two lines for a holder whose path holds a newline. Each path is now spelled as `shape_materialize.shown_source` spells a `--pin-source`: as it is, and as Python's `repr` spells it where a character in it is not printable. - `scripts/shape_materialize.py`: both answers of `folder_cannot_be_made` (a file or a bad stat in the way, and a folder this user cannot write in), the folder in `refuse_unmakeable_folder`, and the folder in `make_folder`. - `scaffold-project.py`: `scaffold-target-exists`. - `scripts/family.py`: `family-target-exists`, and the mark's path in `family-operation-in-progress` (`where.as_posix()`). A printable path is named byte for byte, so no existing row changes and `why.endswith(CANNOT_WRITE_IN)` in `refuse_unmakeable_folder` still holds. No help text changed, so `docs/cli.md` is untouched. No README, handbook or template sentence says how a refusal names a path (`git grep` of `shown_source`, `target-exists` and `folder-cannot-be-made` in AGENTS.md, README.md, `docs/handbook.html` and the templates found none), so no doc changes. - **`shown_source`, not `shown_path`.** Since #634, `shape_materialize.shown_source` returns `repo_shape.shown_path(source)` (`scripts/shape_materialize.py`: `return shown_path(source)`), so the seven call sites this change adds (four in `shape_materialize.py`, one in `scaffold-project.py` and two in `family.py`) spell a path as `shown_path` does. Renaming them to `shown_path` stays out of this PR: it would only move imports in `scaffold-project.py` and `family.py` and reword docstrings in `family.py`, `shape_materialize.py` and the test file, and the review's N1 left it optional. I did not take it. The predicted merge with main works with the alias: `family.py --help` and `scaffold-project.py --help` exit 0 on it, and `shown_source` and `shown_path` return the same string for an ESC name, a line-break name and a plain path. - **`shown_source` is imported by name into each file's existing `from shape_materialize import (...)` block.** The first commit put it on a line of its own after the block; lane 4's #660, #688 and #693 add an import at that same place, and `merge-tree` reported add/add conflicts in `scripts/family.py` and `scaffold-project.py`. The second commit moves the name into the block, and those conflicts are gone. - **"The holder in the other refusals of the same block" is one refusal.** The `# What the holder is in the middle of, and what its sparse checkout leaves out` block holds `family-operation-in-progress`, `family-sparse-excluded` and `member-path-unaddable`. Only the first prints a path of the holder's. `family-sparse-excluded` names `members/<Project>` and `.gitmodules`, and `member-path-unaddable` names `members/<Project>`. - **No scaffold row for `--local-remote-dir`.** A control character there is refused earlier as `scaffold-unwritable-value`, which already names the path with `repr`. The unit test covers `make_folder`, which only a race reaches by the command line. `tests/test_dry_run_parity.py`: - `SCAFFOLD_CASES` gains five rows: ESC and line break in the `--work-dir` name, ESC in a folder name above it, and ESC and line break in a work dir that holds a file named `Atlas` (`scaffold-target-exists`). - `FAMILY_CASES` gains five rows: ESC and line break in `--work-dir`, ESC in `--into` (`the family folder`), and ESC and line break in a work dir that holds a file named `Orbit` (`family-target-exists`). - Two rows join `UNWRITABLE_FOLDER_AT` (ESC in the scaffold's and in `init`'s `--work-dir`, a folder this user cannot write in). - Rows named `ESC-...` and `line-break-...` are skipped on Windows, whose file names cannot hold them (`POSIX_NAMES`, `case_params`). Every expectation spells the path with `repr`, and not with `shown_source`. - `assert_refused` now fails any stderr line that starts with `WARNING forged-line`. - Three unit tests: `test_each_folder_question_spells_a_name_that_is_not_printable` (ESC, line break; `folder_cannot_be_made`, `refuse_unmakeable_folder` and `make_folder` by themselves), `test_a_folder_this_user_cannot_write_in_is_spelled_safe_to_show`, and `test_a_printable_name_is_named_byte_for_byte` (a name with a space and an accented letter stays unquoted). `tests/test_family_refuse_up_front.py`: `test_a_holder_path_with_a_newline_still_shows_its_merge` asserted the raw newline in the refusal (`"Ink\nRouter/.git/worktrees/" in ran.stderr`). It is now `test_a_holder_path_with_a_control_character_still_shows_its_merge`, over a line break, an ESC and a forged line, and asserts the one-line `repr` form (`refusal()` fails unless the stderr is two lines). It is the one existing test this change had to edit. Results at head `ab55c29f` (Python 3.12.3, Linux): - `tests/test_dry_run_parity.py tests/test_dry_run_asks_remotes.py tests/test_family_dry_run.py tests/test_family_init_landing.py tests/test_repo_hygiene.py tests/test_pin_source_unreadable.py tests/test_scaffold_leg_path_collisions.py tests/test_scaffold_reuse_seeded_legs.py`: **494 passed**. - `tests/test_family.py tests/test_family_refuse_up_front.py tests/test_family_init_values.py tests/test_family_add_refusals.py`: **356 passed, 19 skipped** (the skips are `test_family_init_values.py:219` and `:291`, template-quoting premises). - Not run: the full suite (the coordinator's gate), and macOS and Windows (CI is Linux-only since #597). The new hostile-name rows are POSIX only by design. On the base code: the review overlaid this head's two test files on main `2fac9790` and ran `tests/test_dry_run_parity.py` and `tests/test_family_refuse_up_front.py` with `-k "dry_run_parity or control_character"`. The result is **20 failed, 87 passed** (76 deselected, 43.5 s). The 20 are exactly the new and edited tests: the 5 scaffold and 5 `init` rows, the 4 unwritable-folder rows, the 3 unit tests (`test_each_folder_question_spells_a_name_that_is_not_printable` in two cases, and `test_a_folder_this_user_cannot_write_in_is_spelled_safe_to_show`), and the 3 cases of the edited `test_a_holder_path_with_a_control_character_still_shows_its_merge`. The one new test that passes on main's code is `test_a_printable_name_is_named_byte_for_byte`, by design: a printable name is named byte for byte on both. An earlier draft of this body said 21 failed, 2 passed. That run was on `8062810d` with a wider selection, and it held a scaffold row for ESC in the local remote dir's name (`ESC-in-the-local-remote-dir-name`) that this head's tests no longer have; its second pass was the existing `test_the_refusal_quotes_gh_s_first_line_and_escapes_what_is_unprintable`, which also passes in the new run, one of the 87. `family.py init` and `scaffold-project.py --dry-run` against a file named `wd-ESC[31mRED`, a file named `wd-\nWARNING forged-line: pwned`, and a work dir with a file `Atlas` or `Orbit` in it, run with a stub `gh` and no real `gh`: - On main, through `cat -v`: `... the family folder /.../wd-^[[31mRED/Orbit cannot be made: /.../wd-^[[31mRED is a file, not a folder`, and a stderr line `WARNING forged-line: pwned cannot be made: ...` of its own. - On this head: `REFUSED family-folder-cannot-be-made: the family folder '.../wd-\x1b[31mRED/Orbit' cannot be made: '.../wd-\x1b[31mRED' is a file, not a folder`, and one line for the forged name: `--work-dir '.../wd-\nWARNING forged-line: pwned' cannot be made: '...' is a file, not a folder`. `scaffold-target-exists` and `family-target-exists` are one line each in the same form. Each spelled site reverted alone in a throwaway clone at `19d126cd` (the call sites are the same at `ab55c29f`); the new tests that fail for each: - `folder_cannot_be_made`, the `_not_a_folder` answer: 8 failed (six CLI rows and the two unit tests). - `folder_cannot_be_made`, the `CANNOT_WRITE_IN` answer: 5 failed (the four unwritable rows and the unit test). - `refuse_unmakeable_folder`: 12 failed. - `make_folder`: 2 failed (the unit test only). - `scaffold-target-exists`: 2 failed (its two rows). - `family-target-exists`: 2 failed (its two rows). - `family-operation-in-progress`: 3 failed (line break, ESC, forged line). `git merge-tree --write-tree` of this head `ab55c29f` against origin/main `20c8873b` and against every ref on origin that could meet it, fetched 2026-10-16: the 41 branches (`fix/*`, `l3-*`, `wip/*`, `docs/*` and `release/v1.0`) and the 55 PR heads from #630 up. That is 96 refs. (The same list was run for #730's head `7f2bb36b`.) - **origin/main `20c8873b`: clean** (the predicted tree `00b5b24c`, which differs from main in these five files only: 216 lines added, 25 removed). - **#630's head `6fd33a93`: clean.** It conflicted with main in `docs/handbook.html` when this body was first drafted, and no longer does, with or without this change. This change touches no README or handbook. - **The refs that edit one of this change's five files**, and so could meet it, all merge clean with this head: `fix/646`, `fix/651`, `fix/654`, `fix/660`, `fix/661`, `fix/684`, `fix/686`, `fix/688`, `fix/689` and `fix/693`, the heads of #670, #725, #728, #743 and #744, and the heads of the landed #634, #638, #639, #640, #643 and #671. #634 has landed (`30676c90`), so the `fix/660` note about a conflict with it is gone. - **Four refs conflict with origin/main on their own**, and so with this head: `docs/release-notes-v1.0.0` and `release/v1.0`, and the pre-squash heads of the landed #637 and #641. Their conflicts do not come from this change. Every other ref, 92 of the 96 in all, is clean with this head, including the refs that conflict with main in `templates/family-root/scripts/validate-family.py` (#682, #745, #747, `fix/648`, `fix/681`, `fix/704`) or in `shape-doctor.py` (#742, `l3-683`) on their own. So do the old heads of the landed #638 and #639, and lane 3's `l3-677-pins-hand-step-wip` branch, and each of them merges clean with this head too. - **Tests on the predicted merge**, run by the review on the merge with main `2fac9790`: `test_dry_run_parity`, `test_family_refuse_up_front`, `test_pinned_copy_text`, `test_pin_source_unreadable`, `test_dry_run_asks_remotes`, `test_family_dry_run` and `test_family_init_landing`: 445 passed in 215 s. `test_family`, `test_family_init_values`, `test_family_add_refusals`, `test_derived_id_refusals`, `test_repo_hygiene`, `test_naming_policy`, `test_scaffold_leg_path_collisions` and `test_scaffold_reuse_seeded_legs`: 758 passed, 19 skipped in 243 s. I did not re-run them on `20c8873b`; the merge is clean, and the two commits since `2fac9790` (#700 and #702) change only `shape-doctor.py`, `templates/assembly-root/scripts/validate-pins.py` and four test files, none of them a file of this change. - Before the second commit, `fix/660`, `fix/688` and `fix/693` each conflicted with this head in the import region of `scripts/family.py` or `scaffold-project.py` (a separate import line each side added at the same place). That is why the import joins the existing block. #634 is on main, and this change does not edit `shown_source` or its docstring in `scripts/shape_materialize.py`. The review approved with nits. Its S1, the stale base-run numbers and merge lines, is taken here, in this body: no code changed, and the head is still `ab55c29f`. Its N1, renaming the call sites to `shown_path`, is not taken (see "Decisions and their evidence"). It also re-ran 5 of the 7 mutation probes on `ab55c29f` and got the same failure counts as above (12, 2, 3, 2 and 2), and ran 144 hostile-name dry runs (16 names in 9 scenarios): on main 69 rows put a control character on stderr and 6 print a forged line of their own; on this head all 144 exit 2 with no traceback, no control character and no forged line. - `scripts/family.py` `init`'s plan prints `folder` and `landing` lines with the path raw (`print(f"landing {holder} ...")`, `print(f"folder {folder} ...")`). On a dry run or a refused run the plan is on stdout before the refusal, so `WARNING forged-line: pwned/Orbit (--work-dir: no family folder)` still appears there. It is the plan, not a refusal, and not one of the issue's lines. - Other refusals that name a holder or folder path raw, none of them in the issue's lines: `family-holder-detached` and `family-on-tracking-branch` (`{family.root}`), `family-index-locked` (`lock.as_posix()` in the detail), `family-landing-in-standard` (`{holder}`, `{checkout}`, `{beside / family / family}`), `family-holder-already-here` (`{into}`), and `family-remote-exists` and `scaffold-remote-exists` (`{target}`, a local remote path). 1. **family.py `init`'s plan prints the holder and folder paths raw.** `scripts/family.py` `landing` and `folder` rows (about :1756-1759) print `holder` and `folder` as they are, so a forged line or an ESC reaches stdout in a `--dry-run` and in a run refused after the plan. Direction: spell them with `shown_source`. 2. **The same raw spelling in the refusals listed above.** `family-holder-detached`, `family-on-tracking-branch`, `family-index-locked`, `family-landing-in-standard`, `family-holder-already-here`, `family-remote-exists` and `scaffold-remote-exists` each print a path the person supplied or git keeps. Direction: the spelling this change gives its sites. Review: rv692, lane 5's independent review, approved ab55c29 with nits, and its body fix (S1) is published. CI (Brett Heap's ruling 27 and his fast-refresh ruling of 2026-10-15T22:48:35Z): H0 ab55c29 passed tests, tests-git-bash and SonarCloud; the head 13f643d is a clean update-branch merge of main 431c8cf, with tests-git-bash and SonarCloud green; and the local gate passed on its predicted squash tree 4327947 over main 431c8cf: the dry_run_parity, family_refuse_up_front, repo_hygiene, agents_md_codes, cli_reference sets, 330 passed. Landed on Brett Heap's word of 2026-10-17T10:59:39Z, relayed by lane 3 on #473: "merge lane 5's approved v1.1.0 drafts". Closes #692 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained #748 (#662, `431c8cf8`), #731 (#692, `d66a9054`), #810 (#674, `d17564d9`), #809 (#688, `42aed788`), #816 (refs #684, `fa04d2cd`), #813 (#689, `f6d17bc5`) and #723 (#663, `bad05d21`) since 64ec47c. No conflict, and none of them touches README.md, the handbook, AGENTS.md, docs/ or the README cap's ledger, so every handbook marker reads as before. README.md is 1474 lines, its cap; AGENTS.md 494, its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#813 and #723, v1.0.0's quoted-header and plain-path-clone limits closed and the counts current to main bad05d2 (#563) Each entry is the one RV630 corrected in its pre-review of the B2 stage, put at its anchor, and checked against its squash on main: the `git merge-tree --write-tree` of main before the squash with the head the entry was written from equals the squash's tree for all seven. - #810 (#674, `d17564d9`): a new bullet, "`apply`'s lines, its branch and its headers.", after "Headers, branches and messages.". It closes v1.0.0's quoted `files:`/`shape:` header (v1.0.0.md:439) and `apply`'s half of the paths listed as staged (:433); `bump-leg.py`'s half waits for #746. - #731 (#692, `d66a9054`): appended to "Folders, asked before they are made.". - #813 (#689, `f6d17bc5`): appended to "`--tracking-branch`.". - #809 (#688, `42aed788`): a new bullet, "What `family.py init` records and commits as.", after "Names that are not UTF-8.". - #816 (part of #684, `fa04d2cd`): a new bullet, "More holder states, named.", after "family.py refuses before it writes.". #816 closes no issue; #684 keeps its reftable case and is on v1.1.1. - #748 (#662, `431c8cf8`), then #723 (#663, `bad05d21`): in the CI bullet, after #713's sentence. With #554 and #578, #748 closes v1.0.0's limit on fixtures that clone a just-pushed remote by plain path (:530). The counts, in the header comment, the counts paragraph, "What changed" and the open list: main `bad05d2`; 120 pull requests since `bfed143`; at 18:02Z, 3 of the `v1.1.0` milestone's 133 issues open and 130 closed. The open list is #563 (#630), #672 (#746) and #686 (#795); #684 and #695 (#738) are on v1.1.1. #746 and #795 have not landed, so neither has an entry yet. Nothing since 2000488 touches pyproject.toml, src/openreposhape/, install.sh or .github/workflows/. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained #746 (#672, `fc867207`) since bad05d2. No conflict, and it touches no README.md, handbook, AGENTS.md, docs/ or README cap ledger file, so every handbook marker reads as before. README.md is 1474 lines, its cap; AGENTS.md 494, its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…it closed as one clause, the list's and the known limits' markers made final, the triage lines named by their issues, and the counts current to main fc86720 (#563) - #746 (#672, `fc867207`): a new bullet, "`bump-leg.py`'s branch and its lines.", after "`bump-leg.py --dry-run` gives the real run's answer.". `git merge-tree --write-tree bad05d2 a5139a3`, the head #746 was gated and landed on, equals the squash's tree `bb920ac1`. The head the entry was written from, `4a014a79`, sat on #723's head before its squash and does not merge clean with main; `scripts/bump-leg.py` is one blob (`226e7b0d`) at `4a014a79`, `a5d473ee`, `a5139a32` and the squash, and the squash's own lines in each of its five files equal that head's, so the entry stands as written. - v1.0.0's limit on the paths listed as staged (v1.0.0.md:433) is one clause, in #746's entry: with #810 it closes. #810's half-clause is cut. `bump-leg.py --dry-run` over a pin its validators refuse (:440) stays a limit, which the dry run now says. - "What changed" runs to main `fc86720`, and says #795 is added if it lands next; Known limits' re-read marker and "as read so far" are cut. The counts marker stays as the release-body tool reads it. - Known limits name each triage by its issue: "the checklist triage of the L4 lines on #457 and #459" and "of the L1 lines on #457", where they named lanes 4 and 1 and D9, and each id reads "#457's L4-05", "#459's L4-24" and so on. - Counts in the four places: main `fc86720`; 121 pull requests since `bfed143`; at 19:08Z, 131 of the `v1.1.0` milestone's 133 issues closed, the open two #563 (#630) and #686 (#795). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main gained #795 (#686, `6f64eea2`) since fc86720. No conflict, and it touches no README.md, handbook, AGENTS.md, docs/ or README cap ledger file, so every handbook marker reads as before. README.md is 1474 lines, its cap; AGENTS.md 494, its cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f64eea, RV630's lead and two named limits under Known limits, and the counts current to main 6f64eea (#563) - #795 (#686, `6f64eea2`): a new bullet, "`bump --to`, reachable or not.", before "family.py refuses before it writes.", the pending file's text as written. `git merge-tree --write-tree fc86720 <head>` equals the squash's tree `0f2d2ddd` for `17bbbaba` and for the final head `cf143c69`. For the head the text was written from, `cf5e64b3`, the merged tree differs from the squash only in `tests/test_family_bump_reachability_words.py`, so `scripts/family.py` is as the text says. - "What changed" runs to main `6f64eea`, with no word of a later pull request. - Known limits, RV630's words: the lead says the list is not all of v1.0.0's open limits, and names two this release worked on and left open, the `--into` sentence (#695) and `bump-leg.py --dry-run` over a pin its validators refuse (#746, #672). - Counts in the four places: main `6f64eea`; 122 pull requests since `bfed143`; at 20:15Z, 132 of the `v1.1.0` milestone's 133 issues closed, the one open #563 (#630). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
|
(lane openRepoShape-3, 2026-10-17T20:25:55Z) RV630b's final delta review of #630, part 2 (
RV630b: final delta review of #630,
|
| Claim | Measured on main |
|---|---|
| "fetches every branch … whatever fetch refspec the mount has" | :6568 is _ORIGIN_BRANCHES = "+refs/heads/*:refs/remotes/origin/*", used in the fetch at :7186. The old fetch at fc867207:6995 was a plain git fetch -q origin. |
"prunes the remote-tracking branches origin has deleted" |
:7186 runs fetch -q --prune --no-prune-tags origin …. This is Brett's ruling relayed in the squash message ("prune the fetch the tool already makes"). |
| "a commit a branch holds is no longer refused for a narrowed refspec" / "one only a deleted branch held is no longer pinned" | The squash message and the code comment at :7165-7184 agree. |
| "only a tag of the remote reaches is told that a tag is not a branch" | :6735-6743; the refusal still applies, only the words change. |
"no remote named origin is told so" |
:6727 (member-to-local-only), :6858 (member-to-unknown) and the note at :7191. |
| "where each was told the pin cannot be fetched, or to fix the network" | The old words are at fc867207:6589 ("fix the network or the credential") and :6597 ("A pin the rest of the world cannot fetch"). |
- Style: it uses the same bold lead and
(#PR, #issue)order as the neighbouring entries ((#625, #613),(#578, #543),(#626, #612)). It sits right after the family.py:bump --torefuses a commit only the member's mount holds, reachable from no branch of the member's remote, asbump-leg.pyrefuses its twin, so a holder is never pinned to a commit nobody else can fetch (#613) #625/family.pybump --to: pins a commit only the member's mount holds, whichbump-leg.pyrefuses as local-only #613 reachability sentence, which is the logical place. - family.py
bump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686 is right: the issue is CLOSED as COMPLETED, on milestone v1.1.0, closed by [795]. The squash says "Closes family.pybump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686", and the issue's title matches the entry's three states.
Line 120 reads runs to main \6f64eea`., ending with a plain period. The sentence spans :119-120: "The list" / "runs to main 6f64eea`.".
Known limits.
cmpof the brief's lines 24-35 against the head's:681-692exits 0, so the text is byte for byte the brief's.- The 134 lines from "New in v1.1.0:" to the next
##matchd697b366's:681onward (cmpexits 0). - The README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:
init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 limit is true on main and on the head. README:906-907(head) and handbook:1365give only "(the directory you are standing in, by default)".family.py:1804-1840on main refusesfamily-landing-in-standard. README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 is OPEN on v1.1.1, and its first line is exactly this. It matches v1.0.0:492. - The bump-leg.py: the branch is read in full and pushed as
refs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746/bump-leg.py: a root on+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672 limit is true on main.bump-leg.py:1700-1712: the dry run returns before the validators.:1786-1794: the dry run now says "A dry run does not run the project's own validators … can still endbump-leg-validators-red, with every byte rolled back.":1606-1609: the real run raisesbump-leg-validators-redwithROLLED_BACK, and a refusal returns 2 (:1901).- bump-leg.py: a root on
+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672 is CLOSED by bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746. Its dry-run line's chosen direction was "say so; no behaviour changes", so "left open" is right. - It matches v1.0.0
:440and agrees with bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's own entry at:280("where its behaviour is unchanged").
Counts.
- Measured:
gh api …/milestones/2gave open=1 closed=132 at 20:21:19Z;git rev-list --first-parent --count bfed143..origin/maingave 122;rev-parse --short=7gave6f64eea. - Line 4 reads
6f64eea. Lines 14-16 read6f64eea, 122, 20:15Z, and 132 of 133. - With digits and shas masked, the counts marker and the header comment are the same as at
d697b366(diffempty). - The open-list paragraph is at
:943-946(the brief said 930-934): "(1 open, 132 closed)", the open list holds only "Publish openRepoShape to TestPyPI and PyPI asopenreposhape, so onepipx installputs the tools and git-filter-repo on any machine (design first) #563 (README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630)", and the family.pyadd/bump/remove: amembersfile deleted unstaged, agit bisect, an empty sparse-checkout file, an emptyrebase-merge/and a merge conflicted infamily.yamlmeet a refusal or remedy that does not fit (#626's gaps) #684/README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 sentence is present. family.pyadd/bump/remove: amembersfile deleted unstaged, agit bisect, an empty sparse-checkout file, an emptyrebase-merge/and a merge conflicted infamily.yamlmeet a refusal or remedy that does not fit (#626's gaps) #684 is OPEN on v1.1.1 (gh issue view 684), and so is README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695.
Release-body dry run.
- Facts: main_sha
21fdc03(the predicted squash), 2026-10-17 20:22, pr_count 123 (bfed143..21fdc03a), closed 132, open 1, with nofills(no fill marker is left). - Run on the notes as they stand in the predicted squash (byte-identical to the head's), with
--release-commit 21fdc03a5f6c7110e054816ec6f850f871782773 --repo <clone> --dry-run, under the stub gh. - Result:
tool-exit=0. It dropped the leading comment (:3-6), "How this release is cut (remove at tag time)" (:828-932) and "Before tagging (remove at tag time)" (:933-950). - It filled the counts; replaced
<release-commit>×1; rewrote 3 links and checked all 3 at21fdc03a; and left no WARNING. - Grepping the body for placeholder / remove at tag time /
<release-commit>/ FIRST DRAFT /<!--/ illustrative finds nothing (exit 1). There are 0 CRs. - Body: 826 lines, 52202 bytes, sha256
3672feb3dd2ce64caec36fecbf8f4e2bbe7ccce97a20ab4f989790dbb45ef9d7. - Commit ids it lists:
21fdc03(line 9),bfed143(line 10), and6f64eea(line 115, line 120's text, which is deliberate and not stale).
Tests, run in a detached worktree at aef568f0 with /usr/bin/python3, a stub gh first on PATH exiting 97, GH_TOKEN=bogus, an empty GH_CONFIG_DIR and no GIT_ALLOW_PROTOCOL:
| Command | Result |
|---|---|
pytest -q -p no:cacheprovider --basetemp=…/bt-RV630b-docs -o tmp_path_retention_policy=failed on the 8 docs files (test_repo_hygiene, test_readme_sections, test_cli_reference, test_release_workflow, test_agents_md_codes, test_setup_by_hand_commands, test_readme_windows_quoting_sentence, test_update_add_codes_documented) |
300 passed in 39.63s |
scripts/render-cli-reference.py --check |
[ok] docs/cli.md is what the generator produces (exit 0) |
git diff --check origin/main...aef568f0 |
exit 0, clean |
| README cap | 1474 lines = 1422 + 2 + 5 + 12 + 22 + 10 + 1 = 1474, at the cap |
| AGENTS.md cap | 494 lines; the cap is <= 494 (test_repo_hygiene.py:667) |
The worktree was clean after the runs.
Predicted squash. git merge-tree --write-tree origin/main aef568f0 exits 0, giving tree dd0f94e86d8c97bc756335ffb68d3a5fd38c3919, which is the same as the head's own tree.
- My local predicted commit on
origin/mainis21fdc03a5f6c7110e054816ec6f850f871782773, and it changes the same 12 files (+1232 −107). - The brief named no other lane-3 v1.1.1 PRs, so I merged against none.
SonarCloud and CI on the head
From gh pr checks 630 and the check runs for aef568f0:
- SonarCloud Code Analysis: success, "Quality Gate passed" (20:17:36Z, head_sha
aef568f0). packageandtests-git-bashpassed.testswas still in progress at 20:23Z. Under Brett's 18:57:19Z ruling it is not awaited; main's push run on the release commit is still required before the tag.
MUST / SHOULD
None.
NITs (optional; READY either way)
- NIT-1,
docs/release-notes/v1.1.0.md:119-120.- Scenario: the list cites README, AGENTS.md, docs/cli.md and the handbook:
pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 itself, at:668"(README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630, shape-doctor.py: run from a partial clone of the standard, theshape currencyrow fetches what the clone lacks, and with its remote out of reach calls each copy the standard changed since the pinboth, so a doctor that fetches nothing fetches, and offline says DRIFTED of copies nobody edited #683)" and:677"(README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630, Tracking: shape-doctor.py, reporting a repository's compliance #460)". README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 lands after6f64eea, as the release commit. - Measurement: at tag time the published body will say "At main
<RC>… 123 pull requests" and also "The list runs to main6f64eea". - This is not false. If you want it exact, the fix is:
The list runs to main \6f64eea`, and to README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630, this release's docs, which lands last.`
- Scenario: the list cites README, AGENTS.md, docs/cli.md and the handbook:
- NIT-2,
docs/release-notes/v1.1.0.md:450-453.- Scenario: per the squash message and
:7175-7184,--prunealso drops a ref put underrefs/remotes/origin/by hand, and does so in--dry-runtoo. Offline, nothing is pruned, so the deleted-branch case is refused only once the fetch works. - Fix: replace
prunes the remote-tracking branches \origin` has deleted, so a commit a / branch holds is no longer refused for a refspec narrowed by hand, and one / only a deleted branch held is no longer pinned.withprunes, in `--dry-run` too, every `origin/` ref that `origin` no longer backs, so a commit a branch holds is no longer refused for a refspec narrowed by hand, and one only a deleted branch held is no longer pinned once the fetch works.` (rewrap it to the file's width).
- Scenario: per the squash message and
- NIT-3: the PR body, not the file. Some of its text is stale against part 2:
- "## What" still says "to main
fc86720(121 pull requests)" and "lane openRepoShape-4's ten lines and lane openRepoShape-1's twenty-six D9 lines". - "## Open placeholders" items 1-2 still say
fc86720, 121, 131 of 133 at 19:08Z, and 2 open. - Part 1's plan line "'The list is complete to the release commit.'" is not what part 2 did.
- Fix:
6f64eea, 122, 132 of 133 at 20:15Z, 1 open (Publish openRepoShape to TestPyPI and PyPI asopenreposhape, so onepipx installputs the tools and git-filter-repo on any machine (design first) #563 (README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630)); "the checklist triage of the L4 lines on Tracking: adopt-project.py, converting an existing repository in place #457 and Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459 / the L1 lines on Tracking: adopt-project.py, converting an existing repository in place #457"; and "The list runs to main6f64eea." This matters only if the body feeds the squash message.
- "## What" still says "to main
Mutants
No pytest reads v1.1.0.md's content: tests/ cites it only in a docstring at test_release_workflow.py:580, and test_repo_hygiene.py:1567 names only v1.0.1.md. Text drift in the entry, line 120 or Known limits is therefore killed by no test, only by review.
The counts marker is guarded by the release-body tool. In a scratch copy I changed "had landed" to "landed", and the tool returned exit 1, an empty body, and REFUSED: line 14: the paragraph under **[placeholder: the counts at tag time.]** is not the sentence the tool fills.
Lane: openreposhape-3 (openRepoShape-3)
|
Sorry @brettheap, you've used your own review budget of 250,000 diff characters for the last 7 days. You can request another review in 2 days and 4 hours by commenting |
…holding a control character before they name it, and install.sh refuses a `:` before its fetch, so a carriage return or an escape is no longer echoed as typed (#760) (#804) Against main. #726 landed as `dcf62ed`, and this branch is restacked onto main as `7e4181c0`: it merges #726's final head `a22b94c4`, then main, taking this branch's side in the four files #726 also changed. Over main it carries exactly its own two commits: `git diff a3cde99 efd061a` and `git diff origin/main 7e4181c` have the same patch-id. #667's two checklist candidates, with rv667's NIT 2. Two commits on a3cde99: **7569ffa8**, and the fix round's **efd061a7**. Against a3cde99, `git diff --numstat`: `install.sh` +40/-3, `openRepoShape` +30/-16, `tests/test_install_sh.py` +132/-2, `tests/test_openreposhape_command.py` +176/-21, and nothing else. README, the handbook, `AGENTS.md` and `docs/cli.md` are unchanged (`render-cli-reference.py --check`: ok): no documented sentence names these refusals, and neither file is a pinned shape copy, so no `upstream-changed` row follows. One new commit, **efd061a7**, on 7569ffa, pushed to `fix/760-bin-dir-control-chars` (a fast-forward). The PR is now two commits on a3cde99. `git diff --numstat 7569ffa efd061a`: `install.sh` +12/-7, `openRepoShape` +14/-11, `tests/test_install_sh.py` +34/-29, `tests/test_openreposhape_command.py` +61/-35, and nothing else. Against a3cde99 the PR is `install.sh` +40/-3, `openRepoShape` +30/-16, `tests/test_install_sh.py` +132/-2, `tests/test_openreposhape_command.py` +176/-21. README, the handbook, `AGENTS.md` and `docs/cli.md` are still unchanged. The red Linux run on 7569ffa was #645's `copytree` race (`shutil.Error` in `test_doctor_pin_restore_merges.py`'s setup), which #713 fixed on main. It is not this PR's fault, and nothing here touches it. ### Finding 1 (should-fix): taken. A C1 control passed the arm under a UTF-8 locale when the value also held a byte that was not UTF-8 **Reproduced first, on 7569ffa, with bash 5.2.21.** `LC_ALL=C.UTF-8`, `$OPENREPOSHAPE_BIN_DIR` set to `<dir>/caf`, the byte 0xE9, U+009B, `31mFORGED/bin`, and `openRepoShape --install`: exit 0, the command placed, and the value printed raw in four lines (`od -c` of the output shows `c a f 351 302 233 3 1 m F O R G E D`). It is lane 1's reproduction, and it is the failure the finding names. Cause: for a value that is not valid in the locale's multibyte encoding, bash matches byte by byte, and read one byte at a time U+009B's two bytes (`0xC2 0x9B`) are not controls, as under `C`. **The fix is lane 1's, tested by them, in both files** (`install.sh`'s `main` and `openRepoShape`'s `install_commands`), in place of the `case`: ```sh if (LC_ALL=C; case "$bin_dir" in *[[:cntrl:]]* | *$'\302'[$'\200'-$'\237']*) exit 0 ;; *) exit 1 ;; esac); then die "…unchanged…" fi ``` The command's has `"$dir"`. Under `LC_ALL=C` the class is the ASCII controls and DEL, byte by byte, and the second pattern names the two bytes of each of U+0080 to U+009F (`0xC2`, then `0x80` to `0x9F`). The arm is the same in every locale. The subshell leaves the locale of every later line as it was. I added a `*) exit 1 ;;` arm so the construct has a default case (finding 2). Both files' comments now say what the check does, and no longer say which C1 controls match is the locale's. **Measured, command `--install` (bash 5.2.21; each cell is exit, whether the command was placed, whether the value reached the output):** | value | locale | 7569ffa | efd061a | |---|---|---|---| | U+009B | `C`, `POSIX` | 0, placed, in 4 lines | 2, nothing placed, nothing echoed | | U+009B | `C.UTF-8` | 2 | 2 | | U+0080, U+009F | `C`, `POSIX` | 0, placed | 2, nothing placed | | U+0080, U+009F | `C.UTF-8` | 2 | 2 | | 0xE9 then U+009B then `FORGED` | `C`, `POSIX`, `C.UTF-8` | 0, placed, in 4 lines | 2, nothing placed, nothing echoed | | a `:`, 0xE9, U+009B | all three | 2, the colon refusal names the directory, the C1 control in its line | 2, the control-character refusal, nothing of the value | | an e with an acute accent, U+00A0 | all three | placed | placed | | a lone 0x9B byte | all three | placed | placed (see the checklist) | | U+2028 | `C`, `POSIX` | placed | placed | | U+2028 | `C.UTF-8` | **2** | **placed** (see below) | **Measured, install.sh with a fake `curl` that logs each request and exits 22, a version named:** | value | locale | 7569ffa | efd061a | |---|---|---|---| | U+009B, U+0080, U+009F | `C` | exit 2 after 1 request | exit 2, 0 requests | | 0xE9 then U+009B | `C`, `C.UTF-8` | exit 2 after 1 request | exit 2, 0 requests | | a `:`, 0xE9, U+009B | `C`, `C.UTF-8` | exit 2, 0 requests, the colon refusal holds the C1 control | exit 2, 0 requests, nothing of the value | | an e with an acute accent, U+00A0 | `C`, `C.UTF-8` | 1 request (the value is accepted) | 1 request (the value is accepted) | **Side effect, as lane 1 said: U+2028 and U+2029 are refused in no locale now.** Only a UTF-8 locale's `[[:cntrl:]]` matched them, and no one chose that. They are not controls a terminal acts on, and a line that holds one prints as it is. `NOT_CONTROLS` places both under `C` and `C.UTF-8`, through both files, so the decision is held and not left to the locale. If a ruling wants them refused, `*$'\342\200'[$'\250\251']*` joins the pattern in both files and the two rows turn into refusals. **What the locale still does, measured:** over every byte string of up to three bytes from a set of fourteen (2955 strings: ASCII, ESC, DEL, 0x80, 0x85, 0x9B, 0x9F, 0xA0, 0xA9, 0xC2, 0xC3, 0xE2, 0xE9), under `C`, `POSIX` and `C.UTF-8`, the new arm gives the same answer in all three locales, and the arm without `LC_ALL=C;` differs from it on exactly one string: U+2029 (0xE2 0x80 0xA9) under `C.UTF-8`. So on glibc the locale's one observable effect is those two characters, and that is what pins `LC_ALL=C` in the tests (see the mutants). It stays in the code as lane 1 specified it, so the match is byte by byte by construction on a libc whose single-byte `iscntrl` says otherwise for 0x80 to 0x9F in a UTF-8 locale. macOS's was not measured. ### Finding 2 (nit, two SonarCloud S131 issues): not taken, partly cleared The colon `case` in `install.sh` has no `*)` arm and keeps its S131, as nine others open in that file do. The control check's `case` now has one, so its S131 should go. That is expected from the code, not measured: SonarCloud's run on this push says. ### Finding 3 (info): #804 depends on #726 #726 has landed on main as dcf62ed. This branch still descends from a3cde99, so `git merge-tree --write-tree origin/main efd061a` (origin/main 40e678e) conflicts in `install.sh`, `openRepoShape` and both test files, as lane 1 found for a cherry-pick. I did not restack: the coordinator retargets and restacks by the stacked-PR procedure. ### Ruling on the colon refusal naming the directory (lane 1 agreed; record it when #760 closes) Lane 1 agreed that the colon refusal names the directory, and asked that it be recorded on #760 at close: #760's Verify line says "neither stream holds the value" for all four values, colon included, and the colon's is named, by a choice every `$HOME` default needs. With this commit the premise is whole: a value that reaches the colon refusal holds no ASCII control character, DEL or C1 control, in any locale and whatever else it holds, so naming it cannot forge a line or reach the terminal. ### Tests at efd061a (every run through `run9.sh`: gh stub first on `PATH`, bogus token, empty `GH_CONFIG_DIR`; dirty=0) Logs are in `$Q/v116/f804/` (`$Q` is the lane's scratchpad). | Run | Result | Log | |---|---|---| | The new and changed cases (`-k "c1_control or unprinted_letter"`) | **36 passed** (18 in each file) | `new-final.log` | | `test_openreposhape_command.py`, `test_install_sh.py`, `test_install_sh_filter_repo.py`, `test_openreposhape_package.py`, `test_repo_hygiene.py`, `test_agents_md_codes.py`, `test_cli_reference.py` | **478 passed**, 103.92s | `runA-final.log` | | the three files `tests-git-bash` runs, with every `Path.write_text` writing CRLF (the plugin that wave 7 used) | **274 passed**, 65.38s | `runC-final.log` | | `test_release_workflow.py`, `test_doctor_validator_rows.py`, `test_doctor_machine_row.py` | **93 passed**, 50.72s | `runB-final.log` | | `test_shape_doctor.py -k "shim or installed_doctor or shipped_doctor or refused_doctor or signal"` (it runs the launcher's `--doctor`) | **20 passed**, 388 deselected | `runD-final.log` | - **What changed in the tests.** - The C1 case, which skipped where no UTF-8 locale read U+009B as a control, is gone with its helper `utf8_locale_with_c1_controls`. In its place a table of five values (`C1_BIN_DIRS`) runs under both `C` and `C.UTF-8`, through the command and through install.sh. The values are U+009B, U+0080, U+009F, 0xE9 then U+009B, and a `:` beside that. Nothing skips for want of a locale. - The placed-name test takes four names (`NOT_CONTROLS`): an e with an acute accent, U+00A0 (the byte after the range's last), U+2028 and U+2029, under both locales, through both files. The installer's version looks for its `preflight:` line among lines split at a newline only, because `str.splitlines` also splits at U+2028 and U+2029. - A lone 0xE9 is `chr(0xDCE9)`, which reaches the child as that byte through `surrogateescape`. No escape is typed into any file, and `body-guard.py` is clean on the four files. - **On the base.** The new and changed cases are 36: **18 fail on 7569ffa's code and 18 pass** (`new-base2.log`). The 18 that pass are guards: U+009B, U+0080 and U+009F under `C.UTF-8` (6, which the head already refused), and the placed names under `C` and the e and U+00A0 under both (12). Of the 18 that fail, 8 surface as a `UnicodeDecodeError` raised inside `subprocess.run` (the head echoes the lone 0xE9, and `run_cmd` and `Net.run` decode strictly) and 10 as assertions. That is a rough failure message and no more: decoding with `errors="surrogateescape"` in those two helpers would turn the 8 into assertions, and I left it, as the round is one commit and no mutant of the helpers can fail on the fixed code. Over a3cde99's code, the larger selection of #760's cases (`-k` of the refusal, installer, default-directory, control, letter and C1 tests, 62 cases) gives **42 failed, 20 passed** (`new-base-a3cde999.log`); over 7569ffa's it gives 18 failed, 44 passed (`new-base-7569ffa8.log`). ### Mutants of the two new hunks, and the earlier commit's rows against the new arm Each runs in a scratch clone against 95 selected cases (the refusal, installer, default-directory, control, letter, C1, Windows-spelling and symlink cases), one edit at a time, restored after. "Failed" is how many of the 95 failed. **All 28 are caught.** Logs: `mut2/` and `mut3/`; the runner is `mutate2.py`; the counts are in `mutants2.out` and `mutants3.out`. | Mutation | Command | install.sh | Where it fails | |---|---|---|---| | no C1 bytes in the pattern | 10 | 10 | every C1 value, under both locales, in the file mutated | | `LC_ALL=C;` dropped | 4 | 2 | U+2028 and U+2029 under `C.UTF-8` (the installer's test runs the command too) | | `[[:cntrl:]]` dropped, C1 bytes kept | 17 | 9 | carriage return, tab, DEL, escape and the drive and colon rows | | range starts one late (`\201`) | 2 | 2 | U+0080, both locales | | range ends one early (`\236`) | 2 | 2 | U+009F, both locales | | range ends one late (`\240`) | 4 | 2 | U+00A0 is refused (the placed-name test) | | lead byte `\303` for `\302` | 10 | 10 | every C1 value | | the whole arm removed | 27 | 19 | every control value | | arm narrowed to a newline | 23 | 17 | every control but a newline | | `[![:print:]]` for the pattern | 16 | 8 | the e with an acute accent under `C`, and more | | refusal echoes the value | 27 | 19 | every control value | | arm after the drive arm (command) | 4 | | the two drive rows (both files) | | arm after the colon arm | 8 | 5 | the colon and the drive rows, the colon beside a C1 control | | arm after the symlink loop (command) / check (install.sh) | 9 | 6 | the link case, the colon and drive rows | | arm after the release lookup (install.sh) | | 19 | a request was made, for every control value | ### Not checked, and known limits - **bash 3.2.57** is not here. Lane 1 built it and measured the same table on it, and the pattern is theirs. I did not run it. - **macOS and other libcs.** `LC_ALL=C` is what is meant to hold there. Not run. - **A GBK or GB18030 locale.** The bytes `0xC2 0x80` to `0xC2 0x9F` are 32 hanzi there (`0xC2 0x9B` decodes to one with Python's `gbk` codec), and the arm now refuses a directory that holds one as "a control character", where `[[:cntrl:]]` placed it. Measured with Python's codecs only: no such locale is installed here. Big5, Shift_JIS (`cp932`), EUC-KR and EUC-JP read those bytes as invalid. A candidate, not a fix: it needs the locale's charset asked first. - **Windows (Git Bash).** The C1 and placed-name tests skip there, as before; the three files `tests-git-bash` runs pass with CRLF `write_text` emulated (274). **1. The command refuses any control character in the directory, first** (`install_commands`, `openRepoShape`). - Before: only a newline was refused first. Measured on a3cde99: `$OPENREPOSHAPE_BIN_DIR` set to `<dir>/bin`, a carriage return, then `FORGED` placed the command, exit 0, and printed `…/bin^MFORGED is not on $PATH`. An escape sequence did the same (`…/bin^[[31mFORGED`). The value reached four lines: `installed at`, `placed in`, `is not on $PATH` and the `export PATH` line. - Now: the arm is `*[[:cntrl:]]*` and the two bytes of each C1 control (U+0080 to U+009F), run under `LC_ALL=C`, still first, before the Windows spelling and the colon. Its refusal says "holds a control character, such as a newline, a carriage return, a tab or an escape" and nothing of the value, so no refusal after it can echo an ASCII control character, DEL or a C1 control, in any locale and whatever else the value holds, that forges a line or reaches the terminal. Exit 2, nothing placed. **2. install.sh carries both arms and refuses before any request** (`main`, `install.sh`). - Before: a `:` or a newline reached the fetch. install.sh fetched the launcher, ran `--install`, and said only "the v9.9.9 openRepoShape's --install refused (exit 2)"; a release older than #667 had no such refusal and placed the command under the directory. Measured with a fake `curl` that logs its calls, version named: a3cde99's install.sh made one request for each of a colon, a newline, a carriage return and an escape; this head makes none. - Now, copied from the command as #667 copied the drive-letter `case` (install.sh arrives alone down a pipe): the control-character arm right after the variable is read, before the Windows respelling; the colon arm after the respelling, as the command orders them. Both are before the symlink check and every line that names the directory. - The control-character refusal never names the directory. The colon refusal names it, as the command's does (see decisions). - **`[[:cntrl:]]`, not `[![:print:]]`**, as the issue directs. `[![:print:]]` is what `checked_repo` uses for `$OPENREPOSHAPE_REPO`, but under `LC_ALL=C` the two bytes of an e with an acute accent are not printable, so it would refuse a directory called `caf` and that letter. A test places that directory under `C` and under `C.UTF-8`, through both files; the mutants "`[![:print:]]`" fail it. - **A C1 control is refused in every locale.** The check runs under `LC_ALL=C` and names U+0080 to U+009F's two bytes (`\302`, then `\200` to `\237`), whatever else the value holds; U+2028 and U+2029 are refused in none. Left to the locale, `[[:cntrl:]]` matches a C1 control only in a value that is valid UTF-8 (bash 5.2.21, Linux): U+009B alone is refused under `C.UTF-8` and passes under `C` and `POSIX`, and one byte that is not UTF-8 before it (`caf`, 0xE9, U+009B) makes bash match byte by byte, so it passes under `C.UTF-8` too (lane 1 measured the same on bash 3.2.57). The tests run five C1 values under `C` and `C.UTF-8`, through both files, and place U+00A0, U+2028 and U+2029 beside the e with an acute accent. - **A tab and DEL are refused too.** They are control characters, the issue says every one, and no `$PATH` line prints either as it is. - **The colon refusal names the directory; the control-character one never does.** The issue's Verify says "neither stream holds the value" for all four values, but the command's colon refusal (#726) names `'$dir'`, and a test of #726's pins that for the `$HOME` default, where the person typed nothing and needs the directory named. A colon value holds no ASCII control character, DEL or C1 control by then, in any locale, so naming it cannot forge a line or reach the terminal. Lane 1 agreed with this ruling (2026-10-17T11:06:51Z) and asked that it be recorded when #760 closes: the issue's Verify line says "neither stream holds the value" for all four values, and the colon's is named. install.sh's colon refusal does the same. The tests hold the control values to "no `forged` tail, no escape, DEL or C1 in either stream", and the colon values to "named, in quotes". If the coordinator rules that a colon must go unnamed too, it is one message in each file and the same assertion. - **The order is held, not just the width.** A drive spelling with a newline or an escape must be refused as a control character (not as "spelled for Windows" when no `cygpath` answers), a colon with an escape must be refused as a control character, and a link in the directory must not make the symlink refusal print the value. **The figures in this section are 7569ffa's.** At efd061a they are the fix round's table above: 36 new and changed cases, 478 passed in the seven files, 274 passed with CRLF `write_text`, and 93 and 20 passed in the others. The base figures are 18 failed and 18 passed over 7569ffa's code, and 42 failed and 20 passed over a3cde99's for the 62-case selection. 31 cases in the new and extended tests (26 are new; the command's five older refusal cases run again in the same test), run with the one existing test the selection matches: 32 passed in 1.75s on the head, dirty=0. - `tests/test_openreposhape_command.py` (15): the refusal over 11 values (a colon, two directories, a drive-relative name, a newline, a carriage return, an escape, a tab, DEL, a drive spelling with a newline, a drive spelling with an escape, a colon with an escape; the first five were 5 cases, six are new), a letter under `C` and `C.UTF-8`, U+009B under a UTF-8 locale, and a link in a directory holding a carriage return. A shared `assert_a_bin_dir_is_refused` holds exit 2, the wording, the ending, and that no `forged` tail, escape, DEL or C1 reaches either stream. - `tests/test_install_sh.py` (16): the same 11 values through install.sh and the command, in one test (exit 2 for both, the fake `curl` records no call, nothing placed or made in either world, `$TMPDIR` empty), the `$HOME` default with a colon, the symlink case, a letter under both locales (placed, named as typed), U+009B under a UTF-8 locale. - **On the base a3cde99** (the head's two test files over the base's code): `24 failed, 8 passed` in the 32. The 8 are the command's three colon cases and the `$HOME` default (the base already refuses them), and the letter under both locales through both files (the base places it; they guard the width). Of the 24, 22 fail on behaviour (a request was made, or the value was placed and echoed, or the refusal came from the wrong arm); the command's `a-newline` and `a-drive-spelling-and-a-newline` fail only on the refusal's new wording. - **On the head 7569ffa, dirty=0:** - `tests/test_openreposhape_command.py tests/test_install_sh.py tests/test_install_sh_filter_repo.py tests/test_openreposhape_package.py`: **302 passed** in 68.90s. - `tests/test_repo_hygiene.py tests/test_agents_md_codes.py tests/test_cli_reference.py tests/test_setup_by_hand_commands.py tests/test_readme_windows_quoting_sentence.py tests/test_gh_guard.py tests/test_workflow_concurrency.py tests/test_release_workflow.py tests/test_readme_sections.py`: **297 passed** in 38.97s. - `tests/test_shape_doctor.py -k "shim or installed_doctor or shipped_doctor or refused_doctor"` (it runs the launcher's `--doctor`): **20 passed**, 388 deselected. - **The three files `tests-git-bash` runs, with Windows' `write_text` emulated** (a plugin makes every `Path.write_text` write CRLF): **244 passed** in 52.64s. A probe test takes the plugin's effect: 1 passed with it, 1 failed without. - **install.sh, version named, each of a colon, a newline, a carriage return, an escape:** a3cde99: exit 2 after 1 request (the launcher's fetch), refused by the fetch's error or by the command. This head: exit 2, 0 requests, install.sh's own refusal (the colon one names the directory; the others name nothing of it). - **The command, `--install`:** a3cde99: a carriage return and an escape: exit 0, the command placed under the directory, the value in four lines. A newline: exit 2. This head: exit 2 for all three, nothing placed. - **Needs Windows or another system to be real:** none of these rows. See below for what was not run. The fix round above adds 14 mutants of the new pattern line and 4 order mutants, and re-ran this table's rows against the new arm (the whole arm removed, narrowed to a newline, `[![:print:]]`, the refusal echoing, and the two later placements). Each is still caught. Each row changes one arm or hunk in a throwaway clone and runs the new cases and #726's drive and symlink cases (65 selected). "Failed" is how many of the 65 failed. | Mutation | Failed | Where | |---|---|---| | command: no control-character arm | 18 | the command's cases and install.sh's, which runs it | | command: arm narrowed back to a newline | 14 | carriage return, escape, tab, DEL (both files) | | command: `[![:print:]]` for `[[:cntrl:]]` | 2 | the letter under `C` (both files) | | command: arm after the drive arm | 4 | the two drive spelling cases (both files) | | command: arm after the colon arm | 6 | colon with an escape, the two drive cases | | command: arm after the symlink loop | 7 | the link case, colon with an escape, the two drive cases | | command: refusal echoes the value | 18 | every control value | | install.sh: no control-character arm | 10 | the link case, U+009B, the control values | | install.sh: arm narrowed to a newline | 8 | carriage return, escape, tab, DEL, the link case, U+009B | | install.sh: `[![:print:]]` | 1 | the letter under `C` | | install.sh: arm after the drive block | 2 | the two drive spelling cases | | install.sh: arm after the symlink check | 4 | the link case, colon with an escape, the two drive cases | | install.sh: arm after the release lookup | 10 | a request was made, for every control value; and the link refusal named it | | install.sh: refusal echoes the value | 10 | every control value | | install.sh: no colon arm | 4 | the three colon values, the `$HOME` default | | install.sh: colon arm before the drive block | 17 | #726's drive cases (the respelling never runs) | | install.sh: colon arm after the release lookup | 4 | a request was made, for the colon values | | install.sh: colon refusal names nothing | 4 | the three colon values, the `$HOME` default | All 18 are killed, on the final head. A first run on the pre-reorder tests gave the same counts. - origin/main bf1f099: clean. The parent a3cde99: clean (this head descends from it). - All 47 `refs/heads/fix/*` and `l3-*` branches on origin (none of `l2-*` or `l4-*` exists; mine excluded): clean. Only #726's branch edits `install.sh`, `openRepoShape`, `tests/test_install_sh.py` or `tests/test_openreposhape_command.py`, as the issue said. - Lane 5's other wave-7 branches (`fix/757` through `fix/777`, local heads at the time): clean; none edits these four files. - **bash 3.2** is not here; lane 1 measured the table and the fix on 3.2.57 built from GNU source. - **Windows.** A control character, or a non-ASCII value, crossing the environment into Git Bash is not run here: the letter and U+009B tests skip on Windows, and the link and `$HOME` tests do too (no file there holds a control character or a `:`). The 11-value test runs there as #726's newline case did, and its result is `tests-git-bash`'s to give. CRLF `write_text` was emulated (244 passed). - **macOS** and a locale other than `C`, `POSIX` and `C.UTF-8`: not run. No test skips for want of a locale. - **A GBK or GB18030 locale.** The bytes `0xC2 0x80` to `0xC2 0x9F` are 32 hanzi there, and the arm now refuses a directory that holds one as "a control character". Measured with Python's codecs only: no such locale is installed here. - **Release notes.** `--install` and install.sh now refuse a control character in `$OPENREPOSHAPE_BIN_DIR` where `--install` placed the command and printed the value as typed, and install.sh refuses a `:` before its fetch. Lane 3's #630 drafts `docs/release-notes/v1.1.0.md`; a line for it: "`install.sh` and `openRepoShape --install` refuse a `$OPENREPOSHAPE_BIN_DIR` holding a control character (a newline, a carriage return, a tab, an escape or a C1 control), and `install.sh` a `:`, with exit 2 before it makes a request." Not edited here. - README, the handbook, `AGENTS.md`, `docs/cli.md`: no sentence became false. 1. **U+202E, U+200B, U+2028 and U+2029 in `$OPENREPOSHAPE_BIN_DIR` are echoed as typed** (the body's candidate 1, now also the two separators). None is a control a terminal acts on; U+202E can make a directory read as another. 2. **A lone 0x9B byte** (an 8-bit C1 control, not UTF-8) matches in no locale, before and after this commit. Only an 8-bit terminal with C1 controls on acts on it. (Lane 1's candidate 2.) 3. **A GBK or GB18030 locale reads `0xC2 0x80` to `0xC2 0x9F` as hanzi**, so the arm refuses a directory holding one. Measured with codecs only. 4. **Lane 1's other candidates stand as they listed them:** a leading `-` makes `--install` run `mkdir -p "$dir"` with no `--`; U+202E and U+200B; a `$HOME` holding a control character is refused without saying the directory came from `$HOME`; install.sh's Windows-spelling refusal names nothing where the command's names `'$dir'`; macOS's `[[:cntrl:]]` for C1 under `en_US.UTF-8` is unmeasured. Review: lane 1's re-verification of #804 is PR-READY (#804 issuecomment-6238973516). CI (Brett Heap's ruling 27): the head 7e4181c passed tests, tests-git-bash and SonarCloud; main has touched none of its files since 6626a3c; and the local gate passed on its predicted squash tree cbc85da over main a9d1579: the install_sh, openreposhape_command, repo_hygiene, agents_md_codes and cli_reference sets, 404 passed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session: "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #760 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ranch changed different README sections, so five handbook markers take main's blobs, the Families marker keeps this branch's, and no line conflicts (#695) #630 (190133d) changed README.md in five sections and moved each one's `reads README.md §` marker in docs/handbook.html. This branch changes only README.md § Families (marker 82d7f808..., set by 75e35f1). Main's own § Families is still b3d19092..., what it was before this branch. No marker line changed on both sides, so git merged README.md, AGENTS.md and docs/handbook.html without a conflict, and nothing was resolved by hand. Each marker below is `readme_fingerprint` of its merged section, computed with tests/test_repo_hygiene.py's own functions over the merged README.md (`readme_segments`, then `readme_fingerprint`), never typed. Old is this branch's 75e35f1, new is this merge: - Starting a project in a new organisation (seven markers) old dd174d763a931052478f2fd15a7f22535524f6f4 new 45a3f886c0203e91f17a7851cf2c1c71a2681b0c - The three legs (three markers) old cce66b56eec9dffdd5a1a23bdd8e58d57f90b12e new 1d9f5bf98bddd1f70522c86515de3f15900fe141 - Adopting an existing repository (one marker) old ca4b6731cf6de1caeac426eeff5eb7184c53f562 new b23e9f9819cedc3e6ca6321c811b6d11da8de508 - Keeping a project's shape current (two markers) old 545639ebf5e9ab7837df5c012f216832dc7de56f new df780eb5a96cdcaf9b30a8ac8162bca00f90405b - Layout of this repository (one marker) old 94701efcccdb2a17e8aa80fbb3343c381ea71e4a new 9446cd710bf3a992751e12e318553b9b22914c21 Unchanged, and still equal to their sections: preamble ddadd23a..., Families: a holder for projects that ship separately 82d7f808..., The double pin, and the lockstep invariant 57fffa08..., Bootstrap is COPIED into the project, not fetched 41f2c883..., Carrying in-flight work to another workstation b84512ad..., The degrade rule e28bf2cd... and Licence 57e2469b.... The tree of this merge differs from origin/main by exactly #738's own change: `git diff origin/main <this commit>` and `git diff 24a977c 75e35f1` have the same patch-id (04fc641d...). #630 brings no new README heading (still twelve segments), so tests/test_family_docs_one_spelling.py and the docs edits need no new coverage. Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…parent this branch was stacked on, as it landed (#786) #782 (#741) landed as squash a9d1579, whose tree is a59cb53's (`git diff --stat a59cb53 a9d1579` is empty). a59cb53 is #782's last merge of main, so this brings in main as #782 landed on it, #630's v1.1.0 docs (190133d) among it. The one conflict is docs/handbook.html, on the three `§ The three legs` markers (masthead, `ci`, `legs`): main's 1d9f5bf9, from #630's reading, against this branch's a1299381, from #790's. README.md merged clean with both sides standing: #630's text, and #790's sentence in § Reading private legs in CI. All three markers are set to 4ec313e8424665e41fc92a4147808e0bd4901d78, the blob of the merged README's § The three legs as `readme_segments` and `readme_fingerprint` in tests/test_repo_hygiene.py compute it (`git hash-object --no-filters` of the segment agrees). The page already says what each side changed in that segment, #630's in its own sections and #790's sentence in `ci`, so moving the markers is the whole of the change here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…side #785's `$'…'` sentence (#785) README.md conflicted in one hunk, the "Windows, natively" paragraph: main's #630 rewording (the two-liner is the Windows way in that installs nothing, pipx's `openRepoShape` runs the same flow) is kept whole, and #785's two lines on `$'…'` follow it, as they followed the old text. docs/handbook.html conflicted in seven hunks, each one § Starting marker (masthead, start, partway, ci, legs, doublepin, park). The prose merged cleanly: #630's pipx text and #785's `$'…'` sentence stand together in `start`. The seven markers were recomputed from the merged README.md with tests/test_repo_hygiene.py's own readme_segments and readme_fingerprint (3d4a01ffadc322f88dfffd9cef50b1c48c58b9b3), and handbook_marker_problems reports none. Every other marker is main's and still current. README.md is 1476 lines, at its cap: main's 1474 + 2 from tests/readme-cap.d/785.txt. AGENTS.md is 494 lines, at its cap of 494. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… `--into` sentence names the `family-landing-in-standard` refusal, `make siblings` is described as it stops before cloning, and `members/` has its one-spelling rule (#695) (#738) The family docs now say what `family.py init`, `make siblings` and the `members/` directory do since #488, #578 and #574. Base: `8062810d`. Head: `c42286de`: the work (`73fa3ff4`) and one commit for the review's nit on L20. **The branch now holds `origin/main` at `24a977c0`, through one merge commit, `75e35f14`.** No other commit was added, and nothing was rebased or amended. ### What failed Since #682 landed (`dc7d3fbb`), `git merge-tree --write-tree HEAD origin/main` against `c42286de` stopped on one line of `docs/handbook.html`: Auto-merging docs/handbook.html CONFLICT (content): Merge conflict in docs/handbook.html It is the `families` section's marker, `<!-- reads README.md § Families: ... blob <hash> -->`. #682 changed a sentence of README § Families, so main's marker is `b3d19092...`. This PR changed other sentences of the same section, so its marker is `0364c579...`. Git saw one line changed two ways. Lane 2's note on #738 (2026-10-17T12:47:17Z) named the same line. I reproduced the conflict on the head I started from, then merged by hand. ### The fix The merged README § Families is neither side's text, so the marker takes its fingerprint, `82d7f8087b9ee4d097d33e15fbfe5ae754a04a79`. I computed it with `tests/test_repo_hygiene.py`'s own `readme_fingerprint` over the merged `README.md`. It is the value lane 2's note gave, and it did not move when main moved on to `24a977c0`. | marker value | where it came from | handbook outline test | |---|---|---| | `b3d19092...` | main's side | fails: "has moved since the page read it" | | `0364c579...` | this PR's side | fails, same message | | `82d7f808...` | the merged README § Families | passes | I re-read both sides before taking the value: - Main's side brought one sentence into README § Families (line 955, "`make bootstrap` reports and skips one it lacks a credential for."), and #682 had already put the matching sentence on the page. - This PR's side is its own 55 README lines and 27 handbook lines in that section, which merged clean. - Both sides' sentences stand in the merged README and in the page. ### Did the merge change README or handbook text beyond the conflict line? No. I compared each changed file's diff against the parent that did not make it: - `README.md`: the merge against `origin/main` shows exactly this PR's own change, and the merge against `c42286de` shows exactly main's own change since `8062810d` (one line, #682's). No line differs. - `AGENTS.md`: the same two comparisons, no line differs. - `docs/handbook.html`: the same two comparisons differ in the marker line only, in both directions. - README's handbook markers (23 on the page) and README's line cap are untouched. `tests/test_repo_hygiene.py` passes with the cap and the markers as they are. This PR still lands only in lane 3's README turn. If another README PR changes § Families first, the marker moves again, and the hygiene test names it. ### Tests All on `75e35f14`, through the gh stub, with a bogus token and an empty `GH_CONFIG_DIR`. | run | files | result | |---|---|---| | core | `test_readme_sections.py`, `test_family_docs_one_spelling.py` (the one test file this PR changes), `test_repo_hygiene.py`, `test_agents_md_codes.py`, `test_cli_reference.py` | 240 passed | | handbook readers | `test_readme_windows_quoting_sentence.py`, `test_setup_by_hand_commands.py`, `test_update_add_codes_documented.py` | 20 passed | | family-root templates | `test_pinned_copy_text.py`, `test_template_values_single_pass.py`, `test_family_init_values.py`, `test_family_init_landing.py`, `test_siblings_family_name.py`, `test_family_makefile_siblings.py`, `test_family_siblings_placement.py` | 582 passed, 19 skipped | | bootstrap (main's new files, over the template this PR reworded) | `test_bootstrap_spelling.py`, `test_bootstrap_without_git.py`, `test_bootstrap_leg_moves.py` | 251 passed | ### Mutants This round adds no test and no code, so its one hunk is the marker value. Each mutant changes that value in a shared scratch clone at `75e35f14` and runs `tests/test_repo_hygiene.py::test_the_handbook_follows_the_readmes_outline`: | mutant | result | |---|---| | none (control) | 1 passed | | main's marker `b3d19092...` | 1 failed | | this PR's marker `0364c579...` | 1 failed | | the right marker with its last digit changed | 1 failed | | the unresolved conflict block (`<<<<<<<`, both lines, `>>>>>>>`) | 1 failed | ### Merge checks - `git merge-tree --write-tree HEAD origin/main` is clean at `24a977c0`. - Main moved to `40e678e6` (#805, `templates/family-root/scripts/siblings.py` and `tests/test_siblings_inputs.py`) after this merge. It touches no README, handbook or AGENTS text, and the merge-tree against it is clean. - Lane 2's #822 is stacked on this PR. Its reviewer found it merges clean with this PR landed at the same marker value. 1. **The `--into` sentence** (README.md § Families and the handbook). It gave the default, "the directory you are standing in", and not that `init` refuses that default inside a checkout of this standard. It now says so and names the code: `(the directory you are standing in, by default; refused inside a checkout of this standard, \`family-landing-in-standard\`)`. AGENTS.md already said it. 2. **The `make siblings` paragraph** (README.md § Families and the handbook). It said `make siblings` "WARNS and prints the exact `mv`". Since #578 it also clones nothing until the `mv` is run, so the next run clones each member once, and a link of the family's name beside the folder passes. It now says that. Lane 3's two #460 sentences ride in it (L20, L21, which lane 3 marked `-> #695`): - the folder must be named EXACTLY after its repository, case included, on every disk; - for a holder at `~/projects/InkRouter` the move makes the family folder `~/InkRouter`, beside `~/projects`, and the holder `~/InkRouter/InkRouter`, not `~/projects/InkRouter/InkRouter`. I wrote L20 with the paths spelled out. Lane 3's wording was "beside that folder's parent", which two readings can take. My first wording, "beside that folder, not inside it", still left one: the thing beside `~/projects` is the family folder, not the holder (rv695 N1). The printed move for that holder is `mv ~/projects/InkRouter ~/InkRouter.holder`, `mkdir -p ~/InkRouter`, `mv ~/InkRouter.holder ~/InkRouter/InkRouter` (probe below, and `test_family_siblings_placement.py` pins it). 3. **The one-spelling rule for `members/`.** One sentence, the same words in five places: AGENTS.md (family item 2), README.md § Families, the handbook, the holder's README and the holder's `AGENTS-shape.md`: > A holder keeps `members/` in ONE spelling on every disk: `add` refuses `family-members-dir-variant` (`member-path-taken` for a variant of the member's own path) until a human renames what sits under another spelling. `templates/family-root/AGENTS-shape.md` changes by that one sentence. It is in `FAMILY_COPIED_VERBATIM`, so every holder reads `upstream-changed` for it on its next `update-shape.py check`. The issue's third line names it, and lane 3 asked that it ride with the next pinned-file edit. It can share the one v1.1.0 shape bump with the bootstrap fix. `templates/family-root/README.md` is templated and not pinned: an existing holder keeps its own README. - README.md is 1458 lines, one under its cap of 1459 (1422 + 5 + 22 + 10 in `tests/readme-cap.d/`); it was 1459 at `73fa3ff4`. The brief said 1449; that is the cap before #627 landed. AGENTS.md is 492, its cap. - The new words come to about nine README lines and three AGENTS.md lines. I fitted them by cutting wording that is stated elsewhere. **If you would rather keep the cut sentences, the alternative is a `tests/readme-cap.d/695.txt` of about +8, which needs Brett's word.** What I cut: - README § Families: - the aside "the same lands-where-you-were-standing rule projects have"; - four rewordings that lose no meaning: - the intro of "Use a family when the parts deploy separately": "folding them into one assembly root would give them one gate, one release and one pin, which is the opposite of what 'deploy separately' means" is now "one assembly root would give them one gate, one release and one pin, the opposite of 'deploy separately'"; - "`--work-dir` remains the override" is now "`--work-dir` is the override"; - "The doubled name is kept on purpose" is now "The doubled name is on purpose"; - "`family.yaml` is what tells the two apart" is now "`family.yaml` tells the two apart"; - the caption "Eight services, each its own project, one holder:" under "The InkRouter example" (the section's intro and the block under it say it; this is the one line cut to make room for the L20 rewording, and the handbook keeps its caption); - the tool's rationale after "Nothing ever moves a checkout" (the shell, the editor, the agent lanes, "an act by surprise"); it stays in the handbook, the holder's README and the tool's own output, and README keeps the reason that matters (a linked worktree's `.git` file carries an absolute path); - ", not merely a repository at the right commit"; - the private-legs reason in the `make bootstrap` paragraph; - "to a policy two live projects already carry a copy of" in the naming paragraph. - AGENTS.md: - the converse in the family intro ("one project is what parts that ship together already are"; README keeps it); - the sentence "`bump` takes the same repeatable `--trailer`" in family item 3 (stated in "Advancing a leg" step 6 and in the holder's `AGENTS-shape.md`); - "Do not pass `--yes` on your own initiative" in "Updating" step 2, now "Never pass `--yes` yourself", the words `test_repo_hygiene.py` requires elsewhere in the file. - The handbook marker for README § Families is recomputed with `readme_segments` / `readme_fingerprint`: `89ef0ab5ec88...` to `0364c5794c46...` (it was `dc85a9260a67...` at `73fa3ff4`, before the L20 rewording and the caption cut). No other README section changed, so no other marker moves. New file `tests/test_family_docs_one_spelling.py`, 17 tests: the one-spelling sentence in each of the five docs, the two codes held as strings by `scripts/family.py`, and the `--into`, `make siblings`, "named EXACTLY" and "straight in a folder" sentences in README and the handbook, plus the old "WARNS and prints" sentence gone from both. The handbook is read as the text a browser shows (`<code>` as backticks). - On base `8062810d` (the file copied in): **15 failed, 2 passed**. The two that pass are the code-exists checks. - On the head `c42286de`, after the L20 rewording: `test_repo_hygiene.py`, `test_readme_sections.py`, `test_agents_md_codes.py` and the new file: **217 passed** in 3.37s. The new file's `STRAIGHT_IN_A_FOLDER` is in the new README and not in the old one. - Before that, the targeted runs, each at `8062810d` plus the working tree that was then the head (`73fa3ff4`): - `test_repo_hygiene.py`, `test_readme_sections.py`, `test_agents_md_codes.py`, `test_cli_reference.py`, `test_readme_windows_quoting_sentence.py`, `test_setup_by_hand_commands.py`, `test_update_add_codes_documented.py` and the new file: **253 passed**. - Fifteen files that read AGENTS.md, the holder's docs or the refusals (`test_family_init_landing`, `test_siblings_family_name`, `test_family_switch_branch_exists`, `test_family_tracking_branch`, `test_template_text_placeholders`, `test_template_values_single_pass`, `test_template_quoted_values`, `test_free_plan_hint`, `test_doctor_recorded_answers`, `test_update_shape_add`, `test_update_shape_header_comments`, `test_family_add_refusals`, `test_family_members_case_variants`, `test_family_member_path`, `test_doctor_legs_members_next`): **987 passed**. - Eight files: `test_adopt_plan`, `test_adopt_github_exits`, `test_adopt_late_failure`, `test_setup_project_py`, `test_family`, `test_family_siblings_placement`, `test_family_makefile_siblings`, `test_park_resume_targets`: **598 passed, 1 skipped** (`SHAPE_MEDXEHR_CLONE` unset: a plan test needs a read-only clone of MedxSoft/MedxEHR, a cloud tool). - `test_update_shape.py -k "shape_line or stale_readme or check_says_what_apply or readme_with_no_shape"`: 7 passed (the tests that read the holder README's last line). The rest of `test_update_shape.py` and `test_shape_doctor.py` were not run: they copy the family-root tree and match none of the lines I changed (`git grep` of the changed sentences under `tests/` finds nothing), and a first run of them crawled under a load of 90-100 on the machine. - The full suite was not run, as the brief says. - Row 1, on base: from a checkout of the standard, `python3 scripts/family.py init --org Acme --family Orbit --dry-run` ends `REFUSED family-landing-in-standard ... Remediation: pass \`--into ..\``. The README sentence did not say it would; it now does. - Row 2, on head: `parent_folder_warning` of the holder's `siblings.py` for a holder at `<t>/home/projects/InkRouter` prints `NOTHING WAS CLONED` and the move `mv .../projects/InkRouter .../InkRouter.holder`, `mkdir -p .../InkRouter`, `mv .../InkRouter.holder .../InkRouter/InkRouter`. For a folder named `inkrouter` on Linux it stops the same way: the name test is exact on every disk. - Row 3: the refusals the sentence names are the ones `test_family_members_case_variants.py` and `test_family_add_refusals.py` drive (60 + 65 tests, passed above). - No row needs real GitHub or a cloud tool. My own run did not happen: a usage checkpoint stopped it before it began. The reviewer (rv695) ran 30 mutants against `73fa3ff4` in a throwaway clone and **all 30 were killed**: five places, four mutants each for the one-spelling sentence, and five each for the README and the handbook sentences. The L20 sentence has changed since, and `STRAIGHT_IN_A_FOLDER` with it; no mutant was run on the new words. What the guards cover there: the sentence has a parametrized case per doc, so reverting it fails that case, and a reverted README hunk leaves the handbook marker stale. - origin/main `55c6ba7d` (it moved past `2fac9790` by #700): clean. rv695 found `73fa3ff4` clean against `2fac9790`. - #630 (lane 3), head `6fd33a93`: clean. It merged main `de926142`, and its handbook conflicts at the three markers are gone. On the merged tree README.md is 1472 lines and AGENTS.md 494 (counted here; rv695 found them under #630's own ledger at `73fa3ff4`). - #682 (lane 5), head `8b39cd19`: conflicts at one line, the handbook marker for README § Families (`0364c5794c46...` here, `c40baa828dca...` there). README.md and the rest of the handbook merge clean, and the merged paragraph reads correctly, because both PRs change that section. Whichever lands second recomputes the marker from the merged README: on this head plus #682 the recomputed value is `da54c8ec2665...` (computed with `readme_fingerprint`, not test-run). - I dropped the earlier list of clean PRs: it was taken against older heads, and several of them have landed. (Brett Heap, 2026-10-16T13:36:23Z, on #459 line 141: "document it in the v1.1.0 release notes". `docs/release-notes/v1.1.0.md` exists only on #630, so this PR does not create it.) > A holder made with `--family X --name Y` before #642 (the fix for #629) keeps `cd Y` in its own README, and `update-shape.py apply` never re-renders a README, so change that one line by hand to `cd X`: the holder's directory is named after its repository, `X`, never after the display name. Such a README also shows `Y` in its layout block and its doubled-name sentence, as prose; only the `cd` line fails when run. - The holder's README does not get lane 3's L20 and L21: lane 3 asked for them in the README paragraph, and the holder's README already names the folder after the repository. - Checklist candidate, not for this PR (rv695 N6): the holder's own docs say the folder is "named after the family" (`templates/family-root/README.md:73`, `templates/family-root/AGENTS-shape.md:99`) and not "EXACTLY, case included", which the standard's README and handbook now say and `siblings.py` tests (`folder.name == family`). Lane 3 asked for L21 only in the standard's README paragraph, so it is not owed here; it could ride in the same re-pin. Review: lane 5's independent review approved c42286d with its nits fixed; 75e35f1 is its update onto main (lane 2's note, issuecomment-6234057822); Codex found no major issues. CI (Brett Heap's ruling 27): H0 is the head 0b26ace itself, which passed tests, tests-git-bash and SonarCloud; main has touched none of its files since its base 2e476a7; and the local gate passed on its predicted squash tree 68dd413 over main 112eeed: the family_docs_one_spelling, readme_sections, readme_windows_quoting_sentence, setup_by_hand_commands, update_add_codes_documented, family_tracking_branch, family_switch_branch_exists, pinned_copy_text, repo_hygiene, agents_md_codes, cli_reference, family sets, 527 passed. Landed on Brett Heap's word of 2026-10-17T21:47:28Z, in lane 5's session (posted on #473 as issuecomment-6241758069): "merge lane 5's v1.1.1 PRs once reviewed and green". Closes #695 Lane: openreposhape-5 (openRepoShape-5) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…every NEXT, keep line and WHY, as itself where the report's encoding writes its bytes and escaped whole where it does not, so a line naming `Projets/Équipe` or a Latin-1 name runs as printed (#785) (#792) Closes #785. `ascii_text` in `shape-doctor.py` replaced every character of a path that is not ASCII with `?`, in every NEXT, keep line and WHY and in `--json`, so such a line named another path and did not run as printed. The flattening was there so that printing never raised, and since #214 `main()` installs a handler that makes a print safe. Now `quote_arg` quotes an ASCII value as before, byte for byte. A value that is not ASCII is quoted as itself where every such character is printable and the report's encoding writes it (on POSIX, with the bytes the path holds). Otherwise the whole value is escaped: as `$'...'` with octal escapes for `sh`, or as `('...' + [char]0x00C9 + '...')` for PowerShell. `ascii_text` still flattens the typography it always did, and any other character only where the report cannot show it. `--json` is written for UTF-8 whatever stdout holds, and a name read back under a legacy multibyte filesystem encoding keeps its bytes. `$'...'` runs in bash, zsh and ksh but not in dash, and README.md, the handbook and AGENTS.md now say so. The PowerShell form is asserted as text and was not run in PowerShell, which the host lacks. `tests/test_doctor_non_ascii_paths.py` runs the spellings under several locales and stdout encodings. Lane 3's independent reviewer RV-a (opus) found it READY (#792 issuecomment-6232864874), and READY again after two fix rounds (#792 issuecomment-6234129925). RV792m found its merge round with #630 READY (#792 issuecomment-6243295865), and RV792p the round that builds its test's environment from #818's `paste_env()` (#792 issuecomment-6243694009). It lands on Brett Heap's word "merge lane 3's v1.1.1 drafts after the v1.1.0 tag" (2026-10-17T13:00:57Z). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… that follow the host are quoted for the shell they are printed in, PowerShell or Git Bash, and names `init`'s `cd` line among `family.py`'s (#711) #711 quotes `shape-doctor.py`'s lines, setup's family lines and `family.py`'s NEXT, switch -c, add, cd and git rm -r --cached lines for the shell they are printed in: `sh`'s under Git Bash (`MSYSTEM` set), PowerShell's in PowerShell. README's Windows sentence and the handbook's Windows panel still said PowerShell's. The wording waited in docs-wording.md while #630 owned both files. #630 has landed (190133d) and is merged here with main b9b7069, and the plan is recomputed on it: the old text is byte-identical at README.md:158-161 and docs/handbook.html:737-741. - README.md: "Printed commands are quoted for a POSIX shell, so paste them into Git Bash" is kept word for word. The exceptions name `init`'s `cd` line too, and are "quoted for the shell they are printed in, PowerShell or Git Bash, on Windows." One line more. - docs/handbook.html: the same words in `panel-windows`, and the limit `host_shell_dialect`'s docstring names, which README leaves out: a shell started from inside Git Bash inherits `MSYSTEM`, so a PowerShell started there is handed Git Bash's quoting, and cmd.exe is handed PowerShell's. - The seven markers of README's § Starting a project in a new organisation move together, 45a3f886 -> 4c523ffe. The page's other sections that read it (masthead, partway, ci, legs, doublepin, park) were re-read: none says which shell a printed line is quoted for. - tests/readme-cap.d/711.txt: +1, the line this adds. README goes 1474 -> 1475 and the cap 1475 -> 1476 (1422 + 2 + 5 + 12 + 22 + 10 + 1 + 1 + 1), one over README as main has it since #738 cut a line. tests/test_readme_windows_quoting_sentence.py's SENTENCE matches the new sentence once, and the handbook's panel holds it once, so the test does not change. Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…book: under Git Bash (`MSYSTEM` set) the lines that follow the host are quoted for `sh`, through one question, `host_shell_dialect`, so the doctor's lines, setup's family lines and `family.py`'s `NEXT`, `switch -c`, `add` and `cd` lines paste whole in the shell that printed them, and README's Windows sentence says so (#711) (#821) The tools that quote a printed line for the host chose PowerShell's dialect wherever `os.name` was `nt`, and it is `nt` under Git Bash too, which runs the Windows Python. So Git Bash was handed `'C:\Users\Jane O''Neill'`, which it reads as `C:\Users\Jane ONeill`, a bare `C:\work\Atlas`, which it reads as `C:workAtlas`, and `Copy-Item` and `$env:PYTHONPATH = ...;`, which it has no command for. Brett Heap chose option (a) of #532's Git Bash question at 2026-10-16T13:07:34Z. - scripts/repo_shape.py: `host_shell_dialect(name=None, environ=None)` answers "nt" (PowerShell) on a Windows host whose environment has no `MSYSTEM`, or an empty one, and "posix" (`sh`) everywhere else, Git Bash and every MSYS2 shell included. `shell_quoted` and `shell_line` ask it when they are given no `platform`; an explicit one keeps its meaning. Its docstring names the limit: a PowerShell started from inside Git Bash inherits `MSYSTEM` and is answered `sh`'s. - setup-project.py carries a copy, as it does of the quoting, because it runs before any checkout of the standard exists. `SHELL_BARE`, decided at import from `os.name`, is gone: `shell_word` asks the question at each call and takes the alphabet and the escape from one answer, which the old code read twice. Setup's family lines follow it (L2-29). - shape-doctor.py: `quote_arg`, `restore_from_head`, `validator_command`, `bump_leg_command`, `checkout_notes`, `submodule_update_next` and `copy_command` ask it, so Git Bash gets `sh`'s quoting, `cp` and `env PYTHONPATH=...`. `scaffold_command` keeps its entry point and interpreter on the machine (`python setup-project.py` on Windows, from either shell) and takes only its quoting from the question. The interpreter is `PYTHON`, the machine's, on every line. - scripts/family.py is unchanged: its host-dialect lines go through `shell_quoted` and `shell_line`, `_print_next_steps`'s `cd` included. Tests: tests/test_git_bash_dialect.py emulates PowerShell, Git Bash, an empty `MSYSTEM` and POSIX on any host (each module under test gets an `os` whose `name` is the host's; the real `os.name` is never touched) and reads each line back with `shlex.split` or `powershell_words`: the question's answers, setup's copy held to them, `shell_quoted` and `shell_word` held to one answer on every host, and the lines of setup, family.py and the doctor. #724's inventory in tests/test_readme_windows_quoting_sentence.py now fails a function that quotes for the host and reads `os.name` itself. tests/test_family_printed_quoting.py reads the host through the question, and tests/test_setup_project_py.py unsets `MSYSTEM` where it patched the gone `SHELL_BARE`. Fix round 1 (rv711, no MUST): no pinned script may import `host_shell_dialect` (siblings.py's older-library test strips it too, and the skew test reads all seven pinned scripts that import from repo_shape); the guard counts the doctor's nine line builders, the functions that hand a `platform` on to a quoter, as quoters, so `copy_command(s, d, os.name)` in a new row fails it, and names a quoter's body by `Class.method`; the Git Bash rows read back a kept repository's line and a lane trailer spelled by the line's own quoter. A lane name cannot hold an apostrophe (`LANE_NAME_RE`), so that trailer test hands the quoter one through a stand-in. README and the handbook: README's Windows sentence keeps "Printed commands are quoted for a POSIX shell, so paste them into Git Bash", names `init`'s `cd` line among `family.py`'s, and says the exceptions are "quoted for the shell they are printed in, PowerShell or Git Bash, on Windows." #785's sentence after it says a path `shape-doctor.py` cannot print as itself is written `$'...'` "Off Windows and in Git Bash": its `quote_arg` now asks `host_shell_dialect` too, so Git Bash is handed `sh`'s spelling of such a path. The handbook's Windows panel says the same, and adds that a shell started from inside Git Bash inherits `MSYSTEM`, so a PowerShell started there is handed Git Bash's quoting, and a cmd.exe started anywhere else is handed PowerShell's. The seven markers of README's § Starting a project in a new organisation move to its new blob, e0d2b672, and tests/readme-cap.d/711.txt files the two lines this adds (README 1476 -> 1478, the cap 1477 -> 1479). NIT 6 of lane 2's review of #808, which landed first: the one-question guard's `calls_to` counts a quoter handed on as a value (`host_values`) as well as a call, so `map(shell_quoted, w)` beside a branch on `os.name` is seen. Stacked on #724, whose final head 949fd4d is merged here and which has landed as 2000488, merged here with main. #630, which rewrote the README and handbook around this sentence, has landed too (190133d), and so has #792 (#785, 5cbec22), which wrote into the same paragraph and into `quote_arg`; main d7befb9 is merged here with both. The copied `scripts/repo_shape.py` changes, so it rides in v1.1.1's shape bump. Closes #711 Lane: openreposhape-2 (openRepoShape-2) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>



Lane: openreposhape-3 (openRepoShape-3)
Claim: #563 (comment)
Refs #563. This is #563's third pull request, the docs, and it does not close the issue.
Head
aef568f0, on main6f64eea2. It lands LAST, before the tag, becausepipx install openreposhapeworks only once 1.1.0 is on PyPI. Rounds A, A3, A4, B1 and both parts of B2 of the final refresh are in; every other v1.1.0 pull request has landed. Lane 2's #627 (README and the handbook) has landed and is merged here. #563's other two pull requests have landed: #590, the package (fe237ec), and #599, the release workflow (32c22b0). The base is nowmain. The diff against it is this pull request's twelve files:README.md,AGENTS.md,docs/handbook.html,docs/cli.md,scripts/render-cli-reference.py,adopt-project.py,docs/release-notes/v1.1.0.md,tests/readme-cap.d/563.txt,tests/readme-cap.d/460.txt,tests/readme-cap.d/683.txt,tests/test_repo_hygiene.pyandtests/test_agents_md_codes.py. The commit list also shows #590's and #599's branch commits, because those branches were merged here before they were squashed on main.What
Decision 6 of #563's design (issuecomment-6191672077), approved by Brett Heap at 2026-10-15T13:32:51Z ("approve all six for #563"): the one-line install stays first, and
pipx install openreposhapecomes second, and first on native Windows.tests/readme-cap.d/563.txt, + 2 fromtests/readme-cap.d/460.txtfor Tracking: shape-doctor.py, reporting a repository's compliance #460's L1 below, + 1 fromtests/readme-cap.d/683.txtfor the doctor's exit 3, rounds A3, A4 and B2):pipx install openreposhape, where pipx comes from, what it places, how it upgrades, and to use only one installer.openRepoShape --preflightexits 2, and that the installed doctor KEEPS "its clone" (no longer "its temporary clone").src/openreposhape/row, and the standard-library sentence says the package is a second way in, never a requirement.test_agents_md_is_short_enough_to_be_read):install.shandpipx install openreposhapeare the human's to run.yesthere (:831-832). This settles Tracking: the test suite, CI and the docs #461's line "The handbook's step 3 has no Git Bash path".test_every_handbook_copy_of_the_one_line_install_is_the_same(tests/test_repo_hygiene.py:1578) holds all three handbook copies of the one line toONE_LINE_INSTALL.filter-repo-missingnames git-filter-repo's install ininstall.sh's order: the pipx or uv already there, then brew, the apt/dnf pipx route, then pacman. It sayspipx install openreposhapebrings it for itsadopt-project.scripts/render-cli-reference.py: the checkout group says whatpipx install openreposhapeplaces.fc86720(121 pull requests).Also #460's L1 (
5b140bd)#630 also carries one line of #460, which Brett Heap gave to lane 3 at 2026-10-16T12:24:41Z. Lane 3's triage (T460a) put it here: every file it needs is already in #630, and #630 lands after #627 anyway.
validate-pins.pyasksgh apifor a neutral product's pin with the operator'sghcredentials, andmake bootstrap,bump-leg.pyandupdate-shape.py applyrun it in the operator's environment, so none of them says any more that a project runs its own gate offline or thatapplyis otherwise offline (#411) #465 removed a sentence from README'svalidate-pins.pyparagraph because its other half was false: a declared pin file that is not there is the refusalneutral-pin-missing(exit 2), not drift. The removal also took the true half, that a digest that recomputes differently is the findingneutral-pin-digest-mismatch(exit 1). v1.0.0's notes list that as a known limit (docs/release-notes/v1.0.0.md:507).neutral-pin-digest-mismatch(exit 1), and a checkout that cannot answer is the findingneutral-pin-source-unreadable; a declared pin file that is not there is the refusalneutral-pin-missing(exit 2)." The handbook's twin inlegs(:1239-1242) says the same.templates/assembly-root/scripts/validate-pins.py.neutral-pin-digest-mismatchis areport.finding, so exit 1.neutral-pin-missingis araise Refusal, whichmainturns into exit 2.tests/readme-cap.d/460.txt(+2). The threeThe three legsmarkers move:legschanges text and marker; the masthead andcichange the marker only. Lane 2's README.md and the handbook: say that a source's ownsingle-repository.yamlrecord arrives asleg: drop, that the adopted Makefile'sCONTRACTS_DIRfollows the spec leg's mount, and that a second setup run is refused at the plan, so the three sentences #566, #572 and #573 left false or incomplete are true of main (#606) #627 moved the same three markers; the final refresh's merge of main resolved them by recomputing every marker (see below).Rounds
Round 1 (
adb4313..82f9403): the docs, the hint and the notes draft.RV630 round 1: no MUST. SHOULD 1-6 and nits 1, 3-9 were taken (
b347943,c3e9a3d,0470497,b006f16):Merges:
2a7865emerged main4cf5fb07(tests: a tests-git-bash job runs the launcher's and install.sh's suites 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's Git Bash job);ca5c49fmerged 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's750366bc, which carries 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 Git Bash test spellings;493c6admerged maind4a7fab9, after 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 and 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 landed;9e504c0merged main879d8047(adopt-project.py:adopt-bad-idnames an id derived from--projectas derived, so--project North_Windno longer says--id 'north_wind' must matchover a flag nobody passed (#601) #620);7b7e657merged mainde926142(adopt-project.py: the read-across follow-up nameslegs.code_pathandlegs.spec_pathand no path, and thecontracts/rule's reason names none, so the hand edit of a leg's mount the tool asks for leaves no plan-time path incheck, the split commit or the pull request (#600) #628, validate-family.py and family.py: a gitlink no section of HEAD's.gitmodulesgives ismember-gitmodules-absent,removerefuses a holder with no.gitmodulesbeforedeinit, and two gitlink remedies name what they missed (#611) #639, adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621, README.md and the handbook: say that a source's ownsingle-repository.yamlrecord arrives asleg: drop, that the adopted Makefile'sCONTRACTS_DIRfollows the spec leg's mount, and that a second setup run is refused at the plan, so the three sentences #566, #572 and #573 left false or incomplete are true of main (#606) #627, bump-leg.py and setup-project.py: thegit switch -cline says under it what to do where an earlier act left that branch, soalready existsis no dead end (#607's sentence, in the two tools it left) #657, openRepoShape in Git Bash: themachine:row findspython, a drive-letter$OPENREPOSHAPE_REPOis a path, a Windows-spelled$OPENREPOSHAPE_BIN_DIRis respelled or refused, andtests-git-bashruns #598's installer cases (#649) #656, tests/test_doctor_next_is_a_command.py: the comment-tail guard reads each string expression whole, across+and an f-string's pieces, and flags a#led by two or more blanks or a tab with or without a blank after it and a#that opens text after a part that is not a constant, socmd #x,cmd + " " + "# x"andf"{cmd}{pad}# x"no longer reach a NEXT unseen (#679) #698). The handbook conflicted at eight marker hunks only: the seven § Starting markers, the threeThe three legsmarkers that share their hunks, and the one § Adopting marker. Each took this branch's side, and then every marker was recomputed from the merged README.Each merge was clean or resolved to the sides RV630 predicted.
Refresh round (
61b3907):4cf5fb0;RV630 round 3: no MUST. SHOULD 1-2 and nits 1-5 were taken (
ddd3c5f):ln -sfact: a copy unlessMSYS=winsymlinks:nativestrictis set, which follows main for want ofopenRepoShape.ref;repo_shape.py;adopt-bad-idnames an id derived from--projectas derived, so--project North_Windno longer says--id 'north_wind' must matchover a flag nobody passed (#601) #620's entry.Final refresh, round A (
7b7e657,6fd33a9):--projectmust be), ITS IDENTITY AND ITS FULL HISTORY, and becomes the assembly root", with no line added. Its Tracking: adopt-project.py, converting an existing repository in place #457 tick waits for README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 to land.legs.code_pathandlegs.spec_pathand no path, and thecontracts/rule's reason names none, so the hand edit of a leg's mount the tool asks for leaves no plan-time path incheck, the split commit or the pull request (#600) #628 (with lane 2's re-run-planline for a plan written before v1.1.0), adopt-project.py and scaffold-project.py: the Free-plan secrets hint goes by the assembly root's own visibility on GitHub and names the root as its reason, so a public source adopted with private legs is no longer told its organisation secrets resolve to EMPTY (#602) #621, bump-leg.py and setup-project.py: thegit switch -cline says under it what to do where an earlier act left that branch, soalready existsis no dead end (#607's sentence, in the two tools it left) #657, validate-family.py and family.py: a gitlink no section of HEAD's.gitmodulesgives ismember-gitmodules-absent,removerefuses a holder with no.gitmodulesbeforedeinit, and two gitlink remedies name what they missed (#611) #639, openRepoShape in Git Bash: themachine:row findspython, a drive-letter$OPENREPOSHAPE_REPOis a path, a Windows-spelled$OPENREPOSHAPE_BIN_DIRis respelled or refused, andtests-git-bashruns #598's installer cases (#649) #656, tests/test_doctor_next_is_a_command.py: the comment-tail guard reads each string expression whole, across+and an f-string's pieces, and flags a#led by two or more blanks or a tab with or without a blank after it and a#that opens text after a part that is not a constant, socmd #x,cmd + " " + "# x"andf"{cmd}{pad}# x"no longer reach a NEXT unseen (#679) #698 and README.md and the handbook: say that a source's ownsingle-repository.yamlrecord arrives asleg: drop, that the adopted Makefile'sCONTRACTS_DIRfollows the spec leg's mount, and that a second setup run is refused at the plan, so the three sentences #566, #572 and #573 left false or incomplete are true of main (#606) #627. Every pull request sincebfed143is now cited.Round A3 (interim, before the tag) (
a86a422,d2d3d76, 2026-10-17):bf1f099b: 25 pull requests, no conflict. README.md and the handbook were untouched by them, and every marker stayed current.shape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742): README.md:1208-1220 and the handbook'sdoctortwin now name every reason the checkout cannot answer, asshape-doctor.py's EXIT CODES does. The list covers a pin's commit it lacks, a partial clone missing what is compared (shape-doctor.py: run from a partial clone of the standard, theshape currencyrow fetches what the clone lacks, and with its remote out of reach calls each copy the standard changed since the pinboth, so a doctor that fetches nothing fetches, and offline says DRIFTED of copies nobody edited #683), a read that fails, a crashed validator of its own (shape-doctor.py:validator_rowreads a validator's exit 1 as "reports a finding" whatever caused it, so a validator that CRASHED (a:in the standard's path splits thePYTHONPATHthe template fallback runs under, andvalidate-manifest.pydies onimport repo_shape) is a FINDING row quotingModuleNotFoundError, and a root whose manifest is valid goes fromCOMPLIANT, SHAPE BEHINDtoINVALID (manifest)#278, shape-doctor.py's twonamingrows read only the naming script's exit code, so a crash of the STANDARD's ownvalidate-repository-naming.pyis a FINDING about the project (INVALID (naming), exit 1, where #357 makes a crashed template CANNOT ANSWER), and a directory whose name is not UTF-8 getsnaming FINDING Atl?as: UnicodeEncodeError: ..., the script's traceback quoted as a fact about the name #410), and its own naming policy not loading (shape-doctor.py: a checkout of the standard whose owncontracts/repository-naming.yamldoes not load makes a compliant projectINVALID (naming), exit 1, "the naming policy is not satisfied (exit 2)", so a person is failed for the checkout's fault, and the module's EXIT CODES list leaves out an unfinished merge #678). This costs +1 line, recorded intests/readme-cap.d/683.txt. The twoKeeping a project's shape currentmarkers move.planrefuses--id ''and--elected-by ''by name, a derived id's refusal says to change--project, or in place to rename the source first, and an org read from a url is named as not given (#658) #699, adopt-project.py: a--work-dirthat is a file or cannot be made is refused by name inplan,checkandexecute, and--helpsays what--work-diris and where--orgcomes from (#650) #719, adopt-project.py:--sourceis read by its spelling, anorg/repoin any spelling is cloned by its name, the plan recordssource.cloned, and a clone no plan records is removed, so a recorded clone that is gone or emptied is refused or cloned again for what it is (#652) #749, adopt-project.py: underLC_ALL=C PYTHONUTF8=0,plan,checkandexecutehand git and gh each name, url and branch and the leg description as the UTF-8 bytes it spells, so none of them ends in aUnicodeEncodeErrorbefore any leg exists (#653) #727, adopt-project.py:_default_branchkeeps a branch name's byte that is not UTF-8, so a source whose HEAD ororigin/HEADis oncaf<e9>is refused or adopted past it, and no longer endsplan,checkandexecutein a traceback (#425) #717, scaffold-project.py, adopt-project.py and family.py: the Free-plan secrets hint goes by what GitHub says of the root or holder and of its organisation's plan, and names the answer GitHub did not give, soinit --reuse-empty-repono longer calls a public holder private, an unread root is not taken as the run's visibility, and an unread plan is not silent #728, adopt-project.py:checkasks this machine's git what the split stores for each file the plan keeps or moves and each pinned copy, so one it would store as other bytes no longer passescheckand fails after both legs exist (#654) #743, adopt-project.py: a source symlink whose target begins with a NUL byte ischeck'splan-empty-symlink, whichexecuterefuses before any leg, so\0spec.mdno longer fails the first clone after both legs exist, or verifies over a spec leg no clone can check out (#388) #720, adopt-project.py:checktakes--local-remote-dirand resolves each relative submodule url the plan keeps in a leg from the bare repositoriesexecute --local-remote-dirwould create there, so under that test pathcheckno longer refuses an adoptionexecuteverifies, or passes oneexecuterefuses, over such a url (#417) #718, adopt-project.py:_verifyfails a split whose rootproject.yamlis not the shape's manifest (adopt-manifest-not-shape), so a plan that gets pastcheck'splan-root-blocks-manifestno longer endsadoption verifiedover an assembly the doctor calls NOT A SHAPE ROOT (#389) #722, adopt-project.py, shape-doctor.py: a plan's answer against a rule is recorded and an empty.gitmodulesasks nothing, sodocs/answeredcodeis not MISPLACED afteradoption verified, nor a.gitmodulesemptied bygit rmasked on every run (#655) #721 (adopt); validate-pins.py and shape-doctor.py: one leg's gitlink off its pin beside another leg's digest or workflow reference names the step that comes first, that gitlink put back by hand, and then the other leg'sbump-leg.pyline, so neither finding says "First clear that finding" over a finding no line can clear first (#677) #702, Pinned shape copies: one bump, so AGENTS-shape.md is true of every root, the scaffold's chain WARNING offers only its own sources and spells--pin-source, the root's not-used note spells it too, and THE LOCKSTEP RULE'sbumpruns (#610) #634 (update-shape, bump-leg and a project's own scripts); adopt-project.py and scaffold-project.py: a--pinnaming an annotated tag's own id pins the commit the tag names, and one naming a tree or a blob is refused, so a pin file no longer records a tag's id under "A COMMIT, NEVER A TAG" (#651) #725, scaffold-project.py and family.py:scaffold-bad-idandfamily-bad-idname an id derived from--projector--familyas derived and say to change that flag, so--project North_Windno longer says--id 'north_wind' must matchand sends the person to an--idthe naming policy then refuses (#659) #671, validate-manifest.py, the naming gate and setup-project.py: a chain link no tree answers for is offered what each caller reads, so the root's validator and setup's step (4) no longer offer--link-source, which neither takes, and the gate spells every--link-sourcevalue and path it prints #744 (scaffold, setup, family and the shared readers); shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742, shape-doctor.py: a checkout whose owncontracts/repository-naming.yamldoes not load is CANNOT ANSWER, exit 3, for a project, where it readINVALID (naming), exit 1; any other naming exit 2 "refuses"; and the EXIT CODES list names an unfinished merge (#678) #701, shape-doctor.py: an own-copy validator row whose crash is a root's lostscripts/repo_shape.pynamesgit -C <root> checkout HEAD -- scripts/repo_shape.pyas its NEXT (none where HEAD no longer holds it) and the validator in its WHY (#697) #737, shape-doctor.py: thepinsrow keeps its gate whereupdate-shape.py checksays REFUSAL AHEAD beside a merged-away row, so it no longer says that theapplycheckprints drops the row when thatapplyis refused, and theshape currencycounts name every statecheckcounts (#676) #751, shape-doctor.py: a conflicted shape pin's NEXT reads both sides' pins, so a copy conflicted beside it that only the side not taken has afiles:row for is named in itswhyandleft_for_a_personfor a person to merge, and never taken (#687) #736, shape-doctor.py: theplacementrow asks a leg's top-levelsingle-repository.yamlthe staying-single record's question only wherekind:makes it the record, so one declaringrelease-configis asked the general question asplanasks it, and an answered entry names that rule (#691) #734, shape-doctor.py:submodule_configandgitmodules_entryread a holder's.gitmodulesthrough onegit config -z --list, so a section name holding a space or a byte that is not UTF-8, or a url holding NEL or U+2028, gives the doctor the mount url git andmake siblingsuse, under any locale (#668) #700, shape-doctor.py: themembersrow says, member by member, whatmake siblingsdoes with the directory beside the holder, in its own word and inplace's order, so it no longer says it places a member it leaves exactly as it is, and it spells every value it quotes, so an ESC or a CR in a url no longer reaches the terminal (#669, #673) #705, openRepoShape --doctor: a signal sent to the command's shell alone waits for the doctor, so its clone of the standard is never removed whileshape-doctor.pyruns from it, and a doctor ended by a signal leaves nothing (#680) #740 (the doctor). There is also one docs line for the exit-3 sentence.bf1f099.$OPENREPOSHAPE_BIN_DIRis respelled or refused before install.sh's fetch, and the PATH note quotes the directory it prints and refuses one holding:or a newline (#667) #726, validate-family.py and validate-pins.py: a staged gitlink removal says to commit it,member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747, bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682, render-cli-reference.py and tests/: the CLI reference's children write no bytecode under templates/, the launcher guard reads family.py's siblings child, the quoting-sentence inventory follows an aliased quoter, a suffixless tool and each tool's calling functions, and an S5778 pytest.raises builds its argument first (#665) #724, bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746, bootstrap.py and AGENTS-shape.md: a leg with an operation in progress or an ignored file in the way is refused, P9 names the branch it left, and no git isgit-not-runnable, somake bootstraploses nothing (#704) (#694) #745 and README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 are open drafts;pinsrow keeps its gate whereupdate-shape.py checksays REFUSAL AHEAD beside a merged-away row, so it no longer says that theapplycheckprints drops the row when thatapplyis refused, and theshape currencycounts name every statecheckcounts (#676) #751's tick of Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458's line (issuecomment-6158353941, "→ shape-doctor.py: thepinsrow sayscheck'sapplydrops merged-away rows wherechecksays REFUSAL AHEAD (a pinned copy kept as a link), and theshape currencycounts leave out five ofcheck's states #676"), a GitHub write this round does not make;pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630 lands;Round A4 (lane 2's review) + B1 (landed since bf1f099) (
d6a8556,b2a62ec, 2026-10-17):20004887, with no conflict. bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's README sentence and its handbook twin merged as text with their own Families marker.doctorand the notes' docs line. This covers the case where the validator cannot be given itsPYTHONPATH(shape-doctor.py:validator_rowreads a validator's exit 1 as "reports a finding" whatever caused it, so a validator that CRASHED (a:in the standard's path splits thePYTHONPATHthe template fallback runs under, andvalidate-manifest.pydies onimport repo_shape) is a FINDING row quotingModuleNotFoundError, and a root whose manifest is valid goes fromCOMPLIANT, SHAPE BEHINDtoINVALID (manifest)#278).tests/readme-cap.d/683.txtis now +2, and its text says which causes come from shape-doctor.py and update-shape.py: run from a partial clone of the standard, theshape currencyrow asks without fetching whether the clone holds what the comparison reads, and is CANNOT ANSWER with a fetch for a person to run where it does not, and a read that fails is refused, never a copy calledboth(#683) #742 and which from shape-doctor.py: a checkout whose owncontracts/repository-naming.yamldoes not load is CANNOT ANSWER, exit 3, for a project, where it readINVALID (naming), exit 1; any other naming exit 2 "refuses"; and the EXIT CODES list names an unfinished merge (#678) #701. BothKeeping a project's shape currentmarkers are re-hashed.LC_ALL=C PYTHONUTF8=0,plan,checkandexecutehand git and gh each name, url and branch and the leg description as the UTF-8 bytes it spells, so none of them ends in aUnicodeEncodeErrorbefore any leg exists (#653) #727's entry names the leg description and its em dash.--sourceis read by its spelling, anorg/repoin any spelling is cloned by its name, the plan recordssource.cloned, and a clone no plan records is removed, so a recorded clone that is gone or emptied is refused or cloned again for what it is (#652) #749's entry namessource-ambiguous.checktakes--local-remote-dirand resolves each relative submodule url the plan keeps in a leg from the bare repositoriesexecute --local-remote-dirwould create there, so under that test pathcheckno longer refuses an adoptionexecuteverifies, or passes oneexecuterefuses, over such a url (#417) #718 has its own bullet;--pin-source, the root's not-used note spells it too, and THE LOCKSTEP RULE'sbumpruns (#610) #634's entry gainsgit_out's spelling, and validate-manifest.py, the naming gate and setup-project.py: a chain link no tree answers for is offered what each caller reads, so the root's validator and setup's step (4) no longer offer--link-source, which neither takes, and the gate spells every--link-sourcevalue and path it prints #744's the gate's--link-source-labeland--link-remedy;.yml, and both of siblings.py's headers print the holder's root and folder spelled, so theS_ISREGchild check madelambda mode: True, or either path printed raw, now fails a test (#664) #706 (tests: two mutants survive review: theS_ISREGchild check of validate-pins.py's workflow scan (a directory namedsub.yml, a dangling and a looping symlink, #434) and siblings.py's holder-root spelling in both headers (#454) #664), setup-project.py: a family folder is named after the holder's REPOSITORY, so theQuill/Quillthatfamily.py init --name QuillWorksmakes is detected with no flag, and theQuillWorks/QuillWorksthatmake siblingsrefuses is not (#696) #703 (setup-project.py:family_folder_herereads the family's DISPLAY name, so the layoutfamily.py initmakes for a family created with--nameis not detected as a family folder and the old display-name layout is #696), family.py:add's refusal of a member's own gitlink held only under another spelling of its row's path moves it back to that path in the index alone, so the printed remedy no longer takes the member's gitlink away from its row (#646) #670 (family.pyadd: where another member's own gitlink sits at a case variant (Members/<P>for its rowmembers/<P>), the refusal's remedy printsgit rm -r --cachedorgit mv, which takes that member's gitlink away from its row #646), bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682 (bootstrap.py and validate-family.py:family bootstrap okwith no member checked out, an earlier row'smember-uninitializedhiding a case-variant refusal, andmember-gitlink-unnamedwith no command under--no-members#648), install.sh and openRepoShape: a Windows-spelled$OPENREPOSHAPE_BIN_DIRis respelled or refused before install.sh's fetch, and the PATH note quotes the directory it prints and refuses one holding:or a newline (#667) #726 (install.sh and openRepoShape: a Windows-spelled$OPENREPOSHAPE_BIN_DIRis respelled in install.sh's own lines and refused before its fetch, and the PATH note quotes the directory it prints (#649's two leftovers) #667), adopt-project.py, shape-doctor.py and tests/test_shape_doctor.py: the verification row's colon,run_validator's "costs no child an import" and the decoy test'ssitecustomizeclaim say what main does (#666) #730 (Comments that say what main no longer does:submodule_config's reason,run_validator's "costs no child an import", the decoy test's sitecustomize claim under-S, and the verification row's colon in adopt-project.py's header #666) and render-cli-reference.py and tests/: the CLI reference's children write no bytecode under templates/, the launcher guard reads family.py's siblings child, the quoting-sentence inventory follows an aliased quoter, a suffixless tool and each tool's calling functions, and an S5778 pytest.raises builds its argument first (#665) #724 (tests: four guards that do not hold what they say: a.pycthe CLI reference's child writes under templates/, a launcher guard blind tofamily.py, a quoting-sentence inventory with three holes, and an S5778pytest.raises#665).$OPENREPOSHAPE_BIN_DIRis respelled or refused before install.sh's fetch, and the PATH note quotes the directory it prints and refuses one holding:or a newline (#667) #726's and bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's come from lane 5's lines. bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's adds the unmerged.gitmodulesfrom its last round and says that ok became exit 1..pycfiles closes.2000488.member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747 (validate-family.py: a stagedgit rmis named again and fails retyped, themember-gitlink-absentremedy ends in a refusedadd, a shape-pin row with nopath:readsNone, and #639'smvorphans a live checkout and is stated by OS #681; v1.0.0.md:466 and :467 close), bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746 (bump-leg.py: a root on+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672; :433 closes forbump-leg.py), bootstrap.py and AGENTS-shape.md: a leg with an operation in progress or an ignored file in the way is refused, P9 names the branch it left, and no git isgit-not-runnable, somake bootstraploses nothing (#704) (#694) #745 (bootstrap.py (the family root's and the assembly root's):make bootstrapwith no git on PATH ends in aFileNotFoundErrortraceback, exit 1, where the validators refusegit-not-runnable, exit 2 #694, bootstrap.py: moving a leg off a branch overwrites an ignored file and ends a merge, P9 does not name the branch it leaves, a branch at its upstream's tip is left when HEAD is on a branch, a HEAD outsiderefs/heads/is read two ways #704; :463 closes) and README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 (README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695), each only if it lands;2000488;pinsrow keeps its gate whereupdate-shape.py checksays REFUSAL AHEAD beside a merged-away row, so it no longer says that theapplycheckprints drops the row when thatapplyis refused, and theshape currencycounts name every statecheckcounts (#676) #751's tick of Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458's line and L4-47's tick on Tracking: adopt-project.py, converting an existing repository in place #457;Round B2 (final), part 1: landed since b2a62ec
Head
d697b366e2e2a25a62c5db60acbee992581eb351, on mainfc867207, pushed as a fast-forward fromb2a62ec7on 2026-10-17. Part 2 waits only for lane 4's #795 (#686).Commits. Each merge of main was clean. None of the 15 pull requests touches README.md, the handbook, AGENTS.md,
docs/or the README cap's ledger, so every handbook marker reads as before.441dcb41merges main40e678e6;6a6817cdtakes RV630's four nits and five entries.8049456bmerges main64ec47c3;cd56ed78takes RV630's pre-review of the stage, with validate-family.py and validate-pins.py: a staged gitlink removal says to commit it,member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747's and repo_shape.py, shape_materialize.py and adopt-project.py: a value the YAML reader refuses for opening with an indicator is named by its line and says to double-quote it or reword it,plan --org @xnames--orgagain under the reader's own remedy, andchecknames one badtracking_branchonce (#708) #817's entries.42d4df8cmerges mainbad05d21;1aeed03cadds seven entries and the counts.4d14e4a7merges mainfc867207;d697b366adds bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's entry, makes two markers final, names the triage lines by their issues, and refreshes the counts.Inserted, each from its squash on main. For all 15,
git merge-tree --write-tree <main before the squash> <the pull request's final head>exits 0 and equals the squash's tree:copytreeof a fixture no longer meets a detached repack deleting its loose objects (#645) #713holderfixtures copy the family fixture withshutil.copytreewhile git's background gc or maintenance can still be packing it, so a test errors at setup when a loose object vanishes mid-copy (#440's race, through a file copy) #645741679e7760892f6add --local-remote-dirrefuses a member clone URL.gitmodulescannot hold as text,--dry-runalike, each clone-URL remedy names a link's target and a lone surrogate, and the READMEs quote the clone URL, so the line undera b/pastes as one command (#693) #812--local-remote-dir:family.py addtakes a directory whose name is not UTF-8 into.gitmodules, the clone-URL refusal's remedy misses a link's target and a lone surrogate, and the READMEs' clone line is rendered unquoted #693cbff780cd8fc9c37--local-remote-dirmembers/renamed withgit mvis renamed back in two steps through a free name, after what the disk lacks under it is put back, and a printed--sparsesays the git it needs, so no remedy fails as typed (#685) #767add: #574's spelling remedy gives a generic<new name>aftergit mv members Members,git rm --cachedfor a pulledMembers/README, and agit mv --sparsethat needs git 2.34 or later #68598f149762fd92cfbmembers/renamed withgit mvgit-not-runnable, somake bootstraploses nothing (#704) (#694) #745refs/heads/is read two ways #704, bootstrap.py (the family root's and the assembly root's):make bootstrapwith no git on PATH ends in aFileNotFoundErrortraceback, exit 1, where the validators refusegit-not-runnable, exit 2 #69424a977c03b3e8923project:or url is a finding,.gitmodulesis read as git reads it, the warning's prose is spelled, and a tag-only branch is named (#690) #805make siblings): the printedmvgoes astray over an existing.holder/.folderor a dangling link, a lone surrogate or NUL inproject:is a traceback, a url with NEL or U+2028 is cut, two warnings print paths raw #69040e678e6d8ac1bb9make siblings's inputsmember-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747git rmis named again and fails retyped, themember-gitlink-absentremedy ends in a refusedadd, a shape-pin row with nopath:readsNone, and #639'smvorphans a live checkout and is stated by OS #6816626a3c5994e7168validate-family.py's remedies, typed as printedplan --org @xnames--orgagain under the reader's own remedy, andchecknames one badtracking_branchonce (#708) #817adopt-project.py checkexits 2 with no line and no remedy on a hand-writtenresolution:that opens with a backtick (#635),plan --org @xno longer names--org(#635), andchecknames one badtracking_branchtwice (#644) #70864ec47c353515a95url.<file URI>.insteadOfrewrite thatclone_just_pushed,read_through_gitand the adopt tests'pastedeach built, and the empty-mountsubmodule update --initsites use it (#662) #748url.<base>.insteadOfrewrite thatclone_just_pushedandread_through_giteach build, and the plain-pathgit submodule update --initsites of #554's review use it (#440) #662431c8cf8bc67aa88family-operation-in-progressname a path asshown_sourcenames it, so an ESC or a line break in a path no longer reaches the terminal (#692) #731family-operation-in-progressprints a holder's path across two lines #692d66a905443279472apply's rollback line says to make a shut directory writable first,committed on <branch>:names what the commit holds, a branch named like a tag is pushed and read by its full name, and a quotedfiles:orshape:header is found as the reader finds it (#674) #810apply: a rollback line fails over a read-onlycontracts/unsaid,committed on <branch>:lists a path the commit lacks,--branch v1.0.0cannot push beside a tagv1.0.0, and a quoted"files":header is refused #674d17564d9c05b99b4apply's lines, its branch and its headers--created-by, GIT_AUTHOR_NAME orgit config user.namegave it, git is asked about the first commit's author and committer before the remote exists, and a--familythat is a Windows device name is refused,--dry-runalike (#688) #809--created-byandGIT_AUTHOR_NAMEare never asked whether the manifest can hold them, a whitespace-onlyGIT_AUTHOR_NAMEfails after the remote exists, and a--familya Windows disk cannot hold exactly is accepted #68842aed78856416599family.py initrecords and commits asadd,bumpandremovename a bisect, an empty rebase directory, sparse rules that hold no pattern, a merge conflicted infamily.yamland amembersfile HEAD holds and the work tree does not as what each is, with the way out that works there, and refuse afamily.yamlholding a change HEAD does not (#684) #816add/bump/remove: amembersfile deleted unstaged, agit bisect, an empty sparse-checkout file, an emptyrebase-merge/and a merge conflicted infamily.yamlmeet a refusal or remedy that does not fit (#626's gaps) #684fa04d2cd409e6882+or named like a tag pushes as itself, and a tracking branch whose last part is over 250 bytes, which git cannot lock, is refused byrefuse_bad_branchbefore anything exists,--dry-runtoo (#689) #813+and fails for a branch named like a tag, and a tracking branch of 251-255 characters passes--dry-run, dies at the first commit once every remote exists #689f6d17bc5786b3d6d--tracking-branchmake_fileand the--branch a\udcffbtest by emulation, and the UTF-8 promise and the leg finding's role pinned (#663) #723probe\udcff, Windows guards onmake_fileand the--branch a\udcffbcase, and the UTF-8 promise and the leg finding's role pinned #663bad05d21bf8dda2arefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746+xis toldpush -u origin +x(forced), a branch named like a tag passesbump-leg-on-tracking-branch,bump-leg-root-dirtyprints C1 controls raw, andcommitted <sha>:lists paths the commit lacks #672fc867207bb920ac1bump-leg.py's branch and its linesrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's text was written from4a014a79, which sat on tests: one skip helper for names and branches that are not UTF-8, Windows guards onmake_fileand the--branch a\udcffbtest by emulation, and the UTF-8 promise and the leg finding's role pinned (#663) #723's head before tests: one skip helper for names and branches that are not UTF-8, Windows guards onmake_fileand the--branch a\udcffbtest by emulation, and the UTF-8 promise and the leg finding's role pinned (#663) #723's squash and does not merge clean with main.scripts/bump-leg.pyis one blob (226e7b0d) at4a014a79, ata5139a32and in the squash, and the squash's own lines in its other four files equal that head's, so the text stands.init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 (README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695) is v1.1.1. Its docs entry is not taken. Its upgrade note (cd Y→cd X, with the README'sY/Ylayout) is, because it is true of main since siblings.py and the family-root README:make siblings,parkandresumeprint the family's name and every member row spelled and never end in a traceback onname: "a\ud83db"or a dash underPYTHONIOENCODING=ascii, and the holder's README names its folder after the repository, so--family Quill --name QuillWorkslays out as its README says (#454, #629) #642.RV630's pre-review items taken:
683.txt+1, bothKeeping a project's shape currentmarkers re-hashed); bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's lead; adopt-project.py, shape-doctor.py and tests/test_shape_doctor.py: the verification row's colon,run_validator's "costs no child an import" and the decoy test'ssitecustomizeclaim say what main does (#666) #730's "Comments and docstrings"; the doctor'smembersrow as one bullet;git cloneon" a just-pushed local remote, which is what tests: every test that clones a just-pushed local remote by its plain path now clones it withclone_just_pushed, so the macOSfailed to copy filerace of #440 cannot reach it;load_without_bytecodekeeps the doctor's and the Windows-path tests' loads of the family root's scripts from writing.pycinto the templates; the not-a-list doctor cases hold INVALID, exit 1; andGOOD_KEYSholds th #554 changed; NIT-1, family.py and scaffold-project.py:add --local-remote-dirrefuses a member clone URL.gitmodulescannot hold as text,--dry-runalike, each clone-URL remedy names a link's target and a lone surrogate, and the READMEs quote the clone URL, so the line undera b/pastes as one command (#693) #812's "that needs it"; NIT-2, bootstrap.py and validate-family.py: a member no credential would check out fails the family bootstrap, every row's gitlink is asked before any checkout, and--no-membersprints the case-variant move (#648) #682's first sentence; and its corrections to the pending entries for repo_shape.py, shape_materialize.py and adopt-project.py: a value the YAML reader refuses for opening with an indicator is named by its line and says to double-quote it or reword it,plan --org @xnames--orgagain under the reader's own remedy, andchecknames one badtracking_branchonce (#708) #817, tests/conftest.py: one helper builds theurl.<file URI>.insteadOfrewrite thatclone_just_pushed,read_through_gitand the adopt tests'pastedeach built, and the empty-mountsubmodule update --initsites use it (#662) #748 ("With tests: every test that clones a just-pushed local remote by its plain path now clones it withclone_just_pushed, so the macOSfailed to copy filerace of #440 cannot reach it;load_without_bytecodekeeps the doctor's and the Windows-path tests' loads of the family root's scripts from writing.pycinto the templates; the not-a-list doctor cases hold INVALID, exit 1; andGOOD_KEYSholds th #554 and siblings.py and family.py:make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578"), family.py:add,bumpandremovename a bisect, an empty rebase directory, sparse rules that hold no pattern, a merge conflicted infamily.yamland amembersfile HEAD holds and the work tree does not as what each is, with the way out that works there, and refuse afamily.yamlholding a change HEAD does not (#684) #816 ("non-cone") and README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738 (v1.1.1).v1.0.0's known limits. Said closed:
:463(bootstrap.py and AGENTS-shape.md: a leg with an operation in progress or an ignored file in the way is refused, P9 names the branch it left, and no git isgit-not-runnable, somake bootstraploses nothing (#704) (#694) #745),:466and:467(validate-family.py and validate-pins.py: a staged gitlink removal says to commit it,member-gitlink-absentputs the gitlink back by hand, the variantmvis guarded, and a pin row with nopath:isshape-pin-row(#681) #747),:439(update-shape.py:apply's rollback line says to make a shut directory writable first,committed on <branch>:names what the commit holds, a branch named like a tag is pushed and read by its full name, and a quotedfiles:orshape:header is found as the reader finds it (#674) #810),:433as one clause in bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's entry ("With update-shape.py:apply's rollback line says to make a shut directory writable first,committed on <branch>:names what the commit holds, a branch named like a tag is pushed and read by its full name, and a quotedfiles:orshape:header is found as the reader finds it (#674) #810, that closes…"), and:530(tests/conftest.py: one helper builds theurl.<file URI>.insteadOfrewrite thatclone_just_pushed,read_through_gitand the adopt tests'pastedeach built, and the empty-mountsubmodule update --initsites use it (#662) #748, with tests: every test that clones a just-pushed local remote by its plain path now clones it withclone_just_pushed, so the macOSfailed to copy filerace of #440 cannot reach it;load_without_bytecodekeeps the doctor's and the Windows-path tests' loads of the family root's scripts from writing.pycinto the templates; the not-a-list doctor cases hold INVALID, exit 1; andGOOD_KEYSholds th #554 and siblings.py and family.py:make siblingsasks about the family folder before it clones, clones each member on its tracking branch, andfamily.py siblingsruns the holder's own copy (#543) #578). Still open::492, listed (README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 is v1.1.1), and:440, which bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's entry says the dry run now names.Markers made final in the file. "What changed" now reads "The list runs to main
fc86720; family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795, if it lands next, is added before the tag." Known limits' re-read marker and "as read so far" are cut. The counts marker keeps the sentence the release-body tool fills.Public wording in Known limits. "lane openRepoShape-4's checklist triage" is now "the checklist triage of the L4 lines on Tracking: adopt-project.py, converting an existing repository in place #457 and Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459", and "lane openRepoShape-1's checklist triage (D9)" is "the checklist triage of the L1 lines on Tracking: adopt-project.py, converting an existing repository in place #457". Each id reads "Tracking: adopt-project.py, converting an existing repository in place #457's L4-05", "Tracking: setup, scaffold, family and the shared helpers, creating projects and families #459's L4-24", "Tracking: adopt-project.py, converting an existing repository in place #457's L1-02" and so on.
Counts, in the four places: main
fc86720; 121 pull requests sincebfed143; at 2026-10-17T19:08Z, 131 of thev1.1.0milestone's 133 issues closed. Open: Publish openRepoShape to TestPyPI and PyPI asopenreposhape, so onepipx installputs the tools and git-filter-repo on any machine (design first) #563 (README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630) and family.pybump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686 (family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795). family.pyadd/bump/remove: amembersfile deleted unstaged, agit bisect, an empty sparse-checkout file, an emptyrebase-merge/and a merge conflicted infamily.yamlmeet a refusal or remedy that does not fit (#626's gaps) #684 (family.py:add,bumpandremovename a bisect, an empty rebase directory, sparse rules that hold no pattern, a merge conflicted infamily.yamland amembersfile HEAD holds and the work tree does not as what each is, with the way out that works there, and refuse afamily.yamlholding a change HEAD does not (#684) #816 fixed all but its reftable case) and README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 (README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738) are on v1.1.1.Release paths.
git diff --stat 20004887..fc867207 -- pyproject.toml src/openreposhape/ install.sh .github/workflows/is empty. bump-leg.py: the branch is read in full and pushed asrefs/heads/<b>:refs/heads/<b>, dirty names are spelled,committedlists what git holds, and the dry run says it runs no validator (#672) #746's squash touchesscripts/bump-leg.pyand four test files, and no workflow.Tests, under a stub
ghexiting 97,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR: the eight docs files gave 300 passed in 37.04s atd697b366(and 300 passed in 34.82s at1aeed03c).render-cli-reference.py --checkgave[ok], andgit diff --check origin/mainwas clean. README.md is 1474 lines, its cap:1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) = 1474. AGENTS.md is 494 lines, its cap.Part 2, once family.py:
bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 lands: merge main; family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795's entry before "family.py refuses before it writes."; "What changed" becomes "The list is complete to the release commit."; the counts; the tests; the push.Not done here: the ticks on Tracking: update-shape.py and bump-leg.py, keeping a project's shape copies and leg pins current #458 (shape-doctor.py: the
pinsrow keeps its gate whereupdate-shape.py checksays REFUSAL AHEAD beside a merged-away row, so it no longer says that theapplycheckprints drops the row when thatapplyis refused, and theshape currencycounts name every statecheckcounts (#676) #751's line) and Tracking: adopt-project.py, converting an existing repository in place #457 (L4-47), and any milestone move. Those are lane 3's coordinator's.Round B2 (final), part 2: #795
Head
aef568f02a9c626c9ae93f67e1a508c14684312d, on main6f64eea2, pushed as a fast-forward fromd697b366on 2026-10-17. RV630 reviewed part 1 (d697b366) READY; this part makes the changes it gave, and nothing else.016599b0merges main6f64eea2, clean: family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 touches no README.md, handbook, AGENTS.md,docs/or README cap ledger file, so every handbook marker reads as before.aef568f0changes onlydocs/release-notes/v1.1.0.md(+22 -10).bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795 (family.pybump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686), squash6f64eea2, checked against the squash itself.git merge-tree --write-tree fc867207 <head>exits 0 and equals the squash's tree0f2d2dddfor the final headcf143c69and for17bbbaba. Forcf5e64b3, the head the entry was written from, the merged tree differs from the squash only intests/test_family_bump_reachability_words.py(+94 -21).scripts/family.pyis one blob (a7660e9c) in the squash and in both later heads, so the entry stands as written.family.py:
bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795's entry, a new bullet before "family.py refuses before it writes.":"What changed" (line 120): "The list runs to main
fc86720; family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795, if it lands next, is added before the tag." is now "The list runs to main6f64eea."Known limits, RV630's lead and list, exactly:
Everything after the list in that section is as it was.
6f64eea; 122 pull requests sincebfed143; at 2026-10-17T20:15Z, 132 of thev1.1.0milestone's 133 issues closed. The one open is Publish openRepoShape to TestPyPI and PyPI asopenreposhape, so onepipx installputs the tools and git-filter-repo on any machine (design first) #563 (README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630). family.pyadd/bump/remove: amembersfile deleted unstaged, agit bisect, an empty sparse-checkout file, an emptyrebase-merge/and a merge conflicted infamily.yamlmeet a refusal or remedy that does not fit (#626's gaps) #684 and README.md, the handbook, AGENTS.md and the holder's AGENTS-shape.md:init's--intosentence omits the landing refusal,make siblingsis described as it was before #578, and nothing names the one-spelling rule formembers/#695 (README.md, the handbook, AGENTS.md and the family-root docs:init's--intosentence names thefamily-landing-in-standardrefusal,make siblingsis described as it stops before cloning, andmembers/has its one-spelling rule (#695) #738) stay on v1.1.1. The counts marker keeps the sentence the release-body tool fills.git diff --stat 20004887..6f64eea2 -- pyproject.toml src/openreposhape/ install.sh .github/workflows/is empty.aef568f0commits, under a stubghexiting 97,GH_TOKEN=bogusand an emptyGH_CONFIG_DIR: the eight docs files gave 300 passed in 37.45s.render-cli-reference.py --checkgave[ok], andgit diff --check origin/mainwas clean. README.md is 1474 lines, its cap:1422 + 2 (460.txt) + 5 (550.txt) + 12 (563.txt) + 22 (564.txt) + 10 (606.txt) + 1 (683.txt) = 1474. AGENTS.md is 494 lines, its cap.Tests
At
6fd33a9, with/usr/bin/python3 -m pytest(3.12.3), a stubghexiting 97 first on PATH,GH_TOKEN=bogus, an emptyGH_CONFIG_DIR,-o tmp_path_retention_policy=failed, and noGIT_ALLOW_PROTOCOL. The set is 14 files:test_repo_hygiene,test_cli_reference,test_agents_md_codes,test_openreposhape_package,test_release_workflow;test_install_sh,test_openreposhape_command,test_install_sh_filter_repo;test_setup_by_hand_commands,test_readme_sections,test_readme_windows_quoting_sentence,test_update_add_codes_documented;test_refusal_default_remediation,test_adopt_filter_repo_failure.Result: 544 passed in 357.14s (517 at
5b140bd, before main's new tests).render-cli-reference.py --check:[ok].tests.yml,release.ymland both templatevalidate.yml: no findings.git diff --check: clean.ca5c49f: the two Git Bash suites passed 117 plain and 117 with CRLF emulated.d697b366, ran the same eight files: 300 passed in 37.04s;render-cli-reference.py --checkgave[ok], andgit diff --check origin/mainwas clean.b2a62ec, ran the same eight files: 300 passed in 42.72s;render-cli-reference.py --checkgave[ok], andgit diff --checkwas clean.d2d3d76, ran the docs and release-notes set under the same rules:test_repo_hygiene,test_readme_sections,test_cli_reference,test_release_workflow,test_agents_md_codes,test_setup_by_hand_commands,test_readme_windows_quoting_sentenceandtest_update_add_codes_documented. Result: 293 passed in 45.45s.render-cli-reference.py --checkgave[ok], andgit diff --checkwas clean.Open placeholders in the release notes
Each is marked
**[placeholder: …]**or<release-commit>, or sits in a "remove at tag time" section:fc86720: 121 pull requests sincebfed143; at 2026-10-17T19:08Z, 131 of the milestone's 133 issues were closed. The marker and its sentence keep the form the release-body tool fills.openreposhape, so onepipx installputs the tools and git-filter-repo on any machine (design first) #563 (README, AGENTS.md, docs/cli.md and the handbook:pipx install openreposhapeis the second way in and the first on native Windows, git-filter-repo is installed in install.sh's order and never with a barepip install, and a first draft of the v1.1.0 release notes (#563) #630) and family.pybump: a mount with a narrowed fetch refspec is refusedmember-to-local-onlyon a false detail, a commit only a tag holds is told "a pin the rest of the world cannot fetch", a mount with nooriginis told to fix the network #686 (family.py:bumpfetches every branch of the member'soriginwhatever the mount's refspec and drops theorigin/refs no ref oforiginbacks, says a tag of the remote reaches a commit no branch does instead of "a pin the rest of the world cannot fetch", and tells a mount with nooriginit has none instead of to fix the network (#686) #795)), in "Before tagging", to be re-read at tag time.<release-commit>throughout.No longer placeholders since B2 part 1: "What changed" runs to main
fc86720and says #795 is added if it lands next (part 2 makes it "The list is complete to the release commit."), and v1.0.0's known limits are re-read, with the marker and "as read so far" cut.Not done here, for review
tests/still saypip install git-filter-repo. There are 40 test files, and every hit is a skip reason (git grep -l 'pip install git-filter-repo' HEAD -- tests/). Changing them is a follow-up if wanted.tests/test_release_workflow.pysays "Nodocs/release-notes/v1.1.0.mdexists yet". Its assertions still hold.openRepoShapeon Windows. The release's install check runs it there, and the rehearsal passed that check on windows-latest.🤖 Generated with Claude Code