Opi Release
Release all publishable workspace crates at one lockstep semver, then publish the corresponding GitHub release. Invoke explicitly with a target version.
Inputs
<version>: required semver without thevprefix.--fix: permits only the pre-flight formatter/linter fixes described below. It does not permit a commit before Phase 5.--skip-cross: source/crate release with no locally built cross-platform binaries. It does not weaken crate or documentation checks.
Use the host's native progress and user-interaction mechanisms when available. The workflow must not depend on product-specific task or question tool names. Shell examples are illustrative: use equivalent PowerShell commands on Windows and POSIX commands on Unix without changing their safety semantics.
Boundaries
| Phase | Boundary |
|---|---|
| 1 | Read-only unless --fix was supplied; fixes remain uncommitted. |
| 2-4 | Reversible local preparation with an exact changed-file manifest. |
| 5 | Public: release commit and tag are pushed. Requires fresh confirmation. |
| 6 | Irreversible: crates.io versions cannot be deleted. Requires fresh confirmation. |
| 7 | The GitHub draft becomes public; crate publication remains irreversible. |
Never describe Phases 1-4 collectively as side-effect free. Never use
git reset --hard, git checkout --, git clean, force-push, broad staging,
or automatic workspace-wide cargo clean.
Resume state
Before Phase 1, inspect .opi-release-state.json. If it matches the requested
version, validate every recorded remote fact before offering to resume. Write
state atomically after each public or irreversible transition:
{
"version": "0.0.0",
"release_commit": null,
"tag_pushed": false,
"draft_url": null,
"published": [],
"pending": [],
"github_published": false
}
Do not infer success from the state file alone. Query GitHub/crates.io and stop on disagreement.
Phase 1: pre-flight
Run from a clean main whose HEAD exists on origin/main and whose required
CI checks for that exact SHA succeeded.
Required gates:
Confirm
LICENSE,README.md,Cargo.lock,CHANGELOG.md, and every publishable manifest exist.Confirm
git status --porcelainis empty, the current branch ismain, localHEADequalsorigin/main, and neither local nor remotev<version>exists.Query required check runs for the exact
HEAD; pending, missing, or failed checks block release.Treat the exact-SHA required CI checks as the evidence for format, all-target clippy, workspace tests, doctests, and rustdoc. Record their job URLs and conclusions; do not repeat the same five gates locally. If repository CI no longer covers one of them, run only the missing gate locally and record why.
Check package metadata, publish flags, internal dependency version fields, package contents, tracked secret-shaped files, MSRV, registry auth, crate ownership, and whether the target version already exists.
Run
cargo run -p opi-coding-agent -- --versionand verify the current version before the bump.Treat
cargo auditas required only when the repository/release policy declares it installed; otherwise report the missing optional check.
With --fix, run only cargo fmt --all and the narrowly applicable clippy fix.
Record the resulting paths, run git diff --check, and repeat only affected
local gates. Because the clean exact-SHA CI evidence no longer covers a dirty
fix, stop before release and require the resulting commit's required CI to pass.
Do not stage or commit the fixes.
Report PASS/FAIL/WARN with evidence. Any required failure stops the workflow.
Phase 2: version and packaging preparation
Version and document ownership
opi-release owns the mechanical workspace version mutation: update
workspace.package.version, every publishable internal dependency's exact
version field, and the resulting Cargo.lock changes. Show and record those
paths, then invoke:
opi-document scope=version-bump version=<version>
opi-document owns documented version surfaces and localized counterparts; it
does not edit Cargo manifests or the version itself. Record its exact changed
paths in the same release manifest, show the combined diff, then run
cargo check --workspace.
One dependency graph
Read cargo metadata --format-version 1 --no-deps, select publishable workspace
members, build internal path-dependency edges, and topologically sort them.
Reuse this graph in both dry-run and live publication; do not maintain a second
hand-written order.
Current expected batches are an assertion to verify, not the source of truth:
- Batch 1:
opi-ai,opi-tui,opi-protocol - Batch 2:
opi-agent,opi-sandbox - Batch 3:
opi-coding-agent
The expected edges include opi-agent -> opi-ai,
opi-sandbox -> opi-protocol, and
opi-coding-agent -> opi-ai, opi-agent, opi-protocol, opi-tui.
Run cargo publish --dry-run --allow-dirty -p <crate> in computed order.
Before the new internal versions exist on crates.io, a dependent crate may fail
only because that exact internal version is unavailable; classify that as an
expected ordering constraint. Any metadata, package-content, or unrelated
dependency failure blocks release.
On failure, retain and show the recorded preparation diff. Do not silently revert it; ask whether to fix forward or revert only the known files.
Phase 3: changelog and release notes
Promote the existing ## [Unreleased] content into
## [<version>] - YYYY-MM-DD. Never modify an already released section. Use
commits since the previous tag only to detect omissions or category mistakes,
not to overwrite curated changelog text.
Keep the repository's allowed headings and Conventional Commit mapping. Create
an untracked/transient release-notes file from the finalized version section.
Add CHANGELOG.md and any intentionally tracked note to the exact release
manifest.
Run opi-document scope=targeted if changelog edits create or invalidate a
declared documentation claim; otherwise record why no additional doc surface
was affected.
Phase 4: build and artifact evidence
Ask the user to select one strategy:
- CI-driven builds (recommended): local native smoke only;
release.ymlbuilds supportedopiandopi-sandboxarchives. - Local cross-build: build only targets supported by the host/toolchain.
--skip-cross: no binary release artifacts.
Always run a release build and release tests appropriate to the selected strategy. For every locally produced archive:
- package only expected binaries/files;
- extract into a temporary directory;
- run the native binary's
--versionwhen executable on the host; - inspect foreign binaries using a file-format tool;
- reject missing, duplicate, or unexpectedly large assets;
- generate
SHA256SUMS.txtover the exact archive set and verify it locally.
Keep artifacts under release-artifacts/v<version>/. Build failures retain
narrow evidence for diagnosis. Do not automatically run workspace-wide
cargo clean.
Phase 5: public Git boundary
Show the full release diff, exact staging manifest, target commit, tag, and planned GitHub assets. Warn that the commit/tag push is public, then require explicit confirmation.
After confirmation:
- Stage each reviewed tracked path explicitly. Never use
git add -A,git add ., globs, or command substitution overgit diff. - Run
git diff --cached --name-onlyandgit diff --cached --check; verify the staged set equals the manifest. - Commit
chore: release v<version>and create annotated tagv<version>. - Push
mainand the tag without force. - Create a draft GitHub release from the transient notes.
- For local artifacts, upload the archives and their
SHA256SUMS.txttogether. For CI-driven builds, initially upload notes only. - Record and atomically checkpoint the release commit, tag, and draft URL.
For CI-driven builds, wait for the tag-triggered release.yml. It must build
the declared targets, run the release evidence audit, generate
SHA256SUMS.txt, and upload archives plus checksums to the draft. A missing
asset, failed required target, or failed audit blocks Phase 6.
Phase 6: crates.io publication
Show the validated dependency batches, already-published set, and the warning
that crates cannot be deleted. Require explicit confirmation immediately before
the first cargo publish.
Publish one computed batch at a time. Independent crates within a batch may run in parallel only if output and result attribution remain unambiguous. Verify each version through the registry before advancing, allowing bounded index propagation waits.
Retry automatically at most three times only for transient network/5xx/index propagation failures. Authentication, validation, version conflict, missing dependency, or uncertain partial success requires a user decision.
After every verified crate, atomically update .opi-release-state.json. On a
mid-release stop, offer retry, bounded wait, resume later, or yank-and-abort.
Yanking is itself destructive and requires explicit confirmation; it does not
delete the published version.
Phase 7: finalize and verify
Before publishing the draft:
- Download the GitHub release assets into a fresh temporary directory.
- Verify every downloaded archive using the downloaded
SHA256SUMS.txt. - Extract and smoke the host-native
opibinary; audit the declaredopi-sandboxplatform assets. - Verify all six crates at
<version>:opi-ai,opi-tui,opi-agent,opi-protocol,opi-sandbox, andopi-coding-agent. - Install
opi-coding-agent --version <version>into an isolated cargo root and verifyopi --version. - Report docs.rs state as eventual/non-blocking unless repository policy says otherwise.
Publish the draft only after these checks pass. Mark the resume state complete,
report the final verified release, then remove .opi-release-state.json and
the transient release-notes file. The resume state exists only for an
incomplete release; a completed release is recoverable from public registries
and Git history. Retain non-empty release artifacts by default. Disk-cache
deletion is a separate user decision with an explicit, resolved target path.
Failure recovery
- Before Phase 5: stop with the exact local diff and evidence. Fix forward or revert only after the user chooses the known paths.
- After Phase 5 but before Phase 6: retry draft/asset operations when safe. To abandon, delete the draft/tag only after confirmation and revert the public release commit; never rewrite history.
- During Phase 6: preserve the verified published set. Resume from registry truth, not memory. Published versions can only be yanked.
- After Phase 7: amend the GitHub release through a new auditable action; never mutate crates.io contents in place.
Completion report
Report the release commit/tag, GitHub URL, all six crate URLs/versions, artifact count, checksum verification, selected build strategy, any tier-2 omissions, and the final state-file disposition. A release is not complete merely because the commands exited zero; remote versions and downloaded assets must agree.