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 thecomposershim hands the invocation to the real Composer. - Research-chapter surface.
viv.lockandviv update --lock native,viv lock convert,viv lock merge,viv workspace listandviv isolate(with theextra.viv.isolatekey it writes) are outside this contract. They sit behind explicit opt-in, their behaviour is judged against their chapter indocs/research.md, and they may change between minor releases. - Commands Composer keeps.
search,config,global,self-update,licenses,dependsand the rest of the long tail stay with Composer and aren't part of viv's contract. - viv's own
init,newanddiagnose.viv initwrites acomposer.jsonfrom defaults, without Composer's interactive questions.viv new(alsocreate-project) starts a project in a new directory, andviv diagnoseprints viv's own report. Their output isn't promised to match Composer'sinit,create-projectordiagnose; thevendor/andcomposer.lockthey go on to write follow the same contract asviv install. - Progress wording on stdout during a run. Only the files viv writes and
the plain-text output of commands such as
show,whyandvalidateare 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 (ananyhow::Errorbubbled up from the command, or anoutdated/auditcheck 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.jsonandcomposer.lock(or the pair before/after, for anupdate),- 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.