Repository navigation
Releases
openRepoShape v1.2.0
openRepoShape v1.2.0
v1.2.0 is a minor release, the next after v1.1.1. It adds no command and no
flag. It adds refusals and findings, each under its own code, where a tool
read a path it could not read as absent, ended in a traceback, or said ok
and failed later, after a repository or a leg was made. It fixes how
adopt-project.py check rehearses the split, how the YAML reader takes a
key written twice and a block scalar, how bump-leg.py and family.py bump
fetch, how a holder's make bootstrap says where git's update stopped, and
what the scaffold, setup and both openRepoShape commands refuse before
they act. It changes pinned shape copies, so a project or a family holder
takes those fixes into its own gates by update-shape.py (below). It is
tagged on main.
At main 961640c, on 2026-10-22, 100 pull requests had landed on main
since v1.1.1's commit, 5017830, the last of them these notes (#1155). At
19:54Z, 91 of the v1.2.0 milestone's 91 issues were closed, and 32 merged
pull requests were in it.
What 1.2.0 fixes
- A path this user cannot read is named, never read as absent. From
Python 3.14,Path.is_file(),is_dir()andexists()answer False to
anyOSError, so a path this user could not search read as one that was
not there; up to 3.13 they raise, and the run ended in a traceback. Every
shipped tool now refuses such a path by name, on every Python, but at
eleven sites left to #1110 and over a root's own.git(#1146, #1148;
see the known limits), and a test holds each tool's guards to that rule
(#934, #976, #975, #958, #956, #951, #955, #970, #1072, #1111, for #430). adopt-project.py checkfinds whatexecutewould meet. It
rehearses the split in the work directory, with the source's object
format and none of this machine's hooks; it names a symlink target no
system can make (a NUL after its first byte, or over 4095 bytes), a link
into the clone's.gitand a pin's file reached through a kept link; and
it refuses a hand-edited plan value read as a number or a boolean, each
before either leg exists (#1031, #965, #1070, #1091, #1016).planasks
its flags' own refusals before it clones, andcheckandexecutename
the same first problem (#963, #1015).- The YAML reader refuses a key written twice, and reads a block
scalar as YAML does or refuses it by its line (#974, #1021). The scaffold
andadopt-project.pyspell aneutral_product_pins:entry one way,
quoted wherever a stock reader would misread it (#995, #1090). - Fetches that answer for a branch.
bump-leg.pyandfamily.py bump
no longer move the mount's own branches, and leave out every refspec that
can bring a tag, or that puts a branch under another name, through one
rule both import (#937, #946, #1027, #1068, #1087). A commit only a tag
reaches is named so, whichever kind of tag reaches it (#988). - A holder's
make bootstrapfails by name where git's update
stopped: at a gitlink with no url, at a commit a fetch did not bring,
or where a signal ended git, where it saidfamily bootstrap ok. A token
reaches no trace, a member's make undermake -jNno longer warns of the
jobserver, and the FAILED line names the member's own exit code past a
-make, or past|| trueand a-command (#889, #954, #1045, #1076,
#1075, #950, #1023, #891).validate-family.py's remedies type as
printed, in order (#889, #1025, #1069). - The scaffold and setup. A push or a seed fetch no longer waits at
git's username prompt, a login or key the host refuses names its fix, and
the help says what can still make the re-run wait (#971, #1012, #1101);
the plan names the tracking branch and, on disk, the visibility (#969); a
flag passed empty is refused by name (#972, #1010); a Windows device name
is refused (#1010, #1041); and setup's credential-helper check and its
step (4) read what the run will meet (#1008, #1022, #949, #1011). - Both
openRepoShapecommands. The onepipxplaces refuses what the
bash one refuses wherever it prints a value or hands one to git (#947);
under theClocale a letter past ASCII is no longer "a control
character", the regression v1.1.1 named (#986); and the bash--doctor
checks the ref before it clones (#967). - Lines a person pastes.
make bootstrap'smerge --ff-only,
--continue,--abortandsubmodule updatelines give a leg's path
and an upstream as one shell word (#944, #973); the split message names
each path asplanshows it (#1030); and every remedy that names
family.py bumpnames--family-root(#1002). - A leg on a branch spelled like the tracking branch.
make bootstrap
leaves a leg whose branch folds to the tracking branch on macOS or
Windows (Main, or another Unicode form), where it moved the branch
under HEAD and left the index at the old commit (#948, #984, #1006). - CI. The full suite and the Python 3.9 jobs run on
ubuntu-24.04
(#1095), the release's install check runs the doctor over a scaffolded
root (#945), and every release's notes are held to the one-line install
(#887, #959).
Install and upgrade
-
The newest release, by the one line:
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashIt places the bash
openRepoShape. The same line again upgrades.
… | bash -s -- v1.2.0names this release.openRepoShape --version
printsopenRepoShape (opensoft/openRepoShape @ v1.2.0). -
From PyPI:
pipx install openreposhape==1.2.0for this release, and
pipx upgrade openreposhapeto upgrade one already installed.
openRepoShape --versionprints
openRepoShape 1.2.0 (opensoft/openRepoShape @ 961640c681b54d9258e6c9d3eab8c6f46a26008b).
v1.1.0's notes say wherepipxcomes
from, and that the two installers do not replace each other's command. -
A project's and a holder's shape copies. A copied file reaches no
project by itself. v1.2.0 changes five of the copies an assembly root
pins:scripts/validate-pins.py,scripts/validate-manifest.py,
scripts/bootstrap.py,scripts/repo_shape.pyand
scripts/validate-repository-naming.py. It changes four of the copies a
family holder pins:scripts/validate-family.py,scripts/bootstrap.py,
scripts/siblings.pyandscripts/repo_shape.py. NoMakefile,
.gitignore,.gitattributes,validate.ymlorAGENTS-shape.md
changes, nor the naming policy.
From a checkout at the v1.2.0 release commit,
update-shape.py check --root <path> --at 961640c681b54d9258e6c9d3eab8c6f46a26008breports
each asupstream-changedin a root pinned at v1.1.1 that has not
edited it, and
update-shape.py apply --root <path> --at 961640c681b54d9258e6c9d3eab8c6f46a26008b --yes --branch shape/update-<sha>
re-copies and re-pins them, on the person's yes, as AGENTS.md's
"Updating a project's shape" says. Until then the root's own gates and
make bootstrapare v1.1.1's. -
A holder that keeps its own
scripts/bootstrap.py. The new
siblings.pyimportsmember_make_environment(#1023) anduntraced
(#1075) from the holder'sbootstrap.py. From a pin at v1.1.1 or
earlier,bootstrap.pychanged upstream, so a kept copy readsboth: a
plainapplyrefusesupdate-conflict, andapply --accept-local scripts/bootstrap.pyrefusesupdate-accept-local-missing-name, both
before any write. #1082's gap, where a kept copy readsunchangedand is
not asked again, reaches only a holder pinned at a main commit between
the two releases whosebootstrap.pyis already v1.2.0's (from
ff0c72ebup to #1075's7e00834c). There a plainapplycopies the
newsiblings.py, andmake siblings,make parkandmake resume
then end inImportError(#1082, open). Merge the upstream's
bootstrap.pyinto the kept copy by hand, or take the upstream's bytes;
commit, and runapplyagain. -
A YAML layout the new reader refuses. A project keeps its old
repo_shape.pyuntilupdate-shape.py apply, and the old copy reads the
last of a key written twice without a word, so aproject.yamlthat
holds a key twice passes its ownvalidate-manifest.pyuntil then. At
applythe new copy refuses it,yaml-unparsablewith the line of the
second key, thenupdate-validators-red, then ROLLED BACK. Delete the
first of the two lines to keep the reading the project has now, and run
applyagain. A block scalar's indentation digit (|2,>-2,|2-)
ends the same way; with the digit deleted the value reads as before,
except where the header also holds-or+, or the body holds another
layout #1021 refuses (#974, #917; #1021, #1004). A holder'sfamily.yaml
is read by the same reader. -
The rendered READMEs, and a holder's
cdline. A project's or
holder'sREADME.mdis rendered once, when it is made, and
update-shape.py applyrewrites only a holder's closingShape:line.
So a holder made withfamily.py init --family X --name Ybefore v1.1.0,
whereXandYdiffer, keepscd Yin its README, while a clone of it
is the folderX/. Edit that line by hand, to the basename of
family.yaml'srepository:;applyleaves it as it is, by design
(#921's U097). The comments abovevisibility:inproject.yamland
family.yamlreach new roots only, for the same reason (#1102, #913). -
What a script sees. Runs that passed on v1.1.1 and fail now, among
them:adopt-project.py plan --project NUL,Con,com1orLpt9, exit 2
adopt-bad-project-name, where it wrote a plan and exited 0, and
checkof such a plan, exit 1 (#1041, #1014); the scaffold over such a
--project, exit 2scaffold-bad-project-name, where it made the
three repositories (#1010, #989);adopt-project.py planwith--org '',--elected-on ''or
--reference '', exit 2, where it exited 0; the scaffold and
family.py initwith a flag passed empty, such as--id ''or
--into '', exit...
openRepoShape v1.1.1
openRepoShape v1.1.1
v1.1.1 is a patch of v1.1.0. It adds no command and no flag. It fixes text
that a terminal acts on reaching it, names that are not ASCII or not UTF-8,
.gitmodules files read otherwise than git reads them, and adoptions and
scaffolds that failed late, after the plan was built or a repository was
made. It changes pinned shape copies, so a project or a family holder
takes those fixes into its own gates by update-shape.py (below). It is
cut from release/v1.1 (#850).
At main 5017830, on 2026-10-18, 70 pull requests had landed on main since
v1.1.0's commit, 190133d. At 14:17Z, 71 of the v1.1.1 milestone's 71
issues were closed, #709 among them, closed as not planned.
What 1.1.1 fixes
- Text a terminal acts on stays off it.
install.shand the bash
openRepoShape, the oneinstall.shplaces, refuse a
$OPENREPOSHAPE_BIN_DIRholding a control character, a C1 control
included, or one of 41 code points a terminal may draw as something
other than itself (a bidi control, a character of no width, a soft
hyphen, U+2028, U+2029), before any request and without echoing it
(#760, #837); where the bytes refuse a name, they read it again in a
multibyte locale's own charset, so that locale's letters are placed
(#858). The bash command checks$OPENREPOSHAPE_REPOand
$OPENREPOSHAPE_REFthe same way before--versionanswers (#866); the
pipx command does not (see the known limits).validate-pins.pyspells
every name and path it prints (#712), andfamily.py init's plan rows,
seven refusals andbump-leg.py's refusals that name the root spell the
paths they print (#766). - Names that are not ASCII, or not UTF-8. The doctor keeps such a
character where the report can show it, so a NEXT or keep line naming a
path such ascaféruns as printed, and escapes a command's value whole
where it cannot (#785); it finds a member whose.gitmodulessection name
is not UTF-8 (#732).make bootstrapmoves back a leg's tracking branch
whose upstream is not UTF-8 (#710);update-shape.py,family.pyand
bump-leg.pyread the branch HEAD is on as git's bytes (#765);
setup-project.pyand a project'sbootstrap.pytell each Python child
the encoding it is read in (#836); and a byte-order mark no longer breaks
a YAML file or the upstream notice (#855). .gitmodules, read as git reads it. The doctor and both
validate.ymltemplates read only the sections, keys and values git
reads, so the doctor's NEXT lines name no url, and CI no App-token
repository, that git would not (#733, #741, #786);make siblingsand
the doctor'smembersrow read the same sections, keys and values, but
still read an unmerged file (#733). A holder'smake bootstrapand its
validate-family.pyname a value that git dies on, where bootstrap said
family bootstrap ok(#768), and bootstrap reads an index or HEAD copy
with a line git cannot parse up to that line, as git does (#856).adopt-project.pyasks before it makes anything.--source,--out,
--pin-source, the source's default branch and both leg repositories are
asked about before any leg exists, and refused there (#796, #799, #800,
#797, #798).checkholds a plan to the repository it records (#716),
the line-ending block governs a copy written through a symlinked
directory (#807), the recorded answers cover a directory split by hand
(#801), and the cloneplanandcheckmake is removed only while it is
still the one they made (#803).- One reading of
legs:,members:and a branch name. Alegs:that
is not a list, or an entry that is not a mapping, is said in one sentence
by the naming gate,validate-manifest.py,validate-pins.py, the
doctor, a project'sbootstrap.py,bump-leg.pyand
update-shape.py --pr(#714, #832). A holder'smembers:that is not a
list, or an entry that is not a mapping, is said in its
validate-family.py's words by the doctor,family.pyand the holder's
make bootstrapandmake siblings(#864). The YAML reader keeps
Unicode spaces as text, and a branch name holding a character that does
not print or is white space is refused (#715). - Git Bash gets
sh's quoting. Under Git Bash, the lines the tools
quote for the host are quoted forsh, where they were quoted for
PowerShell (#711). - Names GitHub cannot hold.
scaffold-project.py,
adopt-project.py execute,family.py initand setup's plan refuse a
repository name over 100 characters, an id whosexf-project-<id>topic
is over 50, and an--orgover 39 or one GitHub gives no organisation,
before any repository is made, where an id over 39 failed after the
repositories existed;adopt-project.py checkfinds each of them as
executerefuses it (#838, #863). A project name typed at
setup's prompt is checked as--projectis, a refusal names the check
the value failed, and a name too long for GitHub's repository names is
refused before step (4) (#729, #777). - setup-project.py's rehearsal, and a ref git refuses. A run passed
-- --dry-runmakes none of the preflight's offers, so it no longer
writes git's global config or signsghin, and--shape-refis held to
git's rule part by part (#857), as is a ref given tosetup.shor
install.sh, and one the bashopenRepoShaperecords, reads from its
record or checks out for--doctor(#874). - A project's
make bootstrap. A validator's refusal ends the run
bootstrap REFUSED, exit 2, where it read as a finding; an upstream
path, cache or pin it cannot read is no answer, where the run ended in a
traceback; and a cherry-pick or a revert stopped in a leg whose refs are
a reftable is left alone, where bootstrap ended it (#764). - The doctor. Every git it starts runs with
GIT_NO_LAZY_FETCH=1, so
on a git that reads the variable a read a partial clone cannot answer
fails rather than fetches (#787). In a root that lost
scripts/repo_shape.py, thelegsandmembersrows' NEXT puts the file
back, and theagent-filesrow's WHY puts it back before it names a gate,
where each named a gate that dies on the import (#750, #776). Outside a
shape root, a naming policy in the doctor's own checkout that does not
load, or cannot be used, is ann/arow onthe way inand, in a
repository,the other way in, not a finding or a traceback (#707). - Family holders. More of
family.py add's andvalidate-family.py's
remedies type as printed in the states they name, for a member under
another spelling (#761, #762, #839, #840) and for a gitlink outside
members/(#878); a holder'smake bootstrapno longer prints the
SHAPE_LEGS_TOKENadvice where git fetched nothing, and tells a
member's finding, its refusal and make's own failure apart, through a
member's own nested make too (#839, #884);add,bumpandremove
see a cherry-pick or a revert stopped in a reftable holder (#684); and
make siblingsreports a slot that is not a clone, a link to nothing
among them and, on Python 3.12 and later, a junction to nothing, and
places the members after it (#735, #784, #793). - CI on a release branch.
tests.ymlruns on a push to
release/v<X.Y>as on a push to main, andrelease.yml's header says
how a fix is back-ported (#852, for #850).
Install and upgrade
-
The newest release, by the one line:
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashIt places the bash
openRepoShape. The same line again upgrades.
… | bash -s -- v1.1.1names this release.openRepoShape --version
printsopenRepoShape (opensoft/openRepoShape @ v1.1.1). -
From PyPI:
pipx install openreposhape==1.1.1for this release, and
pipx upgrade openreposhapeto upgrade one already installed.
openRepoShape --versionprints
openRepoShape 1.1.1 (opensoft/openRepoShape @ 50178305e4229275d18e71c2152403b4e9d3004e).
v1.1.0's notes say wherepipxcomes
from, and that the two installers do not replace each other's command. -
A project's and a holder's shape copies. A copied file reaches no
project by itself. v1.1.1 changes six of the copies an assembly root
pins:scripts/validate-pins.py,scripts/validate-manifest.py,
scripts/bootstrap.py,.github/workflows/validate.yml,
scripts/repo_shape.pyandscripts/validate-repository-naming.py. It
changes six of the copies a family holder pins:
scripts/validate-family.py,scripts/bootstrap.py,
scripts/siblings.py,.github/workflows/validate.yml,
AGENTS-shape.mdandscripts/repo_shape.py. From a checkout at the
v1.1.1 release commit,update-shape.py check --root <path>reports each
asupstream-changedin a root pinned at v1.1.0 that has not edited it,
and
update-shape.py apply --root <path> --at 50178305e4229275d18e71c2152403b4e9d3004e --yes --branch shape/update-<sha>
re-copies and re-pins them, on the person's yes, as AGENTS.md's
"Updating a project's shape" says. Until then the root's own gates and
make bootstrapare v1.1.0's. -
The rendered READMEs. A project's or holder's
README.mdis rendered
once, when it is made, andupdate-shape.py applyrewrites only a
holder's closingShape:line. So the new sentence on a.gitmodules
that git refuses, in the root's README and in the holder's (#790), and
the holder's new sentences on the one spelling ofmembers/and on its
folder's name (#738, #822), reach new roots only. An existing holder
reads the last two in its re-pinnedAGENTS-shape.md; #790's sentence
reaches no existing project or holder at all. -
What a script sees. Runs that passed on v1.1.0 and fail now, among
them:- a holder's
make bootstrapover a.gitmodulesva...
- a holder's
openRepoShape v1.1.0
openRepoShape v1.1.0
v1.1.0 puts openRepoShape on PyPI. pipx install openreposhape is a second
way in, beside the one-line install, and the first on native Windows, where
PowerShell cannot run the one line (#563). This release also carries v1.0.1's
one-line install into main (#564), fixes limits that v1.0.0 listed, and runs
CI on Linux only (#597).
At main 190133d, on 2026-10-17, 123 pull requests had landed on main since
v1.0.0's commit, bfed143; at 21:55Z, 133 of the v1.1.0 milestone's 133
issues were closed.
What 1.1.0 adds
-
openRepoShape on PyPI, as
openreposhape(#590, #599, #563). One
command installs it:pipx install openreposhape
It places five commands.
openRepoShapehas the launcher's modes,
<Project>,--preflight,--doctor,--versionand--help, written
again in Python, so native Windows gets the command, as an.exe.
scaffold-project,adopt-project,update-shapeandshape-doctoreach
run the tool of that name with every argument unchanged, sodocs/cli.md
is their reference.git-filter-repo2.47.0 comes as their dependency,
and each command puts it first on thePATHof the tools it starts.The wheel is thin. It carries the release's commit and the
sorted-ls-tree-r-v1digest of its tree, and the tools run from a full
clone of this repository at that commit, made once in your per-user cache
and kept. Before each run the clone must be at that commit, unchanged, and
digest to that value, so a project scaffolded through it pins exactly this
release.$OPENREPOSHAPE_REFnames another ref for one run, with no digest
checked. The package's--installrefuses and names
pipx upgrade openreposhape. Python 3.9 or newer.The version is the tag's, so 1.1.0 is the first version on PyPI. The
v1.0.x line is not published there. -
The one-line install, on main (#564, carried into main by #587):
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashIt installs the newest release's bash
openRepoShape, pinned to the
release tag, and ends withopenRepoShape --preflight, as
v1.0.1's notes say. Since v1.0.1:- it installs git-filter-repo with
uvwhere there is nopipx, and never
withpip install --user, which an externally managed Python refuses
(PEP 668). With neither, it prints one command for this machine: brew's,
pipx's from apt or dnf, pacman's, or uv's (#598, #595); - it ends with two lines,
adoption:and thendone:, so a machine that
is ready to scaffold but not to adopt is told so (#598, #595); - Git Bash, the bash that Git for Windows installs, runs it and the
openRepoShapeit places, tested in CI but for a typedyes, which the
preflight cannot take there (#596, #585); - it refuses a target that is a symbolic link, such as the
openRepoShapepipx places, and so does the launcher's--install
(#563).
- it installs git-filter-repo with
-
CI on Linux only (#597). Brett Heap decided on 2026-10-15 to drop the
macOS and Windows test jobs across the estate.tests.ymlruns the whole
suite onubuntu-latest, on every push to main, every pull request and
every dispatch. Since #593 each push run on main is grouped by its own
commit, so the next landing no longer cancels it, and every landing keeps
its own Linux result. One exception, on his word the same day: a
tests-git-bashjob runs the launcher's andinstall.sh's suites, and
nothing else, on Windows under Git Bash (#596, #585). The rest of the
suite no longer exercises Windows file-system behaviour, and the stock
macOS bash 3.2 is a goal the bash entry points are written for rather
than a checked claim. The release workflow still
installs the package on Linux, macOS and Windows before it reaches PyPI.
Install and upgrade
- The newest release, by the one line: the line above. The same line
again upgrades.… | bash -s -- v1.1.0names this release.
openRepoShape --versionprintsopenRepoShape (opensoft/openRepoShape @ v1.1.0). - From PyPI:
pipx install openreposhape, or
pipx install openreposhape==1.1.0for this release.
pipx upgrade openreposhapeupgrades it.pipxitself comes from
sudo apt-get install -y pipxon Debian and Ubuntu and
brew install pipxon macOS; on Windows,python -m pip install --user pipx,
thenpython -m pipx ensurepathand a new terminal.
openRepoShape --versionprints
openRepoShape 1.1.0 (opensoft/openRepoShape @ 190133d5e704e380c430f87ca9c4e31746de0012), then the
version, tag, commit, digest and the clone it would use. - One installer or the other. Both place
~/.local/bin/openRepoShape.
pipx does not replace a command it did not place, andinstall.shand the
launcher's--installrefuse the link pipx places, naming
pipx upgrade openreposhapeor removing the link. - Native Windows:
pipx install openreposhape, then
openRepoShape <Project> --org <your-org>. Downloadingsetup-project.py
and runningpy setup-project.py <Project> --org <your-org>still works,
and installs nothing. Git Bash runs the one line and the bash
openRepoShapeit places, tested in CI but for a typedyes, which the
preflight cannot take there (#596, #585). - A project's pin on the standard works as before. Follow
How to pin 1.0.0 with the v1.1.0 release
commit. A scaffold run through the package pins that commit and digest by
itself. - A family holder made before #642. A holder made with
--family X --name Ybefore #642 (the fix for #629) keepscd Yin its
own README, andupdate-shape.py applynever re-renders a README, so
change that one line by hand tocd X: the holder's directory is named
after its repository,X, never after the display name. The same README
draws its layout asY/Yand calls the doubledY/Ydeliberate; read
X/Xthere, which is whatmake siblingschecks the folder against.
What changed since v1.0.1
v1.0.1 was cut from release/v1.0 and added only the one-line install.
Everything below landed on main after v1.0.0's commit, by area. The list
runs to main 6f64eea.
adopt-project.py
- The source's
origin, read one way.planreads<org>/<repo>from
everyorigingit accepts, a local clone or mirror path and a URL ending
/included, so a local clone's right--orgis no longer refused
(#591, closing #521 and #427). - Refused before any leg exists.
checkfinds, andexecuterefuses
before any leg exists, a file the root keeps at the shape's own pin path
and a--work-dirthe user cannot write in. Both used to fail only after
both legs were pushed (#581, #524).checkrecomputes each neutral pin's
digest at the plan's commit, in the cloneplanread it from, where that
clone is still on the machine and is no partial clone, so a commit or a
digest edited into the plan no longer reaches the root as
adoption verifiedover a redvalidate-pins.py(#589, #449). Before any
leg too: the branch namesexecutefailed on, such as
adopt_branch: a..b, a source branchAdoptor one hidden byhideRefs
(#583, #520, #420); a kept nested.gitattributesthat outranks the
root's line-ending rule for a pinned copy, and a root.gitattributes
thatgit addwould re-encode (#619, #530); and a kept submodule url on
this disk wheregit ls-remotereaches no repository, which used to end
adoption verifiedover a recursive clone that fails (#618, #418). - Names outside ASCII.
executehandsgit rmeach path the split
moves or drops as the bytes the tree holds, so such a name under
LC_ALL=C PYTHONUTF8=0no longer stops the run withUnicodeEncodeError
after both legs are pushed (#614, #527). - The work directory. A run without
--work-dirremoves the work
directory it made where it holds no file, andexecutenames the one it
keeps.checkandexecuteclone the plan'ssource.repositoryagain
where the clone it records is gone, so neither leaves an empty
openreposhape-adopt-*directory in the system temp folder, nor refuses
source-unclonableonce that folder is cleaned (#594, #526). - Answers kept.
executerecords the plan's answers under
answered_paths:, and a moved submodule names its row, so an answered
examples/is not asked again, and the doctor names no plan for a
deliberate move (#617, #525). - Leg paths that are not the defaults. The Makefile block
execute
appends, and the cross-leg follow-up, name each leg where the plan mounts
it, so--spec-path legs/specexports aCONTRACTS_DIRthat exists and a
nested--code-pathgets the right../path (#572, #522). - A leg path edited by hand. The read-across follow-up names
legs.code_pathandlegs.spec_pathand no path, and thecontracts/
rule's reason names none, so the hand edit of a leg's mount thatplan
asks for leaves no plan-time path incheck, the split commit or the pull
request (#628, #600). A plan written before v1.1.0 whoselegs.spec_path
orlegs.code_pathwas edited by hand keeps its old value-form
read-across follow-up.checkaccepts it, becausefollow_ups:is free
text, andexecutecarries the stale line into the split commit and the
pull request's body. Re-runplanfor such a plan. - A source's own
single-repository.yamlis asked drop or root, drop by
default, under a rule of its own, and no longer gets the general top-level
YAML question (#566, #528). - What a run says is what it does. The plan's last follow-up is a review
of the pull requestexecuteopens itself. A failed verification names
the pull request and the topic it left in place. Under
...
openRepoShape v1.0.1
openRepoShape v1.0.1
v1.0.1 is v1.0.0 plus one change: openRepoShape installs in one line, and the
command it installs stays on the release tag it was installed from (#564).
What 1.0.1 adds
-
The one-line install.
install.shis attached to every release, so
this line always installs the newest one:curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bashIt reads which release is the newest from github.com's
releases/latest
redirect, with noghand no login. It fetches that release's
openRepoShapeand installs it into~/.local/bin, or
$OPENREPOSHAPE_BIN_DIR, pinned to the release tag. It installs
git-filter-repo2.47.0 withpipxwhenpipxis there and
git-filter-repois not, and otherwise prints the one command that
installs it. It ends withopenRepoShape --preflight.install.shitself
never runssudo; the preflight it ends with offers what is missing, each
behind a typedyesat your terminal. Every downloadinstall.shmakes
is HTTPS only, redirects included, at TLS 1.2 or newer. It exits 0 when the
command is
installed and the preflight passed, 1 when the command is installed but the
preflight did not pass or the command does not stay on the release tag, and
2 when it installed nothing. -
An installed command that stays on its release tag. With
OPENREPOSHAPE_RECORD_REF=1, whichinstall.shsets,openRepoShape --installrecords$OPENREPOSHAPE_REFinopenRepoShape.refbeside the
command it places. The installed command then fetchessetup.shat that
ref,setup.shclones the standard at it (--shape-ref), and--doctor
checks its clone out at it, and so does a symlink to the command. In
v1.0.0 an installed command fetched main on every run unless
$OPENREPOSHAPE_REFwas exported.$OPENREPOSHAPE_REFset for one run
still overrides the record, andopenRepoShape --versionprints the ref in
force. The installed command is byte for byte the release's own file.
Install and upgrade
- The newest release: the line above. Run it again to upgrade to the
newest release. - A named release:
curl -fsSL https://github.com/opensoft/openRepoShape/releases/latest/download/install.sh | bash -s -- v1.0.1,
orbash install.sh v1.0.1from a checkout or a download. - What is installed:
openRepoShape --versionprints
openRepoShape (opensoft/openRepoShape @ v1.0.1). - By hand, at main: the README's
gh api repos/opensoft/openRepoShape/contents/openRepoShape … | bash -s -- --install
line still installs main's command, which records no ref and follows main. - Platforms: macOS (the stock
/bin/bash3.2 is enough), Linux and WSL2.
Git Bash on Windows is meant to work, as #564 asks, but nothing has run it
yet: the launcher's and the installer's tests skip on the Windows job.
#585 (v1.1.0) runs them under Git Bash. Native Windows without Git Bash
still starts withpy setup-project.py, as the README says. - A project's pin on the standard works as before. Follow
How to pin 1.0.0 with the v1.0.1 release
commit.
A script that runs --install
openRepoShape --install records a ref only when it is asked to, with
OPENREPOSHAPE_RECORD_REF=1. A script that exports $OPENREPOSHAPE_REF
around --install for its own reasons gets the install it got from v1.0.0:
nothing is recorded, a record already there is removed, and the command
follows main. To pin the command a script installs, set
OPENREPOSHAPE_RECORD_REF=1 and set $OPENREPOSHAPE_REF to the commit or
tag the command should stay on.
Known limits
v1.0.1 changes nothing else, so its known limits are v1.0.0's:
Known limits in v1.0.0.
openRepoShape v1.0.0
openRepoShape v1.0.0
openRepoShape is the convention, as data and as running code, for 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 asked to scaffold one. Its preferred shape, the Triad, is an
assembly root that mounts a spec leg and a code leg. The shape is elective and
confers nothing. Version 1.0 promises that every procedure in AGENTS.md works
end to end for ordinary repositories on Linux, macOS and Windows. On Windows,
make park and make resume run under WSL2, as the README says: native
Windows parks nothing. The release is tagged only when every in-scope issue is
closed and the full test suite passes on all three systems on the release
commit. Edge cases that only unusual input reaches
are listed below as known limits.
What 1.0 contains
- New project:
openRepoShape <Project> --org <org>, or./setup.sh, or
py setup-project.pyon Windows. It runs a preflight, checks the names,
shows the plan and asks for one typedyes. - Direct scaffold:
scaffold-project.py. Add--pin open<Product>@<commit>
for a declared descendant. - Adopt in place:
adopt-project.py plan, thencheck, thenexecute. - Compliance check:
openRepoShape --doctor [<path>]or
shape-doctor.py --root <path>. - Stay current:
update-shape.py check, thenapply. - Advance a leg:
scripts/bump-leg.py --root <path> --leg spec|code --to <40-hex>. - Families:
scripts/family.py init|add|bump|remove|siblings. - Park and resume:
make parkandmake resume. - Each project's own gate:
make bootstrapandmake validaterun pinned
copies of the scripts, so the gate never needs this standard.
Requirements and platforms
gitand Python 3.9 or newer. The tools use the standard library only.gh, logged in, wherever a tool creates repositories or pull requests on
GitHub.git-filter-repois needed for adoption only.makeis optional.openRepoShape --preflightchecks the machine and creates nothing. On
Windows, runpy setup-project.py --preflight. It names what is missing,
and offers to installgh, log it in and set up its credential helper,
each behind a typedyes.setup-project.py --preflightalso offersgit.- Linux and macOS are supported, and the stock macOS
/bin/bash3.2 is
enough. Native Windows usessetup-project.py, after
git config --global core.autocrlf false. WSL2 runs the bash entry
points. Park and resume do not work on native Windows; run them under
WSL2. - CI runs the full suite on
ubuntu-latest,macos-latestand
windows-latest.
How to pin 1.0.0
A project pins the standard in contracts/shape-pin.yaml, which always
records a 40-hex commit. Name bfed143e89f97d3480cf5373051cb1ace82db312 itself rather than the tag: a
tag can be moved and a commit cannot, as the README and AGENTS.md say of every
pin.
-
New project: run
OPENREPOSHAPE_REF=bfed143e89f97d3480cf5373051cb1ace82db312 openRepoShape <Project> --org <your-org>.
The command forwards--shape-ref bfed143e89f97d3480cf5373051cb1ace82db312tosetup.sh. Outside a
checkout,setup.shandsetup-project.pytake--shape-refdirectly. -
Existing project or family holder: run these from a checkout of the
standard.checklists the files that change.applysays how many it
will re-copy and asksType yes to proceed:before it writes anything:./update-shape.py check --root <path> --at bfed143e89f97d3480cf5373051cb1ace82db312 ./update-shape.py apply --root <path> --at bfed143e89f97d3480cf5373051cb1ace82db312 \ --branch shape/update-bfed143e89f97d3480cf5373051cb1ace82db312 git -C <path> push -u origin shape/update-bfed143e89f97d3480cf5373051cb1ace82db312
Then open a pull request from that branch.
-
Confirm: run
./shape-doctor.py --root <path>from a checkout at
bfed143e89f97d3480cf5373051cb1ace82db312.COMPLIANT, exit 0, means the pin names that commit.
What changed on the way to 1.0
adopt-project.py
- In-place adoption. The repository becomes the assembly root and keeps
its name and history. The legs are extracted withgit filter-repo, and the
split lands as one pull request. Every source path is verified by blob
sha, a kept root.gitattributesas the source's bytes plus the block
below.
The plan asks about any path it cannot place, an empty leg is seeded with
--allow-empty-leg, and thexf-project-<id>topic is set
(#5, #16, #17, #24). - Branches and pins. The tracking branch defaults to the source's default
branch, andcheckandexecuterefuse one that is no branch of the
source (plan-tracking-branch-missing). The topic is set before the pull
request, and NEXT namesscripts/bootstrap.pywhen the source kept its own
Makefile (#493).plan --pintakes<product>@<commit>, andexecute
writes the pin file the scaffold writes, so the adopted root's
validate-pins.pypasses; a--pin-sourcethat cannot answer is
pin-source-unreadable(#508). A pinned product such as007is listed
inproject.yamlas text, not as the number 7 (#515). - The source's own name.
planrefuses an--organd--projectthat
name another repository than the source's, compared without case
(adopt-not-the-source).checkandexecuterefuse a plan edited to
name one (plan-not-the-source), and a source whose own name the naming
policy refuses is told to rename it first (#513). - Keywords as typed.
planwrites a value bare only where the project's
own YAML loader reads it back as that text, so--spec-path null,
--id nulland--tracking-branch true, on a source with a branchtrue,
adopt as typed. Before, the first mounted the leg atspec, the second made
the idnorthwind, and the third was refused about a value nobody typed. An
explicit null leg path in a plan (spec_path: null) is a
plan-bad-leg-pathfinding, not the defaultspec. A backstop
(adopt-manifest-misread,plan-manifest-misread) refuses, before any leg
exists, a value the loader would read back as something else from the line
executewrites. The shipped templates quote those lines, so it names none
today (#518). - Refused before any leg exists.
executerefuses everythingcheck
finds before it creates a leg. That covers bad mount paths, root paths that
clash with the shape's files, the source's ownproject.yamlkept in the
root, a symlink with an empty target, values no file can hold
(adopt-unwritable-value), and paths it cannot extract, whichplannow
asks about. A split branch an earlier run left, or a branch in its way, is
adopt-branch-existsorplan-split-branch-taken, and a hand-edited key
of the wrong type, or a date the tools cannot read, is a finding, not a
traceback
(#170, #187, #198, #231, #270, #313, #353, #356, #377, #382, #387, #435). - Symlinks the root keeps.
checkfollows each kept symlink that a
path the shape writes runs through. One the shape cannot write through,
because it dangles, leads out of the tree, enters a leg or a submodule,
loops, or carries the shape's scripts out of their directory, is a
plan-root-symlinkfinding, andexecuterefuses it before any leg
exists. Where git checks a symlink out as a file,executealso refuses
one the shape writes beneath (adopt-symlinks-off). Such links used to
break the run after both legs were pushed, or put the shape's files
outside the assembly. A link at a shape path to something that is there
still gets the copy beside it (#504). - A source's own
.gitattributes. A source that keeps one at its root
gets one marked block of/<copy> text eol=lflines appended to it in
the split, a line for each pinned copy outsideshape/, so a Windows
clone writes those copies with the bytes the pin records. The
verification counts that file as the source's bytes followed by exactly
that block, andupdate-shape.py apply --addextends the block (#511). - Submodules.
.gitmodulesstays in one leg with its submodules. An
unreadable one is aplan-gitmodules-unreadablefinding, not a crash, and
relative URLs are checked from both sides. Verification accepts no root
gitlink but the two leg mounts, each at the commit pushed to its leg
(adopt-root-submodule,adopt-mount-commitand others).execute
records inproject.yamlthe leg each kept submodule went to, and the
doctor holds it there
(#162, #173, #194, #226, #240, #245, #268, #394). - Ignore rules and odd names. Shape files are committed even when the
source's.gitignorematches them, and leg mounts use--force. Findings
spell out control characters and bytes that are not UTF-8, in a
hand-editedquestion:and a submodule's name too, follow-up searches take
paths literally, and a cp1252 console gets escaped names
(#182, #200, #223, #228, #246, #247, #378, #384). - Late failures. After a leg exists, a failure prints
REFUSED <step>.
and what the run left behind (#445). Exit 1 means the verification failed.
Exit 2 withIS pushedis finished by hand with the command it prints,
never by re-running (#371).
update-shape.py and bump-leg.py
- Re-syncing copies.
checkgives each pinned file a verdict
(unchanged,upstream-changed,locally-modified,bothand others)
and names files the standard has added (upstream-added). It exits 1 when
there is work to do.applyre-copies and re-pins, and rolls back if the
project's validators fail.--addand--accept-localeach name one file
and can be repeated. Family holders, and their README'sShape:line, update the same
way, and the commit and refusals name the holder's own manifest
(#10, #29, #47, #52, #85, #152, #159). - **Local edits and merged c...