From 0f808df47a13c7ea2df572b9a9c49af5fe5d7c44 Mon Sep 17 00:00:00 2001 From: Brett Heap Date: Wed, 14 Oct 2026 15:37:53 +0000 Subject: [PATCH 1/7] tests/test_repo_hygiene.py and docs/handbook.html: each handbook section records the hash of every README section it reads, and README.md's line cap is 1422 plus one file per raise in tests/readme-cap.d/, so two README pull requests no longer conflict on the footer's one blob or the cap's one line, and a stale page names exactly which sections to re-read (#519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every README pull request had to edit the same two places: the handbook footer's `README.md blob `, which test_repo_hygiene.py held equal to README.md's whole blob, with an "added (#N)" clause beside it; and the `assert len(lines) <= 1422` line in the same file, with a dated comment for every raise. Any two open at once conflicted on both. Brett Heap's ruling 29 (2026-10-14T15:18:19Z), "Hash per section (Recommended)": "Each handbook section records the hash of the README section it covers; a failing test names exactly which handbook sections to re-read. The single README hash and the per-PR "added (#N)" notes in the footer retire. Each line-cap raise becomes its own small file with its reason." The handbook. README.md is cut into segments: the preamble, then one per `## ` heading up to the next, fenced `## ` lines skipped as `_readme_h2s` always did (it now reads its headings from the same segmenter). A segment's fingerprint is the git blob sha1 of its text with CRLF read as LF, computed in Python and never by `git hash-object`, whose path form applies .gitattributes and whose --stdin form does not. Every page part that reads a segment carries one comment per segment it reads, ``; the masthead gains `id="masthead"` so a failure can name it. The test's old check (4) becomes (a) every marker names a segment that exists, (b) every segment is read by a marker, (c) every marker's blob is its segment's fingerprint now, naming the segment, every page section that reads it and the new value, with the #89 re-read wording, and (d) every heading README_HEADING_TO_HANDBOOK_ID maps has a marker in its mapped section and every page section carries one unless HANDBOOK_SECTION_READING_NO_README (`agents`, which reads AGENTS.md) says otherwise. A mistyped marker, one outside every page part, a repeat in one section, and two segments' markers on adjacent lines (git conflicts on adjacent-line edits, which is what the markers exist to avoid) are refused too. Every check now reports together, so a renamed heading is told about the table and the markers in one run. HANDBOOK_README_BLOB is gone; HANDBOOK_RETIRED_README_BLOB asserts the footer's old fact stays gone. The mapping, read from both documents: preamble by masthead and confers; Starting by masthead, start, partway, ci (the worked example's private-legs paragraph), legs (the naming-check command), doublepin (editing a pin by hand) and park (openRepoTools' install line); The three legs by masthead, ci and legs; The double pin by masthead and doublepin; Keeping a project's shape current by current and doctor; every other section by the one page section README_HEADING_TO_HANDBOOK_ID names. The footer's prose is frozen as history at #502, and one sentence now says each section records the README section it reads and a test checks those records. The cap is README_CAP_BASE (1422) plus the raises in tests/readme-cap.d/: one `.txt` per README-growing change, line 1 `+N`, line 2 an ISO date, then the reason. Every file is validated and a malformed one fails with its path; the message carries the format, so the directory needs no README and does not exist until the first raise. The docstring and dated comments stay as history, with a note that raises from 2026-10-14 on go in the ledger. README.md is unchanged: the page's blob was README's (ae817c5), so the fingerprints recorded are the README as the page already read it. tests/test_readme_sections.py holds the machinery to fixture text: the segmenter (fences, preamble, CRLF), the fingerprint against git's own blob hashes, each failure message, and the ledger. Co-Authored-By: Claude Opus 5.5 --- docs/handbook.html | 35 +- tests/test_agents_md_codes.py | 4 +- tests/test_readme_sections.py | 345 +++++++++++++++++++ tests/test_repo_hygiene.py | 605 +++++++++++++++++++++++++++++----- 4 files changed, 894 insertions(+), 95 deletions(-) create mode 100644 tests/test_readme_sections.py diff --git a/docs/handbook.html b/docs/handbook.html index 5b02b5cb..3cf9ceec 100644 --- a/docs/handbook.html +++ b/docs/handbook.html @@ -468,7 +468,7 @@
-
+

Ratified standard · Apache-2.0

openRepoShape

@@ -476,6 +476,7 @@

openRepoShape

+

The mechanics of a project that spans several repositories: what they are called, how they are pinned to each other, how one clone brings the whole project down, and what an AI assistant does when a human says “scaffold a new project with @@ -484,6 +485,7 @@

openRepoShape

several of them is held together by convention or by nothing. This repository is the convention, as data and as running code.

+

The fastest path — macOS, Linux, WSL2

curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/openRepoShape \
     | bash -s -- --install
@@ -499,6 +501,7 @@ 

The fastest path — macOS, Linux, WSL2

here.

+
The three legs and the three pins @@ -541,6 +544,7 @@

The fastest path — macOS, Linux, WSL2

code/ the implementation and its tests +
Each leg is pinned twice, in the same commit: by the gitlink git records for the submodule, and by contracts/<role>-pin.yaml.
@@ -548,6 +552,7 @@

The fastest path — macOS, Linux, WSL2

+

The one thing to read before electing it

The shape is elective and confers nothing. Electing it changes no gate, @@ -596,6 +601,7 @@

The one thing to read before electing it

+

Setup

Start here

Pick your machine. Each panel says what the machine needs, how the tool offers to supply @@ -1005,6 +1011,7 @@

What you have afterwards — the same on every platform

+

Setup

If it fails part-way

Nothing is created before the typed yes: the preflight, the naming check and @@ -1037,6 +1044,7 @@

If it fails part-way

+

Setup

Reading private legs in CI

The validate workflow’s default GITHUB_TOKEN cannot clone a @@ -1055,6 +1063,7 @@

Reading private legs in CI

Fallback, for an org without the App yet: a fine-grained SHAPE_LEGS_TOKEN PAT, contents:read on the LEGS ONLY.

gh secret set SHAPE_LEGS_TOKEN --org <your-org> --body '<token>'
+

On the Free plan, use --repo, not --org. GitHub delivers org secrets only to PUBLIC repositories, so on a private one secrets.SHAPE_LEGS_APP_ID is silently empty and validate goes GREEN @@ -1090,6 +1099,7 @@

Reading private legs in CI

+

Reference

The three legs, and naming

@@ -1155,6 +1165,7 @@

The three legs, and naming

computed and thrown away. <X>-Install is admitted into nothing at all: it carries a hyphen, so it satisfies no leg form to be admitted into, and is still refused as any leg.

+
python3 scripts/validate-repository-naming.py --explain Atlas Atlas-spec Atlas-code

Ruled by Brett Heap on 2026-09-02: descendant only if it pins open<Product> — and a descendant MAY carry legs, so @@ -1165,6 +1176,7 @@

The three legs, and naming

+

Reference

Adopting an existing repository

A repository that already exists is converted in place. It KEEPS ITS NAME, @@ -1209,6 +1221,7 @@

Adopting an existing repository

+

Reference

Families: a holder for projects that ship separately

A family is a repository that pins other projects’ assembly @@ -1312,6 +1325,7 @@

The InkRouter example

+

Reference

The double pin, and the lockstep invariant

Each leg is pinned TWICE, in the same commit: by the gitlink git records @@ -1366,6 +1380,7 @@

Advancing a leg

validator that goes red — rolling the pin, the workflow files, the leg’s checkout and the index back first, so a refusal leaves the tree exactly as it was found.

+

The leg is left DETACHED at the new commit; make bootstrap re-places it on its tracking branch AT the new pin, or says why not. Editing the pin file by hand to make the validator agree is what the validator exists to refuse.

@@ -1373,6 +1388,7 @@

Advancing a leg

+

Reference

Bootstrap is COPIED into the project, not fetched

scripts/bootstrap.py, the validators and AGENTS-shape.md are @@ -1403,6 +1419,7 @@

Bootstrap is COPIED into the project, not fetched

+

Reference

Keeping a project’s shape current

The copies are the trade: a project runs its own gate, which never consults this standard @@ -1456,6 +1473,7 @@

Keeping a project’s shape current

+

Reference

Is this repository compliant? openRepoShape --doctor

Five commands answered that question and the order of them was tribal knowledge. @@ -1587,6 +1605,7 @@

Is this repository compliant? openRepoShape --doctor

+

Reference

Carrying in-flight work to another workstation

Two verbs on the assembly root, for the day the machine your half-finished work is on is @@ -1606,6 +1625,7 @@

park <Name> and resume <Name>, from an and resume on your PATH — one line, which openRepoShape --install prints and never runs, because a standard that reached into its own tools to finish an install would depend on them at runtime:

+
curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoTools/main/openRepoTools | bash -s -- --install

— so the whole of that sequence is one word from any folder on either machine:

park InkRouter                  # on workstation A, from any folder
@@ -1720,6 +1740,7 @@ 

Who implements it, and what the record is

+

Reference

The degrade rule

make bootstrap looks for a wallet review-authority register at @@ -1734,6 +1755,7 @@

The degrade rule

+

Reference

Layout of this repository

@@ -1847,6 +1869,7 @@

For AI assistants

+

Licence Apache-2.0. See LICENSE in the repository.

github.com/opensoft/openRepoShape

First cut from README.md at c093813 (#71); regenerated at cbca5b4 (#89); @@ -1880,10 +1903,12 @@

For AI assistants

never the tracking branch, and printing the push of it, added (#487); the installed doctor keeping its clone of the standard, which its NEXT lines run from, added (#500); an adoption’s --project as the source’s own name, added (#499); the - merged-away verdict, whose row apply drops, added (#502) — a - page cannot name the commit that contains it, and the blob below is the fact a test checks. - README.md blob ae817c5fdb324ff60440b959a15f3ad3b2a18b43. Every command, flag, exit code - and file name on this page is quoted from that README and from AGENTS.md.

+ merged-away verdict, whose row apply drops, added (#502). That + list is history, and it ends there.

+

Each section of this page records, in a comment inside it, the README + section it reads and the hash of that section as it read it, and a test checks those + records against the README (#519). Every command, flag, exit code and file name on this + page is quoted from the README and from AGENTS.md.

diff --git a/tests/test_agents_md_codes.py b/tests/test_agents_md_codes.py index 89b1b343..171db24a 100644 --- a/tests/test_agents_md_codes.py +++ b/tests/test_agents_md_codes.py @@ -16,8 +16,8 @@ This is the guard, in both directions, and it is the kind this repository already keeps for other copied facts: the Windows two-liner's file names, the -handbook's README blob, and `docs/cli.md` against `--help` -(`tests/test_cli_reference.py`). +handbook's hash of each README section it reads, and `docs/cli.md` against +`--help` (`tests/test_cli_reference.py`). WHICH NAMES. Every backticked token in the document whose whole text matches `CODE_SHAPE` (`[a-z]+(-[a-z0-9]+)+`), never a pattern read out of a refusal diff --git a/tests/test_readme_sections.py b/tests/test_readme_sections.py new file mode 100644 index 00000000..883bf03e --- /dev/null +++ b/tests/test_readme_sections.py @@ -0,0 +1,345 @@ +# SPDX-License-Identifier: Apache-2.0 +"""The machinery #519 put behind the handbook and README.md's line cap. + +`tests/test_repo_hygiene.py` runs it on the real files: README.md cut into +segments, each page section of `docs/handbook.html` recording the segments +it reads and their hashes, and the cap read from `tests/readme-cap.d/`. Those +two tests say whether the files agree TODAY; these say whether the checks +would notice when they stop agreeing, and what they would say. So every case +here is a few lines of fixture text, never the real files, and every message +the checks print is held to what it must tell the person who reads it. + +OS-neutral on purpose: a fixture spells its own line endings, so nothing here +depends on how the checkout wrote README.md (Windows CI checks out LF only +because `.gitattributes` says so), and nothing runs git. +""" + +from __future__ import annotations + +import pytest + +from test_repo_hygiene import ( + README_CAP_BASE, README_CAP_FORMAT, handbook_marker_problems, + readme_cap_ledger_refusal, readme_cap_raises, readme_cap_refusal, + readme_cap_sum, readme_fingerprint, readme_segments) + +#: A README in miniature: a preamble, a `## ` heading holding a `### ` and a +#: fenced `## ` line, and a heading with a colon in it holding a tilde fence. +README = ( + "# Thing\n" + "\n" + "Intro.\n" + "\n" + "## First\n" + "\n" + "```sh\n" + "## not a heading, a shell comment\n" + "```\n" + "\n" + "### Sub\n" + "\n" + "Body.\n" + "\n" + "## Second: with a colon\n" + "\n" + "~~~\n" + "## nor this\n" + "~~~\n" + "Tail.\n" +) + +#: Which README segments each part of the fixture page reads. `masthead` +#: and `licence` become a `
` and a `