This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
typokat — a from-scratch TypeScript type checker in Rust. A checker, not a
compiler: it parses, binds, and type-checks TS and reports tsc-style diagnostics.
Error codes mirror tsc (TK2322 ≡ TS2322). Emit and JS runtime semantics are out of
scope by design. Current M29 module coverage is a narrow type-checking slice (local relative
.ts modules); the planned sole 1.0 profile is Bundler, with physical resolution delegated to
oxc_resolver and alternate host profiles deferred. The goal is to preserve the type model.
M0–M33 are implemented. Coverage: README.md. Full design:
docs/reference/architecture.md.
cargo run -- check path/to/file.ts # check one file (exit 1 if diagnostics)
cargo test # unit tests + conformance corpus
cargo test conformance # just the marker-driven conformance harness
cargo test <name> # a single unit test by name
cargo clippy --all-targets -- -D warnings # must stay cleanThe conformance corpus is a single #[test] that runs every enabled milestone
directory — toggle scope via the MILESTONE_DIRS table in tests/conformance.rs.
Pipeline in src/driver.rs: parse (via oxc) → bind → check. Four pillars (details
in docs/reference/architecture.md):
- Type store (
src/types/) — every type is a hash-consedTypeId(u32)into an arena (noRc<RefCell>), so structural equality is an integer compare. Substitution-aware; identity-bearing property metadata is folded into the hash. - Binder (
src/binder/) — a scope graph with multi-slot symbols (value / type / namespace spaces), which is what lets a class be both a type and a value, and what nominal classes key on. - Relation engine (
src/relate/) —is_assignable, the CPU-heavy core. A 3×u32cache, an assume-true-until-disproven cycle stack for recursive types, andRelation::No(ReasonChain)(never a barebool) so reporting runs the same path. Carries a cache-soundness fix (architecture §6.3) — regressing it drops errors order-dependently, the sharpest bug class in the project. - Statement checker (
src/check/) — a flow-sensitive interpreter (a narrowing environment that forks atif/else/switch) plus the generic inference engine (infer, a separate machine from the relation engine). Split intochecker/submodules.
Soundness > completeness: when in doubt, over-report (the safe direction). Every
deliberate tsc divergence is documented in
docs/reference/divergences.md.
Milestone-by-milestone, spec-first — the process in
docs/reference/dev-method.md §1 is what kept the project sound,
follow it exactly: write the fixture corpus first (the acceptance spec) and commit it on its own,
then implement, then run an independent adversarial review (hunts false negatives, cross-checks
against real tsc --strict). Implementation goes through subagents; the leader supervises and
commits. The soundness/architecture invariants you must not break are in
docs/reference/invariants.md; the roadmap is the
docs/backlog/ (its README carries the definition of done and the
recommended order — next: the model-completeness track that unblocks full lib.d.ts, then the
scale ladder; the bytecode VM stays a deferred, profiling-gated refactor, ADR-0001).
Two layers:
- Conformance corpus (the spec) —
tests/cases/mN_*/*.tsfixtures carrying inline// error[TK…]: substringmarkers, diffed bytests/conformance.rs. Marker conventions and type-display rules: tests/cases/README.md; thetscdivergence + deferred-check ledger: docs/reference/divergences.md. - Official TypeScript suite harness —
tooling/official-suite/runs typokat against the real microsoft/TypeScript conformance baselines as a triage dashboard plus a committed regression scoreboard (run --checkexits 1 on any regression). It is a black-box harness (shells out to the prebuilt binary), independent of the checker build. Details: tooling/official-suite/README.md.
Project docs live in docs/ and follow a fixed structure — start at
docs/CLAUDE.md (the operating manual) and
docs/INDEX.md (the map). In short:
docs/reference/— how the system works now (architecture · dev-method · the binding invariants).docs/decisions/— ADRs (the why), immutable.docs/backlog/— decided work not yet scheduled (the roadmap) ·docs/sprints/— active work-plans ·docs/archive/— shipped.docs/ideas/— proposals, no commitment.
Path is the status (no status: fields); when you finish or supersede something,
move/delete it per docs/CLAUDE.md. (tests/cases/README.md and
tooling/official-suite/README.md stay with the code — they're test-tooling docs, not docs/.)