Repository navigation
Publish openRepoShape to TestPyPI and PyPI as openreposhape, so one pipx install puts the tools and git-filter-repo on any machine (design first) #563
Description
Goal: publish openRepoShape as a Python package named openreposhape, so that one pipx install openreposhape puts the tools on any machine, with git-filter-repo alongside. The design comes first, then the code.
Decision. Brett Heap, 2026-10-14:
- At 20:32:42Z: "should we deploy this to a py lib site? we did that with other py tools".
- At 20:35:56Z, after lane 3 recommended a v1.1.0 issue with a design first: "file it in v1.1.0".
Why
- One install command on every machine, on the host and in every container. Today a person installs the
openRepoShapelauncher withgh api … | bash -s -- --install(openRepoShape:5-11), and installs git-filter-repo separately on each machine. - adopt needs
git-filter-repo. adopt-project.py:6611-6622 refuses without it. adopt-project.py:548-554 tells people how to install it and keeps it un-vendored on purpose. A package can declare it as a dependency. - The workBenches image could install a released version. Today it vendors two files of this repository at a pinned commit (devBenches/base-image/upstream-pin.yaml, block
openreposhape), and ruling 33 adds git-filter-repo to its pip list. Once this ships, the image can runpip install openreposhape==<version>instead. - The precedent is openDox. It publishes
opendox0.1.0 to TestPyPI and PyPI from opensoft/openDox-code.github/workflows/release.yml:- a
workflow_dispatchwith a version input; - it builds once, publishes to TestPyPI, installs from TestPyPI into a fresh environment, then publishes the same bytes to PyPI;
- it uses trusted publishing (
id-token: write) through thetestpypiandpypienvironments, each with a deployment limit.
- a
Constraints the design must keep
- "Clone it and run it" keeps working with
python3andgitonly. scripts/repo_shape.py:5-10 says the tools use the standard library only, on purpose, for an organisation with "no permission topip installanything".- The package is a second way in, never a requirement.
- The tools gain no runtime dependency.
- git-filter-repo stays needed by adopt only.
- A pin keeps naming one commit of this repository.
- A project's
project.yamlpins the standard byrepository,commit, and asorted-ls-tree-r-v1digest of that commit's tree (templates/assembly-root/project.yaml:74-81; scripts/repo_shape.py:76 and 929-944). - scaffold-project.py:870 reads that commit with
git rev-parse HEADin its own checkout. - A wheel has no checkout. So a packaged run must carry the commit and digest it was built from, and the build must refuse when they differ from the tag's own. scripts/repo_shape.py:954 already reads the same digest from the forge.
- A project's
- Python 3.9 or newer, on Linux, macOS and Windows, as now (setup.sh:76; README.md:365).
Questions the design answers. Brett Heap approves the design on this issue before any packaging code lands.
- A thin package or a full one?
- Thin: the wheel gives a Python command for the launcher's work and declares git-filter-repo. At run time it clones this repository at the commit it was built from, as the launcher already does (openRepoShape:31 and 356-378). Pins and digests do not change, and it still needs the network, as today. The launcher is 703 lines of bash, so the modes the package offers are re-stated in Python.
- Full: the wheel ships the tools,
templates/andcontracts/as package data, with the commit and digest it was built from, and runs offline. Every tool that finds its files fromPath(__file__)must then find them in the package. Examples: scripts/shape_materialize.py:3354 and 3646, scripts/validate-repository-naming.py:95, scripts/family.py:134, scripts/bump-leg.py:108, scripts/shape_advisory.py:105. - Lane 3 leans to thin first, because it changes nothing a pin means.
- Commands. Which console commands does the package give? One option is
openRepoShapefor the launcher's modes plusscaffold-project,adopt-project,update-shapeandshape-doctor. The other is one command with subcommands. Either way, Windows gets native commands from the entry points. - Finding git-filter-repo. pipx exposes only the package's own commands, not its dependencies'. So
git filter-repo(adopt-project.py:7194) would not find thegit-filter-repothe package installed. Either the tools put their own environment's scripts directory on PATH for that one call, or the docs saypipx install --include-deps. - Versions. Does the package version equal the git tag (
v1.1.0→1.1.0)? The packaging code comes after v1.0.0, so the first published version would be 1.1.0. - Where it publishes. TestPyPI, then PyPI in the same release, as openDox does? Or TestPyPI only, until a later word?
- The documented way in. Does README keep the
--installone-liner first and add pipx as a second way, or lead with pipx?
Brett Heap's own acts, since these are accounts and settings, never an agent's:
- add the trusted publishers on pypi.org and test.pypi.org;
- create the
testpypiandpypienvironments, with their deployment limits.
Not part of v1.0.0. v1.0.0 is tagged at bfed143 as it stands. On 2026-10-14 neither PyPI nor TestPyPI had a project named openreposhape: both JSON APIs answered 404.
Order: after the v1.0.0 tag and the workBenches re-pin (ruling 33).
Filed by lane openRepoShape-3 on Brett Heap's word above.
Activity
brettheap commentedon Oct 15, 2026
CLAIMED — lane openRepoShape-3, session b30e8b49-f249-4668-adb1-1dbfa93c09e1@Eagle, 2026-10-15T11:48:51Z, for #563
Logged in
lanes/log/openRepoShape-3.mdat937984b0964feaa3dfcf8835f4561e992e782703.The three reads (lane-collision-protocol Rule 1):
brettheap commentedon Oct 15, 2026
(lane openRepoShape-3, 2026-10-15T12:07:50Z)
#563 design:
openreposhapeon TestPyPI and PyPI. There are six decisions, and each can be answered by its number. Line numbers are main ata35f1be1, except those marked#577:, which are the v1.0.1 installer's head868c9bf8; main receives that code for v1.1.0. In short: a thin wheel carries the release's commit and digest, and runs the tools from a checkout at that commit. A pin means what it means today, and no tool gains a dependency.templates/andcontracts/as package data), or the bash launcher shipped as a scriptopenRepoShape(the launcher's modes and flags) plusscaffold-project,adopt-project,update-shapeandshape-doctor, with arguments passed throughgit-filter-repo==2.47.0. The package's commands put their own scripts directory first on PATH for the children they startpipx install --include-deps openreposhapein the docsv1.1.0→1.1.0, stamped at build; the first release is 1.1.0pyproject.toml, checked against the tag (as openDox does)1. Thin
Recommendation. The wheel holds a small stdlib-only package and one file of the tag's tree,
setup-project.py, byte for byte. That file letsopenRepoShape --preflightwork before git is installed:--preflightreturns before the git probe and the clone (setup-project.py:2201-2210).Every other run uses a checkout of this repository at the stamped commit. The checkout is:
#577:openRepoShape:465-469);--installgives a person, prints every NEXT command as a path into its temporary clone of the standard, which its EXIT trap deletes before the person can paste the command, so each one fails withcan't open file; and it refuses--jsonand--placement-plan, which AGENTS.md's doctor steps 7 and 4 use #500,#577:openRepoShape:636-678).Before each run with no override, the command requires the checkout to be at the commit, to be clean, and to digest to the stamped value with its own
scripts/repo_shape.py:926, never a copy. A scaffold then pins exactly that commit and digest (scaffold-project.py:870-872).$OPENREPOSHAPE_REF, set for one run, overrides it, as it overrides the launcher's record (#577:openRepoShape:38-47, 570-588).$OPENREPOSHAPE_REPOstill names a fork or a mirror.--versionprints the version, the tag, the commit, the digest and the checkout's path.#577:openRepoShape:--orgfrom$OPENREPOSHAPE_ORGor a prompt (833-848);--doctor's clone and run (475-497, 601-680).setup-project.py:633already is ("setup.sh:157-206, in Python"); the preflight; and--installwith its record, which pipx and the stamp replace. The launcher's own test cases drive the Python command, on the Windows job too, where the launcher's own tests skip.v<version>, which must be annotated and point at the run's commit, and the checkout must be clean.tree_digestmust equal the forge'stree_digest_from_gh(scripts/repo_shape.py:926, 953).git archiveof the tag, plus the generated stampopenreposhape/_release.py.Alternatives.
scaffold-project.py:870,adopt-project.py:8263,scripts/family.py:1096).update-shape.pyreads the standard's history (update-shape.py:388-436), and so does the doctor's currency row. A wheel with no.gittherefore needs a second source of the tools' own commit, or a clone anyway.templates/andcontracts/are dotfiles or sit under.github/. setuptools globs skip those unless told to; openDox'sweb/**once packed 41 of its 42 files for that reason (openDoxpyproject.toml:319-328).Path(__file__)anchors (scaffold-project.py:76,scripts/shape_materialize.py:3354, 3646and others) hold only if the tree's layout ships intact.venv_inspect.py:139-163). But it gives native Windows nothing, because the launcher needs bash, and curl or gh. Its--installwould also copy it out of the environment pipx manages.Later, the launcher can become a shim over this Python, as setup.sh became one over setup-project.py (#50). That is not part of this issue.
2. Commands
Recommendation.
openRepoShapekeeps the launcher's modes and flags:<Project>,--preflight,--doctor,--versionand--help. Beside it,scaffold-project,adopt-project,update-shapeandshape-doctoreach run that file from the checkout, with every argument unchanged, in the directory where the command was typed.docs/cli.mdalready use, less./and.py, sodocs/cli.mdstays the one flag reference..exe. That gives native Windows theopenRepoShapecommand it lacks today (#577:README.md:155-158).--installrefuses, and namespipx upgrade openreposhapeinstead.One name, two installers, one directory. pipx places its commands in
~/.local/bin(pipxpaths.py:19), which is where install.sh places the launcher.commands/common.py:217-229).--installwouldcpthrough pipx's symlink into the package's environment (#577:openRepoShape:401-408).--install, and with it install.sh, refuse a target that is a symlink. README says to use one installer or the other.bump-legandfamilywould take one line each, if you want them. I left them out becausefamilyis a generic name to put on a PATH.Alternative. One command with subcommands (
openRepoShape adopt plan …). It needs a second grammar beside the launcher's flag modes and its positional<Project>(#577:openRepoShape:683-777), and a second spelling of every documented command.3. Finding git-filter-repo
Recommendation.
dependencies = ["git-filter-repo==2.47.0"]. That version:.github/workflows/tests.yml:61-64, 184-185);#577:install.sh:59-61);adopt-project.py:4638-4639);py3-none-anywheel for Python 3.6 or newer, whose one console script isgit-filter-repo = git_filter_repo:main.pipx exposes only the package's own commands (pipx
pipx_metadata_file.py:120-121, 141-149). So every child that the package's commands start gets the package environment's scripts directory (sysconfig.get_path("scripts")) first on PATH, whenever agit-filter-repois there.shutil.whichand runsgit filter-repo(adopt-project.py:6635-6645, 7218). git findsgit-filter-repoon PATH, and the.exeform too: the Windows job installs it with pip and runs the adoption suites, which skip without it (tests.yml:174-185).shape-doctor.py:6046).filter-repo-missingwherever the tool is absent.The gap. A NEXT line pasted as
python3 <checkout>/adopt-project.py execute …runs outside the package. On a machine whose only git-filter-repo is inside the package's environment, it refuses withfilter-repo-missingbefore anything is created (adopt-project.py:8215). SoFILTER_REPO_HINT(adopt-project.py:548-554) and README.md:747 gain the pipx route. They also drop the barepip install, which Debian 12 and Ubuntu 23.04 and later refuse (#577:install.sh:219-224).Alternatives.
pipx install --include-deps openreposhapecovers pasted lines too. But every reader must remember the flag, plainpiphas no such step, and it collides with agit-filter-repothat install.sh or brew already placed.>=2.47,<3) in place of the pin would install an untested release on the day it appears.4. Versions
Recommendation. The tag is the one source:
vX.Y.Z→X.Y.Z.pyproject.tomlreads it asdynamic = ["version"].0.0.0, and the workflow refuses to publish that, as openDox refuses its placeholder (release.yml:331-332).--versionthat it is unstamped, and follows main, as a launcher with no record does.Alternative. A static
versioninpyproject.toml, bumped in the release commit and checked against the dispatched tag (openDoxrelease.yml:295-337). That is one more line to change per release, and two sources that the check must keep in agreement.5. Where it publishes
Recommendation. openDox's workflow, adapted (openDox
release.yml:51-64):.github/workflows/release.yml, dispatch only, run on the tag with the version as its input. It has four jobs:testpypi, with trusted publishing. Only the two upload jobs getid-token: write.--versionmust name the commit,--preflightmust run, and--doctorover an empty repository must find git-filter-repo in its machine row.pypi. After the upload, PyPI must serve exactly the verified digest.The
pypijob waits for your approval after the check passes. "TestPyPI first, PyPI on a later word" is therefore one run and one set of bytes.tests,tests-macosandtests-windowschecks, the three-OS gate this repository lands by.v*tags. That is the rehearsal before 1.1.0.openreposhapefrom TestPyPI by digest. It never takes a dependency from TestPyPI.release.yml:66-76). An sdist can come later.#577:docs/release-notes/v1.0.1.md:76-134), v1.1.0's "Before tagging" gains one step after step 2, which pushes the tag: dispatchrelease.ymlon the tag and approve both uploads. Step 3'sgh release createthen follows, so the notes never name a version that PyPI lacks.Alternative. TestPyPI only, and PyPI by a later dispatch. That costs a second dispatch and a second build. The second wheel must match the first byte for byte, or PyPI gets bytes that TestPyPI never checked. One run with a held approval needs neither.
6. README
Recommendation. install.sh's one line stays as step 3 (
#577:README.md:228-241), andpipx install openreposhapefollows it as the second way.#577:README.md:112-158), pipx comes first. The two-command download stays after it, as the way that installs nothing.#577:install.sh:13-27).sudo apt-get install -y pipxon Debian and Ubuntu (#577:install.sh:231-233).pip, no packaging" (#577:README.md:1438) becomes constraint 1's own words: the tools use the standard library only, and the package is a second way in, never a requirement.Alternative. pipx first everywhere. That is one spelling on three systems, but the first line then needs pipx installed already, and the install no longer ends with the preflight.
Brett Heap's own acts
openreposhape, with owneropensoft, repositoryopenRepoShape, workflowrelease.ymland environmentpypi. A pending publisher does not reserve the name until its first upload (docs.pypi.org). Both indexes answered 404 foropenreposhapeat 2026-10-15T11:52Z.testpypi. test.pypi.org needs its own account.testpypiandpypi, each with you as the required reviewer and a deployment limit to tagsv*, as openDox-code's two environments have today. This repository has only acopilotenvironment now.v*tags, which openDox's workflow recommends (release.yml:47-49).Lane acts after your approval
In this order, after the workBenches re-pin, as this issue's Order says, and after the #577 carry to main, which brings install.sh and README's step 3:
pyproject.toml;openreposhape/, stdlib only and added toSHIPPEDintests/test_repo_hygiene.py; the five commands, with the launcher's test cases run against them on all three OSes; a CI step that builds the wheel and installs it with pipx; and--installrefusing a symlinked target.release.ymlfrom openDox's, adapted as in 5, with its hash-locked release tools and a test that reads the workflow.docs/cli.md, and the handbook.v1.1.0.openreposhape==1.1.0with pip, in place of its vendored launcher and not beside it. pip writes itsopenRepoShapeto/usr/local/bin, where the image copies the vendored launcher today (opensoft/workBenchesdevBenches/base-image/Dockerfile:217). git-filter-repo then arrives as the package's dependency, which covers ruling 33's need.Evidence: main
a35f1be1; #577 head868c9bf8; openDox-codec94878be; git-filter-repo's PyPI JSON and its 2.47.0 wheel; the source of pipx 1.17.12.Not yet verified. The first PR checks each of these before anything depends on it:
actions/setup-pythonstill offers Python 3.9;git-filter-repo.exeput first on PATH from a pipx environment'sScriptsdirectory (the Windows job's adoption suites suggest it, but no run log was read);git archivewithSOURCE_DATE_EPOCHis byte-reproducible here;openRepoShape, with its capitals, behaves under pipx on macOS and Windows.Ruling 33's workBenches change adds
git-filter-repo==2.47.0, the same version as decision 3, so a laterpip install openreposhapein the image does not conflict with it.brettheap commentedon Oct 15, 2026
(lane openRepoShape-3, 2026-10-15T13:34:30Z) Approved. Brett Heap, at 2026-10-15T13:32:51Z: "approve all six for #563". The design above is the plan.
What lane 3 does next, in order:
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587 (One-line install:curl -fsSL …/releases/latest/download/install.sh | bashinstalls the newest release pinned to it, with git-filter-repo, and checks the machine (v1.0.1) #564's carry into main), because the package re-states the v1.0.1 launcher. Its draft PR runs the three-OS CI, and it lands only after install.sh and openRepoShape --install, carried into main from v1.0.1 (#577): the one linecurl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashinstalls the newest release's command pinned to that release, installs git-filter-repo with pipx where it can, and ends with the preflight (#564) #587.pyproject.tomlandopenreposhape/(stdlib only), with the stamp, the cached checkout and its digest check.openRepoShapeon all three OSes.--installrefuse a symlinked target.release.yml, from openDox's, adapted as in decision 5.Before the first upload, Brett Heap's own acts:
testpypiandpypienvironments.Nothing is uploaded before them, and the first upload is 1.1.0, at the v1.1.0 tag.
tests/test_openreposhape_command.py: the symlink-target test asserts …
install.sh, tests/test_install_sh.py: the symlink-target check uses `…
src/openreposhape/, tests/test_openreposhape_package.py: the argument…
.github/workflows/tests.yml: the package job runs pipx 1.11.1 on the …
.github/workflows/tests.yml: the package job installs pipx from wheel…
Merge remote-tracking branch 'origin/l3-564-carry-to-main' into l3-56…
openRepoShape: the comment over `--install`'s record says the target …
src/openreposhape/, pyproject.toml, tests/test_openreposhape_package.…
tests/test_repo_hygiene.py: every module of src/openreposhape/ is pur…
src/openreposhape/standard.py, tests/test_openreposhape_package.py: a…
.github/workflows/release.yml: the three-OS gate counts only the jobs…
13 remaining items
The openreposhape package: openRepoShape and four tool commands, run …
The release workflow: a dispatch on the annotated tag v<version> buil…
Merge origin/main (d4a7fab) into l3-563-docs
docs/handbook.html and docs/release-notes/v1.1.0.md: step 3's Windows…
README.md, the handbook and the v1.1.0 notes: the adopted repository …
README.md, the handbook and the v1.1.0 notes: the doctor's exit 3 nam…
README.md, the handbook and the v1.1.0 notes: lane 2's review of roun…
README.md, the handbook and the v1.1.0 notes: RV630's four nits, and …
docs/release-notes/v1.1.0.md: RV630's pre-review of the B2 stage, #747'…
docs/release-notes/v1.1.0.md: entries for #748, #731, #810, #809, #816,
docs/release-notes/v1.1.0.md: #746's entry, v1.0.0's staged-paths lim…
docs/release-notes/v1.1.0.md: #795's entry, the list complete to main 6…
README, AGENTS.md, docs/cli.md and the handbook: `pipx install openre…
brettheap commentedon Oct 17, 2026
(lane openRepoShape-3, 2026-10-17T21:55:11Z) Done:
openreposhape1.1.0 is on PyPI and TestPyPI, sopipx install openreposhapeinstalls it.openreposhape-1.1.0-py3-none-any.whl, sha256eb0cb8486272f0a1aec7a9c77db0ffd5fc60a3bf7d68d8c7a4e6a53fb9fa017c. TestPyPI holds the same wheel with the same digest.v1.1.0, which points at the release commit190133d5e704e380c430f87ca9c4e31746de0012. The run wasrelease.ymlhttps://github.com/opensoft/openRepoShape/actions/runs/39071178232.testpypi, thenpypi. Between the two, the install check installed the TestPyPI upload on Linux, macOS, Windows and Python 3.9.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 docs and the v1.1.0 release notes).Lane: openreposhape-3 (openRepoShape-3)