Skip to content

Commit 616523e

Browse files
Define distribution-aware update standard
1 parent 2018c3b commit 616523e

15 files changed

Lines changed: 911 additions & 65 deletions

File tree

.github/workflows/ci.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ jobs:
2525
with:
2626
workspaces: example
2727

28+
- name: Format
29+
working-directory: example
30+
run: cargo fmt --check
31+
32+
- name: Clippy
33+
working-directory: example
34+
run: cargo clippy --all-targets --all-features --locked -- -D warnings
35+
2836
- name: Build
2937
working-directory: example
3038
run: cargo build --locked

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
target/
2+
.DS_Store
23
.claude/
34
.agents/
45
.cursor/
56
.windsurf/
7+
.github/skills/autoresearch/SKILL.md

AGENTS.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ src/
2424
skill.rs # Skill install + status (always present)
2525
config.rs # config show/path (always present)
2626
doctor.rs # Dependency diagnostics (optional, recommended)
27-
update.rs # Self-update (optional)
27+
update.rs # Distribution-aware update (optional)
2828
tests/ # Integration tests verifying contracts
2929
Cargo.toml
3030
```
@@ -171,7 +171,7 @@ Standard:
171171

172172
Optional:
173173
- `doctor` -- check external dependencies (API keys, binaries, endpoints). Returns structured pass/warn/fail. Exit 0 if all pass, exit 2 if any fail.
174-
- `update [--check]` -- self-update from GitHub Releases
174+
- `update [--check]` -- distribution-aware update check/apply
175175

176176
## Rich Help
177177

@@ -215,6 +215,37 @@ For commands that do expensive or irreversible work (API calls, long computation
215215

216216
Lock file format: `{"pid": 12345, "started_at": "2026-04-12T10:00:00Z", "operation": "deploy"}`
217217

218+
## Update Standard
219+
220+
The update rule is one command, distribution-aware update paths.
221+
222+
`update --check` is always safe: no filesystem mutation, no package-manager
223+
upgrade, no shell profile changes, no raw stdout leaks, and exit 0 when the
224+
check completes even if a new version exists.
225+
226+
`update` must respect the channel that owns the installed binary:
227+
228+
- Standalone installer binary: may self-replace from GitHub Releases after exact
229+
platform asset selection, HTTPS download, SHA256 verification, optional
230+
attestation/signature verification, temp-file staging, `<new-binary> --version`
231+
validation, and atomic replacement.
232+
- Homebrew install: do not self-replace; use or return `brew upgrade <formula>`.
233+
- Cargo install: do not self-replace; use or return `cargo install --locked --force <crate>` or `cargo binstall --no-confirm <crate>` when supported.
234+
- npm, Bun package-manager, uv tool, pipx, winget, scoop, apt, and enterprise-managed installs:
235+
defer to the owning package manager or internal rollout process.
236+
- Unknown install source: return `update_mode = "instructions_only"` instead of
237+
blindly replacing the current executable.
238+
239+
`update --check --json` must return a success envelope with
240+
`current_version`, `latest_version`, `status`, `install_source`, `update_mode`,
241+
`upgrade_command`, `release_url`, and `requires_skill_reinstall`.
242+
243+
Release artifacts should be built in CI, not on a developer laptop. For Rust
244+
CLIs, prefer cargo-dist or an equivalent release pipeline that produces GitHub
245+
Release archives, checksums, Homebrew formulae, cargo-binstall-compatible
246+
artifacts, and optional GitHub artifact attestations. See
247+
`docs/update-standard.md` for the full policy and required tests.
248+
218249
## Reference
219250

220251
See the `example/` directory in this repo for a working implementation of the core patterns and the entry point, error type, and output helpers. Config loading, secret handling, XDG paths, doctor, duplicate guard, and HTTP retry are documented as code patterns in the README's Reusable Modules section.

README.md

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ If `inbox list` works, `account list` works. If `--json` forces JSON in one CLI,
8282

8383
### 8. Self-contained and portable
8484

85-
The binary carries its own skill file as an embedded constant (via `const` or `include_str!`). `skill install` deploys it. `update` replaces the binary from GitHub Releases. One artifact. The self-update mechanism is opt-in -- CLIs distributed via package managers or in managed environments should disable it.
85+
The binary carries its own skill file as an embedded constant (via `const` or `include_str!`). `skill install` deploys it. `update` is one command with distribution-aware behavior: standalone installer binaries may self-replace from GitHub Releases, but Homebrew, Cargo, npm, pipx, winget, apt, and managed installs must defer to the package manager or return the exact tested upgrade command. Self-update is opt-in and must be disableable in managed environments.
8686

8787
### 9. Speed is a feature
8888

@@ -129,7 +129,7 @@ The binary describes itself. One command returns a JSON manifest of everything t
129129
"config set <key> <value>": "Set a configuration value.",
130130
"agent-info | info": "This manifest.",
131131
"skill install": "Install skill file to agent platforms.",
132-
"update [--check]": "Self-update from GitHub Releases."
132+
"update [--check]": "Distribution-aware update check/apply."
133133
},
134134
"flags": {
135135
"--json": "Force JSON output (auto-enabled when piped)",
@@ -290,23 +290,31 @@ mycli deploy # "Operation already running. Use --force to overr
290290
mycli deploy --force # Bypasses guard
291291
```
292292

