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 Shape | v2 Shape |
|---|---|
framework.type: typescript | framework.type: typescript (single language) OR framework.type: multi + framework.primary: typescript |
framework.router / orm / ui at top level | Per-language entries at framework.languages.<lang>.{router, orm, ui}; top-level keys still mirrored for back-compat |
python.* sub-block | framework.languages.python. + legacy python. preserved |
| No VR command overrides | verification.<language>.{test, type, build, syntax, lint} |
| No custom VR types | verification_types record |
| No drift fingerprint | detection.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_pathsaccessScopesknownMismatchesdbAccessPatternanalytics,governance,security,team,regression,cloudconventionsautoLearningdetection(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:
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:
- Open the resulting v2
massu.config.yaml - Confirm
framework.typematches the dominant language on disk - For multi-runtime repos, verify
framework.primarypoints at the language with the most source files - Inspect
framework.languages.<lang>.source_dirs— each path must exist - Inspect
verification.<lang>.test— run it locally to confirm it exits 0 (or near-0 modulo pre-existing test failures) - Commit
.bakalongside the new config so reviewers can diff
Common Edge Cases
| Repo Pattern | Issue | Manual Fix |
|---|---|---|
v1 claims type: typescript, actual is Python+FastAPI | Migrator flips type to python, populates languages.python.framework: fastapi | Verify paths.source points at the Python source dir (likely app/ or src/) |
| v1 is TS template on Python+Swift repo | Migrator produces type: multi with both languages | Review framework.primary — pick the language with more files |
router: 'hand-rolled' (non-canonical value) | Migrator preserves user intent verbatim | Leave as-is; custom router strings are supported |
| Multi-runtime TS+Py declared as single-language TS | Migrator emits multi-runtime with both languages | Check that verification.python.test has a working command |
| v2-idempotent | Migrator produces equivalent v2 (no-op) | Safe to re-run; no drift |
Before / After Example
Before (v1)
# 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)
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
+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: [...] # unchangedIdempotence
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.