Repository navigation
Description
Problem Statement
thirdshift works well for me every day, but only on the machine where I build it. To run it anywhere else (my Rocky Linux server, my MacBook, or someone else's WSL setup) I have to clone this repository, install the Rust toolchain and run cargo install --path .. There is no version number anyone can point at, no release, and no way to update short of pulling and rebuilding. That blocks my main goal: running Runs from any of my servers. It also blocks a later one: a one-line install command on thirdshift.app (#29) that anyone can paste.
The README is also out of date. Its Status section still describes the walking skeleton, even though every sub-issue of the original spec (#2) is closed.
Solution
Release thirdshift 0.1.0 as prebuilt binaries on GitHub Releases, installable with one command:
curl -LsSf https://github.com/JacobStephens2/thirdshift/releases/latest/download/thirdshift-installer.sh | shThe installer comes from GitHub, not from a server of mine. It needs no Rust toolchain and puts thirdshift in ~/.local/bin. After that, thirdshift update fetches the latest release in place.
Supported platforms for 0.1.0:
- Rocky Linux 9.8 and later, x86_64
- Ubuntu 24.04.3 and later, x86_64
- WSL2, latest release, with Ubuntu 24.04 or later, x86_64
- macOS Tahoe 26.5.2 and later, Apple Silicon
One static Linux binary covers the first three, and a native build covers macOS.
The same release also publishes the crate to crates.io, which claims the thirdshift name with a real, working crate. The documented install path is still the curl installer.
The CLI gains a few small commands besides the Run: thirdshift update, thirdshift version and thirdshift help.
A release is cut by pushing a v<version> tag. Everything after that is automated and checked before the release is published.
Terms in bold are defined in CONTEXT.md. See also ADR-0001 (a Rust binary with embedded skills): the Factory skills stay compiled into every binary, so the prebuilt binaries are self-contained.
User Stories
Installing
- As a developer, I want to install thirdshift with one
curl … | shcommand, so that I can set up a new server in seconds. - As a developer, I want the installer and the binaries to be served from GitHub Releases, so that I only have to trust GitHub, not a personal server.
- As a developer, I want to install thirdshift without the Rust toolchain, so that my only prerequisites are
claude,ghandgit. - As a developer, I want the installer to put
thirdshiftin~/.local/bin, the same place Claude Code's installer uses, so that it is already on myPATH. - As a developer on Rocky Linux 9.8 or later, I want the Linux binary to run without glibc version errors, so that an older enterprise distribution works as well as the latest Ubuntu.
- As a developer on Ubuntu 24.04.3 or later, I want the Linux binary to run as installed, so that I can use thirdshift on my main machines.
- As a developer on Windows, I want the Linux binary to run inside WSL2 with Ubuntu 24.04 or later, so that I can use thirdshift without a separate Windows build.
- As a developer on a WSL2 machine, I want the README to say that repositories must live on the Linux filesystem, not
/mnt/c, and that WSL1 is not supported, so that I don't hit slow or broken git behaviour and blame thirdshift. - As a developer on an Apple Silicon Mac running macOS Tahoe 26.5.2 or later, I want a native binary, so that I can run thirdshift from my laptop.
- As a Rust user, I want
cargo install thirdshiftto work from crates.io, so that I can install it the way I install other Rust tools if I prefer. - As the maintainer, I want the
thirdshiftcrate name claimed on crates.io with a real crate, so that nobody else takes it. - As a Rust user, I want the crates.io package to contain only what builds thirdshift (the source, the Factory skills, the README and the licence files), so that the download is small and free of development-only agent configuration.
- As a developer who wants to build from source, I want the README to keep a "Building from source" section, with Rust as its prerequisite, so that I can still build from a clone.
Updating
- As a developer, I want to run
thirdshift updateto replace my installed binary with the latest release, so that updating needs no documentation lookup. - As a developer, I want
thirdshift updateto exit 0 and say so on stderr when I'm already on the latest version, so that I can run it any time without worrying. - As a developer, I want
thirdshift updateto exit 1 with an explanation on stderr when it fails (no network, GitHub unavailable), so that a failed update is visible. - As a developer who built thirdshift from source or installed it from crates.io, I want
thirdshift updateto refuse and tell me the command that updates my kind of install, so that it never overwrites a binary it didn't install. - As a developer, I want
thirdshift updateto leave stdout empty, so that stdout keeps its meaning for scripts. - As a developer, I want replacing the binary to be safe while a Run is in progress with the old binary, so that updating one server never breaks a Run on it.
- As a developer, I want a Run never to check for updates or update itself, so that an unattended Run never changes version partway through, or at all.
Command surface
- As a developer, I want
thirdshift versionto printthirdshift <version>to stdout and exit 0, so that I can tell which version a server is running. - As a developer, I want
thirdshift --versionandthirdshift -Vto behave exactly likethirdshift version, so that habit and tooling work too. - As a developer, I want
thirdshift helpto print every form of the command (thirdshift <Issue URL>,thirdshift update,thirdshift version,thirdshift help) with a one-line description of each, to stdout, exiting 0, so that I can find the commands without the README. - As a developer, I want
thirdshift --helpandthirdshift -hto behave exactly likethirdshift help, so that habit and tooling work too. - As a developer, I want the documentation (README and help text) to mention only the bare-word commands, so that there is one way to learn them.
- As a developer, I want
thirdshiftwith no argument to print an error and the help text to stderr and exit 2, so that a bare invocation teaches me how to use it. - As a developer, I want an unknown argument (neither a command nor an Issue URL) to print an error and the help text to stderr and exit 2, so that a typo fails fast and tells me what's valid.
- As a developer, I want an argument that isn't a valid GitHub Issue URL to keep exiting 2 as it does today, now with the help text instead of the one-line usage, so that nothing about URL validation regresses.
- As a developer, I want
thirdshift <Issue URL>to behave exactly as before (the Run, stdout carrying only the pull request URL, exit codes), so that nothing I script against changes.
Releasing
- As the maintainer, I want to cut a release by bumping the version in a pull request, merging it and pushing a
v<version>tag, so that releasing takes three steps. - As the maintainer, I want the tag to trigger a workflow that builds the Linux and macOS binaries, the installer and the release notes, and publishes the GitHub Release, so that nothing is built by hand.
- As the maintainer, I want the release workflow to run the static Linux binary's
versioncommand inside a Rocky Linux 9 container before publishing, so that a binary that won't start on Rocky is never released. - As the maintainer, I want the release workflow to run the static Linux binary's
versioncommand inside WSL2 with Ubuntu 24.04 before publishing, so that a binary that won't start on WSL is never released. - As the maintainer, I want the release workflow to publish the crate to crates.io with trusted publishing, so that no crates.io token is stored in GitHub.
- As the maintainer, I want the crates.io publish step to skip a version that is already on crates.io, so that the first, hand-published 0.1.0 and any re-run of the workflow don't fail the release.
- As the maintainer, I want the release notes to be generated from the merged pull requests into the GitHub Release body, with no changelog file, so that there's nothing extra to maintain.
- As the maintainer, I want a "Releasing" section in the README with the three steps, so that I, and agents, know how to cut the next release.
Continuous integration
- As the maintainer, I want every pull request's CI to run the full test suite on Ubuntu 24.04, so that the Linux build stays green.
- As the maintainer, I want every pull request's CI to run the full test suite on a macOS 26 Apple Silicon runner, so that the macOS build and its process-group handling don't regress unnoticed.
- As the maintainer, I want the slower Rocky and WSL checks to run only in the release workflow, so that pull request CI stays fast.
Documentation
- As a reader of the README, I want the Status section to say the designed behaviour is implemented and to point at open issues for known gaps, so that I get an accurate picture.
- As a reader of the README, I want the Install section to lead with the curl installer, so that the easy path comes first.
- As a reader of the README, I want the Updating section to say
thirdshift update, so that updating is one line. - As a reader of the README, I want a supported-platforms section listing the four platforms and the WSL caveats, so that I know whether my machine is covered.
- As a reader of the README, I want the Usage section to document
update,versionandhelpalongside the Run, so that the whole command surface is in one place. - As a reader of the README, I want the pull request checks step no longer to carry the "except marking a draft ready" caveat, since that has shipped, so that the README describes what actually happens.
Implementation Decisions
Distribution
- Binaries, installer and updater come from
dist(formerly cargo-dist), whose generated release workflow is triggered by av*tag. We picked prebuilt binaries over crates.io alone because the curl installer, and the thirdshift.app install command after it, require that users don't need Rust. - Targets:
x86_64-unknown-linux-muslandaarch64-apple-darwin. The Linux binary is statically linked against musl, so it doesn't depend on the host's glibc. A glibc build from a current Ubuntu runner would fail on Rocky 9 (glibc 2.34). This is cheap because thirdshift's only native dependencies arelibcandsignal-hook. Anything added forupdatemust keep it that way: HTTPS through rustls, never OpenSSL. - No ARM Linux, Intel macOS or native Windows target. WSL2 uses the Linux binary.
- The shell installer's install path is
~/.local/bin. - The updater is built into the binary (
thirdshift update), not shipped as a separatethirdshift-updateexecutable. - crates.io publishing runs in a custom
distpublish job (dist has no built-in crates.io job), authenticated with crates.io trusted publishing through GitHub OIDC. The job checks whether the tagged version is already on crates.io and skips publishing if it is. - The package manifest gains
license,repositoryand anincludelist limited to the sources, the Factory skills (including their licence and credits files), the README and the LICENSE. Development-only directories (.agents,.grok,.claude,docs,tests) stay in the repository but out of the package.homepagestays unset until thirdshift.app is live (create a site for thirdshift at https://thirdshift.app which communicates how it works and has a pressroom / factory / cyberpunk aesthetic #29). - Release notes are generated from merged pull requests into the GitHub Release body. There is no CHANGELOG file.
- Version 0.1.0, which is what the manifest already says. There is no stability promise before 1.0: the CLI, log paths and output may change between minor versions.
Command surface
thirdshift <Issue URL>is the Run, unchanged.thirdshift update,thirdshift versionandthirdshift helpare bare-word commands.--version/-Vand--help/-hare silent aliases: accepted, but never shown in the help text or the README.- Only the first argument is examined, as today. A bare-word command can never collide with an Issue URL, which always starts with
https://. versionandhelpprint to stdout and exit 0. That output is what those commands are for. The rule that stdout carries only the pull request URL applies to Runs, andupdatefollows it too.- A missing argument, an unknown argument, or an argument that fails Issue URL parsing prints an error line plus the help text to stderr and exits 2. This keeps today's exit code and replaces today's one-line usage.
- Argument handling stays hand-written; the surface is too small to need an argument-parsing library.
thirdshift update
- Uses
dist's axoupdater library, which reads the install receipt the shell installer writes, compares the installed version with the latest GitHub Release, and swaps the binary atomically. That swap is safe while another process is running the old binary. - With no install receipt (a build from source, a crates.io install, or a copy placed by hand), it refuses, says why on stderr, names the command that updates that kind of install, and exits 1.
- Only stable releases count as updates.
- Messages go to stderr and stdout stays empty. It exits 0 when it updated or was already on the latest version, and 1 on any failure.
- A Run never calls the updater and never checks for updates.
Continuous integration
-
Pull request CI runs the existing format, lint and test job on
ubuntu-24.04, pinned instead ofubuntu-latestso the supported version is explicit, plus the test job on amacos-26Apple Silicon runner. The test suite needspython3for the fakeghandclaude, which is present on both. -
The release workflow, before the GitHub Release is published, runs the built musl binary's
versioncommand:- inside a
rockylinux:9container on a Linux runner; - inside WSL2 with Ubuntu 24.04 on a
windows-latestrunner, set up by a WSL setup action.
If either fails, the release fails, and no GitHub Release is published and nothing is sent to crates.io.
- inside a
Repository and README
- The README's Install, Updating, Prerequisites, Usage and Status sections are rewritten as described in the user stories. The Rust toolchain moves out of Prerequisites into "Building from source". New sections: supported platforms, including the WSL2 caveats, and Releasing.
CONTEXT.mdis unchanged. Releasing adds no factory domain terms.
Steps only a human can do (ready-for-human)
- Publish 0.1.0 to crates.io by hand (
cargo login,cargo publish) to claim the name, then configure this repository's release workflow as the crate's trusted publisher on crates.io. - Set the GitHub repository's About description to the manifest's description, and clear or keep the homepage link until thirdshift.app is live.
- Review the release-workflow pull request closely: it controls what's published under the project's name.
- Push the
v0.1.0tag. - Acceptance: install with the curl command and do a real Run on the Rocky server, an Ubuntu machine and the M5 Pro, plus a Run inside WSL2 when a Windows machine is available. Run
thirdshift updateon one of them afterwards, or after the next release.
Testing Decisions
-
A good test drives thirdshift the way a user does and asserts only on external behaviour: stdout, stderr and the exit code. It doesn't test argument-parsing internals or updater internals.
-
Command surface. New integration tests use the existing seam: run the built binary as a subprocess and assert on stdout, stderr and the exit code. They cover:
help,--helpand-h: identical help text on stdout, listing all four forms, exit 0, empty stderr;version,--versionand-V:thirdshift <package version>on stdout, exit 0;- no argument, an unknown word, and a non-GitHub URL: help text on stderr, empty stdout, exit 2;
- the help text never mentions the flag aliases.
Prior art: the argument and URL cases in the pre-flight tests, and the shared helpers in the test support module.
-
thirdshift update. One integration test on the same seam: withHOME(and the XDG config location) pointing at an empty temporary directory, so there is no install receipt,updateexits 1, leaves stdout empty and prints the reinstall hint on stderr. It needs no network. The real download-and-replace path belongs to axoupdater and is not tested in this repository. It is covered by the maintainer's acceptance step. -
Platforms. The macOS 26 CI job runs the whole existing suite unchanged. The release workflow's Rocky and WSL jobs are the platform seam for the musl binary. There are no Rust tests for the release workflow itself.
-
Existing behaviour. The existing Run tests must keep passing unchanged, as evidence that
thirdshift <Issue URL>didn't regress.
Out of Scope
- The thirdshift.app site (create a site for thirdshift at https://thirdshift.app which communicates how it works and has a pressroom / factory / cyberpunk aesthetic #29). It comes after this release, and its install command will be the curl command above.
- Open issues Make a command to read the thirdshift activity logs #14 (reading the activity logs), Headless sessions leave smoke-test files in /tmp #23 (smoke-test files in /tmp) and A thirdshift-plugin-* temp dir can outlive its Run #24 (plugin temp dir outliving its Run).
- ARM Linux, Intel macOS, WSL1, native Windows, and repositories on
/mnt/cunder WSL. - Homebrew, npm or other package managers.
- A stability promise or 1.0.
- A Run checking for or applying updates.
- A CHANGELOG file.
- Building on macOS for anything other than its own target.
Further Notes
- The first crates.io publish can't use trusted publishing, because crates.io only lets you set up a trusted publisher on a crate that already exists. That's why 0.1.0 is published by hand before the tag, and why the workflow's publish step must skip versions that are already published.
dist's generated workflow is the source of truth for the release job layout. Customisations (the Rocky and WSL checks and the crates.io job) should go throughdist's own configuration hooks where possible, so that regenerating the workflow after adistupgrade doesn't silently drop them.- The release decision (static musl binaries through
distGitHub Releases, with a mirrored crates.io crate, instead of crates.io alone) is hard to reverse and would puzzle a future reader. It is worth recording as ADR-0003 as part of this work.
Activity