293-
### Pattern 5: Self-Update
293+
### Pattern 5: Update
294294

295-
Three install paths, one update mechanism:
295+
One command, distribution-aware update paths:
296296

297297
```bash
298298
# Install (pick any):
299299
brew tap your-org/tap && brew install your-cli
300300
cargo install your-cli
301301
curl -fsSL https://your-cli.dev/install.sh | sh
302302

303-
# Self-update (built into the binary):
304-
your-cli update --check # check for new version
305-
your-cli update # pull latest from GitHub Releases
303+
# Agent-facing update command:
304+
your-cli update --check # safe check, no mutation
305+
your-cli update # update via the owning channel, or return exact instructions
306306
your-cli skill install # re-deploy updated skill
307307
```
308308

309-
Self-update should be disableable via config (`update.enabled = false`) for managed environments.
309+
Rules:
310+
311+
- Standalone installer install: may self-replace from GitHub Releases after asset selection, checksum/provenance verification, temp-file staging, version check, and atomic replacement.
312+
- Homebrew install: never self-replace; use `brew upgrade <formula>`.
313+
- Cargo install: never self-replace; use `cargo install --locked --force <crate>` or `cargo binstall --no-confirm <crate>` when supported.
314+
- npm, Bun package-manager, uv tool, pipx, winget, scoop, apt, and enterprise installs: defer to the owning package manager.
315+
- Managed environment: support `update.enabled = false` and return `status: "disabled"` with the internal upgrade instruction.
316+
317+
`update --check --json` returns a normal success envelope whose `data` includes `current_version`, `latest_version`, `status`, `install_source`, `update_mode`, `upgrade_command`, `release_url`, and `requires_skill_reinstall`. See [docs/update-standard.md](docs/update-standard.md) for the full standard, release pipeline, and required tests.
310318

311319
---
312320

@@ -978,7 +986,7 @@ src/
978986
your_command.rs # Your domain logic
979987
skill.rs # Skill content auto-derived from CARGO_PKG_NAME
980988
config.rs # config show/path (works out of the box)
981-
update.rs # Self-update (just change repo owner/name in config)
989+
update.rs # Distribution-aware update (set owner/repo/crate/brew names)
982990
```
983991

984992
**4. Update `agent-info`** to list your actual commands with argument schemas. This is the contract agents bootstrap from.
@@ -1005,7 +1013,7 @@ The framework conventions (`env!("CARGO_PKG_NAME")`, config loading, skill insta
10051013

10061014
## Example
10071015

1008-
The `example/` directory contains a modular `greeter` CLI demonstrating all core patterns: agent-info with argument schemas, JSON envelope, semantic exit codes (0-4), `--json` pre-scan, `--quiet` flag, config loading via Figment, skill self-install, and self-update. It includes 40 integration tests that verify every contract.
1016+
The `example/` directory contains a modular `greeter` CLI demonstrating all core patterns: agent-info with argument schemas, JSON envelope, semantic exit codes (0-4), `--json` pre-scan, `--quiet` flag, config loading via Figment, skill self-install, and distribution-aware update output. It includes integration tests that verify the contracts.
10091017

10101018
```
10111019
example/
@@ -1021,7 +1029,7 @@ example/
10211029
agent_info.rs # Enriched capability manifest with arg schemas
10221030
config.rs # config show / config path
10231031
skill.rs # Skill install + status
1024-
update.rs # Self-update
1032+
update.rs # Distribution-aware update
10251033
contract.rs # Hidden: deterministic exit-code trigger for tests
10261034
tests/
10271035
exit_code_contracts.rs # All 5 exit codes verified
@@ -1135,7 +1143,7 @@ libc = "0.2"
11351143
# HTTP (if making network calls)
11361144
reqwest = { version = "0.12", features = ["json", "rustls-tls"] }
11371145

1138-
# Self-update (optional)
1146+
# Update (optional standalone self-replace)
11391147
self_update = { version = "0.42", features = ["archive-tar", "compression-flate2"] }
11401148

11411149
[profile.release]

0 commit comments

Comments
 (0)