@vsuite/migrations
Versioned, ordered migrations that upgrade a raw vsuite.json object to the format of the running vsuite version, and stamp version with that vsuite version. Non-goals: reading or writing files (@vsuite/core loadConfig), schema validation (@vsuite/config), and downgrades.
This is a private, source-only workspace package (exports → ./src/index.ts). It is bundled into the @krizic/vsuite CLI and is not published to npm. Workspace consumers depend on it with "@vsuite/migrations": "workspace:*".
Public API
| Export | Kind | Signature | Description |
|---|---|---|---|
Migration |
type | { version: string; description: string; migrate(config: JsonObject): JsonObject } |
One format change; version is the vsuite release it ships in. |
MigrationResult |
type | { config: JsonObject; from: string; to: string; applied: Migration[]; changed: boolean } |
Result of migrateConfig; changed is from !== to. |
migrateConfig |
function | (raw: JsonObject, to: string, registry?: readonly Migration[]) => MigrationResult |
Applies pending migrations to a clone of raw and sets version to to. Never mutates raw. registry defaults to migrations. |
pendingMigrations |
function | (from: string, to: string, registry?: readonly Migration[]) => Migration[] |
Migrations with from < version <= to, sorted ascending. |
migrations |
const | readonly Migration[] |
The registry, in version order. Currently 0.2.0 through 0.5.0; see Registered migrations. |
compareVersions |
function | (a: string, b: string) => number |
Numeric major.minor.patch comparison (negative, 0, positive). |
configVersion |
function | (raw: JsonObject) => string |
The version a config conforms to; legacy integer 1 maps to "0.0.0". |
Error messages:
configVersion:vsuite.json version must be a vsuite version such as 0.2.0whenversionis neither1normajor.minor.patch.compareVersions:Invalid vsuite version "<v>"; expected major.minor.patch.migrateConfig:vsuite.json was written by vsuite <from>; upgrade @krizic/vsuite to <from> or laterwhen the config is newer thanto.
Dependencies
- Workspace:
@vsuite/json. - Third-party: none.
- Used by:
@vsuite/core.
Diagrams
flowchart TD
A["migrateConfig(raw, to)"] --> B["from = configVersion(raw)<br/>(legacy 1 → 0.0.0)"]
B --> C{"from newer than to?"}
C -- yes --> X["throw: vsuite.json was written by vsuite from"]
C -- no --> D["pending = migrations with from < version <= to, sorted"]
D --> E["config = clone of raw"]
E --> F{"next pending migration?"}
F -- yes --> G["config = migration.migrate(clone of config)"]
G --> F
F -- no --> H["config.version = to"]
H --> R["return { config, from, to, applied, changed: from !== to }"]
Because version is always stamped, upgrading the CLI rewrites version even when no migration applies.
Registered migrations
| Version | Description | Effect |
|---|---|---|
0.2.0 |
Record the vsuite version in vsuite.json | No format change; version is stamped (legacy 1 → "0.2.0" or later). |
0.2.1 |
Move plain-string agent models to { "default": … } |
In every modelProfiles profile, each agent’s string model becomes { "default": <string> }. effort, reasoning, and key order are kept; object or malformed values are left for validation. |
0.3.0 |
Replace the 0.2.x selection with an explicit agent graph | Writes agents, mcpServers, stack, projectSkills, and metrics from a frozen 0.2.1 catalog snapshot; removes skills, capabilities, skillWiring, toolWiring, and preferences.frontend. |
0.4.0 |
Add packs and record each agent/skill source |
Adds an empty packs object, source: "catalog" on catalog agents, and skillSources entries for catalog skills. |
0.5.0 |
Rename gitea-reconciliation-run to reconciliation-run |
Rewrites the skill id in every agent’s skills array and in skillSources, preserving order and dropping duplicates. |
// before (0.2.0)
"modelProfiles": { "default": { "agents": { "code-reviewer": { "model": "github-copilot/claude-opus-5.5", "effort": "high" } } } }
// after (0.2.1)
"modelProfiles": { "default": { "agents": { "code-reviewer": { "model": { "default": "github-copilot/claude-opus-5.5" }, "effort": "high" } } } }Adding a migration
- Choose the release version from the root
package.json(the single version source). If the format change needs a new release, bump the rootversionand runpnpm run version:syncto copy it intopackages/cli/package.json. - Create
src/migrations/<version>.tsexporting aMigrationwhoseversionis that release.migratereceives a clone and returns the new object. - Register it in
src/registry.ts, keepingmigrationsin version order. - Add tests: a per-migration test (for example
tests/0.2.1.test.ts) and a legacy-fixture test intests/registry.test.ts. A guard test intests/registry.test.tsfails if any registered migration is newer than the rootpackage.jsonversion. - Update docs: this README’s Registered migrations, and the schema/README examples if the format changed (
pnpm run schema).
Usage
import { migrateConfig } from "@vsuite/migrations";
const result = migrateConfig({ version: 1, targets: ["opencode"] }, "0.2.0");
// result.from === "0.0.0", result.to === "0.2.0", result.changed === true
// result.applied.map((m) => m.version) → ["0.2.0"]
// result.config.version === "0.2.0"Testing
pnpm vitest run --project @vsuite/migrations0.3.0 — explicit agent graph
explicitAgentGraph turns the 0.2.x selection (agents array, skills, capabilities, skillWiring, toolWiring, preferences.frontend) into the 0.3.0 agents map, mcpServers, stack, projectSkills, and metrics. It reads a frozen snapshot of the 0.2.1 catalog (0.3.0-legacy-catalog.ts), so later catalog changes never change its output. Lossy or judgment-based changes are reported through MigrationContext.warn and surface as MigrationResult.warnings. packages/core/tests/golden.test.ts proves the migrated config generates equivalent output.