A clean-room, pure-Rust workspace for parametric plans — ordered nodes with property trees, keyframes on an exact integer timebase, evaluated into a flat plan you can reason about.
It is also a scaffold. Every part of it is meant to be read, copied and re-purposed: the layering, the command registry, the panic boundaries, the gates, the agent's way in. Nothing here is a product to be defended; it is a set of decisions that are known to work, written down so they can be reused.
cargo xtask doctor # can this machine build it?
cargo xtask ci # is it green? seconds warm, minutes cold
cargo run --release -p craftkit -- --sampleOver 500 tests (cargo test --workspace). No image, font or audio file
anywhere in the tree.
A plan is an ordered list of nodes. A node has a kind, a property tree, and keyframes on any property:
| Kind | What it does |
|---|---|
Source |
introduces a value |
Filter |
transforms the value flowing through it |
Sink |
consumes the value |
Cue |
fires at a tick without carrying a value |
Comment |
says something to the person editing |
Evaluation walks the chain under craftkit_kernel::guard and produces a Plan
— a flat, comparable result, plus diagnostics. Time is an integer: Tick counts
254 016 000 000 ticks per second, so a frame boundary is exact rather than a
rounded float. Not "16.0 seconds"; 403_200_000 ticks, always the same
number twice.
crates/foundation L0 the generic half: readers, values, file plumbing
crates/core L1 craftkit's own core: ticks and ids
crates/kernel L2 the numeric kernel, behind the panic guard
crates/model L2 the document: nodes, properties, keyframes
crates/eval L3 document -> plan
crates/io L3 the versioned .craftkit format
crates/engine L4 the command registry and the Session
crates/control L5 the loopback JSON-lines channel
crates/mcp L5 the MCP server
crates/ui L6 the egui shell, icons, automation names
apps/* the desktop window, the CLI, the browser build
xtask the gates
A crate may depend only on strictly lower layers, and nothing below L6 may
name a UI toolkit. cargo xtask layers checks both against every manifest, so
the boundary cannot erode one convenience import at a time.
Everything is a command. There is exactly one registry —
crates/engine/src/commands.rs — and the toolbar, the scripts, the CLI, the
control channel and the MCP server all reach it. A panel never writes to the
document; it calls session.execute. That is what makes the thing an agent can
drive and a person can click the same surface, so a bug found by one is a bug
in both.
Never index a parameter. Every value that arrives from outside — a command
parameter, a .craftkit file, a line on the control channel — is read through a
typed, range-checked helper in crates/foundation/src/params.rs. An index that is out
of bounds is a panic, and this is a program whose whole promise is not crashing.
Clone before edit, then one trust boundary. Session::execute clones the
document, runs the command under catch_unwind, and on a panic restores the
snapshot and reports an error. A command that panicked is a command that did not
happen. Below that, craftkit_kernel::guard does the same for code craftkit
does not own.
No assets, ever. Every icon is vector paths in crates/ui/src/icons.rs,
drawn in code from original coordinates. There is no sprite sheet, no icon font,
no SVG. cargo xtask assets fails the build if anything embeds a non-source
file, and ATTRIBUTION.md is the ledger.
cargo run --release -p craftkit -- --sample
cargo run --release -p craftkit -- --sample --control 4711 # and drive itcargo run -q -p craftkit-cli --bin craftkit-cli -- commands # the catalogue
cargo run -q -p craftkit-cli --bin craftkit-cli -- run script.json # a script of commands
cargo run -q -p craftkit-cli --bin craftkit-cli -- eval plan.craftkit # what it evaluates to
cargo run -q -p craftkit-cli --bin craftkit-cli -- snapshot script.json --out shot.png
cargo run -q -p craftkit-cli --bin craftkit-cli -- crashes list # crash reportssnapshot rasterises in software: it walks the shape list, samples the font
atlas for text, and writes a PNG. Hard edges, centroid gradients, only the font
atlas uploaded — good enough to look at a frame from a build machine, not a
renderer.
cargo run -q -p craftkit-cli --bin craftkit-cli -- mcp # MCP over stdioOne tool per command, named craftkit_<id>, with the schema generated from the
command's own parameter documentation — so the tool an agent sees cannot drift
from the command a person clicks.
cargo xtask wasm
wasm-bindgen --target web --out-dir apps/craftkit-web/dist \
target/wasm32-unknown-unknown/release/craftkit-web.wasm
# serve apps/craftkit-web/ over HTTP — file:// will not doSame engine, same commands, same shell. The plan is stashed in localStorage
as ordinary .craftkit text, so it moves between a browser and a desktop
unchanged. Saving is explicit: nothing is written without the button. The
panel says whether this browser holds a plan, and a save the browser refused
is drawn in the warning colour.
craftkit-web --selftest draws frames headlessly on a machine with no browser
at all, which is how the web build stays testable in CI.
cargo xtask doctor # blocking / degraded / info about this machine
cargo xtask ci # fmt, anchors, assets, cleanroom, lists, layers, web, clippy, tests, fuzz, wasm
cargo xtask deny # the dependency policy: licences, bans, sources, advisories| Gate | What it protects |
|---|---|
fmt |
one formatting, so diffs are about the change |
anchors |
the mutation script's anchors are in the tree, and each names one place |
assets |
no embedded images; every file attributed; no stale attribution rows |
cleanroom |
no product's name in the tree outside prose (docs/clean-room.md) |
lists |
one deny attribute everywhere; the layer table matches the code and cargo metadata; sorted lists |
layers |
strictly-lower dependencies; no UI toolkit below L6 |
web |
the canvas id, the bundle path, and the service worker's safety rules |
clippy |
-D clippy::all -D warnings, every target |
tests |
every crate, every app, and the gates themselves |
fuzz |
the two readers survive broken files: a design and a script, no crash and no hang |
wasm |
every library crate still builds for the browser |
A gate that cannot run says skipped, with a reason, in the middle of the output. A skip is never printed as a pass.
scripts/mutation-test-gates.sh breaks each rule on purpose and checks that the
gate notices. A gate that cannot fail is worse than no gate, because it teaches
you to trust a check that has never checked anything — and this script found
three that had been quietly unable to fail. What it still cannot catch is in
KNOWN-GAPS.md, which is the first file to read when something
looks missing.
- Copy the tree.
- Rename
craftkitto your project (craftkit-*crates,craftkit/Nformat tag,CRAFTKIT_*environment variables, the control-channel folder). - Replace the crates that carry craftkit's domain with yours:
crates/model,crates/eval,crates/io's format,crates/mcp's vocabulary,crates/ui, andcrates/engine's command set and session. Those are the crates that depend oncraftkit-model, and the gatethe_scaffolding_section_names_every_crate_the_domain_toucheschecks that this list keeps naming all of them. - Keep
crates/foundation— the generic half: the bounded readers, the typed values, the parameter reader, the atomic writer, the temporary folder and the undo stack — andcrates/core's time base. Keepxtask,skills/,docs/recipes/, the agent doors and the crash reporter's shape. Keep the layer names, the error discipline and the deny attribute. - Delete what you do not need — but read
AGENTS.mdfirst, because most of it is about the mistakes you have not made yet.
Start with AGENTS.md for the rules, docs/architecture.md for the shape,
docs/agent-playbook.md for driving it, and docs/recipes/ for the task in
front of you.
MIT OR Apache-2.0. See ATTRIBUTION.md for the full ledger: there is no vendored third-party code, and no file asset of any kind.