Adopt an existing app
adoptBring a Next.js or Node app, including one from Lovable, Bolt or v0, onto MainSDK one module and one pull request at a time, with its behaviour locked first.
migration-skillFree in your app, Apache-2.0Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
mainsdk adopt moves an existing app onto MainSDK without a rewrite. It scans the app, ranks the gaps, locks the flows that must keep working, then changes one module per branch.
At a glance
npx mainsdk adopt scan # main.yaml as the app is, no code change
npx mainsdk adopt report # the gaps against Main 27: secrets, access, data, the rest
npx mainsdk adopt lock # record sign-in and checkout as locked flows
npx mainsdk adopt next # one module, one branch, gated by check and the locked flows
npx mainsdk adopt finish # Main 27 met, or exactly what remainsGuarantees
Whatever the driver.
- The scan is deterministic and changes no code; it writes main.yaml only
- What the app already runs is kept and tagged, never replaced
- Nothing changes until the key flows are locked
- Each module is one branch that passes check and every locked flow; a failed step keeps its branch and leaves the default branch untouched
- Runs fully locally and sends nothing
Drivers
1 drivers. Select one to see its line in main.yaml.
adopt:
driver: builtinChange this one line to swap providers; your calls stay the same.Built-in and local drivers integration
Codes
| Code | Where it appears | Meaning |
|---|---|---|
MAIN-ADOPT-0002 | Checkmainsdk check, before deploy | The stack is not Next.js or Node; adopt stops and changes nothing |
MAIN-ADOPT-0003 | Checkmainsdk check, before deploy | A route changes or reads data and never checks who is calling (access gap) |
MAIN-ADOPT-0004 | Checkmainsdk check, before deploy | The browser queries the database directly, so row rules are the only protection (data gap) |
MAIN-ADOPT-0007 | Checkmainsdk check, before deploy | A call still sits behind an interface's raw() escape hatch; listed by finish with file and line |
MAIN-ADOPT-0001 | RuntimeError responses and logs | adopt next before key flows are locked |
MAIN-ADOPT-0006 | RuntimeError responses and logs | There is nothing to migrate in the module, or it is not a module (secrets, auth, data, other) |
MAIN-ADOPT-0008 | RuntimeError responses and logs | The working tree has uncommitted changes; a module step starts from a clean tree |
MAIN-ADOPT-0009 | RuntimeError responses and logs | No sign-in or checkout flow was found to lock, or a named flow points at text that is not there |
MAIN-ADOPT-0010 | RuntimeError responses and logs | A module step is still open, or verify ran with none open |
MAIN-ADOPT-0012 | RuntimeError responses and logs | The folder is not a git repository, or the module branch could not be created |
MAIN-ADOPT-0013 | RuntimeError responses and logs | The name is not an adopt tool, or not an agent adopt writes a skill for |
Features
Nothing here has shipped yet. Each feature lands in its phase; the spec ticket builds it.
| Feature | Phase | Milestone | Spec ticket |
|---|---|---|---|
| Deterministic scan that keeps what you already run | Phase 1 | 0.5 | migration-skill#scan |
| Gap report in plain language | Phase 1 | 0.5 | migration-skill#gap-report |
| Locked flows before any change | Phase 1 | 0.5 | migration-skill#lock-flows |
| One module per branch | Phase 1 | 0.5 | migration-skill#module-step |
| Launch report | Phase 1 | 0.5 | migration-skill#finish-report |
| Agent skill and the adopt MCP tools | Phase 1 | 0.5 | migration-skill#agent-skill |
Quick start
npx mainsdk adopt scan # main.yaml as the app is, no code change
npx mainsdk adopt report # the gaps against Main 27: secrets, access, data, the rest
npx mainsdk adopt lock # record sign-in and checkout as locked flows
npx mainsdk adopt next # one module, one branch, gated by check and the locked flows
npx mainsdk adopt finish # Main 27 met, or exactly what remainsmainsdk adopt with no step runs the next one, so it is safe to re-run. It runs fully on your machine and sends nothing. Opening the pull request is left to you, your agent or gh: adopt only creates the branch and the commit.
Configuration
adopt has no main.yaml section. Its first step writes main.yaml for you, and its progress lives in .mainsdk/adopt.json, which is git-ignored:
{ "step": "module", "done": ["secrets", "auth"], "next": "data", "locked": 6, "byo": ["byo-identity:clerk"] }byo lists what the app already runs and adopt keeps: a Clerk sign-in stays auth.driver: clerk, an OpenFGA model stays access.driver: openfga. Nothing is replaced.
What you get
| Command | What it does |
|---|---|
mainsdk adopt scan | Writes the same main.yaml on every run. Lists the interfaces the app uses and changes no other file |
mainsdk adopt report | Plain-language gaps in risk order: secrets, then access, then data, then the rest. Each names the Main 27 rule and the fix. --json for tools |
mainsdk adopt lock | Records the sign-in and checkout flows as locked flows. A flow holds while its file exists and still contains the call that makes it work |
mainsdk adopt next | Opens one module (secrets, auth, data or other) on its own branch. --module <name> picks it. Refuses to run before flows are locked |
mainsdk adopt verify | Checks the open module: only its files changed, mainsdk check shows no new finding and every locked flow holds |
mainsdk adopt finish | The launch report: Main 27 is met, or the report lists what remains with file and line. --json for tools |
Code that cannot move to an interface yet stays behind that interface's raw() escape hatch, and finish lists it.
With your coding agent
mainsdk adopt mcp starts a stdio MCP server with six tools: adopt.scan, adopt.report, adopt.lock, adopt.plan_module, adopt.verify_module and adopt.finish. mainsdk adopt skill writes the skill for Claude Code (.claude/skills/mainsdk-adopt/SKILL.md), Cursor (.cursor/rules/mainsdk-adopt.mdc) and any agent that reads AGENTS.md. The agent follows the same five steps and cannot skip the lock.
Errors and checks
| Code | When |
|---|---|
MAIN-ADOPT-0001 | adopt next ran before flows were locked; nothing changed |
MAIN-ADOPT-0002 | The stack is not Next.js or Node; adopt stops and changes nothing |
MAIN-ADOPT-0003 | A route changes or reads data and never checks who is calling |
MAIN-ADOPT-0004 | The browser queries the database directly, so row rules are the only protection |
MAIN-ADOPT-0006 | Nothing to migrate in the module, or it is not a module |
MAIN-ADOPT-0007 | A call still sits behind raw(); listed by finish with file and line |
MAIN-ADOPT-0008 | The working tree has uncommitted changes; a module step starts clean |
MAIN-ADOPT-0009 | No sign-in or checkout flow was found to lock, or a named flow points at text that is not there |
MAIN-ADOPT-0010 | A module step is still open, or verify ran with none open |
MAIN-ADOPT-0012 | The folder is not a git repository, or the module branch could not be created |
MAIN-ADOPT-0013 | The name is not an adopt tool, or not an agent adopt writes a skill for |
The full list is in check codes.
Swapping drivers
adopt has one driver, the built-in one, which reads your files and runs git. There is nothing to swap.
Testing
A failing module step leaves your main branch untouched and keeps its branch for review. The scan is deterministic, so a test can run it twice and compare the two main.yaml files. Run mainsdk check after any step to see the same findings the gate uses.
Next
- Adopt an existing app, the walk-through
- Migrate from raw providers
- The CLI reference
Was this page helpful?