agent-plugins 0.1.1

Sans-IO implementation of the Agent Plugins specification (agent-plugins.org): load, validate, and prepare agent plugins from any IO
Documentation
# Releasing `agent-plugins`

Releases are **tag-driven**. Nothing publishes from a laptop: `cargo release`
produces a tag, and CI turns that tag into a crates.io release only after
re-running every check on a clean runner.

## One-time setup

### 1. Install the tooling

```bash
cargo install just          # or: brew install just / apt install just
just setup                  # installs cargo-release and cargo-audit
```

### 2. Claim the crate name

`agent-plugins` was unclaimed on crates.io when this repo was set up. Claim it
with a manual first publish, from a clean checkout of `main`:

```bash
cargo login                 # paste a token from https://crates.io/settings/tokens
just ci                     # make sure everything is green first
cargo publish
```

After that first publish, every subsequent release goes through CI.

> Why publish the first one by hand? crates.io ties the crate's ownership to
> whoever publishes v0.1.0. Doing it manually means the crate belongs to your
> account, and the CI token that follows only ever needs *publish-update*
> scope for this one crate.

### 3. Give CI a way to publish

Create a scoped token at <https://crates.io/settings/tokens>:

- **Name:** `agent-plugin-rs CI`
- **Scopes:** `publish-update` only (not `publish-new`, not `yank`)
- **Crates:** `agent-plugins`
- **Expiry:** whatever your policy says; 90 days is a reasonable default

Then, in the GitHub repo:

1. **Settings → Environments → New environment**, named `crates-io`.
2. Optionally add yourself as a **required reviewer** — with that on, every
   release pauses for a human click before the publish step runs.
3. **Add secret** `CARGO_REGISTRY_TOKEN` with the token value.

The environment name matters: `.github/workflows/release.yml` pins the publish
job to `environment: crates-io`, so the token is not readable from any other
job, including anything a pull request can trigger.

### Optional: Trusted Publishing instead of a token

crates.io supports OIDC Trusted Publishing, which removes the long-lived
secret entirely. To switch:

1. On crates.io, open the crate's **Settings → Trusted Publishing** and add a
   GitHub publisher for `Toasterson/agent-plugin-rs`, workflow
   `release.yml`, environment `crates-io`.
2. In `release.yml`, replace the `Publish` step's `env:` block with a token
   exchange before `cargo publish`:

   ```yaml
   - uses: rust-lang/crates-io-auth-action@v1
     id: auth
   - run: cargo publish
     env:
       CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
   ```

3. Delete the `CARGO_REGISTRY_TOKEN` secret.

The `id-token: write` permission the publish job already declares is what
makes this work.

## Cutting a release

From an up-to-date `main` with a clean tree:

```bash
just ci                     # optional; CI runs it again anyway
just release-dry patch      # show exactly what would change
just release patch          # do it
```

`cargo release` will, in one step:

1. bump the version in `Cargo.toml` and `Cargo.lock`,
2. promote `## [Unreleased]` in `CHANGELOG.md` to the new version and open a
   fresh Unreleased section,
3. commit as `release: v<version>`,
4. tag `v<version>`,
5. push the commit and the tag.

Pushing the tag starts `.github/workflows/release.yml`, which:

1. re-runs fmt, clippy, tests, and doc tests,
2. checks the tag matches the version in `Cargo.toml` — a mismatch fails the
   release rather than publishing something surprising,
3. publishes to crates.io,
4. creates a GitHub release with generated notes.

`LEVEL` can be `patch`, `minor`, `major`, or an explicit version:

```bash
just release minor
just release 1.0.0
```

## If a release goes wrong

**CI failed before publishing.** Nothing reached crates.io. Fix the problem on
`main`, delete the tag locally and remotely, and release again:

```bash
git tag -d v0.1.1
git push origin :refs/tags/v0.1.1
```

**CI published, but the release is broken.** crates.io versions are permanent
— you cannot replace one. Yank it so new dependents cannot pick it up, then
release a fixed patch version:

```bash
cargo yank --version 0.1.1
just release patch
```

Yanking does not break anyone who already has it in a `Cargo.lock`, which is
the point.

## Keeping the two implementations in step

The conformance corpus in `fixtures/cases/` is mirrored in
[agent-plugin-ts](https://github.com/Toasterson/agent-plugin-ts). A fixture
change belongs in both repos, or the implementations quietly stop agreeing:

```bash
# from a checkout with both repos side by side
just sync-fixtures            # pulls from ../agent-plugin-ts
just test
```

The two packages version independently — a Rust-only fix does not need a
TypeScript release.