The cycle: status → plan → apply¶
Part of Recommendations and Risk.
Command |
Reads |
Writes |
Prompts |
|---|---|---|---|
|
full scan |
nothing |
— |
|
full scan + update plan |
nothing |
— |
|
full scan + update plan |
manifest + lockfile, via the native package manager |
two |
plan and apply run the same code path: apply is plan plus confirmation plus execution. The
plan you are shown by apply is the plan apply executes — it is computed in the same process,
milliseconds earlier.
Why apply re-scans instead of consuming a saved plan¶
There is no plan file. apply performs its own scan and builds its own plan. This costs a second
scan when you run plan first, and buys three things:
No stale plan. A plan saved an hour ago describes a registry that has moved and a tree that may have been changed by a teammate. There is no window between deciding and writing.
No new format to trust. A serialized plan would be a file the tool has to re-validate, and a file an attacker could hand you.
CLI and MCP stay equals. Both front doors call the same service function; neither holds a decision the other would get wrong.
How the write actually happens¶
Per ecosystem, in this order:
Read and parse the manifest first. An unparseable
package.json/pyproject.tomlfails the whole update loudly, before anything is rewritten, rather than degrading package by package.Rewrite direct specifiers in the manifest text.
--pin-allwrites exact==/exact pins.Add repaired peers (npm). A peer the plan repairs (see Repairs unresolved peers in the catalogue) is declared in
devDependencies, ordependencieswhen a production package needs it, at the range the plan shows. A package your manifest already declares anywhere is left as you wrote it.Persist transitive picks as overrides — npm
overrides, uv[tool.uv] override-dependencies— and record what was written under anossiq:metadatakey. On npm each pick is keyed to the copy it replaces ("minimatch@10.2.5": "10.2.6"), so a nested copy of the same name that npm installed for a different range is not dragged along. A copy that has to move together with its family is keyed to the range from its old version to its new one ("@vue/shared@3.5.42 - 3.5.43": "3.5.43"). On the next run, an override whose value no longer matches what OSS IQ last wrote is left alone: you have taken ownership of it, and it is never silently overwritten.Hand resolution to the native package manager —
npm install --ignore-scripts, oruv lock --upgrade-package … && uv sync. OSS IQ does not resolve trees itself, and--ignore-scriptskeeps install-time code out of the update path.On failure, restore the original manifest text and raise.
The two prompts¶
Proceed with 7 updates? [y/N]
then, only if something needs it:
The following updates need explicit acknowledgement - they widen the declared version
constraint (authorized by --update-strategy latest), or carry a known API/module-system break:
requests ~=2.31.0 -> 2.34.2 [direct] (widens ~=2.31.0)
The second prompt exists because two different things deserve a separate “yes”: rewriting a
constraint someone chose deliberately, and taking a version that crosses a known API or
module-system break. breaking_change ignores the runtime, so an ESM-only target in a CommonJS
project always asks, including the ones latest picks on a require(esm)-capable Node. The second case used to pass silently whenever the break happened to sit
inside the declared range — uuid@>11.0.0 admits ESM-only 14.0.2 — so nothing asked. Both are
now named per entry. --yes skips both prompts.
Convergence¶
Updates are resolved in a single pass against the current lockfile. Applying them re-resolves the
tree, which can surface further recommendations, so apply closes by telling you to re-run plan.
Most projects converge in one or two passes.
What the cycle risks¶
Risk |
Detail |
What to do |
|---|---|---|
Rollback restores the manifest, not the world |
If the package manager fails mid-run, the manifest is restored — the lockfile, |
Run |
|
In CI at |
Pin CI runs to |
|
It bypasses the solver, the cooldown, the widening hold and the prompt. Parent-constraint compatibility is not checked. |
Treat forced versions as manual changes; test them |
One pass is not convergence |
The plan describes the tree as it is now, not as it will be after the write. |
Re-run |
Transitive picks persist |
They are written as overrides and stay until removed, and show up as |
Expect them in review; remove them when upstream catches up |