Skip to main content

onevcs

A terminal running one change end to end: onevcs session open answering with a session token and the worktree it cut, a commit on that branch, then onevcs publish while a backgrounded onevcs events --follow fills the window with NDJSON envelopes as they are written — the push, the change request opened, each check reported as it resolves, the merge, and the release probes — ending on "merged at" and the landing commit

Version control and its remote host behind one host-neutral vocabulary, for agent workflows.

The review unit is a change request — GitHub maps it to a pull request, and a later host maps it to whatever it calls the same thing. Vcs owns the repository side (identities, sessions over an isolated worktree, preserved work); RemoteHost owns the host side (opening a change, reading its checks, merging it); a rules file decides, per repository, how a change publishes and what verifies it. Everything a run does is emitted as an NDJSON event stream.

The public surface is the approved contract — docs/contract.md — compiled, and it is implemented: the registry with its lazy migration, the rules engine, sessions over borrowing clones, bounded git, the FIFO merge queue, both publication strategies, recovery and provenance, the merge train, and the event stream.

What one change looks like

onevcs register ~/projects/widgets                 # once per checkout
onevcs rules check widgets                         # which policy, and why
token=$(onevcs session open widgets --branch feature/thing | jq -r .token)
# …work in the worktree the session printed…
onevcs publish "$token"                            # verify, then land it
onevcs events "$token"                             # everything it did, as NDJSON
onevcs release status "$token"                     # …and whether a release carries it

onevcs rules check REPO is the second line of that, on its own: it resolves the checkout to an identity, says which rule of the rules file matched it, and gives the publication and approvals that rule decided with the source of each.

onevcs rules check widgets printing repo, identity, checkout and rules-file paths, the matched rule with its host/owner/name matcher, then publication change-auto and approvals none, each marked "(from rule 1)", and the trailer prefix taken from the rules file

onevcs repos --audit-gates reads the same decision back for every registered identity at once, and beside each one what actually verifies a publication there — the host's required checks, and whether anything on the merge path runs a gate:

onevcs repos --audit-gates listing three identities: a plain git remote whose required checks are "none" because no host answers for it and whose merge path covers nothing, a github.com identity publishing change-open whose coverage is the host's required checks, and a third publishing change-auto and covered by a pre-push hook, each with its policy and approvals and the rule they came from

A session can also open its change request as a draft it holds while its work is still being made, and finish the description off once it has evidence to put in it:

onevcs publish "$token" --draft                    # open it as a draft the session holds
# …trigger a CI run, put up a demonstration change, gather what the reviewer needs…
onevcs change describe "$token" --body-file pr.md  # replace the description with the evidence
onevcs change show "$token" --json                 # what the host holds: url, id, base, draft, title, body
onevcs change ready "$token"                       # lift the draft without landing anything
onevcs publish "$token"                            # …or lift it and land it in one step

Each of those addresses the session's own change request — the one open from its branch into its base — so nothing names a URL. The library forms are session_change, describe_change, and ready_change.

A branch that outlived the session that cut it is landed by name instead, under that same rules-resolved policy: onevcs publish-branch feature/thing --repo ~/projects/widgets for work that finished, and onevcs recover for a step that stopped half way, which publishes it with the attestation that verification cleared it. Whichever of the three refuses a branch names the one that takes it.

A branch that is not ready to land and must not be lost is preserved instead: onevcs preserve feature/thing --repo ~/projects/widgets puts it on that identity's origin under its own name and publishes nothing — no change request, no merge path, no base touched, nothing forced, and no provenance marker cleared. It is what a host with running work reaches for before it shuts down, so the commits its dispatches just made survive the machine going away; the branch is exactly as recoverable afterwards as it was before, and onevcs recoverable still names the verb that lands it, with the origin beside it. The library form is preserve, answering Pushed, AlreadyOnOrigin, or NoRemote, and recoverable is the enumeration beside it.

onevcs recoverable --all over three branches: one whose run was left open and is only in a pool slot's clone, one marked landed whose recorded landing commit is named and which says there is nothing to resume, and one marked "on origin" that onevcs preserve pushed and nothing published — each with its identity, an indented "Found in:" path, why it stopped, and a pasteable "Resume: onevcs publish-branch …" line

