---
title: "How to migrate TypeScript to Hem"
description: "Read a readiness report, apply the safe subset, handle refusals, and decide where the tool stops and judgment starts."
image: "https://hem.run/social/hem.png"
---

> Documentation Index
> Fetch the complete documentation index at: https://hem.run/llms.txt
> Use this file to discover all available pages before exploring further.

# How to migrate TypeScript to Hem

`hem migrate` converts existing TypeScript that it can prove safe to convert, and refuses everything else. It is deliberately conservative: whole-file refusal is the safety contract, so a file either migrates completely or is left exactly as it was.

It is designed for migrating bounded slices of an application rather than converting an entire repository.

## Look before you touch anything

```sh
hem migrate --check src/schedule.ts
```

This writes nothing. It prints a per-file, per-construct readiness report classifying every recognized construct into one of four categories:

| Category | Meaning | What apply mode does |
| --- | --- | --- |
| AUTO | Provably behavior-preserving | Rewrites it |
| SCAFFOLD | Structure converts, semantics need a human sign-off | Rewrites it with a `needs-hands` marker |
| FLAG | A semantic judgment call | Leaves it verbatim and reports it |
| NEEDS-HANDS | An otherwise-automatic construct whose preconditions failed | Leaves it verbatim with a marker |

Read the FLAG entries first. These represent patterns that require manual review: `||` defaults, truthiness conditions, `this` outside a class, and `any`. The tool does not convert them automatically.

## Pick a slice worth migrating

Choose a leaf module that has test coverage and active consumers, ensuring that existing behavior can be verified after migration.

Establish the baseline before changing any code. Run the package's tests, build, and type check on the untouched source and record any existing failures to avoid confusing them with migration issues.

## Apply the safe subset

```sh
hem migrate src/schedule.ts
```

Apply mode writes a `.hem` sibling for every file whose proposed migration both parses and type-checks as Hem. Any file that fails either gate is refused and left untouched. Every file it writes carries a banner marking it as migrated output for review.

Refusals are grouped by their first authored diagnostic, with counts. To see every diagnostic instead of the first blocker per file:

```sh
hem migrate --verbose src/schedule.ts
```

## When a file refuses

You have three options, in rough order of preference.

Keep it in TypeScript. A narrow TypeScript sidecar holding a deliberate contract is a good outcome, not a failure. Hem is an application language with TypeScript at its boundaries.

Extract the implementation and keep the shell:

```sh
hem migrate --extract-implementation src/schedule.ts
```

This selects self-contained exported function implementations, writes them as `.impl.hem`, and rewrites only those bodies in the original file into exact delegating wrappers. Effectful top-level initialization and functions that capture state owned by the shell stay in TypeScript. Focused compilation, wrapper type checking, and an exact declaration comparison all run before anything is written.

Write a smaller Hem leaf by hand. Writing a concise, idiomatic module is often preferable to mechanically converting a file that failed automated checks.

## Verify against the ABI, not the diff

The emitted TypeScript is the ABI, so review it. Compare the generated declarations against the originals and confirm nothing widened: exported types, mutability, optionality, overloads, generic contracts, and callable shape all have to survive.

Overload sets require manual work. The migrator does not turn an implementation
into an overloaded function through an unchecked assertion. Rewrite the operation
as a checked generic or separate functions first. A refused migration leaves the
original TypeScript file untouched; it does not create a shim.

Then run the gates that prove behavior rather than shape: the module's own tests, its consumers' tests, the package build, and the type check you baselined earlier.

## Feed a plan to a tool or an agent

```sh
hem migrate --check --json src/schedule.ts > plan.json
```

`--json` requires `--check` and emits the versioned machine-readable plan. Branch on each file's accepted or refused outcome. Construct counts do not guarantee migration: a file with a high AUTO count can still be refused, and the refusal determines the outcome.

During the private preview, collaborators with authenticated Git access can install
the [Hem skill](https://github.com/ozanvos/hem/tree/main/skills/hem)
with `npx skills add ozanvos/hem --skill hem`; the repository link also requires access.
It uses the CLI to assess and generate a candidate, then guides idiomatic
rewriting, contract review, and behavior
verification. Formal evidence ledgers and campaign tooling are optional references.

## What this tool will not do

Semantic API redesign is a stated non-goal of both modes. `hem migrate` does not reinterpret truthiness checks, replace `||` with `??`, or restructure interfaces; these changes require manual redesign.

For the full option list, see the [CLI reference](/reference/cli).

Source: https://hem.run/guides/migrate-typescript/index.mdx
