Skip to content

openRepoTools v1.1.0

Choose a tag to compare

@brettheap brettheap released this 28 Oct 21:41
· 429 commits to main since this release
2a057a6

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 packaged openRepoTools with the first bash on your PATH and your arguments unchanged, and with no bash it 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, and jq for --install.

    The wheel carries everything --install places, the sixteen commands and repos.tsv, the three skills and the three command files, and the openRepoShape pin, each byte for byte as the release commit holds it. So openRepoTools --install run from the package reaches no network. pipx owns openRepoTools in its bin directory, ~/.local/bin by default, as a link into its own environment, and --install leaves that one link alone: it places the other sixteen, says 1 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.

  • --version names 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 is main by default, and the one-line install, which since the triad migration (opensoft/openRepoTools#186) runs through the assembly root's front door, names the opensoft/openRepoTools commit it resolved main to. A package whose stamp is broken refuses every invocation, --help included, and names the fix, pipx reinstall openrepotools.

  • --install meets each other installer by a rule (opensoft/openRepoTools#228):

    • pipx's link is skipped, by whichever --install meets 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, which pip install --user openrepotools leaves, 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 openRepoTools on your PATH when it is not the package's, so a stale bash file ahead of pipx's link is not mistaken for the release.
  • The hook pair waits for its target (opensoft/openRepoTools#237, opensoft/openRepoTools#247). openRepoTools --install and openRepoTools wip init add the pair of hook entries, SessionStart and the name guard (guard), to ~/.claude/settings.json only once ~/projects/xFactory/lanes-edit.sh exists, is executable and answers guard. 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; our SessionStart entry, which ends in || true, and every hook of yours stay. So a workstation upgrading from v1.0.0 whose ~/projects/xFactory/lanes-edit.sh does not answer guard loses that one entry on its next --install, until openRepoTools wip init, or link-estates and --install again, 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.yml runs on every push to main and to every release/v* branch, as well as on pull requests, nightly and by dispatch, and every run has four jobs: guard-launch-mode, tests, tests-no-submodule and package. The package job 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 --version and an offline --install beside pipx's link, and runs the package's tests, failing if one is skipped.
    • release.yml is 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 on main or on release/vX.Y; the testpypi and pypi environments each name a required reviewer and limit deployments to v* tags; the commit's four CI jobs ran in a push or dispatch run of tests.yml and the newest succeeded; and the tree digest computed here equals the one computed from GitHub's tree listing. It then builds twice from git 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.ZrcN reaches TestPyPI only. Only opensoft/openRepoTools-code may publish a final version, and nothing uploads to PyPI from opensoft/openRepoTools.
  • Releases are annotated tags (opensoft/openRepoTools#228). A release is an annotated tag vX.Y.Z on a commit on main or on release/vX.Y, never a branch and never a version number committed to a file, and a tag is never moved or reused. release/vX.Y is cut at vX.Y.0 and carries the patch tags. A fix lands on main first, then reaches the release branch by a fast-forward, a merge, or a pull request of git 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-handoff names 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 never absent: lane-worktrees sweep exits 3, lane-end refuses over it, lane-reconcile gives it an unreadable row, and lane-handoff withholds 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, and lane-end refuses over it. A leg directory that is not there at all is still absent. A known gap remains (opensoft/openRepoTools#272): the sweep also reads a declared leg beneath a directory this account cannot search as absent, so lane-end does not hold on it. These readers run only in an assembly, a checkout whose project.yaml declares a leg, so a checkout with no project.yaml sees no change.

  • lane-handoff reads the lane's lifecycle whole (opensoft/openRepoTools#280, opensoft/openRepoTools#283). It used to pipe lanes-edit.sh lane-state into an awk that exits as soon as it has printed the one field it wants. Under pipefail, a lane-state that 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's tests job red at random. The handoff now takes lane-state's whole output first and parses it afterwards, so its status is lane-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, 190133d on its main, the commit its tests were verified against (opensoft/openRepoTools#233). v1.0.0 pinned 7f84ca4.
  • The estate commands (park, resume and status):
    • resume and status hold each leg to the branch bootstrap puts it on: the leg row's own branch:, else the root's tracking_branch (opensoft/openRepoTools#238).
    • park, resume and status find a family folder named for its repository or for its display name, and resume names the holder's repository from family.yaml where it is on disk (opensoft/openRepoTools#240).
    • park's not-carried report counts a leg's unpushed commits on the branch its own row declares, else the root's tracking_branch (opensoft/openRepoTools#249).
    • family.yaml values are read the way openRepoShape v1.1.0 reads them, status names one family laid out twice, and resume finds a holder by its id when its record names another folder (opensoft/openRepoTools#251).
  • The register and lanes-edit.sh:
    • swapped names a lane by the register row's spelling, not the log file's (opensoft/openRepoTools#201).
    • superseded_by follows a chain of TAKEOVERs to its last link, so one lane holds the object (opensoft/openRepoTools#204).
    • The lane listing reads a row's state phrase when its log is missing, so a migrated PAUSED row stops showing NO LOG (opensoft/openRepoTools#206).
    • managed-projection reads this checkout's row as the writers do, lane-trees tells an unlistable trees directory from no inventory, and the manual's seven-column row is qualified (opensoft/openRepoTools#213).
    • lanes-edit.sh commit with nothing staged still publishes the local commits that origin lacks, when each of them only adds or changes a register or handoff file, so the recovery's re-run of commit pushes the edit it stranded, and the conflict recovery warns that reset --hard drops a peer's commits (opensoft/openRepoTools#214).
    • A one-line record cannot take a newline: append-line and lane-handoff's why refuse it before writing, and the deferred lane-start command keeps --agent (opensoft/openRepoTools#218).
    • lanes-index follow-ups: export reads one snapshot, history --index says its closing stderr count, a foreign source is refused before the lock, a damaged table reads as unusable, an unread lane stays NOT READ in the index, and the worktrees footer names the workstation (opensoft/openRepoTools#219).
    • A register that could not be read no longer reads as a lane with no session ids, so the dead-lane --force check and duplicate-holder fail closed (opensoft/openRepoTools#221).
    • Register sweeps and reads refuse what they could not read: retire-rows, archive-rows, migrate-state-cells, the DORMANT class, next-free and add-row (opensoft/openRepoTools#230).
    • Lifecycle reads that failed are no longer answers, set-lane-tree's fence closes with its operation, and a lane sidecar is valid only when whole (opensoft/openRepoTools#252).
    • The register's rewrites refuse a copy or write they cannot prove and put a cut-short register back, retire-rows refuses an unpushed log rename before it writes and calls a lane closed only from its published log, and add-row refuses an archive row it cannot read and takes back a row refused after its rebase (opensoft/openRepoTools#263).
    • A lane listing that is not whole no longer removes indexed worktree rows: the inventory read says PARTIAL, lanes --worktrees prints it NOT WHOLE, and lanes-index writes nothing over it; an older helper's tree with no path stays a tree (opensoft/openRepoTools#289).
  • lane, lane-start, lane-end, the name guard and their helpers:
    • lane-end's synopsis says what a DUPLICATE retire does, a state cell that breaks the phrase's grammar is read whole instead of narrowed, and the TAKEOVER grammar table and two tests.yml header comments are corrected (opensoft/openRepoTools#202).
    • lane-start's transcript follow counts a holder only when transcript-holders verifies it, not on kill -0 (opensoft/openRepoTools#203).
    • duplicate-holder and lane-end --retire read a --resume path whole and act on every verified child, not the first (opensoft/openRepoTools#205).
    • The name guard and the SessionStart hook name an unreadable register or binding log instead of guessing a branch (opensoft/openRepoTools#208).
    • lanes filters lanes-edit.sh's no-fetch notice out of its stderr with a grep that now takes its pattern as -e's argument, so under macOS's BSD grep the -- after the pattern ends the options and is no longer read as a file name, and every other line is printed as lanes-edit.sh wrote it, with no grep: --: No such file or directory error and no temporary file's name in front; the suite's repoG#8 assertion can now fail, and the bash files are no longer counted in prose (opensoft/openRepoTools#209).
    • Three wrapper reads that failed no longer read as no answer: lane's window-name check asks the unpushed local row too, lanes' hidden-row probe reads its whole stream, and lane-end --retire-dormant refuses without its stderr capture (opensoft/openRepoTools#210).
    • claude-current falls back from a native Claude Code that does not run to the next one that does, and claude-restart-check recognises a running native in a custom CLAUDE_CURRENT_NATIVE_DIR, symlinked included (opensoft/openRepoTools#211).
    • lane-start fails closed on three fences: a new lane's rename is read back and undone when another window took the name, a row or log that says RETIRED is not started again, and a new lane never takes a window named for another registered lane (opensoft/openRepoTools#216).
    • lane and lane-handoff refuse empty and flag-shaped flag values, and lane-handoff renames the window only once the handoff can no longer be refused (opensoft/openRepoTools#220).
    • A silent tmux is no longer read as a dead window, the companion verdict is read off the lane's live window from any window, and the guard's cure for a lane named before Rule 4 says what it cannot fill in (opensoft/openRepoTools#222).
    • lane-start gains a guard-only binding mode that stamps the transcript being bound, so the offer's yes no longer records a RESUMED nobody is in (opensoft/openRepoTools#223).
    • Under set -e, lanes --worktrees, openRepoTools wip init and lane-end no longer end on macOS's bash 3.2 where a utility run through command fails, and a scan of the shipped scripts keeps that shape out (opensoft/openRepoTools#294).
  • The handoff and its skill:
    • The handoff skill commits the handoff in the checkout it was published in, by its own path, and refuses foreign staged entries and in-progress operations (opensoft/openRepoTools#199).
    • Every shell block in the handoff skill parses, and the skill stops calling /rename the only act there is (opensoft/openRepoTools#200).
    • The handoff skill resolves lanes-edit.sh on PATH first, its comments no longer promise write (c) without a workstation, and it refuses the object-log and register-line writes for a lane that has never had a session recorded (opensoft/openRepoTools#207).
    • rename-lane's Rule 3 handoff stamp is published through a same-directory temporary file and one atomic rename, so a writer killed mid-stamp leaves the handoff whole (opensoft/openRepoTools#212).
    • The handoff skill's step-1 fence refuses Amendment 15(d)'s exit 2 instead of reading it as no answer, and its window ref is checked as a whole string (opensoft/openRepoTools#260).
    • The handoff skill's two PAUSED writes carry the why it computed, sanitized as lane-handoff sanitizes it, and a why carrying a line break is refused before step 1 (opensoft/openRepoTools#264).
  • The lane tools look inside a triad's legs and paired feature trees, so the sweep, lane-end's gate, lane-handoff, lane-reconcile and the daily report can see a lane's work there, and a checkout with no project.yaml is treated as before (opensoft/openRepoTools#246).
  • The test suite and its checks:
    • Follow-ups to the test roots of opensoft/openRepoTools#190: a refused root's value is named, the pin's commit is checked, paired feature worktrees are found, redirected roots show under -q, the lane manual's sections run, and the installer-source test is restated (opensoft/openRepoTools#195).
    • Clause (c)'s TAIL case is read on a checkout at a short fixed path, and lane-start's row stamp is driven at three checkout lengths, so the case no longer answers by the length of the run root (opensoft/openRepoTools#215).
    • The shell suite's wrapper stops the suite's process group on a timeout, an interrupt, TERM or hangup, the launcher precedence of lane <name> is pinned with launchers the suite names, and the pty driver's comment says what it counts (opensoft/openRepoTools#217).
    • The hygiene check refuses host paths under the bench root, with an allowance for quoted grep patterns, and the consumer reads and the durable-intent sample no longer carry them (opensoft/openRepoTools#229).
    • tests/run.sh refuses to start when it cannot record its lock's owner, and its cleanup removes only a lock that is still its own (opensoft/openRepoTools#232).
    • The hygiene check's end-of-options rule splits a line only outside quotes, and the suite stops counting its bash files (opensoft/openRepoTools#239).
    • The hygiene check reads every exit-3 die of lanes-edit.sh against its cause and both documented rows, so a die that claims nothing was pushed, or a fourth die no row documents, fails it (opensoft/openRepoTools#259).
    • The shell suite times each of its sections and its exit-trap teardown, and prints both in its transcript and in CI's step summary (opensoft/openRepoTools#261).
    • The shell suite's wrapper prints the suite's timing table and teardown line on a pass, where CI's log can read them (opensoft/openRepoTools#265).
    • CI and the release workflow run on ubuntu-24.04, because ubuntu-latest is now Ubuntu 26.04, where setup-python has no Python 3.9 and the TERM-ignoring kill test fails: every Ubuntu runner in tests.yml and release.yml is pinned to it, the tests and the 006 contract lines that name the label follow, and macos-latest is unchanged (opensoft/openRepoTools#296).
  • The triad migration's records:
    • The migration's evidence of 2026-10-08 and 2026-10-09 is recorded: test roots and CI patches, the installer entry point, restore and rollback, opensoft/openRepoTools#192's corrections, T018 and the Gate B status (opensoft/openRepoTools#196).
    • T006's assembly-0001 and T010's code-leg tests.yml patch are re-cut against the files that opensoft/openRepoTools#228's series leaves, T009's assembly-root length-caps patch is added, and lines 17 and 33 of the Gate B walk are marked PARTLY until they are reviewed, as part of opensoft/openRepoTools#186 (opensoft/openRepoTools#266).
    • T010's patch for the assembly's exact-pins job is regenerated so that the job runs on ubuntu-24.04, like the code leg's jobs under opensoft/openRepoTools#296, and the Gate B documents name that runner and the patched tests.yml's job lines as opensoft/openRepoTools#296 leaves them, as part of opensoft/openRepoTools#186 (opensoft/openRepoTools#299).
    • After the split, the code leg gets its own front door, the shape's leg AGENTS.md, CLAUDE.md and README.md, with the LF rule in .gitattributes, an ignore list and a LICENSE, which the split had left in the root alone; its tests.yml names the standalone checkout it runs in and changes no job, trigger or run: line; the suite's line caps on an assembly root's AGENTS.md and README.md become 354 and 529, the re-cut assembly-0001's own counts; and the shell suite's lane-start row-stamp cases no longer answer by the length of tests/run.sh's TMPDIR, for opensoft/openRepoTools#304, as part of opensoft/openRepoTools#186 (#1).
  • These release notes say that the fixes for opensoft/openRepoTools#255 and opensoft/openRepoTools#280 are part of 1.1.0, and list by area what else changed on main since v1.0.0 (opensoft/openRepoTools#293).

Install and upgrade

  • From PyPI: pipx install openrepotools, or pipx install openrepotools==1.1.0 for this release, then openRepoTools --install. pipx itself comes from sudo apt-get install -y pipx on Debian and Ubuntu and brew install pipx on macOS; then run pipx ensurepath and open a new terminal. The package is tested against the layouts of pipx 1.11.1, 1.17.8 and 1.18.3. openRepoTools --version prints the five lines above.

  • From the one-line install to pipx: run

    pipx install --force openrepotools
    openRepoTools --install

    --force is needed. The one-line install left a regular file at ~/.local/bin/openRepoTools, and a plain pipx install openrepotools meets it: pipx only warns that the file exists and that it is not modifying it, places no link, and exits 0. The bash file stays at that path and keeps running, so --version still names a repository and ref and not a release, and its next --install finds no payload beside it and fetches main. --force puts pipx's link in the file's place. Then openRepoTools --install skips that link and replaces the other sixteen files with the package's. On a workstation with no openRepoTools yet, pipx install openrepotools is enough.

  • Upgrade: pipx upgrade openrepotools, then openRepoTools --install again. Until that second command, the placed files, skills, command files and hook entries stay the earlier release's, and the link is skipped again.

  • Remove: pipx uninstall openrepotools removes only the command and its environment. The placed files, the skills, the command files, the hook entries and the receipt stay, and a checkout's --install then places a regular openRepoTools at the freed path.

  • One install route or the other, never both. Until opensoft/openBenches#165 lets workBenches' scripts/setup-estate-commands.sh accept pipx's link, a workstation uses pipx's route or workBenches' ./setup.sh. ./setup.sh refuses a symlinked openRepoTools before it installs anything, and after installing it requires a regular file identical to its vendored copy, so on a workstation with pipx's link every ./setup.sh refuses its estate step and names the link. This is Brett Heap's option (c), of 2026-10-18. State of opensoft/openBenches#165 at the tag: open, and not landed, as read on 2026-10-28.

  • The one-line install and a checkout work as before, and their --version still prints a repository and ref and no release: a checkout's ref is main by default, and the one-line install's is now the opensoft/openRepoTools commit that the assembly root's front door resolved main to.

  • Windows is reached through WSL2, as for every bash command here. The package has no native Windows command, and it is a wheel only, with no source distribution.

Tagged on Brett Heap's word (2026-10-28T21:14:02Z, "yes, tag v1.1.0 at 2a057a6 and dispatch it").

Full list

These notes are docs/release-notes/v1.1.0.md in opensoft/openRepoTools-spec.