onevcs status REF answers what became of a piece of work, asked by whichever name you hold — a change request's URL, a session token, a branch, or a commit. It reports the identity's resolved policy, the session, every checkout and per-run clone holding the branch, whether the change landed and what says so, the host's checks, the last thing its merge path said about it, and the command that advances it. Landing is decided from the base's own history — a recorded landing, the change request's number in the base's log, or a landing trailer — so a change that squash-merged reads as landed rather than as unpublished however far the base has moved since, and one that history cannot decide reads as unknown rather than as work nobody published. A host that cannot be reached leaves its section unavailable instead of failing the command.

onevcs status over an open change request: a "work:" header naming the branch and identity, then identity, session, branch and publication sections — the session closed and stale, the branch one commit ahead with its provenance complete, the publication open and not landed with its change-request URL and merge policy — then a checks table with one required check completed successfully, one still in progress, and one advisory check failed, the merge path's pass verdict and its log, and a "next:" line saying nothing advances the work while the host is still deciding

A branch is often worked on by more than one session — a run stops and the next one continues the name — so the older session's record names the one that continued it, and every answer about a session or a branch follows that chain to its newest record before it reports a landing. A copy of the branch that was superseded is still reported as holding it and no longer decides anything about it. A chain this host cannot follow — a session record removed underneath one, a link into another repository, a cycle — reports unknown and says why, rather than falling back to whichever record still read: a wrong no there reads as an instruction to publish work the base already carries.

onevcs release answers what happens after a change lands, so an upgrade can be sequenced behind the release that carries it rather than behind the merge. onevcs release targets REPO lists what a repository releases and whether it adopts fast (the work is enough) or published (the release is what is depended on); onevcs release latest REPO [--target NAME] says what is out right now; and onevcs release status REF [--target NAME] says whether the release carrying one landed change has happened yet, asked by the same four names onevcs status takes — released: crate 1.5.0 (automated, probed), not released, or not answered.

onevcs release targets for one repository: its identity, adoption: published, its default target, the declaration it read out of that repository's own release-targets.toml, and a table of two declared targets — each automated, each answered by that declaration's own probe script given the target's registry-qualified identifier

A target's style decides its shape. An automated target carries a probe — a script the repository carries, or a one-liner run through sh — and is answered by running it under a bounded timeout; a human-step target carries no probe at all, because the release happens when a person does something, and is answered by onevcs release acknowledge REF --target NAME --version VERSION after they have done it. Recording the same version again is a no-op; a different one is refused until --supersede replaces it, which keeps the version it replaced.

Two answers stay apart everywhere, and a consumer routes on the difference: "not released" is a probe that answered, and "not answered" is a probe that did not — a timeout, a non-zero exit, or output that is not one usable version. A landing whose probe never answered is "not answered" for ever rather than being compared against a reading taken later, because the release carrying that very change may already be in it. Configure targets in $ONEVCS_HOME/releases.yml; a host with none behaves exactly as it did before there was one.

onevcs import BRANCH --repo PATH [--from SOURCE] [--as NAME] makes a branch reachable from an identity's registered checkouts, so a later run's clone can see work a stopped run left in its own. It writes refs and nothing else — no checkout, no working tree — and refuses a non-fast-forward overwrite by naming the commits it would lose.

onevcs session open places a session on a pool of warm worktree slots when the host keeps one for the repository — $ONEVCS_HOME/workspaces.yml, per identity, sizes the pool and the overflow past it, and names the paths to delete on every return. An idle slot is taken first, one is cut while the pool is under its size, a session past the pool is placed under runs/ exactly as before until the overflow is spent, and then session open refuses with exit code 4 rather than waiting. Closing a session on a slot returns it — detached onto the base, reset, cleaned of untracked files but not of ignored ones — so the next session on it finds target/, node_modules/ or .venv/ still there. onevcs pool status REPO [--json] reports the capacity and every slot; onevcs pool prune REPO [--json] removes the idle ones whose clone retains no branch. A host that writes no file keeps every session fresh, as it always has; --pool N and --overflow N|unlimited on one open override its resolution for that session alone.

