vcs-cli-support 0.2.0

Shared plumbing for CLI-wrapping crates: an argv injection guard, a managed client with retry (fetch + lock contention) and credential injection, and processkit error classifiers.
Documentation
# Changelog — vcs-cli-support

All notable changes to the `vcs-cli-support` crate are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this crate adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This crate is versioned and published independently of the other workspace
crates; tag releases as `vcs-cli-support-v<version>`.

## [Unreleased]

### Added
-

### Changed
-

### Fixed
-

## [0.2.0] - 2026-06-27

### Added
- **Credential provisioning (opt-in).** A new `credentials` module: the
  `CredentialProvider` async trait (dyn-compatible, matching processkit's
  `ProcessRunner` pattern) plus the `Credential`/`Secret` types (`Secret` redacts
  itself in `Debug`/`Display`) and built-in adapters (`StaticCredential`,
  `EnvToken`, `provider_fn`). `ManagedClient` gained `with_credentials` +
  `with_token_env` + `resolve_credential`: when a token-env binding is set it
  injects the resolved token into every command's environment (the forge
  `GH_TOKEN`/`GITLAB_TOKEN` path); `git_credential_helper` builds a git
  `credential.helper` invocation that keeps the secret out of `argv`. Default is
  no provider → ambient CLI auth, unchanged. Adds an `async-trait` dependency.
  `ManagedClient` also gained an `exit_code` verb (used by the forge clients).
- **Lock-contention retry.** `is_lock_contention(&Error)` classifies a *pre-execution*
  **whole-repository** lock-acquisition failure (git's `index.lock`, jj's
  working-copy / op-heads lock) — the one error class safe to retry on a mutation,
  since the command never ran. Per-ref lock failures (`cannot lock ref`,
  `<ref>.lock`) are deliberately *excluded*: a multi-ref `push`/`fetch` can fail a
  ref lock after earlier refs already moved, where a retry would not be idempotent.
  `RetryPolicy` (attempts + exponential backoff + full jitter)
  and the `retry_async` executor express the strategy; `ManagedClient` is a
  `CliClient` wrapper that applies it to every command (the `vcs-git`/`vcs-jj`
  clients now hold one). Retry is opt-in (default `RetryPolicy::none()`). Adds a
  `tokio` (time) dependency for the backoff sleep.
- `signalled_is_terminal_not_transient` test — pins that an `Error::Signalled`
  (signal-killed process) is terminal, not a transient fetch error (so it is
  never auto-retried), even when its captured stderr contains an otherwise-transient
  marker.

### Changed
- Bumped `processkit` to **0.11.0** (from 0.9.1). The classifiers' input `Error`
  gained partial output on the `Timeout`/`Signalled` variants and new first-class
  variants (`Signalled`/`NotFound`/`CassetteMiss`); the `#[non_exhaustive]`
  fall-through keeps every classifier returning "no" for unfamiliar variants. The
  0.10→0.11 step is light for us: processkit's **`stats` feature is now opt-in**
  (we never used the metrics surface, so default builds are leaner with no code
  change), `OutputEvent` now carries an `OutputLine` (we don't stream output
  events), and a cancel-precedence race fix plus a control-character-sanitizing
  one-line `Error` `Display` (0.10.2) come for free — no API change on our side.

### Removed
- The **`cancellation`** feature — cancellation is now core in processkit 0.10, so
  `Error::Cancelled` is always constructible (the
  `cancelled_is_not_transient_or_otherwise_classified` test is now unconditional).
  Breaking for anyone who enabled `vcs-cli-support/cancellation`.

### Fixed
- **Lock-retry safety:** `is_lock_contention` no longer classifies per-ref lock
  failures (`cannot lock ref`, `<ref>.lock`/`packed-refs.lock`) — a multi-ref
  `push`/`fetch` can fail a ref lock after earlier refs moved, where a retry would
  not be idempotent. It now matches only the whole-repo/working-copy locks
  (`index.lock`, jj working-copy / op-heads), which are genuinely pre-execution.
- `reject_flag_like` now also refuses an interior NUL, and applies the leading-`-`
  check to the *trimmed* value (so `" --flag"` with leading whitespace is refused).
- `EnvToken` treats a whitespace-only environment value as unset (`None` → ambient),
  and `git_credential_helper`'s inline helper emits nothing when its secret env var
  is unset/empty (git falls through to ambient instead of using an empty credential).
  `ManagedClient::resolve_credential` likewise drops a whitespace-only secret (not
  just an empty one), so every adapter shares one "no usable credential ⇒ ambient" rule.
- `ManagedClient::output` dropped its dead lock-retry wrapper (it returns `Ok` on a
  non-zero exit, so the retry predicate could never fire); credential injection on
  `output` is unchanged.
- **Transient-fetch classifier tightened:** dropped the bare `timed out` marker from
  `is_transient_fetch_error`'s list. It subsumed the specific `connection timed out`
  / `operation timed out` entries and would also match unrelated non-network
  "timed out" messages (a lock wait, a hook), triggering a spurious fetch retry. The
  specific timeout phrases are retained.

## [0.1.0] - 2026-06-08

### Added
- Initial release: the `processkit`-coupled plumbing the CLI wrappers share —
  `reject_flag_like` (the argv injection guard, parameterized by program name),
  the `FETCH_ATTEMPTS`/`FETCH_BACKOFF` fetch-retry policy, and the error
  classifiers `is_merge_conflict` / `is_nothing_to_commit` /
  `is_transient_fetch_error`. Extracted from the copies previously duplicated
  across `vcs-git` and `vcs-jj` so the transient-failure marker list and the
  classifiers can no longer drift between backends.

### Changed
- Bumped `processkit` to **0.8**`Error` (taken by the classifiers) stays
  `#[non_exhaustive]`; an unfamiliar variant classifies as "no" on every
  classifier (covered by a test). Breaking for consumers matching
  `processkit::Error` exhaustively.
- New off-by-default **`cancellation`** feature (forwards to
  `processkit/cancellation`): the classifiers only match `Exit`/`Timeout`, so
  `Error::Cancelled` already falls through every one to "no"; the feature only lets
  a test construct the variant to pin that (not transient, not a conflict, not
  nothing-to-commit) as a first-class assertion.
- `reject_flag_like` also refuses whitespace-only values (as meaning-changing as
  empty ones), not just empty and leading-`-`.

### Fixed
-

[Unreleased]: https://github.com/ZelAnton/vcs-toolkit-rs/compare/vcs-cli-support-v0.2.0...HEAD
[0.2.0]: https://github.com/ZelAnton/vcs-toolkit-rs/compare/vcs-cli-support-v0.1.0...vcs-cli-support-v0.2.0
[0.1.0]: https://github.com/ZelAnton/vcs-toolkit-rs/releases/tag/vcs-cli-support-v0.1.0