Tooling in practice
Goal: get to know the
mkCLI day to day, the runtime tools (observer/profiler/ debugger), and how the package manager resolves versions.
The compiler is a query engine
Section titled “The compiler is a query engine”The foundational decision: the compiler is not a batch that parses, checks, and dies. It is an
incremental library of queries (the rust-analyzer model). Almost every tool is a consumer of the understanding
the compiler has of the code (the LSP asks “what is the type of X?”, the linter requests semantic info, the
doc gen the signatures). That is why there is a single binary, no separate gopls/zls. The
incrementality is per declaration: edit one function, re-check that declaration and what depends
on it.
The mk CLI
Section titled “The mk CLI”Everything in one binary (mk is an alias for makoto):
mk run app.mko // compile and runmk build // compile the project → binarymk test // run the tests (@test) and benchmarks (@bench)mk fmt // zero-config formatter (one canonical format; no bikeshedding)mk lint // closed linter (fixed rules; ends the format war)mk get <repo> // adds a depmk tidy // syncs deps/lockfilemk doc // generates docs (from /// and from the .mkoui)mk new / mk init // new project / initialize in the directorymk update / mk versionTwo compiler modes: portable (one file on the PATH, default) and installed (mk install host
explodes into N versionable stages, for partial updates, like swapping only the TS parser instead of
everything).
Tests and benchmarks
Section titled “Tests and benchmarks”Tests live next to the code (@test) or in file_test.mko/tests/. The execution matrix
@testmode(order, state) covers (sequential|parallel) × (isolated|shared); the default
(sequential, isolated) is the safe one. Open the matrix to hunt a race:
@testfn parse_aceita_valido() { r := parse("42") test.expect_eq(test.expect_ok(r), 42)}Benchmarks are @bench; the runner is configurable (effort), but the comparator is fixed (the statistic
that decides regression-vs-noise, anti-p-hacking).
Runtime tools
Section titled “Runtime tools”They lean on the runtime’s observability (pull, default-off; you don’t pay for what you don’t turn on):
- observer: the frontend of
runtime.processes()/trace(), with the supervision tree, live processes, channels, and the|e|of deaths (BEAM’s:observer). - profiler: opt-in sampling/tracing, with structured textual output (an LLM reads the profile as text and reasons about it) plus a flamegraph.
- step-debugger: built for the actor model, with
process-onlybreakpoints (pauses only that one) oreverything, and linked breakpoints (ABC trigger together) to catch the interaction between processes at the exact moment.
ev := runtime.trace(.Named("worker"), [.send, .receive], sink: .channel, sample: 100)loop e in ev { match e { Send(m) => ...; Receive(m) => ... } }Package manager
Section titled “Package manager”Version resolution by MVS (Minimal Version Selection) plus the mk.sum lockfile (auto-generated
checksums, reproducible build). The registry is hybrid: official libs by name (first-party),
third parties by URL (Go style). The registry’s wire format is the language’s native serialization.
Codegen has two homes: compiler extensions (§20, in-process, like the UI) for what touches the
pipeline, and the task runner for external tools (a protoc, a minifier), declared with
their capabilities in the mk.project. No REPL (an explicit decision).
You have reached the end of the tour. From here, the canonical docs (language-design.md, language-grammar.md,
official_libraries.md, extensions.md, tooling.md) are the complete reference; this book was the
front door. Good Makoto.