A warm slot that is never swept grows without bound, so the workspaces file may also name what maintenance is per identity — maintain: {command: ["cargo", "sweep", "--time", "7"], timeout: 30m} — and onevcs pool maintain [REPO] [--older-than SPAN] [--json] runs it in each idle slot's worktree, one slot at a time, claiming the slot so a session open goes elsewhere rather than waiting, and records the attempt on the slot. onevcs holds no schedule: when to maintain is the caller's — a driver's idle tick, a cron, a person — and --older-than is what makes every caller cheap, by skipping a slot maintained within that span. It exits 0 when nothing ran or every command succeeded, 1 when any command failed or timed out, naming the artifact that holds what it wrote.

onevcs pool status for one identity: a pool of 2 with 2 slots created, 1 idle and 1 in use, an overflow of 4 with none in use, and a per-slot block for each — slot 1 in use by a named session, slot 2 idle and carrying the timestamp and "succeeded" outcome of its last maintenance, both with their slot path and the execution checkout they borrow from

onevcs sweep [--dry-run] [--min-age-hours HOURS] [--format text|json] reclaims the workspaces those landings leave behind. Every branch published by name cuts a run root — a clone, a worktree, and the merge path's preserved logs — under the state root, and until this rule existed nothing removed one; thirty-one of them filled a host's disk twice in a single run. The rule runs whether or not anybody asks: each landing enforces it over its own family before cutting the next run root, and this verb is the same judgement asked deliberately and over every family at once.

A workspace is removed only where this tool can prove it is finished: its merge path recorded a verdict, no live session holds its occupancy lease, it was last written outside the age floor --min-age-hours sets, and removing it is something this host can do at all. Everything else is retained and reported with the reason — a workspace somebody is still publishing in is never removed, and neither is one belonging to another manager on a shared state root — and the per-run lifecycle clones a session keeps as recovery history, the pool of warm slots beside them, and the unpublished branches sessions left behind are outside it entirely, each named in the report with the verb that reaches it. --format json writes the same report as one object a consumer reads by field name.

Two things a proven-finished workspace still gets to keep. Its preserved logs last at least as long as the age floor, because they are what an operator reads after a publication failed; and a clone still holding work that never reached the origin is kept past the floor too, until enough newer ones stand in front of it — under the same bound the per-run lifecycle clones a session keeps are kept under. Reclaiming a workspace also stops the processes that publication left running — a publication runs the repository's own verification and verifications start daemons, and unlinking files a live process holds open frees none of their blocks.

Everything durable lives under one state root — ONEVCS_HOME, otherwise ~/.onevcs.

The library forms of the pool are workspace_capacity(&request), which says how many more opens an identity admits and whether one request would be placed now, pool_status, pool_prune and pool_maintain(scope, older_than); Span is the one duration grammar of the host files (7d, 36h, 90m, 600s), and first_matching matches a RuleMatch list against a repository by exactly the matcher the three host files use.

GitHub is reached through gh, so whatever gh auth status reports is the credential. A fine-grained personal access token needs Actions: Read on the repository beside its contents and pull-requests access: GitHub will not let that credential class resolve a check run at all — there is no Checks permission to grant one — so a change request's checks are read from its workflow runs, and change_checks says so in the sources it answers with. Anything a third-party integration posted as a check run or a commit status is invisible to such a token, and a credential that can read neither source is refused rather than reported as having no checks.

Install

cargo install onevcs      # crates.io
pip install onevcs-cli    # a prebuilt wheel, no Rust toolchain
npm install -g onevcs-cli # a prebuilt binary, no Rust toolchain

All three install the same onevcs binary. Prebuilt binaries exist for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64); every release also attaches the archives and their .sha256 checksums for a direct download.

Use

onevcs --help

--help is the command surface, and publish reserves its own exit codes for a verification that failed, a request that was invalid, and a base that moved under it.

onevcs --help in a terminal: the one-line description, the usage line, and the whole command list — register, repos, resolve, session, publish, publish-branch, change, preserve, recover, recoverable, status, import, integrate, sync, sweep, events, artifact, rules, release and pool — each with the one-line summary of what it does, then the global help and version options

