Stability

This is the contract viv offers scripts, CI pipelines and the composer shim that drive it instead of Composer.

What viv promises

For the commands on the Scope page, viv writes a vendor/ directory — including vendor/composer/* — and, for update, update-lock and require/remove, a composer.lock that are byte-identical to what Composer 2.10 (the version pinned in devbox.json) writes for the same composer.json, lock file and cache state. This is the contract, not an implementation detail: if viv's output differs from Composer's for a covered command, that's a bug.

Composer moves; viv tracks one version of it at a time. Each release states which Composer version it targets, and the compat sweep (see below) is run against that version before the release ships.

The package repository type is compat mode and is covered: viv update against one produces Composer's lock byte for byte.

What is not covered

  • Plugins. Only the native adapters viv ships (see docs/plugin-strategy.md) are covered. Any other plugin is out of scope; viv either skips it or the composer shim hands the invocation to the real Composer.
  • Research-chapter surface. viv.lock and viv update --lock native, viv lock convert, viv lock merge, viv workspace list and viv isolate (with the extra.viv.isolate key it writes) are outside this contract. They sit behind explicit opt-in, their behaviour is judged against their chapter in docs/research.md, and they may change between minor releases.
  • Commands Composer keeps. search, config, global, self-update, licenses, depends and the rest of the long tail stay with Composer and aren't part of viv's contract.
  • viv's own init, new and diagnose. viv init writes a composer.json from defaults, without Composer's interactive questions. viv new (also create-project) starts a project in a new directory, and viv diagnose prints viv's own report. Their output isn't promised to match Composer's init, create-project or diagnose; the vendor/ and composer.lock they go on to write follow the same contract as viv install.
  • Progress wording on stdout during a run. Only the files viv writes and the plain-text output of commands such as show, why and validate are contractual. Spinner text, progress counters and similar in-flight chatter can change between releases without notice.
  • Timing. The numbers in the README track viv's speed but aren't a promise; a release can get slower on a given machine without breaking the contract, though the project tries hard not to let that happen.

Versioning

viv stays in the 0.x series; no 1.0 is planned, because a 1.0 promises maintenance that nobody has asked for yet (see docs/research.md). Minor releases can make breaking changes, called out in JOURNAL.md. Within that, a minor release may:

  • add new commands or flags,
  • make internals faster,
  • change stdout progress wording or timing.

A minor release may not change the output bytes (vendor/, composer.lock, or the contractual stdout of show/why/validate) that Composer's pinned version would produce for a given input, unless it's fixing a bug in a previous release's output.

When a release moves the pinned Composer version, its JOURNAL.md entry says so, and the compat sweep results in compat/results/ are regenerated against the new version as part of that release (see AGENTS.md, "Before a release").

Interface for the shim and scripts

viv's exit codes, defined in src/main.rs:

  • 0 — success.
  • 1 — a command failed (an anyhow::Error bubbled up from the command, or an outdated/audit check that isn't a lock mismatch or vulnerable package), or the command line itself was invalid: a bad flag, a missing required argument, an unknown subcommand (cli_error).
  • 2 — the dependency resolver couldn't find a solution (resolver_error).

1 covers both a command that failed and a command that was never recognised as valid; 2 is reserved for the resolver alone, so a script can tell "no solution exists" from "you typed the command wrong" (#236). outdated --strict-style checks that report "something out of date" use 1 rather than a dedicated code; treat any non-zero exit as failure unless a command's own docs say otherwise.

stderr is for humans: warnings, confirmation prompts and diagnostics can reword between releases without notice. stdout for show, why, validate and the other commands that mirror Composer's plain-text output is contractual and covered by the same guarantee as vendor/ and composer.lock.

Deprecations

A flag or behaviour viv drops isn't removed outright. It stays for one minor release as a no-op: it's accepted, has no effect, and prints a single warning to stderr the first time it fires. The next minor release removes it.

viv rewrites composer.json only when a command edits it (add, rm, init, validate --fix); install and update only ever read it.

The first instance is --no-normalize on install and dump-autoload (#95): since 0.6, neither command touches composer.json, so the flag has nothing left to disable. It's kept as a silent-except-for-the-warning no-op so that a script or CI job that still passes it doesn't fail, and prints --no-normalize is a no-op on install since 0.6; install no longer touches composer.json (or the dump-autoload equivalent) once. update joins this list from 0.8: it stops normalizing composer.json itself (only add/rm/init still edit it), so --no-normalize there prints --no-normalize is a no-op on update since 0.8; update no longer touches composer.json.

As of 0.8, add/rm (and their require/remove aliases) join that list under a different reason (#145): both always normalize composer.json after editing it now, so --no-normalize prints --no-normalize is a no-op on add since 0.8; add always normalizes composer.json now (or the rm equivalent) once and otherwise does nothing.

How to report a contract break

If viv's vendor/, composer.lock or contractual stdout differs from what the pinned Composer version produces, file an issue with:

  • composer.json and composer.lock (or the pair before/after, for an update),
  • the Composer version you compared against,
  • a diff of the mismatch (a directory diff for vendor/, or the two lock files).

The compat sweep is the tool that finds these mismatches before release; if you can reproduce the break with a project from its corpus, name it in the issue and that's enough.