diff --git a/README.md b/README.md
index 4964bc3c..ae0a60fe 100644
--- a/README.md
+++ b/README.md
@@ -237,9 +237,14 @@ gh api repos/opensoft/openRepoTools/contents/openRepoTools \
-H 'Accept: application/vnd.github.raw' | bash -s -- --install
```
-It places SEVENTEEN files into `~/.local/bin` — `openRepoTools`, `park`, `resume`,
-`status`, `lane`, `lanes`, `lane-handoff`, `lane-rename`, `lanes-edit.sh`,
-`lane-start`, `lane-end`, `link-estates`, the alias table `repos.tsv`,
+A workstation uses ONE install route or the other, never both — pipx's `openrepotools`
+or workBenches' `./setup.sh` — until opensoft/workBenches#165 lets its
+`scripts/setup-estate-commands.sh` accept pipx's link (Brett Heap's decision of 2026-10-18).
+
+`--install` places SEVENTEEN files (SIXTEEN beside pipx's link, below) into
+`~/.local/bin` — `openRepoTools`, `park`, `resume`, `status`, `lane`, `lanes`,
+`lane-handoff`, `lane-rename`, `lanes-edit.sh`, `lane-start`, `lane-end`,
+`link-estates`, the alias table `repos.tsv`,
`claude-current`, `claude-restart-check`, `lanes-index` and `lane-worktrees` — 755, idempotently: a
second run prints `already installed … (unchanged)` per file, one whose bytes have
drifted prints `updated at`, and one whose bytes were right and whose MODE was not
@@ -259,9 +264,17 @@ THAT EXISTS, so a bin directory that is not there yet is refused for the parent
that would not take it, and a create needs a directory's search bit as well as
its write bit. A refusal creates none of those directories either. `cp` follows
a symlink, and an install through one leaves the command uninstalled and writes
-these bytes into whatever it points at. Then a `17 of 17 placed in
` line,
+these bytes into whatever it points at. The ONE link it skips instead is pipx's,
+at `openRepoTools` (below). Then a `17 of 17 placed in ` line,
and the `export PATH=…` line if that directory is not on your `PATH`. Last, it names this repository's virtual environment, `${XDG_CACHE_HOME:-~/.cache}/openRepoTools/venvs/openRepoTools/` (one per repository, outside the estate; #162), says whether it is there, and prints the two commands that make it: it never makes one, and `tests/run.sh` runs from it once it has `pytest`.
+**Beside pipx's link it places SIXTEEN and leaves the seventeenth to pipx.** A
+`/openRepoTools` that is a link into pipx's `openrepotools` environment is
+skipped, not refused and not replaced: a line in its place says what updates it,
+it is counted apart, and it gets no receipt row. The count line then reads
+`16 of 17 placed in , and 1 left to pipx`, and from any run but pipx's own it
+goes on to name where the other sixteen now come from.
+
It also places **fourteen things that are not files in that directory**: THREE
SKILLS, `/handoff`, `/lane-swap` and `/restart`, each at
`${CLAUDE_PROFILES_HOME:-~/.claude-profiles}/shared/skills//SKILL.md`
@@ -299,7 +312,10 @@ own `UserPromptSubmit` hooks are left exactly where they are, which is why that
arm keys on the VERB and not on the word anywhere in a path. It
never writes a profile's own `settings.json`: the launcher owns that one.
-Twenty-nine artifacts always, plus the two hook entries once the path they call answers `guard` (#237), and the count is the invariant. It was sixteen until A11
+Twenty-nine artifacts always, plus the two hook entries once the path they call
+answers `guard` (#237), and the count is the invariant. Beside pipx's link it is
+TWENTY-EIGHT placed always, one left to pipx and 28 receipt rows, with the hook
+pair still on top once that path answers. The count was sixteen until A11
Addendum 4 ruling 9 gave `--install` a command-file list and `commands/swap.md`
in it, at the same pair of paths a skill takes — because `opensoft/workBenches#74`
deletes the launcher's copy and `/swap` would otherwise be installed by nobody;
@@ -342,9 +358,13 @@ where `raw.githubusercontent.com` is blocked.
| `$LANES_WORKSTATION` | — | this workstation's name, exported by the workBenches launcher. Outside a container it defaults to `hostname -s`; **inside one with no value every writer refuses**, because a container id is not a workstation and the log is never rewritten (Amendment 11, decision 8(d)) |
| `$PROJECTS_DIR` | `~/projects` | where `wip init` clones your workspace repository |
-`openRepoTools --version` prints `openRepoTools ( @ [)`. There is no
-version number here, for the reason openRepoShape has none: the identity is a
-commit.
+A release is an ANNOTATED TAG, `vX.Y.Z`, and the version comes only from the tag:
+no file here holds one. The release build writes it into the `openrepotools`
+package it builds, whose `openRepoTools --version` then names the release in its
+first line, `openRepoTools ( @ )`. A package built
+from a checkout carries the committed stamp, `0.0.0`, and says it is not a release:
+`openRepoTools 0.0.0 (an unstamped package)`. A checkout, and the one-line
+install, print `openRepoTools ( @ ][)`, as they always have.
## Using them
diff --git a/docs/release-notes/v1.0.0.md b/docs/release-notes/v1.0.0.md
new file mode 100644
index 00000000..b8ca5002
--- /dev/null
+++ b/docs/release-notes/v1.0.0.md
@@ -0,0 +1,15 @@
+# openRepoTools v1.0.0
+
+This tag marks the baseline for versioned releases: openRepoTools as it stands on `main` at `aaa7bc8`, on 2026-10-17.
+
+openRepoTools holds three things:
+- **The estate commands.** `park `, `resume ` and `status ` work on the estates that [openRepoShape](https://github.com/opensoft/openRepoShape) scaffolds.
+- **The lane tooling.** `lanes-edit.sh`, `lane-start`, `lane-end`, `link-estates` and their helpers record the work, under the lane collision protocol. `docs/README-lanes.md` is their manual.
+- **The installer.** `openRepoTools --install` places the tools, and `openRepoTools wip init` creates the workspace they read.
+
+This release pins openRepoShape at `7f84ca4`, the commit its tests were verified against.
+
+**Not on PyPI.** The first PyPI release will be **v1.1.0**. It will be published from the code leg after the triad migration (#186), through a tag-gated release workflow with TestPyPI first, modelled on openRepoShape's. Until then, `openRepoTools --version` still prints the repository and ref it was installed from.
+
+Tagged on Brett Heap's word (2026-10-17, "Yes, on main aaa7bc8").
+
diff --git a/docs/release-notes/v1.1.0.md b/docs/release-notes/v1.1.0.md
new file mode 100644
index 00000000..975a2556
--- /dev/null
+++ b/docs/release-notes/v1.1.0.md
@@ -0,0 +1,90 @@
+# openRepoTools v1.1.0
+
+
+
+v1.1.0 puts openRepoTools on PyPI. It is openRepoTools as it stands at `` in `opensoft/openRepoTools-code`, tagged on ``. `pipx install openrepotools` is a second way in, beside the one-line install and a checkout's `--install`, and `openRepoTools --version` from the package names the release it runs. `` pull requests landed on `main` between v1.0.0's commit, `aaa7bc8`, and this release's.
+
+v1.0.0 is not on PyPI, and 1.1.0 is the first version there. This release is published from `opensoft/openRepoTools-code`, after the triad migration (opensoft/openRepoTools#186), and its tag is made there. v1.0.0 stays in `opensoft/openRepoTools`, where it was made, and no tag is carried from one repository to the other.
+
+## What 1.1.0 adds
+
+- **openRepoTools on PyPI, as `openrepotools`** (opensoft/openRepoTools#228). `` is its page. Two commands install it:
+
+ ```sh
+ pipx install openrepotools
+ openRepoTools --install
+ ```
+
+ The package places one command, `openRepoTools`. It runs the packaged `openRepoTools` with the first `bash` on your `PATH` and your arguments unchanged, and with no `bash` it refuses and names it. It needs Python 3.9 or newer and declares no Python dependencies. The toolset's own requirements are unchanged: `bash`, `git`, and `jq` for `--install`.
+
+ The wheel carries everything `--install` places, the sixteen commands and `repos.tsv`, the three skills and the three command files, and the openRepoShape pin, each byte for byte as the release commit holds it. So `openRepoTools --install` run from the package reaches no network. pipx owns `openRepoTools` in its bin directory, `~/.local/bin` by default, as a link into its own environment, and `--install` leaves that one link alone: it places the other sixteen, says `1 left to pipx`, and writes 28 receipt rows where a full install writes 29.
+
+ The version is the tag's, and no file in the repository carries a version number: the release build stamps it into the wheel.
+- **`--version` names the release** (opensoft/openRepoTools#228). From the package it prints five lines:
+
+ ```text
+ openRepoTools 1.1.0 (opensoft/openRepoTools-code @ )
+ version 1.1.0
+ tag v1.1.0
+ commit
+ digest (sorted-ls-tree-r-v1)
+ ```
+
+ A package built outside a release prints `openRepoTools 0.0.0 (an unstamped package)` and says so. A checkout and the one-line install print what they printed in v1.0.0, a repository and ref (`main` by default) and no version number. A package whose stamp is broken refuses every invocation, `--help` included, and names the fix, `pipx reinstall openrepotools`.
+- **`--install` meets each other installer by a rule** (opensoft/openRepoTools#228):
+ - pipx's link is skipped, by whichever `--install` meets it: the package's, a checkout's or the one-line install's. A checkout's or the one-line install's count line then says where the other sixteen now come from.
+ - pip's console script at the target `openRepoTools`, which `pip install --user openrepotools` leaves, is refused in every mode before anything is placed, and the refusal names pipx as the one package route.
+ - Any other link at a target still refuses the whole install, and no refusal advises removing pipx's link.
+ - After a package-mode install, a note names the first `openRepoTools` on your `PATH` when it is not the package's, so a stale bash file ahead of pipx's link is not mistaken for the release.
+- **The hook pair waits for its target** (opensoft/openRepoTools#237, opensoft/openRepoTools#247). `openRepoTools --install` and `openRepoTools wip init` add the pair of hook entries, `SessionStart` and the name guard (`guard`), to `~/.claude/settings.json` only once `~/projects/xFactory/lanes-edit.sh` exists, is executable and answers `guard`. Until then they say why, and how to complete it, and our exact guard entry is taken back out of a file that already carries it; our `SessionStart` entry, which ends in `|| true`, and every hook of yours stay. So a workstation upgrading from v1.0.0 whose `~/projects/xFactory/lanes-edit.sh` does not answer `guard` loses that one entry on its next `--install`, until `openRepoTools wip init`, or `link-estates` and `--install` again, completes the pair. Before this, a fresh workstation got both entries before workspace initialization had provided that file, so the name guard could fail with a command-not-found error at every prompt.
+- **CI proves the package, and a release workflow publishes it** (opensoft/openRepoTools#228).
+ - `tests.yml` runs on every push to `main` and to every `release/v*` branch, as well as on pull requests, nightly and by dispatch, and every run has four jobs: `guard-launch-mode`, `tests`, `tests-no-submodule` and `package`. The `package` job builds the wheel twice at the commit's time and requires the two to be one file, checks every packaged payload file against the commit's blob in bytes and in mode, checks the wheel's `.dist-info`, installs the wheel with pipx, runs the unstamped `--version` and an offline `--install` beside pipx's link, and runs the package's tests, failing if one is skipped.
+ - `release.yml` is run by hand, on an annotated tag, with the version. It is adapted from openRepoShape v1.1.0's. Its build refuses unless the ref is the version's annotated tag and the tag names the run's commit; that commit is on `main` or on `release/vX.Y`; the `testpypi` and `pypi` environments each name a required reviewer and limit deployments to `v*` tags; the commit's four CI jobs ran in a push or dispatch run of `tests.yml` and the newest succeeded; and the tree digest computed here equals the one computed from GitHub's tree listing. It then builds twice from `git archive`, stamps the build, and requires one file.
+ - It uploads by trusted publishing, with no token: to TestPyPI, then an install check on Ubuntu, on macOS (run again under macOS's own bash 3.2) and on Ubuntu with Python 3.9, then to PyPI. Each upload waits for Brett Heap's approval in its environment. A pre-release `X.Y.ZrcN` reaches TestPyPI only. Only `opensoft/openRepoTools-code` may publish a final version, and nothing uploads to PyPI from `opensoft/openRepoTools`.
+- **Releases are annotated tags** (opensoft/openRepoTools#228). A release is an annotated tag `vX.Y.Z` on a commit on `main` or on `release/vX.Y`, never a branch and never a version number committed to a file, and a tag is never moved or reused. `release/vX.Y` is cut at `vX.Y.0` and carries the patch tags. A fix lands on `main` first, then reaches the release branch by a fast-forward, a merge, or a pull request of `git cherry-pick -x`. A version is uploaded once, and a bad upload is fixed by the next patch.
+
+## What else changed since v1.0.0
+
+- **The openRepoShape pin moved.** This release pins openRepoShape at v1.1.0, `190133d` on its `main`, the commit its tests were verified against (opensoft/openRepoTools#233). v1.0.0 pinned `7f84ca4`.
+- **``**: the other changes that landed on `main` after `aaa7bc8` and before the release commit, by area, one line each with its pull request.
+
+## Install and upgrade
+
+- **From PyPI:** `pipx install openrepotools`, or `pipx install openrepotools==1.1.0` for this release, then `openRepoTools --install`. `pipx` itself comes from `sudo apt-get install -y pipx` on Debian and Ubuntu and `brew install pipx` on macOS; then run `pipx ensurepath` and open a new terminal. The package is tested against the layouts of pipx 1.11.1, 1.17.8 and 1.18.3. `openRepoTools --version` prints the five lines above.
+- **From the one-line install to pipx:** run
+
+ ```sh
+ pipx install --force openrepotools
+ openRepoTools --install
+ ```
+
+ `--force` is needed. The one-line install left a regular file at `~/.local/bin/openRepoTools`, and a plain `pipx install openrepotools` meets it: pipx only warns that the file exists and that it is not modifying it, places no link, and exits 0. The bash file stays at that path and keeps running, so `--version` still names a repository and ref and not a release, and its next `--install` finds no payload beside it and fetches `main`. `--force` puts pipx's link in the file's place. Then `openRepoTools --install` skips that link and replaces the other sixteen files with the package's. On a workstation with no `openRepoTools` yet, `pipx install openrepotools` is enough.
+- **Upgrade:** `pipx upgrade openrepotools`, then `openRepoTools --install` again. Until that second command, the placed files, skills, command files and hook entries stay the earlier release's, and the link is skipped again.
+- **Remove:** `pipx uninstall openrepotools` removes only the command and its environment. The placed files, the skills, the command files, the hook entries and the receipt stay, and a checkout's `--install` then places a regular `openRepoTools` at the freed path.
+- **One install route or the other, never both.** Until opensoft/workBenches#165 lets workBenches' `scripts/setup-estate-commands.sh` accept pipx's link, a workstation uses pipx's route or workBenches' `./setup.sh`. `./setup.sh` refuses a symlinked `openRepoTools` before it installs anything, and after installing it requires a regular file identical to its vendored copy, so on a workstation with pipx's link every `./setup.sh` refuses its estate step and names the link. This is Brett Heap's option (c), of 2026-10-18. State of opensoft/workBenches#165 at the tag: ``.
+- **The one-line install and a checkout** work as before, and their `--version` still prints a repository and ref, `main` by default, and no release.
+- **Windows** is reached through WSL2, as for every bash command here. The package has no native Windows command, and it is a wheel only, with no source distribution.
+
+Tagged on Brett Heap's word (``, "``").
+
+## Full list
+
+- The `v1.1.0` milestone:
+
+## Before the tag
+
+Before the tag, fill in every placeholder below or cut its paragraph, then cut this section, the draft comment at the top and the last line of this file. The GitHub Release is created from this file only after PyPI serves 1.1.0 (FR-042), so these notes never name a version PyPI lacks. Cutting the release follows the RELEASING header of `.github/workflows/release.yml`.
+
+- [ ] **``**: the 40-hex commit the tag `v1.1.0` names, in `opensoft/openRepoTools-code`, on `main` or on `release/v1.1`. It appears in the first paragraph and in the `--version` lines. The build refuses it unless its `tests.yml` runs have all finished and the newest is green.
+- [ ] **``**: that commit's tree digest by `sorted-ls-tree-r-v1`, as the install check's `--version` prints it.
+- [ ] **``**: the UTC date of the tag.
+- [ ] **`` and ``**: the exact UTC second and Brett Heap's own words, as the tag message gives them.
+- [ ] **``**: PyPI's page for `openrepotools` 1.1.0, filled in once PyPI serves it. Neither pypi.org nor test.pypi.org held the project when this was read on 2026-10-18 (both answered 404), and the page is expected at `https://pypi.org/project/openrepotools/1.1.0/`.
+- [ ] **The hook pair, with no placeholder**: #237 landed as opensoft/openRepoTools#247 (`586dea2`, merged 2026-10-18T14:29:20Z), and #237 is in the `v1.1.0` milestone on Brett Heap's word. Re-read the hook-pair paragraph against `main` at the release commit: which entries wait, what the message says, and that our exact guard entry is taken back out of a file that carries it while the pair is deferred (at drafting, `openRepoTools:187` and `:3377-3392`; README says it too).
+- [ ] **``**: the number of pull requests that landed on `main` in `opensoft/openRepoTools` between v1.0.0's commit, `aaa7bc8`, and the release commit, counted at the tag.
+- [ ] **``**: the list under "What else changed since v1.0.0", written from the history from `aaa7bc8` to the release commit. The pin line above it is already written.
+- [ ] **``**: whether opensoft/workBenches#165 has landed, and whether workBenches has taken a version that accepts pipx's link. If it has, rewrite the "One install route or the other" paragraph to say so; if not, keep it as it stands.
+- [ ] **The pin, with no placeholder**: `contracts/openreposhape-pin.yaml` at the release commit still names `190133d`. If the pin moved, correct the pin line, and the commit it names must be on openRepoShape's `main`.
+- [ ] **The milestone**: re-read the `v1.1.0` milestone. It held two issues at drafting time, #228 and #237. Each that lands before the tag gets its line above, and each that does not is moved out by Brett Heap.
+
+This draft is `opensoft/openRepoTools-spec`'s `docs/release-notes/v1.1.0.md`.
diff --git a/specs/006-release-pypi/tasks.md b/specs/006-release-pypi/tasks.md
index 524b66f4..da23cd7b 100644
--- a/specs/006-release-pypi/tasks.md
+++ b/specs/006-release-pypi/tasks.md
@@ -329,13 +329,13 @@ index does not serve.
**Independent Test**: the three verify commands below.
-- [ ] T022 [P] [US6] [S] Create `docs/release-notes/v1.0.0.md` as exactly the output of `gh release view v1.0.0 --repo opensoft/openRepoTools --json body --jq .body` (research R15) — files: `docs/release-notes/v1.0.0.md` — verify: `gh release view v1.0.0 --repo opensoft/openRepoTools --json body --jq .body | cmp - docs/release-notes/v1.0.0.md`
-- [ ] T023 [P] [US6] [S] Draft `docs/release-notes/v1.1.0.md`, as detailed below — files: `docs/release-notes/v1.1.0.md` — verify: `tail -n 1 docs/release-notes/v1.1.0.md | command grep -q 'openRepoTools-spec.*docs/release-notes/v1.1.0.md' && command grep -q '^## Before the tag' docs/release-notes/v1.1.0.md && command grep -q 'pipx install --force openrepotools' docs/release-notes/v1.1.0.md`
+- [X] T022 [P] [US6] [S] Create `docs/release-notes/v1.0.0.md` as exactly the output of `gh release view v1.0.0 --repo opensoft/openRepoTools --json body --jq .body` (research R15) — files: `docs/release-notes/v1.0.0.md` — verify: `gh release view v1.0.0 --repo opensoft/openRepoTools --json body --jq .body | cmp - docs/release-notes/v1.0.0.md`
+- [X] T023 [P] [US6] [S] Draft `docs/release-notes/v1.1.0.md`, as detailed below — files: `docs/release-notes/v1.1.0.md` — verify: `tail -n 1 docs/release-notes/v1.1.0.md | command grep -q 'openRepoTools-spec.*docs/release-notes/v1.1.0.md' && command grep -q '^## Before the tag' docs/release-notes/v1.1.0.md && command grep -q 'pipx install --force openrepotools' docs/release-notes/v1.1.0.md`
- what v1.1.0 brings: the `openrepotools` package on PyPI; `pipx install` and the offline `--install` that leaves `openRepoTools` to pipx; `--version` naming the release; and the `package` job and the release workflow;
- the move from the one-line install (FR-024): `pipx install --force openrepotools`, then `openRepoTools --install`, and why `--force` is needed;
- each value not yet known as a named ``, every one listed in a closing `## Before the tag` checklist;
- a last line naming the file's own path in the spec leg, `opensoft/openRepoTools-spec`'s `docs/release-notes/v1.1.0.md`.
-- [ ] T024 [P] [US6] [S] Correct `README.md`, as detailed below — files: `README.md`, `tests/test_repo_hygiene.py` — verify: `! command grep -qE 'pipx install( --force)? openrepotools' README.md && bash tests/run.sh -k 'repo_hygiene or readme'`
+- [X] T024 [P] [US6] [S] Correct `README.md`, as detailed below — files: `README.md`, `tests/test_repo_hygiene.py`, `tests/test_test_roots.py` — verify: `! command grep -qE 'pipx install( --force)? openrepotools' README.md && bash tests/run.sh -k 'repo_hygiene or readme'`
- rewrite the paragraph that says "There is no version number here". It now says that releases are annotated tags, that the version comes only from the tag, and what `--version` prints, stamped and unstamped, quoting contracts/cli-output.md's first lines;
- correct three passages for a workstation with pipx's link: the SEVENTEEN-files count (sixteen placed and one left to pipx), the symlink paragraph (pipx's `openRepoTools` is the one link skipped), and the artifact-count invariant, "Twenty-nine artifacts always, plus the two hook entries once the path they call answers `guard`" since #237 (twenty-eight placed always, one left to pipx and 28 receipt rows, with the hook pair still on top once that path answers, so that nothing contradicts README's #237 text);
- add no pipx install route;
diff --git a/tests/test_repo_hygiene.py b/tests/test_repo_hygiene.py
index f080c8fb..1addfd59 100644
--- a/tests/test_repo_hygiene.py
+++ b/tests/test_repo_hygiene.py
@@ -2482,12 +2482,90 @@ def test_readme_is_short_enough_to_be_read():
lockstep-invariant section the leg pins' `resync_runbook:` names
(`README.md#the-lockstep-invariant`) and the three-repository front door.
Today's single repository keeps 486.
+
+ 486 -> 505 HERE, on 2026-10-18
+ (opensoft/openRepoTools #228, feature 006, T024), for what a workstation
+ with pipx's link reads. Twenty lines, each one a thing a person MEETS: the
+ version paragraph, 3 -> 7 (FR-043), which said "no version number" and
+ now says that a release is an annotated tag, that the version comes only
+ from the tag, and what `--version` prints stamped, unstamped and from a
+ checkout; the one sentence that a workstation uses one install route or
+ the other until workBenches#165 lets its `setup-estate-commands.sh`
+ accept pipx's link, with its blank (FR-047, Brett Heap's decision of this
+ day), 4; the paragraph that says what `--install` does beside that link,
+ with its blank, 7; the sixteen-of-seventeen pointer on the file count and
+ the one-link-skipped sentence in the symlink passage, 1 and 1; and the
+ count invariant's twenty-eight placed, one left to pipx and 28 receipt
+ rows, 3 (FR-044). No pipx install route is added (FR-045). Every dated
+ entry above stays.
+ THE ASSEMBLY'S 510 DOES NOT MOVE HERE, although the 2026-10-08 entry makes
+ it this count plus T006's twenty-four, which would be 529. Its file does
+ not exist yet, and the README part of `assembly-0001` no longer applies to
+ today's README, at three hunks: the one at `README.md:237`, which adds the
+ assembly's install sentence, fails against this entry's own rewrite of the
+ file-count sentence; the one at `README.md:260`, which moves `tests/run.sh`
+ under `code/`, fails against this entry's one-link-skipped sentence in the
+ symlink passage; and the one at `README.md:300`, which does the same for
+ `commands/swap.md`, fails against #237's (#247's) rewrite of the count
+ paragraph. None of the three can be counted at this head, so
+ 529 would be arithmetic over a patch that does not apply. The re-cut that
+ makes the assembly's file again sets its cap, as the 2026-10-18 AGENTS.md
+ entry above says for its 347, and `tests/test_test_roots.py` holds 510
+ until then.
"""
lines = doc("README.md").read_text().splitlines()
- cap = 486 if ROOTS.assembly is None else 510
+ cap = 505 if ROOTS.assembly is None else 510
assert len(lines) <= cap, f"README.md is {len(lines)} lines; the cap is {cap}"
+def test_readme_says_what_a_release_is_and_offers_no_package_route():
+ """What T024 put in README is held here, because the line cap is the only
+ thing that bites otherwise (the review of PR 4 of #228, S2: a README
+ that offered the pipx route, dropped the one-route sentence, took back
+ "no version number" or counted 17 of 17 beside pipx's link, each stayed
+ green).
+
+ SC-009 and FR-045: README names no package route. No line holds `pipx
+ install` or `pip install` before `openrepotools` in any spelling of the
+ name, which is the check T024's own verify makes once, and makes only for
+ the lower-case `pipx install openrepotools`. FR-047: the install section
+ cites opensoft/workBenches#165. FR-043: the version paragraph holds the
+ three first lines `--version` prints, stamped, unstamped and from a
+ checkout, spelled as `contracts/cli-output.md` gives them. FR-044: the
+ count line beside pipx's link and the count invariant's receipt rows.
+
+ FR-045's later PR (the change's tasks.md 6.4) rewrites this test: it adds
+ the pipx route, so the first assertion goes with it, and the other three
+ follow the text it moves."""
+ text = doc("README.md").read_text(encoding="utf-8")
+
+ route = re.compile(r"(?i)\bpipx?\s+install\b.*\bopenrepotools\b")
+ offered = [line for line in text.splitlines() if route.search(line)]
+ assert not offered, (
+ "README.md offers a package route, which FR-045 and SC-009 forbid "
+ f"until the workBenches condition holds: {offered}")
+
+ install = re.search(r"(?ms)^## Install\n(.*?)(?=^## )", text)
+ assert install, "README.md has no `## Install` section"
+ assert "opensoft/workBenches#165" in install.group(1), (
+ "README.md's install section does not cite opensoft/workBenches#165, "
+ "which FR-047 requires beside the one-route sentence")
+
+ stamped = "openRepoTools ( @ )"
+ paragraphs = [" ".join(p.split()) for p in re.split(r"\n\s*\n", text)]
+ version = [p for p in paragraphs if stamped in p]
+ assert version, f"README.md has no paragraph holding `{stamped}`"
+ for first_line in (stamped, "openRepoTools 0.0.0 (an unstamped package)",
+ "openRepoTools ( @ ][)"):
+ assert first_line in version[0], (
+ f"README.md's version paragraph does not hold `{first_line}`")
+
+ for count in ("16 of 17 placed in , and 1 left to pipx",
+ "28 receipt rows"):
+ assert count in text, (
+ f"README.md does not say `{count}` beside pipx's link (FR-044)")
+
+
#: A host-absolute path baked into a committed file (the estate's Rule 1):
#: it names one machine, one user, or one session, and silently breaks the
#: moment the repository moves to a different machine or a different user's
diff --git a/tests/test_test_roots.py b/tests/test_test_roots.py
index 0b8ab198..f9c0dca9 100644
--- a/tests/test_test_roots.py
+++ b/tests/test_test_roots.py
@@ -360,11 +360,15 @@ def test_the_pin_rules_are_read_with_the_assemblys_code_prefix(tmp_path, prefixe
def test_the_length_caps_are_the_assembly_roots_where_one_is_named(tmp_path):
- """At an assembly root the two caps are 347 and 510 — this repository's 323
- and 486 plus the twenty-four lines each that T006's root guidance adds
- there — and they are asked of the ASSEMBLY's files: one line over FAILS,
- the cap itself passes. Red at `origin/main`, which counted this checkout's
- own README and AGENTS.md whatever was named."""
+ """At an assembly root the two caps are 347 and 510. The dated entries in
+ `tests/test_repo_hygiene.py` made them from this repository's then caps,
+ 323 and 486, and the twenty-four lines each that T006's root guidance adds
+ there, and they stand there while this repository's own are 330 and 505 at
+ this head: `assembly-0001` no longer applies to today's AGENTS.md or
+ README, so the re-cut that makes the assembly's files again sets them. And
+ they are asked of the ASSEMBLY's files: one line over FAILS, the cap itself
+ passes. Red at `origin/main`, which counted this checkout's own README and
+ AGENTS.md whatever was named."""
def lines(n: int) -> str:
return "".join(f"line {i}\n" for i in range(n))
at_cap = assembly(tmp_path / "at", readme=lines(510))
]