Every picture in this README is a real capture of this CLI: the release binary driven against a scratch host of real origins, clones and hooks, with no network and no credential, rendered by just screenshots. The digest of each still is committed, so CI refuses the moment the output it shows stops matching what the binary prints; the animation at the top is a rendering of one such run's own output, regenerated with just screenshots-gif.

Embed it

A command answers a process: an exit code and a line of prose. A caller embedding the crate wants the decision, so the same operations answer values.

let published = onevcs::publish(&providers, &token, &PublishRequest::default())?;
match published.outcome {
    PublishOutcome::Merged(sha) => journal.landed(sha),
    PublishOutcome::ChangeOpen(url) | PublishOutcome::Queued(url) => journal.awaiting(url),
    // A draft cannot land while it stands; publishing again with no `draft` lifts
    // it, and so does `ready_change`. Whether it awaits a dependency's release or
    // is held by the session still making it is the `DraftReason` the request carried.
    PublishOutcome::ChangeDraft(url) => journal.held_back(url),
    PublishOutcome::NothingToPublish => journal.nothing(),
    PublishOutcome::Failed { kind, reason, retained } => journal.failed(kind, reason, retained),
}

close_session and session are the same for the rest of a session's life, and EventStream::open(&token) reads its events as Envelopes, each attributed to the session that wrote it — so a caller following several publications at once can tell them apart. The command line is a rendering of these rather than a second path through them.

A consumer that wants some of what a session writes reads it through the filter grammar the three producing libraries share — EventStream::open_filtered(&token, filter), or onevcs events TOKEN --filter SPEC with the spec inline as JSON or in a file. An envelope passes when it matches any include matcher (or include is absent) and no exclude matcher, matching source by family, phase and the reserved label keys exactly, and kind by glob (change-*):

include:
  - {source: vcs, kind: "gate-*"}
exclude:
  - {kind: lock-wait}

Every envelope carries the phase of a change's life its producer stamped it with — development (the work being made, including a push of the session's own branch), integrate (the merge queue, the merge, a sync conflict, and a push of any other branch), review (the change request opened, checked, and merged), and release (a probe, an acknowledgement, an observation). review is also where a draft is opened, described, and lifted. Naming a phase is how a consumer asks for "the review of this change" without listing the kinds in it, so a kind added to that phase later arrives in the read that already wanted it.

EventStream takes the phases the session can actually produce: development and integrate always, review only where the resolved merge policy is not local-direct, and release only where $ONEVCS_HOME/releases.yml configures targets for that repository. Naming a phase a session does not have is refused by name, because a filter answered with silence and a session that did nothing look alike; naming none takes what there is. And where release is one of them, the read also hands back that repository's release-observed and release-acknowledged events whose landing commit is this session's own — the correlation onevcs can already make, so a consumer never has to find the repository's release stream. That set grows after the session closes, because a release happens when it happens.

Before a caller has a token, session_holders(repo) answers who is in a repository: one SessionHolder per recorded session, carrying the token the calls above take, the branch and worktree it holds, and whether its owner is still running (Liveness::Live) or the session is the remains of a run that stopped.

Test against it, without a real GitHub

Embedding the crate, run_with(&cli, providers) takes the two implementations a run reaches Vcs and RemoteHost through; run is that with Git and GitHub, and every entry point above goes through the same seam. onevcs-testing ships in-memory and file-backed implementations of both, so a consumer's suite drives a real onevcs through a real journey against a host it seeded:

cargo add --dev onevcs-testing

They emit the events the real implementations emit — a claim this repository's own suite checks by running one publication journey on both backends and holding the two event streams to each other.

Develop

just bootstrap   # from a clean clone
just check       # the deterministic gate: format, clippy, tests, coverage, docs
just gate        # check, plus the diff-scoped LLM-judge tier — the pre-push bar

just --list is the full index.

License

MIT. See LICENSE.

Release files for onevcs-cli 0.32.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for onevcs-cli 0.32.2
File
onevcs_cli-0.32.2-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
onevcs_cli-0.32.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
onevcs_cli-0.32.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
onevcs_cli-0.32.2-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
onevcs_cli-0.32.2-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 11.6 MB

Release files / onevcs_cli-0.32.2-py3-none-win_amd64.whl

