Skip to content

Migration Guide: v1 to v2

Step-by-step guide for migrating massu.config.yaml from schema_version=1 to schema_version=2 using migrateV1ToV2 — preserves user overrides, fixes stale detection


Migration Guide: v1 to v2

The autodetect release introduces schema_version: 2, which restructures framework into a per-language map and adds a verification block for VR command overrides. Existing v1 configs continue to load unchanged; v2 is the default for every new config generated by massu init.

This guide covers the migration using migrateV1ToV2(), a pure function that takes a v1 config and a live DetectionResult and produces a v2 config while preserving every user override.

What Changes Under v2

v1 Shapev2 Shape
framework.type: typescriptframework.type: typescript (single language) OR framework.type: multi + framework.primary: typescript
framework.router / orm / ui at top levelPer-language entries at framework.languages.<lang>.{router, orm, ui}; top-level keys still mirrored for back-compat
python.* sub-blockframework.languages.python. + legacy python. preserved
No VR command overridesverification.<language>.{test, type, build, syntax, lint}
No custom VR typesverification_types record
No drift fingerprintdetection.rules for custom framework signals

What Is Preserved Verbatim

migrateV1ToV2 is a pure function with no filesystem side effects. It preserves every user-authored field exactly as written:

  • rules[]
  • domains[]
  • canonical_paths
  • accessScopes
  • knownMismatches
  • dbAccessPattern
  • analytics, governance, security, team, regression, cloud
  • conventions
  • autoLearning
  • detection (user detection rules)
  • verification_types (user-defined VR types)
  • toolPrefix

User-supplied framework.router, framework.orm, and framework.ui at the top level win over detection when they are set to a non-empty, non-none value. Detection only fills in the gaps.

Migration Paths

Path A: programmatic via migrateV1ToV2

The pure function is exported from packages/core/src/detect/migrate.ts. Use it from a script or test:

typescript
import { parse as yamlParse, stringify as yamlStringify } from 'yaml';
import { readFileSync, writeFileSync, copyFileSync } from 'fs';
import { runDetection } from '@massu/core/detect';
import { migrateV1ToV2 } from '@massu/core/detect/migrate';

const projectRoot = process.cwd();
const configPath = `${projectRoot}/massu.config.yaml`;

// Back up first
copyFileSync(configPath, `${configPath}.bak`);

const v1 = yamlParse(readFileSync(configPath, 'utf-8'));
const detection = await runDetection(projectRoot);
const v2 = migrateV1ToV2(v1, detection);

writeFileSync(configPath, yamlStringify(v2));

Path B: CLI via massu config upgrade (planned, not shipped in 1.0.0)

The plan defines a massu config upgrade CLI command that wraps the steps above — detects, migrates, writes .bak, then writes the v2 file. This command is not in the 1.0.0 cut and will ship in a follow-up release. Until then, use Path A or the manual steps in the next section.

Manual Edit Checklist

For repos that need hand-tuning (stale v1 configs where the declared language doesn't match reality), perform these manual steps after running migrateV1ToV2:

  1. Open the resulting v2 massu.config.yaml
  2. Confirm framework.type matches the dominant language on disk
  3. For multi-runtime repos, verify framework.primary points at the language with the most source files
  4. Inspect framework.languages.<lang>.source_dirs — each path must exist
  5. Inspect verification.<lang>.test — run it locally to confirm it exits 0 (or near-0 modulo pre-existing test failures)
  6. Commit .bak alongside the new config so reviewers can diff

Common Edge Cases

Repo PatternIssueManual Fix
v1 claims type: typescript, actual is Python+FastAPIMigrator flips type to python, populates languages.python.framework: fastapiVerify paths.source points at the Python source dir (likely app/ or src/)
v1 is TS template on Python+Swift repoMigrator produces type: multi with both languagesReview framework.primary — pick the language with more files
router: 'hand-rolled' (non-canonical value)Migrator preserves user intent verbatimLeave as-is; custom router strings are supported
Multi-runtime TS+Py declared as single-language TSMigrator emits multi-runtime with both languagesCheck that verification.python.test has a working command
v2-idempotentMigrator produces equivalent v2 (no-op)Safe to re-run; no drift

Before / After Example

Before (v1)

yaml
# schema_version omitted — defaults to 1
framework:
  type: typescript
  router: trpc
  orm: prisma
  ui: nextjs

paths:
  source: src

toolPrefix: myproject

rules:
  - pattern: "packages/api/**"
    rules:
      - "All routes must use protectedProcedure"

domains:
  - name: billing
    routers: ["**/routers/billing.ts"]
    pages: ["**/billing/**"]
    tables: ["invoices", "subscriptions"]
    allowedImportsFrom: ["shared"]

After (v2)

yaml
schema_version: 2

framework:
  type: typescript
  # Mirrored from languages.typescript for back-compat with legacy readers
  router: trpc
  orm: prisma
  ui: nextjs
  languages:
    typescript:
      framework: next
      test_framework: vitest
      router: trpc
      orm: prisma
      ui: nextjs
      source_dirs:
        - src
      test_dirs:
        - src/__tests__

paths:
  source: src

verification:
  typescript:
    test: npm test
    type: npx tsc --noEmit
    build: npm run build

toolPrefix: myproject

# Preserved from v1 — migrator never rewrites user rules
rules:
  - pattern: "packages/api/**"
    rules:
      - "All routes must use protectedProcedure"

# Preserved from v1
domains:
  - name: billing
    routers: ["**/routers/billing.ts"]
    pages: ["**/billing/**"]
    tables: ["invoices", "subscriptions"]
    allowedImportsFrom: ["shared"]

Diff Summary

diff
+schema_version: 2
 framework:
   type: typescript
   router: trpc
   orm: prisma
   ui: nextjs
+  languages:
+    typescript:
+      framework: next
+      test_framework: vitest
+      router: trpc
+      orm: prisma
+      ui: nextjs
+      source_dirs: [src]
+      test_dirs: [src/__tests__]

 paths:
   source: src

+verification:
+  typescript:
+    test: npm test
+    type: npx tsc --noEmit
+    build: npm run build

 toolPrefix: myproject
 rules: [...]         # unchanged
 domains: [...]       # unchanged

Idempotence

migrateV1ToV2(v2Config, detection) returns an equivalent v2 config. Re-running the migrator on an already-migrated config is safe and produces no drift. This is covered by src/tests/config-upgrade.test.ts in the core package.

Rollback

Before running migrateV1ToV2, always back up the source config (cp massu.config.yaml massu.config.yaml.bak). The planned massu config upgrade CLI performs this automatically. If the v2 output is wrong for your repo, restore from the .bak file — v1 configs continue to load unchanged in 1.0.0.