Craft Anything is a native, pure-Rust app that shows anyone how a piece of software works (programs, apps, files,
websites, phones) with evidence-backed answers, then helps them rebuild it. It ships a desktop app (egui), a CLI and
an MCP server, all driven by one engine. Display name Craft Anything; machine names craft-anything-*; bundle id
ai.storyteller.craft-anything. Standards shared across the Crafting Apps live in ../craftrules (read its
AGENTS.md); follow them. Repos never share code.
Original work only: never copy code, scripts, schemas, names, assets or wording from another project, and never name other reverse-engineering products in source, docs, UI or commit messages.
crates/
evidence L0 Evidence records, canonical ids, confidence, unknowns, ToolError (the error contract)
formats L0 Standalone parsers (no workspace deps, no I/O): ELF/PE/Mach-O/WASM/DEX/APK/ASAR/plist/.NET/Go/…
disasm L1 Our own native engine (Clean room): decode, functions, CFG, xrefs, call graph, pseudo-C
js L1 JavaScript/Electron/web bundles: parse, module graph, IPC, source maps, unbundling
analysis L2 Analyzers producing evidence (triage, dossiers, trace, compare, app graph, rebuild ledger)
platform L2 Native services: owned processes, PTY capture, sandbox, engine store, atomic files
engines L3 Optional external engines: Ghidra bridge (Deep mode), adb, CDP, Frida, JADX, Apktool…
engine L4 Session, projects (.cany), workspaces (what the person is making), the inbox
(questions/approvals), ToolSpec registry, settings, consent, the tool list
ai L5 AI backends (Anthropic, OpenAI, OpenRouter, local, agent CLIs, SSH) + agent loop + crews,
and the rebuild workflow (study → build → check → refine)
mcp L5 MCP server: progressive toolsets, resources, prompts
setup L5 Connect to coding-agent apps; doctor; uninstall
ui-egui L6 The egui interface (Beginner + Experienced). The only crate that may use egui/eframe/winit/rfd.
testkit Test helpers and synthetic sample builders
apps/ craft-anything (desktop) · craft-anything-cli (CLI + `mcp`) · craft-anything-web (browser viewer)
xtask/ cargo xtask layers | tools [--check] | wasm | secrets [--history] | ci | version [set X.Y.Z]
Layering is enforced by cargo xtask layers. Register new crates in xtask/src/main.rs.
- Never crash (
../craftrules/standards/never-crash.md). Hostile input is our main input. Nounwrap/expect/panic!/todo!/unimplemented!/unreachable!/indexing on input-derived values in non-test code; every production crate root carries the deny attribute. Checked arithmetic, bounded recursion, capped allocations. Every crash fix gets a regression test. - Everything is a tool. User-visible behaviour = a
ToolSpecincrates/engine/src/tools/<area>.rswith id, title, summary, beginner text, toolset, effects, requires, a schemars params struct, andrun. Register the module with one line intools/mod.rs. UI, CLI, MCP and the AI agent all dispatch by id. Programmatic calls never open dialogs. - Evidence is the result. Analysis tools return
ToolOutput::Evidencebuilt withEvidenceBuilder(honest confidence, limitations, coverage, locations). Large results: summary first, paged views (offset/limit). - Clean room vs Deep.
session.clean_room()is true unless Deep mode is on (deep_mode, or--deepfor one CLI run). Clean room never looks inside the original: only the tools inengine/src/clean_room.rsrun, so a new tool is Deep-only until it's listed there, and it may be listed only if it touches no target or watches from outside, as a visitor or user would. Deep mode uses every tool; built-in engines must work fully without Ghidra, which only adds. Evidence records the engine and the flag. - Effects are honest. Declare every side effect.
EXECUTES_FOREIGN_CODEandMUTATES_DEVICEtrigger consent. - Never break wasm for L0–L2 (
cargo xtask wasm). - Thin UI. UI state is serde data; colours/radii come from theme tokens.
- No secrets, no personal data in requests, logs, tests or the repo (it is public): no API keys, tokens,
private keys,
.envfiles or local paths, only obvious fakes likesk-test.cargo xtask secretschecks what git would commit (--historychecks every commit); enable the hook once per clone withgit config core.hooksPath .githooks. GenericCraftAnything-devUser-Agent. - Safe Rust only.
unsafe_code = "forbid"everywhere, no exceptions. Prefer pure-Rust crates; any dependency must be safe to use from safe code. - Never crash, never lock up, around other programs. Every external program (Ghidra, Java, adb, a browser,
an agent CLI, ssh, git…) runs through
craft_anything_platform::processonly, neverstd::process::Commanddirectly:- stdin is closed (or a pipe we write to), never inherited, so nothing can sit waiting at a prompt; ssh gets
BatchMode=yes; tools get their non-interactive flags; - every run has a deadline (wall clock and idle), output is capped and drained on threads so pipes never fill, and the child (and its process group / Job Object) is killed on timeout, cancel or drop;
- a missing program, non-zero exit, bad output or timeout is a
ToolErrorwith a plain-English remediation, never a panic or a hang; - nothing slow ever runs on the UI thread: the UI sends work to background threads and polls results.
- stdin is closed (or a pipe we write to), never inherited, so nothing can sit waiting at a prompt; ssh gets
- Never lock up on a lock. Use
craft_anything_platform::sync::Mutex, neverstd::sync::Mutex(clippy refuses it). It stops a real deadlock as soon as it forms, the waiter's work unwinding to itscatch_unwindas an error; the watchdog reports anything held too long. Long jobs are fine: label the guard (guard.label(id), asEngine::calldoes for tools) and they're waited for, not treated as stuck. A guard that isn't bound to a name lives until the statement ends, so read the session once:cargo xtask locksrefuses a second.session()in reach of the first.
These apply to every change by every agent and contributor, whether or not anyone mentions them.
docs/ROADMAP.mdis the plan: each item has an id (R<milestone>.<n>), an owner, a status and pending changes (- [ ]lines). Owners are roles, never people's names: maintainers (the project's maintainers steer it; agents may take its unclaimed pending changes, but anything marked decision waits for a maintainer) or open (anyone may take it).- Claim before you start: put
— claimed: <name>, branch <branch>on the pending change and push your branch early (or open a draft PR) so others see it. Never take a change someone else has claimed. - Work in the order the roadmap's "Order and dependencies" table gives; an item starts only when its dependencies are done.
- A person's explicit instruction for a task overrides these rules for that task only; otherwise they apply to every change, without being asked.
- Pick work from
docs/ROADMAP.mdonly. Don't start an item that is claimed,blocked, or waiting on a decision, and don't start one whose dependencies aren't done. - Claim the pending change first (name + branch, in its own small commit), so no two agents do the same work.
- Read the code you'll touch and check STATUS.md and the roadmap for gaps it depends on. If you find new gaps or dependencies, add them to the roadmap rather than widening your change.
- Never commit to
main. Branch from an up-to-datemainwith a standard prefix, then the roadmap id if there is one:feature/,bugfix/,hotfix/(urgent fix to released code),refactor/,docs/,test/orchore/(tooling, deps, CI). Example:feature/r1.1-target-kind. - One roadmap item (or pending change) per PR. Keep PRs small and reviewable. No unrelated refactors or drive-by formatting.
- Open a PR into
main. It is squash-merged, so the PR title is the commit message: plain English, present tense, what changed for the person or developer. - Rebase on
mainbefore asking for review. Never force-push someone else's branch. - Parallel agents work in their own git worktree and their own
CARGO_TARGET_DIR(see "Parallel agents" below). - Before every commit and push, check
git branch --show-current; push by name (git push origin <branch>).
- Headless first. Every capability works without the GUI: engine → tools → CLI/MCP, with the GUI as one
thin client. Orchestration, persistence and process control never live in
ui-eguiorapps/*. If the UI needs something, it calls the same engine API (or tool) the CLI does. - Extend through seams. New target kinds, checks, builders, asset providers and engines are trait
implementations registered in one place. Don't add another arm to a
matchover a closed enum. Prefer traits and generics with sensible built-in implementations, so forks can swap parts without editing core code. - Data over code. Catalogues (stacks, engine recipes, model ids, prices, install pins) are typed tables, not string matches or prompt text.
- Structured results. Decide on typed fields, never on English text.
- Versioned files. Anything we persist has a version field, a schema and a migration path.
- Respect the layers (
cargo xtask layers); put code at the lowest layer that can own it.
- The checks in "Before you finish" pass for the crates you touched. New behaviour has tests; crash fixes have regression tests.
- Docs updated in the same PR: the roadmap item ticked, STATUS.md if what works changed,
docs/tools.mdif you added tools. - Report what you verified and what you didn't. Never claim a live run you didn't do.
- Made a mistake worth not repeating? Add it to
docs/pitfalls.mdin the same PR. - Found work that's missing or left for later? Add it as a
- [ ]line under its roadmap item in the same PR (cargo xtask pendinglists all open work).
docs/extending.md says where each kind of addition goes (tools, target kinds, checks, stacks, coding agents,
asset providers, engines, plugins).
docs/pitfalls.md lists mistakes already made here (git, tests, builds, design) and the rule for each.
It is part of these rules: read it before your first change (Claude Code loads it automatically through
CLAUDE.md).
cargo fmt --all
cargo test -p <crates you touched>
cargo clippy -p <crates> --all-targets -- -D warnings
cargo xtask layers
cargo xtask locks
cargo xtask secrets # no keys, tokens or local paths in what you commit
cargo xtask wasm # if you touched L0–L2
cargo xtask tools # if you added tools (commit docs/tools.md)Then append a terse entry to log/devlog.md (what landed, numbers, what's still open). log/ is gitignored:
the devlog is a local scratch record; anything others need goes in the PR, docs/STATUS.md or docs/ROADMAP.md.
Own target dir (CARGO_TARGET_DIR=target/agent-<name>), own files. Shared files (engine/src/tools/mod.rs,
engine/src/lib.rs, workspace Cargo.toml) get small surgical edits; re-read right before editing. Write manifests
atomically (temp file outside crates/, then mv). If someone else's edit breaks the build, wait and retry.