Repository navigation
Releases
openRepoTools v1.2.0
openRepoTools v1.2.0
v1.2.0 makes every register write fetch before it edits and confirm on origin after it pushes, writes a lane's handoff by that same act, and stops the lane tools from losing a lane they could not read whole. It is openRepoTools as it stands at ecd1bce7b1079ef81215d6166b6db9ee7d481adf in opensoft/openRepoTools-code, tagged on 2026-11-10. 24 pull requests landed on the code leg's main between v1.1.0's commit, 2a057a6d, and this release's: #2 to #25, the last of which made that main ecd1bce. 11 more landed on the spec leg's main, in opensoft/openRepoTools-spec, after v1.1.0's notes: opensoft/openRepoTools-spec#6 to opensoft/openRepoTools-spec#18, all but opensoft/openRepoTools-spec#14 and opensoft/openRepoTools-spec#17, which are open.
v1.2.0 is the second version on PyPI, after 1.1.0. It is published from opensoft/openRepoTools-code, as 1.1.0 was, and its tag is made there. Feature 007, the SQL register (opensoft/openRepoTools#165), is not part of it: on Brett Heap's word of 2026-11-10 it moves to the v1.3.0 milestone, and no part of it ships here.
What 1.2.0 adds
- Every register write fetches first, and says
pushedonly once origin has it (opensoft/openRepoTools#198; #17 with opensoft/openRepoTools-spec#15). This is act 2 of lane-collision-protocol Amendment 23. Eachlanes-edit.shwriter, routed or not, now fetches and fast-forwards its checkout under the checkout's lock before it reads the file it edits. Where the checkout cannot be brought to origin's tip, it refuses with exit 2, nothing written, and names the checkout and its recovery: a fetch that failed,LANES_NO_FETCH=1among them; an operation in progress or unmerged entries; HEAD off the branch; staged entries that are not the write's own; unpushed commits; a diverged branch; a pending change to a path the write commits; or a fast-forward git refused. The write then commits by pathspec, pushes with no pull, and reads origin back.pushed origin/<branch> (attempt N)and exit 0 now mean that origin holds the commit, and a push that went, or timed out, and cannot be confirmed is exit 3,pushed, not confirmed: <sha>, never success. A push refused because origin moved replays the one edit on origin's new tip, with the verb's own checks run again, aclaim's rescan among them, up to six attempts in all. At that bound, or where a push is refused with origin unmoved, the commit is kept on HEAD and named, with exit 6, and every later write from that checkout refuses until the lane publishes or archives it.lanes-edit.sh commitwith nothing staged is the publish act: it sends the commits origin lacks by a fast-forward push only, and refuses (2) a stack that origin has moved past, naming the archive and thereset --keepthat settle it. - A lane's handoff is written by one verb, under the lock (opensoft/openRepoTools#198; #18 with opensoft/openRepoTools-spec#16, #20 and #19).
lanes-edit.sh write-handoff <lane>writes a lane's handoff in the checkout its writes go through, as a Rule 3 stamp (--stamp), a new top block (--prepend) or a whole file (--whole), under that checkout's lock and with the fetch, the publish, the read-back and the replay that every other write has. With--prependit proves that the block and the old file both survive before it replaces the file.lanes-edit.sh handoff-path <lane> [--create]says where that file is.lane-startwrites its Rule 3 stamp, andlane-handoffits PAUSED block, each with onewrite-handoffcall, and the handoff skill writes its new top block with onewrite-handoff --prependcall, so none of the three commits, pulls or pushes on its own any more. After its first write,lane-startrefuses a launch only at a restart's own fences; where the stamp could not be written it says so and prints thewrite-handoff --stampcall that writes it. A launch reads only the filehandoff-pathnames, and where that file is not there it goes on with no first prompt and names the file. Wherelane-handoffor the skill could not write the top block, the block is kept and printed with the onewrite-handoff --prependcall that puts it in. UnderLANES_NO_GIT=1,lane-startandlane-handoffsay that the handoff is written and nothing is committed or pushed. - Each lane can write through its own clone of the register, by opt-in (opensoft/openRepoTools#198; #16 and #20). W is the checkout of the register that
workspace.yaml'spath:names. WithLANES_ROUTE_WRITES=1and noLANES_REPO, every write goes through the lane's own clone,<W>-lanes/<lane>/, made on first use, while reads still read W. W then takes no commit: aLANES_REPOthat names W refuses every write (2), and W moves only bylanes-edit.sh refresh-workspace, a fast-forward, whichlane-startruns before and after its writes.lanes-edit.sh lane-checkout <lane> [--create]names a lane's clone, or makes it.lane-worktrees' report lists<W>-lanes/<lane>under "Register checkouts (Amendment 23 S1)", with what each holds that origin lacks, and never under FOREIGN. WithoutLANES_ROUTE_WRITES=1, which is how this release ships, every write goes throughLANES_REPO, W by default, and every lock is that checkout's, as in 1.1.0. lane-end --sweep-checkoutsremoves the clones nothing holds (opensoft/openRepoTools#198; #21). It lists the clones under<W>-lanes/that CLOSED lanes left, and the clones of the register beside W, and it is a dry run by default: exit 3 where something is removable, and 0 where nothing is. With--yes, run as the laneLANES_LANEnames, it removes only those nothing holds, each decided again under its own lock after a fetch. W, a live lane's clone, the runner's own, the one a caller'sLANES_REPOnames and a symbolic link are never candidates. Work origin lacks, uncommitted or untracked files, a stash, an operation in progress, a repository that borrows the clone's objects, a linked worktree, a process working inside it or naming it inLANES_REPO, and a file system mounted in it each hold a clone. What it removed is recorded in oneNOTEDline in the runner's own log, and exit 1 is a read that failed, or a removal that did not finish or was not recorded. It refuses (2), the dry run too, from a window whoseLANES_WORKSPACE_ROOTnames a lane's own checkout, and--yesrefuses (2) while that variable names any directory butworkspace.yaml'spath:: run it with the variable unset.- The lane tools no longer lose a lane they could not read whole (the five issues of the
v1.2.0milestone, carried to the legs as part of opensoft/openRepoTools#186):- A lane whose recorded directory holds a space is no longer left out of
worktrees --all, ofworktrees <lane>and of the inventory index:worktree_rowshands the directory on quoted where it holds a space, aslane-startwrites it (opensoft/openRepoTools#295, #3). - A lane whose recorded directory holds a TAB or a backslash is listed, indexed and reached as recorded. The listing's directory column is escaped as
lane-worktreesescapes a porcelain field, andlanes,laneandworktreesdecode it. Every read of a stored sub-field gives back what was written, becausepayload_subfieldno longer passes it throughawk -v. Such a lane's lifecycle and trees are now looked for beside its checkout, and records the old read filed under$PROJECTS_ROOT/.lane-stateor a sibling directory stay there, unread, with no migration. The binding and pause checks judge the text that is written and not itsawk -vexpansion, sohost a\101bis no longer passed asaAb, andrequest-handoffrefuses a requesting host or container outside letters, digits,.,_,-and:, on the request and--forcepaths and on either one's--dry-run, with exit 2 and "Nothing was written" (opensoft/openRepoTools#301, #4 with opensoft/openRepoTools-spec#7). - A lane's control root, or a directory above it, that this account cannot search is read as not read, not as no trees:
lane-stateandlane-treesexit 10, not 8, and say why,worktreesnames the laneUNREADso thatlanes-index synckeeps its rows,lane-reconcileisindeterminate, and a write under such a root fails instead of landing under$PROJECTS_ROOT(opensoft/openRepoTools#291, #5 with opensoft/openRepoTools-spec#8). - A tree record that names no absolute path no longer swaps its tree's row in the index for one keyed by the record's id:
worktreesnames the laneUNREAD, solanes-index syncandreconcile --source inventorykeep the lane's rows and say why, and one such record holds every row of its lane until it is put right (opensoft/openRepoTools#302, #6 with opensoft/openRepoTools-spec#9). - A named
worktrees <lane>read saysUNREAD, with exit 0, where the lane's log or the register could not be read, where it used to say none (8). Thelaneslisting says what it could not read and exits 1 where it names no lane for that reason, a tree's writer is unknown and not0where the lane's binding was not read, andlane-stateexits 10, not 8, over a log it could not read, solanes-indexholds that lane's rows (opensoft/openRepoTools#290, #7 with opensoft/openRepoTools-spec#10). A known gap remains (opensoft/openRepoTools#303): most other reads of a lane's log still cannot tell a failed read from a lane with no log.
- A lane whose recorded directory holds a space is no longer left out of
What else changed since v1.1.0
- The openRepoShape pin moved. This release pins openRepoShape at v1.1.1,
5017830on itsmain, where 1.1.0 pinned v1.1.0,190133d. Four of the eight files the suite mounts...
openRepoTools v1.1.0
openRepoTools v1.1.0
v1.1.0 puts openRepoTools on PyPI. It is openRepoTools as it stands at 2a057a6d84e58c8acb4bda20d2b7b667b552c3fe in opensoft/openRepoTools-code, tagged on 2026-10-28. pipx install openrepotools is a second way in, beside the one-line install and a checkout's --install, and openRepoTools --version from the package names the release it runs. 60 pull requests landed between v1.0.0's commit, aaa7bc8, and this release's: 59 on main in opensoft/openRepoTools, up to 33ce26d, the commit the triad's legs were carved from, and then #1 on the code leg's main. The split itself, opensoft/openRepoTools#344, which made opensoft/openRepoTools the triad's assembly root, landed there after them and is not one of the 60.
v1.0.0 is not on PyPI, and 1.1.0 is the first version there. This release is published from opensoft/openRepoTools-code, after the triad migration (opensoft/openRepoTools#186), and its tag is made there. v1.0.0 stays in opensoft/openRepoTools, where it was made, and no tag is carried from one repository to the other.
What 1.1.0 adds
-
openRepoTools on PyPI, as
openrepotools(opensoft/openRepoTools#228). https://pypi.org/project/openrepotools/1.1.0/ is its page. Two commands install it:pipx install openrepotools openRepoTools --install
The package places one command,
openRepoTools. It runs the packagedopenRepoToolswith the firstbashon yourPATHand your arguments unchanged, and with nobashit refuses and names it. It needs Python 3.9 or newer and declares no Python dependencies. The toolset's own requirements are unchanged:bash,git, andjqfor--install.The wheel carries everything
--installplaces, the sixteen commands andrepos.tsv, the three skills and the three command files, and the openRepoShape pin, each byte for byte as the release commit holds it. SoopenRepoTools --installrun from the package reaches no network. pipx ownsopenRepoToolsin its bin directory,~/.local/binby default, as a link into its own environment, and--installleaves that one link alone: it places the other sixteen, says1 left to pipx, and writes 28 receipt rows where a full install writes 29.The version is the tag's, and no file in the repository carries a version number: the release build stamps it into the wheel.
-
--versionnames the release (opensoft/openRepoTools#228). From the package it prints five lines:openRepoTools 1.1.0 (opensoft/openRepoTools-code @ 2a057a6d84e58c8acb4bda20d2b7b667b552c3fe) version 1.1.0 tag v1.1.0 commit 2a057a6d84e58c8acb4bda20d2b7b667b552c3fe digest 31123e2d08efe36583e72956ed8b8dbbe4b968c75402b59af5b8952be83ef6d5 (sorted-ls-tree-r-v1)A package built outside a release prints
openRepoTools 0.0.0 (an unstamped package)and says so. A checkout and the one-line install print a repository and ref, as in v1.0.0, and no version number: a checkout's ref ismainby default, and the one-line install, which since the triad migration (opensoft/openRepoTools#186) runs through the assembly root's front door, names theopensoft/openRepoToolscommit it resolvedmainto. A package whose stamp is broken refuses every invocation,--helpincluded, and names the fix,pipx reinstall openrepotools. -
--installmeets each other installer by a rule (opensoft/openRepoTools#228):- pipx's link is skipped, by whichever
--installmeets it: the package's, a checkout's or the one-line install's. A checkout's or the one-line install's count line then says where the other sixteen now come from. - pip's console script at the target
openRepoTools, whichpip install --user openrepotoolsleaves, is refused in every mode before anything is placed, and the refusal names pipx as the one package route. - Any other link at a target still refuses the whole install, and no refusal advises removing pipx's link.
- After a package-mode install, a note names the first
openRepoToolson yourPATHwhen it is not the package's, so a stale bash file ahead of pipx's link is not mistaken for the release.
- pipx's link is skipped, by whichever
-
The hook pair waits for its target (opensoft/openRepoTools#237, opensoft/openRepoTools#247).
openRepoTools --installandopenRepoTools wip initadd the pair of hook entries,SessionStartand the name guard (guard), to~/.claude/settings.jsononly once~/projects/xFactory/lanes-edit.shexists, is executable and answersguard. Until then they say why, and how to complete it, and our exact guard entry is taken back out of a file that already carries it; ourSessionStartentry, which ends in|| true, and every hook of yours stay. So a workstation upgrading from v1.0.0 whose~/projects/xFactory/lanes-edit.shdoes not answerguardloses that one entry on its next--install, untilopenRepoTools wip init, orlink-estatesand--installagain, completes the pair. Before this, a fresh workstation got both entries before workspace initialization had provided that file, so the name guard could fail with a command-not-found error at every prompt. -
CI proves the package, and a release workflow publishes it (opensoft/openRepoTools#228).
tests.ymlruns on every push tomainand to everyrelease/v*branch, as well as on pull requests, nightly and by dispatch, and every run has four jobs:guard-launch-mode,tests,tests-no-submoduleandpackage. Thepackagejob builds the wheel twice at the commit's time and requires the two to be one file, checks every packaged payload file against the commit's blob in bytes and in mode, checks the wheel's.dist-info, installs the wheel with pipx, runs the unstamped--versionand an offline--installbeside pipx's link, and runs the package's tests, failing if one is skipped.release.ymlis run by hand, on an annotated tag, with the version. It is adapted from openRepoShape v1.1.0's. Its build refuses unless the ref is the version's annotated tag and the tag names the run's commit; that commit is onmainor onrelease/vX.Y; thetestpypiandpypienvironments each name a required reviewer and limit deployments tov*tags; the commit's four CI jobs ran in a push or dispatch run oftests.ymland the newest succeeded; and the tree digest computed here equals the one computed from GitHub's tree listing. It then builds twice fromgit archive, stamps the build, and requires one file.- It uploads by trusted publishing, with no token: to TestPyPI, then an install check on Ubuntu, on macOS (run again under macOS's own bash 3.2) and on Ubuntu with Python 3.9, then to PyPI. Each upload waits for Brett Heap's approval in its environment. A pre-release
X.Y.ZrcNreaches TestPyPI only. Onlyopensoft/openRepoTools-codemay publish a final version, and nothing uploads to PyPI fromopensoft/openRepoTools.
-
Releases are annotated tags (opensoft/openRepoTools#228). A release is an annotated tag
vX.Y.Zon a commit onmainor onrelease/vX.Y, never a branch and never a version number committed to a file, and a tag is never moved or reused.release/vX.Yis cut atvX.Y.0and carries the patch tags. A fix lands onmainfirst, then reaches the release branch by a fast-forward, a merge, or a pull request ofgit cherry-pick -x. A version is uploaded once, and a bad upload is fixed by the next patch. -
The lane tools name what they could not read in a triad's legs (opensoft/openRepoTools#255, opensoft/openRepoTools#271). It closes two gaps in opensoft/openRepoTools#246, which taught the lane tools to look inside a triad's legs; each broke that change's rule that a read that failed is never an empty one.
lane-handoffnames every tree-inventory record that is not a whole schema-1 one in its NOT POLLED note, and the lane stays SWAPPING. A leg directory this account cannot search is unreadable and neverabsent:lane-worktrees sweepexits 3,lane-endrefuses over it,lane-reconcilegives it anunreadablerow, andlane-handoffwithholds its tree. A dependency nested in a leg is unreadable to the sweep too where this account cannot search its directory, or a directory above it inside the leg: the sweep exits 3, andlane-endrefuses over it. A leg directory that is not there at all is stillabsent. A known gap remains (opensoft/openRepoTools#272): the sweep also reads a declared leg beneath a directory this account cannot search asabsent, solane-enddoes not hold on it. These readers run only in an assembly, a checkout whoseproject.yamldeclares a leg, so a checkout with noproject.yamlsees no change. -
lane-handoffreads the lane's lifecycle whole (opensoft/openRepoTools#280, opensoft/openRepoTools#283). It used to pipelanes-edit.sh lane-stateinto anawkthat exits as soon as it has printed the one field it wants. Underpipefail, alane-statethat wrote again after that died of SIGPIPE, so now and then the read exited 141, the handoff said the lifecycle could not be read, and it recorded no RUNNING to SWAPPING to SWAPPED transition. That failed safe, but whether it happened depended on timing alone, and it could turn a pull request'stestsjob red at random. The handoff now takeslane-state's whole output first and parses it afterwards, so its status islane-state's own. The read of the operation of a lane that is still SWAPPING, which only names that operation in a note, is made the same way.
What else changed since v1.0.0
- The openRepoShape pin moved. This release pins openRepoShape at v1.1.0,
190133don itsmain, the commit its tests were verified against (opensoft/openRepoTools#233). v1.0.0 pinned7f84ca4. - The estate commands (
park,resumeandstatus):resumeandstatushold each leg to the branch bootstrap puts it on: the leg row's ownbranch:, else the root'stracking_branch(opensoft/openRepoTools#238).
...