Stack Migration
Structured migration from existing patterns to Outfitter Dev Kit.
Quick Start
Run the scanner to generate a migration plan:
bun run path/to/skills/migration/scripts/init-migration.ts
This creates .outfitter/migration/ with:
audit-report.md— Scan results and scopeplan/— Stage-by-stage task files
Generated Structure
.outfitter/migration/
├── audit-report.md # Scan results, scope, recommendations
└── plan/
├── 00-overview.md # Status dashboard, dependencies
├── 01-foundation.md # Dependencies, context, logger
├── 02-handlers.md # Handler conversions
├── 03-errors.md # Error taxonomy mappings
├── 04-paths.md # XDG path migrations
├── 05-adapters.md # CLI/MCP transport layers
├── 06-documents.md # Documentation updates
└── 99-unknowns.md # Items requiring review
Migration Stages
| Stage | Blocked By | Focus |
|---|---|---|
| 1. Foundation | — | Install packages, create context/logger |
| 2. Handlers | Foundation | Convert throw → Result |
| 3. Errors | Handlers | Map to error taxonomy |
| 4. Paths | — | XDG paths, securePath |
| 5. Adapters | Handlers | CLI/MCP wrappers |
| 6. Documents | All | Update docs to reflect patterns |
| 99. Unknowns | — | Review anytime |
Conversion Patterns
Exceptions to Result
Before:
async function getUser(id: string): Promise<User> {
const user = await db.users.findById(id);
if (!user) throw new Error(`Not found: ${id}`);
return user;
}
After:
import { Result, NotFoundError, type Handler } from "@outfitter/contracts";
const getUser: Handler<{ id: string }, User, NotFoundError> = async (
input,
ctx
) => {
const user = await db.users.findById(input.id);
if (!user) return Result.err(NotFoundError.create("user", input.id));
return Result.ok(user);
};
Console to Structured Logging
Before:
console.log("Processing", userId);
After:
ctx.logger.info("Processing", { userId });
Hardcoded Paths to XDG
Before:
const configPath = path.join(os.homedir(), ".myapp", "config.json");
After:
import { getConfigDir } from "@outfitter/config";
const configPath = path.join(getConfigDir("myapp"), "config.json");
Error Taxonomy
| Original | Outfitter | Category |
|---|---|---|
NotFoundError |
NotFoundError |
not_found |
InvalidInputError |
ValidationError |
validation |
DuplicateError |
ConflictError |
conflict |
UnauthorizedError |
AuthError |
auth |
ForbiddenError |
PermissionError |
permission |
Generic Error |
InternalError |
internal |
Migration Strategy
- New code first — All new code uses stack patterns
- Leaf functions — Start with functions that don't call others
- Bottom-up — Migrate dependencies before dependents
- Feature boundaries — Complete one feature at a time
Compatibility Layer
Wrap legacy code during transition:
import { Result, InternalError } from "@outfitter/contracts";
function wrapLegacy<T>(
fn: () => Promise<T>
): Promise<Result<T, InternalError>> {
return fn()
.then((value) => Result.ok(value))
.catch((error) =>
Result.err(InternalError.create(error.message, { cause: error }))
);
}
Reporting Issues
If migration reveals problems with @outfitter/* packages, use the stack:migration-feedback skill to create GitHub issues on outfitter-dev/outfitter.
Categories:
- bug — Package behavior doesn't match expected
- enhancement — Missing feature that would help migration
- docs — Documentation unclear or missing
- unclear-pattern — How to handle specific scenario
Related Skills
stack:patterns— Target patterns referencestack:review— Verify migration completenessstack:scaffold— Templates for new componentsstack:migration-feedback— Report issues to stack repo