diff --git a/AGENTS.md b/AGENTS.md
index 3e8105c6..207bdd26 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -23,9 +23,9 @@ hand-edited; `tests/test_cli_reference.py` fails when the two disagree.
types `yes` at its prompt; you never answer it for them. No terminal: section 2.
**Never answer an install prompt.** The preflight asks `Type yes to continue:`
-for each thing it offers to run — an install, `gh auth login`, `gh auth
-setup-git`. The human types it; you never install software or log in for them.
-The one-line install (`install.sh`) is theirs to run: relay it, never run it.
+for each offer — an install, `gh auth login`, `gh auth setup-git` — and the
+human types it; you never install software or log in for them. The one-line
+`install.sh` and `pipx install openreposhape` are theirs: relay, never run.
**Never pass `--allow-upstream-org` on your own initiative.** It guards only
`--org opensoft`, the namespace of openRepoShape's upstream owner. If setup.sh
@@ -60,11 +60,13 @@ curl -fsSL https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup.s
--visibility The fastest path — macOS, Linux, WSL2, Git Bash
in-flight work to another workstation, are
opensoft/openRepoTools’ and have a one-liner of
their own.
In PowerShell, without WSL2 or Git Bash, the way in is two commands
- instead, because there is nothing to install — see Start
+ The second way, from PyPI, is Setup Python is the one manual install. Install Python 3.9 or newer from
+ Python is the one manual install the flow needs. Install Python 3.9 or newer from
python.org/downloads, ticking Add
python.exe to PATH — its own checkbox, and a different one from py
launcher, which the installer ticks for you. Then The preflight names anything missing and, for Or, where raw downloads are blocked, the same bytes through the API: That places a native Two commands rather than one pipe, on purpose. Windows PowerShell 5.1
— still the default shell on a stock install — re-encodes piped text as
@@ -810,6 +826,10 @@ In Git Bash, the one-line install runs too: It places the bash Each way installs ONE command into The second way, from PyPI, wherever
+ It places an It checks this machine, offers what is missing and creates nothing. It exits 0 when
- everything is present and 1 when it is not; it exits 2 instead, before any check, when
- It exits 0 when everything is present and 1 when it is not, and creates nothing; it
- exits 2 instead, before any check, when It exits 0 when everything is present and 1 when it is not, and creates nothing; the
+ bash command exits 2 instead, before any check, when It exits 0 when everything is present and 1 when it is not.
It exits 0 when everything is present and 1 when it is not; it exits 2
- instead, before any check, when It exits 0 when everything is present and 1 when it is not; the bash command
+ exits 2 instead, before any check, when With pipx’s command, the same flag on
+ Setup Setup Reference Reference A repository that already exists is converted in place. It KEEPS ITS NAME,
- which A repository that already exists is converted in place. It KEEPS ITS NAME
+ (which Ambiguous is an answer, and no source line is overwritten.
Reference Reference For an assembly root the rows are naming, manifest and
pins (the project’s OWN validators when it carries them — they
are the pinned copies, which is the point — else the template copies, and the row says
@@ -1613,8 +1656,10 @@ Exit 3 is usage or environment (a flag argparse rejects is exit 2) — nothing about
it is a statement about the tree you pointed at: no such path, a refused
Reference Python 3 standard library only. No Python 3 standard library only. No tool needs ` beside
+ `members/ `, a member mounted by hand with no row, a `.gitmodules`
+ section with no url), a `.gitmodules` that git reads otherwise than
+ `git config` does (two sections for one path, a section name git
+ ignores, a value beginning with `-`), and a `.gitmodules` a merge left
+ unmerged now fail, and the line names the cause and the cure. This
+ changes an exit code a script can see: ok became exit 1 (#682, #648).
+
+### shape-doctor.py
+
+- **Both ways in.** A git repository that is not a shape root is told of
+ the adoption and the scaffold, and the adoption line ends
+ `--org pipx install openreposhape:
+ the same command and four more, named for the tools, with git-filter-repo
+ as their dependency. In PowerShell, without WSL2 or Git Bash, it is the first way, and
+ two commands that install nothing are the other — see Start
here.The one thing to read before electing it
Start here
@@ -708,24 +710,30 @@ The tool offers the rest
What this machine needs
- git from
git-scm.com/downloads, and gh
- with winget install --id GitHub.cli -e.winget install --id GitHub.cli -e. The commands need one more manual
+ install, pipx: python -m pip install --user pipx, then
+ python -m pipx ensurepath and a new terminal.
The tool offers the rest
gh, its login and its
credential helper, prints the command for THIS machine and asks once — Type yes to continue: —
then runs it and carries on. On Windows those offers are the winget ones:
winget install --id GitHub.cli -e for gh and, only under
- py setup-project.py --preflight, winget install --id Git.Git -e
- for git: a run refuses a missing git before its preflight starts.
- There is no openRepoShape command to install in PowerShell: that two-liner
- IS the Windows way in there. Git Bash, the bash that Git for Windows installs, runs the
- openRepoShape command and the one-line install, and CI tests both there, but
- for a typed yes: CI has no terminal, and in Git Bash the preflight that the
- one-line install ends with cannot take one, so none of its offers can be accepted there.
+ py setup-project.py --preflight or pipx’s
+ openRepoShape --preflight, which runs the same preflight,
+ winget install --id Git.Git -e for git: a run refuses a missing
+ git before its preflight starts.
+ In PowerShell the openRepoShape command comes from
+ pipx install openreposhape, a native openRepoShape.exe beside
+ four tool commands, and the two-liner below is the way in that installs nothing. Git
+ Bash, the bash that Git for Windows installs, runs the bash openRepoShape
+ command and the one-line install, and CI tests both there, but for a typed
+ yes: CI has no terminal, and in Git Bash the preflight that the one-line
+ install ends with cannot take one, so none of its offers can be accepted there.
Printed commands are quoted for a POSIX shell, so paste them into Git Bash. Only
shape-doctor.py’s lines, setup-project.py’s family
lines and family.py’s NEXT, switch -c,
@@ -777,7 +785,7 @@ Make one machine setting first
Install the
openRepoShape command, onceInstall the
- openRepoShape command, once, inside WSLFetch the flow, in the directory the new project should land in
+ Install with pipx, fetch the flow, or use Git Bash’s one line
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstall.sh is attached to every release and
installs the newest, with no gh and no login. Its command is PINNED to the
@@ -801,6 +809,14 @@ Fetch the flow, in the directory the new project should
gh api repos/opensoft/openRepoShape/contents/openRepoShape \
-H 'Accept: application/vnd.github.raw' | bash -s -- --installpipx install openreposhape
+openRepoShape <Project> --org <your-org>openRepoShape.exe with the same
+ modes as on the other tabs, four commands that run the tools of those names, and
+ git-filter-repo as their dependency; the second line is then the whole run,
+ and openRepoShape --preflight the machine check. Or, installing nothing,
+ fetch the flow into the directory the new project should land in, and the steps below
+ run it:Invoke-WebRequest https://raw.githubusercontent.com/opensoft/openRepoShape/main/setup-project.py -OutFile setup-project.pyFetch the flow, in the directory the new project should
an exhausted stdin, so it cannot ask for the one
yes before three
repositories exist — it refuses instead. Downloading the file first leaves stdin
attached to the terminal you are typing at.
+ curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashopenRepoShape, whose preflight cannot
+ take a typed yes in Git Bash, so none of its offers can be accepted there.~/.local/bin,
idempotently — a second run prints unchanged — and prints the
export PATH=… line if that directory is not on PATH.
@@ -836,6 +856,19 @@ Fetch the flow, in the directory the new project should
its usage and exits 1 — the four things it does, one line each: install
it, check the machine, diagnose a repository, scaffold a
project.
+
pipx is (sudo apt-get install -y pipx on Debian and Ubuntu,
+ brew install pipx on macOS, then pipx ensurepath):pipx install openreposhapeopenRepoShape with the same
+ modes, in Python, whose --install refuses and names pipx upgrade
+ openreposhape, and four commands that run the tools of those names with every
+ argument unchanged — scaffold-project, adopt-project,
+ update-shape, shape-doctor, so docs/cli.md is
+ their reference — with git-filter-repo 2.47.0 as their dependency.
+ They run a clone of the standard at the release’s commit, kept in your cache and
+ checked against its digest before every run. Use one installer or the other: both place
+ ~/.local/bin/openRepoShape.Check the machine, creating nothing
openRepoShape --preflightpy setup-project.py --preflightgit or a Python 3.9 or newer is missing.git or a Python 3.9 or newer is
- missing. This is also where a python3 below 3.9 is named.git or a Python 3.9 or newer is missing.
+ git or a Python 3.9 or
+ newer is missing. This is also where a python3 below 3.9 is named.python setup-project.py --preflight is the same command on a machine with no
py: Python from the Microsoft Store installs python and no
- launcher at all.git or a Python 3.9 or newer is missing.openRepoShape --preflight runs
+ the same check.
+ git or a Python 3.9 or newer is
+ missing.Rehearse first, creating nothing
scripts/bootstrap.py — and gh is never called. On Windows
the same flag goes on the Python entry point:
py setup-project.py Atlas --org <your-org> --local-remote-dir .\rehearsalopenRepoShape Atlas --org <your-org>.openRepoShape Atlas --org <your-org> --local-remote-dir ./rehearsal--local-remote-dir <dir> runs the whole of the real thing against
three BARE repositories in <dir>: the same preflight (where
@@ -938,7 +975,9 @@ Run it
and asks once, Type yes to continue:; type yes. Afterwards
./<Project> is the cloned root.
python setup-project.py <Project> --org <your-org> is the
- same command on a machine with no py. setup-project.py is the
+ same command on a machine with no py, and pipx’s
+ openRepoShape <Project> --org <your-org> the same run again.
+ setup-project.py is the
flow itself, and setup.sh a shim over it: the same preflight, the same
naming check, the same plan, the same one yes, the same self-bootstrap
outside a checkout. It never needs make and never looks for a
@@ -1048,7 +1087,7 @@ What you have afterwards — the same on every platform
If it fails part-way
@@ -1085,9 +1124,9 @@ If it fails part-way
Reading private legs in CI
@@ -1147,9 +1186,9 @@ Reading private legs in CI
The three legs, and naming
@@ -1203,7 +1242,10 @@ The three legs, and naming
assembly root named for the pinned repository — else by gh api, with the
operator’s gh credentials, when gh can read it. Finding
neither is a named SKIP, exit 0: no checkout and no network does not make a descent a lie. A
- checkout that cannot answer is the finding neutral-pin-source-unreadable.
+ digest that recomputes differently is the finding
+ neutral-pin-digest-mismatch (exit 1), and a checkout that cannot answer is the
+ finding neutral-pin-source-unreadable; a declared pin file that is not there is
+ the refusal neutral-pin-missing (exit 2).
make bootstrap, bump-leg.py and update-shape.py apply
run this validator in the operator’s environment, so each makes that call too; none
takes --pin-source.
@@ -1227,12 +1269,12 @@ The three legs, and naming
Adopting an existing repository
- --project must be, ITS IDENTITY AND ITS FULL HISTORY and becomes the assembly root, while
+ --project must be), ITS IDENTITY AND ITS FULL HISTORY, and becomes the assembly root, while
<Project>-spec and <Project>-code are NEW repositories
extracted with history-preserving filters. The source is never deleted, never renamed and
never force-pushed; the only change to its history is ONE split commit, arriving on a branch
@@ -1241,10 +1283,11 @@ Adopting an existing repository
# ... a human or an AI answers every `review_required` entry in the plan
./adopt-project.py check --plan adoption-plan.yaml
./adopt-project.py execute --plan adoption-plan.yaml --yesgit filter-repo is a hard dependency —
- pip install git-filter-repo, or the same name from apt or brew. It is
- deliberately not vendored: history extraction has one correct implementation, and a copy is a
- second one that drifts.git filter-repo is a hard dependency: the one-line install
+ installs it or prints the command for this machine, and
+ pipx install openreposhape brings it for its adopt-project. It is
+ deliberately not vendored: history extraction has one correct implementation, and a copy is
+ a second one that drifts.contracts/path-classification.yaml maps path globs to
spec | code | root | ambiguous, first match wins, each with a reason. The fourth
@@ -1388,7 +1431,7 @@ The InkRouter example
Bootstrap is COPIED into the project, not fetched
Keeping a project’s shape current
@@ -1550,7 +1593,7 @@ Keeping a project’s shape current
Is this repository compliant?
@@ -1565,8 +1608,8 @@ openRepoShape --doctorIs this repository compliant?
the one that writes at all, and it writes one file, at the path you give: a relative one
lands in the directory you stand in, inside the repository when you stand there. It
compares against the checkout it is RUN FROM, so the verdict is offline. Installed, it
- clones the standard into a temporary directory and KEEPS it for the NEXT lines that name
- it: its last line names it, safe to delete after they run.
+ clones the standard and KEEPS the clone for the NEXT lines that name it: its last line
+ names it, safe to delete after they run.
openRepoShape --doctorIs this repository compliant?
openRepoShape --doctor--placement-plan, a shape-doctor.py not in a checkout of the
- standard, or a pin naming a commit this checkout does not carry
- (CANNOT ANSWER). Drift outranks a red validator on the
+ standard, or this checkout unable to answer (CANNOT ANSWER): a
+ pin’s commit it lacks, a partial clone missing what is compared, a read that fails, a
+ validator of its own that crashed or could not be run, or its own naming policy not
+ loading. Drift outranks a red validator on the
verdict line, deliberately: an edited shape copy is exactly what makes
validate-pins.py red, so answering INVALID (pins) would send the
reader at the symptom while the fix is update-shape.py. The pins row is printed
@@ -1685,7 +1730,7 @@ Is this repository compliant?
openRepoShape --doctorThe degrade rule
Layout of this repository
@@ -1855,8 +1900,9 @@ Layout of this repository
Layout of this repository
pip, no packaging, no
- engineering-domain dependency — the shape must run where nothing can be installed.
+ pip or any
+ dependency, so the shape runs where nothing can be installed; the
+ openreposhape package is a second way in, never a requirement.
tests/test_repo_hygiene.py enforces it. Changes to this repository are tested with
python3 -m pytest tests -q, which scaffolds into bare repositories in a temporary
directory; never by creating a real GitHub repository.For AI assistants
Type yes to continue: for each thing it offers to run — an install,
gh auth login, gh auth setup-git. The human types it; you never
- install software or log in for them. The one-line install (install.sh) is
- theirs to run: relay it, never run it.install.sh) and
+ pipx install openreposhape are theirs to run: relay them, never run them.--allow-upstream-org on your own initiative. If
setup.sh refuses because the organisation you were given resolves to opensoft, STOP and relay
what it said; the fix is almost always an explicit --org <their-org>.(.*?)", page, re.S)
+ for line in block.splitlines() if "download/install.sh" in line]
+ assert len(copies) >= 3, copies
+ assert set(copies) == {ONE_LINE_INSTALL}, copies
+
+
#: Where each document describes `openRepoShape --doctor`: the file, the
#: line its passage begins with, what ends it, and whether it describes the
#: installed form's kept clone. `docs/cli.md` has two: the doctor's purpose in