☰
VSuite

@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.0 when version is neither 1 nor major.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 later when the config is newer than to.

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

  1. Choose the release version from the root package.json (the single version source). If the format change needs a new release, bump the root version and run pnpm run version:sync to copy it into packages/cli/package.json.
  2. Create src/migrations/<version>.ts exporting a Migration whose version is that release. migrate receives a clone and returns the new object.
  3. Register it in src/registry.ts, keeping migrations in version order.
  4. Add tests: a per-migration test (for example tests/0.2.1.test.ts) and a legacy-fixture test in tests/registry.test.ts. A guard test in tests/registry.test.ts fails if any registered migration is newer than the root package.json version.
  5. 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/migrations

0.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.