- MCP handlers in
src/mcp.tsare thin wrappers — no business logic - Each handler: validate input → delegate to utility → format output
- Handler bodies should be ≤10 lines
- Business logic lives in
src/utils/as standalone async functions - Follow the pattern in
src/utils/create-project.ts:Optionsinterface for inputResultinterface (or discriminated union) for output- Single exported async function
- Throw on unrecoverable errors (invalid project, missing files)
- Return
{ success: false, reason }for expected failures (dirty git, already installed, no username)
- Utilities never call
console.log,chalk,ora, orprocess.exit() - Side effects (fs, network, git, shell) go through importable modules so they can be mocked with
vi.mock() - Use
withProjectDir()fromsrc/utils/with-project-dir.tsfor cwd-switching
- Tests are colocated:
foo.test.tsnext tofoo.ts - Pure functions get direct tests (no mocks needed)
- Side-effectful functions mock their I/O deps with
vi.mock() pnpm testmust pass before merging- Run
pnpm testandpnpm run typecheckto validate changes