Download URL onevcs_cli-0.32.2-py3-none-win_amd64.whl
Size 2.5 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
3adca81b7d2c7d70ce49d5e26b5b56c827f446e70f2cd0dee779e9d031adc54a
BLAKE2b-256 checksum
How to use checksums
88660033a9370d92438092f403884bdf6ebe72c7968017df0b7efb12271d0404
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / onevcs_cli-0.32.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL onevcs_cli-0.32.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.4 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
d30a94519bc4c07e8659211fca4850ab000bddbec20f5a58d4fbcf7b36f4ed91
BLAKE2b-256 checksum
How to use checksums
9f8aa17ce94c510d3e150b476d655f434733b580f3e91f05f1279ff723d2333b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / onevcs_cli-0.32.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL onevcs_cli-0.32.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.2 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
ee81278028985cbade461ac3131f5f4b58d52e08b7ba331396a7aaa9fba2cef6
BLAKE2b-256 checksum
How to use checksums
c79187bcd7205ab017b77d10e87bd4278233033a8876757897122604f1dd9c78
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / onevcs_cli-0.32.2-py3-none-macosx_11_0_arm64.whl

Download URL onevcs_cli-0.32.2-py3-none-macosx_11_0_arm64.whl
Size 2.2 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
bb516ee4550ed2c4b2c109ce9d967840c8989ced063c362c1d4da00b8e158ccb
BLAKE2b-256 checksum
How to use checksums
64f4225f4f84de89eb0f1bebbc616c3bf690e073c9da091089179eb0d499e8a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release files / onevcs_cli-0.32.2-py3-none-macosx_10_12_x86_64.whl

Download URL onevcs_cli-0.32.2-py3-none-macosx_10_12_x86_64.whl
Size 2.3 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
57f19d92ad550d98fb24eb61264d5c1f20122914436a40ad6f366323883b0f0e
BLAKE2b-256 checksum
How to use checksums
499ac439fcd164544f636197b65a0762e5f44d5b287192e0c4cdf650489817ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.13.14

Release history Release notifications | RSS feed

This release

0.32.2 This release

5 release files

0.32.1

5 release files

0.32.0

5 release files

0.31.2

5 release files

0.31.1

5 release files

0.31.0

5 release files

0.30.2

5 release files

0.30.1

5 release files

0.30.0

5 release files

0.29.1

5 release files

0.29.0

5 release files

0.28.0

5 release files

0.27.0

5 release files

0.26.0

5 release files

0.25.0

5 release files

0.24.2

5 release files

0.24.1

5 release files

0.24.0

5 release files

0.23.0

5 release files

0.22.0

5 release files

0.21.0

5 release files

0.20.0

5 release files

0.18.0

5 release files

0.17.1

5 release files

0.17.0

5 release files

0.16.3

5 release files

0.16.2

5 release files

0.16.1

5 release files

0.16.0

5 release files

0.15.9

5 release files

0.15.8

5 release files

0.15.7

5 release files

0.15.6

5 release files

0.15.5

5 release files

0.15.4

5 release files

0.15.3

5 release files

0.15.2

5 release files

0.15.1

5 release files

0.15.0

5 release files

0.14.1

5 release files

0.14.0

5 release files

0.13.0

5 release files

0.12.3

5 release files

0.12.1

5 release files

0.12.0

5 release files

0.11.1

5 release files

0.11.0

5 release files

0.10.0

5 release files

0.9.0

5 release files

0.8.1

5 release files

0.8.0

5 release files

0.7.0

5 release files

0.6.1

5 release files

0.6.0

5 release files

0.5.0

5 release files

0.4.2

5 release files

0.4.1

5 release files

0.4.0

5 release files

0.3.1

5 release files

0.3.0

5 release files

0.2.10

5 release files

0.2.9

5 release files

0.2.8

5 release files

0.2.7

5 release files

0.2.6

5 release files

0.2.5

5 release files

0.2.4

5 release files

0.2.3

5 release files

0.2.2

5 release files

0.2.1

5 release files

0.2.0

5 release files

0.1.3

5 release files

0.1.2

5 release files

0.1.1

5 release files

0.1.0

5 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page