# Roadmap The shared plan for reaching the product described in the README, for people and agents alike. `docs/STATUS.md` says what works today; this file says what comes next, who owns it and what an agent may pick up. Drafted 2026-10-18 from a survey of `main` at `1d47a3e` (624 tests passing); brought up to date on 2026-10-22. ## How to use this file - Each item has an **id** (`R.`), an **owner**, a **status** and a list of **pending changes**. Pending changes are small, independently mergeable units. - **Claiming:** before you start, put `— claimed: , branch ` on the pending change and push your branch early (or open a draft PR) so others see it. Don't take a change someone else has claimed. The full working rules are in `AGENTS.md` ("Working rules", mandatory). Items owned by **maintainers** are steered by the project maintainers: agents may take their *unclaimed pending changes*, but design decisions marked **decision** wait for a maintainer. - **Finishing:** tick the change (`[x]`), add the commit or PR, update `docs/STATUS.md` if what works changed, and add a line to `log/devlog.md`. - **Status:** `todo` · `in progress` · `blocked ()` · `done`. - Follow `AGENTS.md` (layering, never crash, everything is a tool, `platform::process` for every external program) and the architecture rules below. ## Architecture rules for new work The project should be easy to fork and bend. New features land behind a seam, not inside a `match`: 1. **Traits plus a registry, not enums.** Target kinds, checks, builders, asset providers and external engines are traits with a registry that holds built-in implementations. Adding one means adding a file and one registration line (as tools already work). 2. **Data over code for catalogues.** Stacks, game-engine build recipes, model ids and prices, and engine install pins live in typed tables (or TOML files embedded with `include_str!`), not in string matches or prompt text. 3. **Headless first; flows are tools.** Everything works without the GUI. Anything a person can start from the UI (rebuild, follow-ups, answering questions, engine installs, launching a result) is reachable through `Engine::call` and the CLI, so MCP, agents, scripts and other frontends get it for free. Orchestration, persistence and process control never live in `ui-egui` or `apps/*`. 4. **Versioned files.** Every file we write and read back (`craft.json`, `config.toml`, `project.toml`) carries a version, has a JSON schema, and has a migration path. 5. **Structured results.** Decisions such as "did the build pass" read typed fields, never English text prefixes. ## Milestones at a glance | Milestone | Goal | Owner | Where it stands (2026-10-22) | |---|---|---|---| | M0 Foundations | Docs match the code, licences checked, rule violations fixed, one live end-to-end run | maintainers | Done; protecting `main` is a maintainer setting still to switch on | | M1 Extensibility | Seams for target kinds, checks, builders, asset providers, engines, plugins | maintainers | Done except runtime and behaviour checks (with R2.4, R2.5) and engine transports (R1.5) | | M2 Product completeness | Every target kind gets study → build → check → refine with a real check; building works without an agent app | maintainers | Websites complete; apps and games build and run; API-key builds and other agents are built but not yet run live | | M3 Engine depth | More tools, and the known partial ones | open | 284 tools; device-backed tools wait for an Android device; game asset export continues | | M4 Release | Packaging, CI, signing, managed tier | maintainers (later) | CI passes; release workflows for every platform are on `main`; v0.1.0 is released, signed, on every platform | ## Open work Every unticked `- [ ]` line in this file is open work, under the item it belongs to. `cargo xtask pending` lists them all, grouped by item (`--count` for the totals). When you find work that's missing, add it as a `- [ ]` line under the right item in the same PR, rather than leaving it in a PR description or a chat. ## Order and dependencies Work in phases. Items in the same phase can run in parallel. An item starts only when everything in its **Depends on** column is done; a *soft* dependency should come first but doesn't block. M3 is an open lane that can run at any time. | Phase | Item | Depends on | Why here | |---|---|---|---| | 0 Baseline | R0.1 Docs match `main` | — | Every agent reads them | | 0 | R0.7 Live end-to-end run | — (needs a signed-in `claude`) | A working reference before refactors | | 0 | R0.3 Licence hygiene | — | Cheap; parallel | | 0 | R0.4 Rule-10 fixes | — | Clears direct process calls before R1.6 moves them | | 0 | R0.5 Small correctness items | — | Parallel | | 0 | R0.2 Salvage PR #1 | **decision** | Unblocks M4 later | | 0 | R0.8 Tests never touch the real machine | — | Found when a test started a real download | | 0 | R0.6 Tool naming | **decision** | Fixes ids before new tools (R1.6) and plugins (R1.8) | | 1 Seams | R1.1 `TargetKind`, split `rebuild.rs` | R0.7 *(soft)* | Most scattered `match` code; everything in M2 builds on it | | 1 | R1.2 `Check` trait | R1.1 | Checks hang off the target kind | | 1 | R1.3 Builder adapter registry | — | Independent of R1.1; parallel | | 1 | R1.4 Asset provider trait | — | Parallel | | 1 | R1.5 External engine trait in L3 | — | Engine installs in R1.6 sit on it | | 1 | R1.9 Versioned files | — | The project service in R1.6 writes new files | | 2 Headless | R1.6 GUI stops being the glue | R1.1, R1.5, R1.9, R0.4, R0.6 | Everything after it is reachable without the GUI | | 3 Product | R2.1 Building with API keys | R1.3, R1.6 | Removes the agent-app requirement | | 3 | R2.2 More coding agents | R1.3 | | | 3 | R2.3 Website coverage, better metric | R1.2 | | | 3 | R2.6 Several rebuilds at once | R1.6 | | | 3 | R2.7 The request decides how to build | — | A request that names a stack was ignored | | 3 | R2.8 Clean room means clean room | — | Clean room still read the original's code and files | | 3 | R2.9 Everyday use: typing, projects, settings | — | Found by live use of the app | | 3 | R2.10 Pinned builds | — | Keep a build to open while the project changes | | 4 Depth | R2.4 Runtime study for apps and games | R1.2, R1.6 | | | 4 | R2.5 Behaviour checks for non-web builds | R1.2 | | | 4 | R1.7 Dynamic tool registry | R1.6 | | | 4 | R1.8 Plugins | R1.7, R0.6 | Freezes the extension API, so it comes last | | Any | R3.1–R3.10 Engine depth, game archives and assets, app facts | — | Open lane | | Last | M4 Release | R0.2, R0.3 and phase 3 | | --- ## M0 Foundations ### R0.1 Bring the docs up to date with `main` Owner: maintainers · Status: done (branch `docs/r0.1-status-refresh`) STATUS.md is dated 2026-10-16 (branch `feat/simple-mode-mvp`). It doesn't cover games, engine detection and builds, fal.ai assets, Deep/Auto mode or the long-run limits. - [x] Refresh STATUS.md: games, Deep mode with Ghidra, Auto mode, asset budget, current limits - [x] ATTRIBUTION.md lists the third-party material the repository ships - [x] AGENTS.md: `log/` is gitignored; say the devlog is local, or track it - [x] Regenerate the tool list (asset tools); README tool count (228); claude-code.md rounds ### R0.2 Salvage PR #1 (`ci-release`) Owner: maintainers · Status: done (branch `chore/r0.2-release-pipeline`, supersedes #1) The PR branches from the initial commit, about 35 commits behind `main`. CI isn't a priority, but its packaging, `cargo xtask version` and web viewer are worth keeping. - [x] **decision**: rebase completely onto `main` (all six commits kept, `ci-release` untouched) - [x] Port `apps/craft-anything-web` (wasm viewer) and `cargo xtask version` - [x] Port the platform fixes: capture is Linux and macOS only (FreeBSD loses output); the spawn lock was reverted inside the PR itself - [x] Keep workflows parked until M4: manual-only (`workflow_dispatch`); `release.yml` unchanged ### R0.3 Licence hygiene Owner: open · Status: done (branch `chore/r0.3-licences`) No `deny.toml` exists. The survey found no GPL, AGPL or SSPL crates, and only MPL-2.0 `option-ext` (via `dirs`) in the shipped app. - [x] Add `deny.toml` (allow MIT, Apache-2.0, BSD-2/3, ISC, Zlib, 0BSD, Unicode-3.0, BSL-1.0, CDLA-Permissive-2.0, OFL-1.1, MPL-2.0 as an explicit exception), and `cargo xtask licenses` - [x] Generate a third-party licence bundle (`cargo about`) for releases (`about.toml`; first run at M4) - [x] NOTICE/ATTRIBUTION: egui's embedded default fonts (OFL-1.1, Ubuntu Font Licence) - [x] Policy in this file: external programs we *run* (Ghidra, rizin, tshark, Frida, JADX) may have any licence. We never link or bundle copyleft code. (Lives in `docs/licences.md`.) - [x] Follow-ups from the first `cargo deny check`: rustls 0.23.46; ruzstd 0.9.1 (a tiny `.zst` could exhaust memory); `publish = false` in every workspace crate; `cargo deny check` passes (advisories, bans, licences, sources) - [ ] Revisit the two advisory exceptions in `deny.toml`: `rsa` (no fix; we only verify with public keys) when rsa 0.10 lands, and unmaintained `zbus_names`/`zvariant` (Linux-only via keyring and accesskit) when those move on - [x] `crates/engines/src/game.rs` (installer, PowerShell signature check: no deadline) - [x] `crates/ui-egui/src/project.rs`, `crates/ui-egui/src/engine_ui.rs` - [x] Add a check to `cargo xtask layers` that fails on `std::process::Command` outside `platform` ### R0.5 Small correctness items from the survey Owner: open · Status: done (branch chore/r0.5-cleanups) - [x] Checked arithmetic on file offsets in `elf.rs`: `va_to_off` adds a segment's file offset to an address delta unchecked, so a `PT_LOAD` at offset `u64::MAX` overflows, found by a lint scan of `crates/formats` (#43) - [x] Capped allocation in `unreal_iostore.rs`: a `.utoc` header naming 2^31 compression methods of length 0 collected empty names until memory ran out (6 GiB from a 559-byte file); only the 255 a block can point at are read, the rest skipped. Found by mutation fuzzing of `crates/formats` - [x] No endless loop on code at the top of the address space in `disasm/src/program.rs`: the prologue and PLT scans stepped past `u64::MAX`, which in release wrapped to 0 and walked the whole 64-bit space (`native_functions_list` on a 4 KB ELF whose `.text` is at `0xffffffffffffffc0` never returned, and Stop couldn't end it). Found by mutation fuzzing of `crates/disasm` - [x] The Agent console keeps its size: its history and prompt box came out 17 points taller than the bottom panel, which grew by that every frame until its maximum and then cut off the top of the Agent / Jobs / Log tabs (`chat::conversation`) - [x] `yaxpeax-arm` 0.5.0 panicked ("register number out of range") on an A32 `STRD`/`LDRD` with Rt = 15 (e.g. the word `0xc0c7fef3`), so one such word failed the whole analysis of a 32-bit ARM program; the word now decodes as invalid - [x] Unchecked `o + 4`/`o + 8` on section and program header offsets in `formats/src/dart.rs` (`Elf::parse`): a header table near `usize::MAX` panicked in debug builds (in release the wrapped reads were thrown away, since the entry's first field was already out of range); an entry that starts past the end of the file is now skipped - [x] `cargo xtask layers` also scans `apps/` - [x] Engines in `KNOWN` without a `ToolPaths` override (tshark, objdump, rizin, java) - [x] Missing `database` icon for SQLite in the UI (`kind_icon`) - [x] Deduplicate the five private `fn spec(` helpers in the tool modules - [x] rustfmt drift on main (11 files) - [x] Checked arithmetic on file offsets in `fsread/extfs.rs`: an inode offset near `usize::MAX` overflows `o + 128` (two sites), found by a lint scan of `crates/formats` (#42) - [x] `macho.rs` `method_list`: `list + 8 + i * entsize` adds to a pointer read from the file, so a segment that ends at `u64::MAX` overflowed it (found by a lint scan; now reproduced by a regression test, and the entry addresses are checked) ### R0.8 Tests never touch the real machine Owner: open · Status: done, except protecting `main` (a maintainer setting) `Session::default()` points at the real config folder and engine store, so a test can read, write or download into them. - [x] `Session::isolated(dir)` and `engine::testing::session()`: config, plugin caches, engine store and projects in a temp folder; `settings_set` and plugins follow `Session::config_dir()` - [x] Every test uses it (41 files); `cargo xtask layers` fails on `Session::default()` anywhere - [ ] Main maintainers: protect `main` (PRs required, squash only) ### R0.6 Tool naming Owner: maintainers · Status: done Craft Anything's own ids, grouped by what a person does (`native_functions_list`, `web_page_capture`…). Each tool answers to its id (and CLI spelling) only. - [x] Id scheme in `docs/tool-ids.md`: `__` (no dots: MCP and model APIs allow only letters, digits, `_` and `-` in tool names) - [x] A test keeps every id in the scheme - [x] **decision** (2026-10-20): rename properly, before launch - [x] Code decides on a tool's own id and declared effects, never its name as text (`Registry::canonical`, `Effects::CARRIES_TARGET_CONTENT`, id constants next to their tools) - [x] Renamed at the source: 227 tools get their own ids and a duplicate install tool is retired; text naming tools uses the ids; each tool's Rust function has its id as its name - [x] Earlier names retired before launch: tools, MCP prompts and their arguments answer only to their own names (2026-10-22) - [x] New tools (R1.6 onwards) use the new scheme from the start (`project_*`, `engines_*`, `settings_*`, `rebuild_*`, `build_*`, `plugins_*`) - [x] A second naming pass before launch (2026-10-22): 81 tool ids and two MCP prompts get plainer names (`adb_apps_list`, `native_function_study`, `web_walkthrough_record`…), tool inputs say `function` instead of `procedure`, the error codes are regrouped (`bad_input`, `not_found`, `engine_missing`, `over_limit`…; categories say what to do: `input`, `engine`, `missing`…), and the rebuild ledger uses its own words (strengths `unit-test` … `outside-check`, cases `works`, `refuses`, `bad-input`, `cancel`, `shutdown`) ### R0.7 One live end-to-end rebuild, kept as an opt-in test Owner: maintainers · Status: done (branch `test/r0.7-live-e2e`; runs on 2026-10-18, Windows 10) - [x] `craft-anything-cli rebuild https://example.com "Clone this website" --rounds 1` completes: Ready in 1.5 min, 96% alike (98% at 1440 px, 94% at 390 px) - [x] App target: a small desktop app → Rust + egui, Ready in 9 min (checked by "does it build" only) - [x] Game target: Muck → Rust reached Build (game written and compiled, the builder screenshotting its own result) and was stopped on purpose before Check; a polished game isn't the goal. It first failed at once because "in Rust" was ignored (fixed in #4) - [x] Opt-in test (`CRAFT_E2E=1`) that runs the website case (`apps/craft-anything-cli/tests/live_rebuild.rs`) --- ## M1 Extensibility ### R1.1 `TargetKind` trait; split `rebuild.rs` Owner: maintainers · Status: done Today `enum Kind {Website, Game, App}` is matched in about ten places across `ai/src/rebuild.rs` (1.5k lines) and the UI. - [x] **decision** (approved 2026-10-18): `TargetKind` (`id`, `words`, `detect` → score, `default_stack`, `named_stack`, `goal`, `brief_sections`, `checks`, `refine`…) and `Check` (`id`, `run(CheckCx)` → `CheckResult`) as trait objects in a `KindRegistry` - [x] Split `rebuild.rs` into `rebuild/{mod,kind,study,check,brief,locate,output,tests}.rs` with no change in behaviour (branch `refactor/r1.1-split-rebuild`) - [x] Website, App and Game become implementations (`rebuild/kinds/`); `RunOptions::kinds` holds the registry; briefs proven byte-identical to before across 162 combinations - [x] The UI reads labels from the trait (`CraftApp::kinds`); the `Kind` enum, `choose` and `kind_of` are gone - [x] Data table for stacks: `rebuild/stacks.toml` (steps, phrases that ask for a stack, engine), read by `rebuild::stacks`; game engines' own names stay in `engines::game::Builder` ### R1.2 `Check` (comparator) trait with structured results Owner: maintainers · Status: in progress (runtime-visual and behaviour checks: R2.4, R2.5) - [x] `CheckResult { check, passed, similarity, note, artifacts }` replaces `note.starts_with("Build succeeded")`; older `craft.json` checks are read compatibly - [x] Built-in checks: `BuildCheck` (per engine recipe) and `WebVisualCheck` (per width) - [x] Web routes (R2.3: every captured page is compared) - [ ] Later checks: runtime-visual and behaviour (R2.4, R2.5) - [x] Widths, thresholds and rounds come from settings (`[build]`: `good_enough`, `max_rounds`, `web_widths`, `max_pages`, `picture_good_enough`); a check may carry its own bar (`CheckResult::good_enough`) - [x] The builder tests its own work during its turn, for every kind: `rebuild_check` asks the running build (through `checks/asks/`) to run the same checks as round 0, not counted as a round; refusals for an in-process API builder, whose tools hold the engine - [x] What a check couldn't compare is kept (`CheckResult::unknowns`) and keeps it from being good enough: missing evidence never counts as alike - [x] Claims: a check lists what the build must do, each judged on its own (`CheckResult::claims`); one that fails keeps it from being good enough. Websites claim per page: no sideways scroll where the original has none, no new errors, every file loads, the original's landmarks; and that each of the original's menus and pop-ups opens (`page_claims`) - [x] A windowed app pictured by runtime study claims each of the original's interactive controls (buttons, menus, tabs, check boxes, fields, links; `screenshot::INTERACTIVE`) by label; text labels stay advisory, since they may hold content the builder replaces - [x] Apps and games make claims too: each self-test step of a game (the core loop's, an untested one failing, then the game's own) and each number from the original in the mechanics sheet being used in the code - [x] Console apps: with runtime study on, a console original (PE console subsystem) is run with each input in `runs::CASES` (no arguments, --help, --version) through `runtime_walkthrough_record` and kept in capture/runs/; `RunsCheck` runs the rebuild the same way and claims, per input, the same exit and the same output (80% of the words, the programs' own names set aside); the brief asks for a console program instead of a picture - [x] Console originals on Linux and macOS: an ELF or Mach-O executable that loads no windowing library (`runs::WINDOW_LIBRARIES`) is taken for one; on every platform, a program that sat silent and open through every input is treated as windowed after all and pictured instead (`ran_like_console`) - [x] The brief is the builder's own loop for every kind: study overview first, focused follow-ups with the tools, `app/UNKNOWNS.md`, build, `rebuild_check` until it would pass, then review the checklist and unknowns before replying - [x] The builder's open questions are unknown records (`core_unknown_add` / `core_unknown_edit` / `core_unknowns_show` in the project's investigation folder) instead of a markdown file; its own check lists the open ones and the finished run says how many are left - [x] The brief says which toolsets to turn on (`enable_toolset`: browser, javascript, native, managed, formats) and, for websites, how to study behaviour with the web debugging tools (event listeners, execution, scripted walkthroughs, exported scripts), never pasting the code - [x] A console original is rebuilt with the "Rust (console)" stack by default (`AppKind::default_stack` reads the file); live: a small console tool rebuilt end to end, the builder used `rebuild_check` and unknown records, and all 6 run claims held - [x] Live run where a coding agent uses `rebuild_check` mid-turn on a game: a request-only snake game in Rust passed build, picture, mechanics and all 5 self-test claims (2 steps of its own) - [x] A round's verdict is one line over all its checks and claims (`self_check::summary`), in the builder's own check and the finished run's status, not the last check's note ### R1.3 Builder adapter registry Owner: maintainers · Status: in progress (Codex resume needs a real install) - [x] `AgentAdapter` trait (`ai::cli_agents::adapters`): ids, args, required flags, stream parsing, resume, process setup, sign-in preflight; `AgentAdapters` registry in `CliSpec`/`RunOptions`; unknown agents get the plain adapter. The rebuild's Claude-only sign-in check now asks whichever adapter runs - [x] ACP evaluated: it's a long-lived, session-based JSON-RPC protocol, so it fits as another `Conversation` implementation (an `AcpConversation` beside `CliConversation`), not as a per-turn `AgentAdapter`. Claude Code stays native (unmodified binary, documented flags). Follow-up: R2.2 - [ ] Codex: resume support (`codex exec resume `), verified on a real install ### R1.4 Asset provider trait Owner: open · Status: done (branch `feature/r1.4-asset-providers`) - [x] `AssetProvider` (image, 3D, later audio) with a cost estimate; fal becomes one impl - [x] Model ids and prices in a table; prompts read prices from it (today `assets_note` repeats "$0.53") - [x] Default budget as a setting (each project stores its own, but the default is the constant `ASSET_BUDGET_USD`) - [x] Follow-ups from R1.4: Settings → Builds sets the default budget and the picture model; the app's "raise budget" adds the setting through `project_assets_budget`; the tally no longer shows "$-0.00"; the app's price text comes from the asset table - [x] Each asset provider its own key: `assets:` in the key store, `AssetProvider::key_env`, `AssetKeys` in the builder's tool server, `provider` on the `assets_key_*` tools - [x] Settings → AI shows a key field for each asset provider (`AssetProvider::keys_page` gives its "Get a key" link; the app keeps which providers have a key, by id) - [x] Check the FLUX schnell price in `assets.toml` against fal's current list: FLUX schnell $0.003/MP, FLUX dev $0.025/MP, Tripo P1 $0.40 / $0.50 textured all match (2026-10-19) - [x] Game assets are the person's choice: a per-project opt-in (`generate_assets` in craft.json, the switch on Make and on the project, `project_assets_allow`, `rebuild --generate-assets` and `rebuild_start`'s `generate_assets` for the CLI and MCP) that the brief and the asset tools need along with a key; turning it on without a fal.ai key asks for one; Nano Banana Pro (`fal-ai/nano-banana-pro`, $0.15 a picture, checked 2026-10-20) is the default picture model, and the table prices per picture or per megapixel - [x] A run no longer overwrites what the person changed while it builds: the fields they own (asset budget, generated assets, always-allowed actions) change only through `Workspace::set_person`, and every other save keeps them as they are on disk. The app's asset switch works during a build again - [x] A resumed builder hears the asset choice as it is now: `TargetKind::follow_up_sections`; a game's follow-up carries the asset note (budget, spent) when assets are on with a key, or says they're now off when some were made before - [ ] Run Nano Banana Pro once with a real key (a picture, then a 3D model from it) and check the picture's size and the invoice ($0.15) - [ ] fal bills pictures "by rounding up to the nearest megapixel"; estimates use the exact size. Confirm on a real invoice whether a 1024×1024 picture bills as 1 or 2 megapixels, then round in the table (needs a key) ### R1.5 External engine trait in L3 Owner: open · Status: in progress (branch `feature/r1.5-engine-table`: table and pins done; transports pending) - [x] One engine table drives `KNOWN`, `ToolPaths`, install pins and the Engines settings page (`engines::ENGINES` in `engines/src/table.rs`; a test keeps `tool_paths!` identical to it) - [x] Install pins: SHA-256 for every download; tar.gz/tar.xz (TODOs in `install.rs`) (adb r37.0.1 on all three desktops, JADX 1.5.6, Ghidra and its Windows Java; tars unpack in memory up to 512 MB) Pending changes: - [x] Move the CDP client from `engine/src/tools` (L4) to `engines` (L3) (`engines::cdp`; `CancelToken` now lives in `platform::cancel`, re-exported from the engine) - [x] Move the GDB and JADX transports from `engine/src/tools` (L4) to `engines` (L3) (`engines::gdb`: the MI session; `engines::jadx`: the single-class run) - [x] Single-file pins (`ArchiveFormat::File { name }`, found by `locate` under that name in the engine store); Vineflower 1.12.0 is pinned with it (Maven Central's SHA-256, the same as the GitHub asset digest), and Apktool 3.0.3 (GitHub asset digest); a located `.jar` runs with the configured Java (`external_disasm_tools::program_for`) - [x] Temurin Java 21 pinned for Linux and macOS (x64 and arm64; Adoptium's `.sha256.txt`, the same as the GitHub digests); macOS archives are stripped to `Contents/Home`, so Ghidra's setup (`ghidra::get::can_install`) now works there too - [ ] Ghidra setup tried for real on Linux and macOS (the Java pins were checked by tests only) - [x] Rizin 0.9.1 pinned for Windows x64 (the static zip; GitHub asset digest), installed for real by the opt-in `a_pinned_engine_installs_for_real` (`CRAFT_ANYTHING_TEST_INSTALL`) - [ ] More pins once their hashes are checked against the publisher: Chromium (no published checksums found yet), Rizin for Linux (the tar.xz unpacks to ~800 MB, so it needs streaming extraction) and macOS (a .pkg) ### R1.6 Headless first: the GUI stops being the glue Owner: maintainers · Status: done The rebuild run itself lives in `ai::rebuild` and the CLI uses it, but the GUI still owns parts of the product that a headless run doesn't get: - **Project feed:** `feed.json` is written and rebuilt only by `ui-egui/src/project.rs` (`load_feed`, `save_feed`, `history`), so a CLI run leaves no feed and the app can't show it. - **Game engine and Ghidra setup:** inventory, downloads and guided installs run from `ui-egui/src/engine_ui.rs` (calling `engines::game` and `engines::ghidra` directly). - **Launching and previewing results:** `project.rs` and `engine_ui.rs` start programs with `std::process::Command` and manage the preview server. - **Decisions in the view:** target kind and stack (`rebuild::choose`, `kind_of`) and asset readiness are worked out in `make.rs` and `project.rs`. - **CLI is missing:** list/show projects, answer the inbox, follow-ups on a finished project, engine installs, play/preview, settings get/set. Pending changes: - [x] The project feed moves to `ai::rebuild::feed` (versioned `feed.json`); `RebuildRun` writes it (events, the person's messages and notes, answers), so CLI runs keep it too; the app only shows it - [x] Projects as tools (`Projects` toolset): `project_list`, `project_show` (status, checks, waiting questions, paged conversation), `project_inbox_answer`; the feed's data and file live in `engine::feed`, the run's folding in `ai::rebuild::feed` - [x] Follow-ups and rebuild control as tools (`rebuild_start` with pictures, `rebuild_status`, `rebuild_note`, `rebuild_note_withdraw`, `rebuild_stop`), registered from the AI layer (R1.7); the CLI's `rebuild` and the tool share `rebuild::project::new_project` - [x] A note sent while Claude Code builds reaches it at its next step, not after the turn: the run leaves it in `inbox/notes/`, a PostToolUse hook (`--settings`, `craft-anything-cli builder-notes`) claims it and hands it to the model as context, and the run shows it as read; unclaimed notes still go in after the turn (`rebuild::notes`) - [ ] The same for other coding agents with tool hooks, and for the API-backend loop (between tool calls) - [x] Engines and settings as tools (`Setup` toolset): `engines_list` (external engines, Ghidra, game engines and readiness), `engines_install` (Ghidra, Godot, pinned engines, with progress; Unity/Unreal get their download page), `settings_show`, `settings_set` - [x] Find registered Linux Unreal installations and their bundled Clang toolchain; support an explicit `UE_ROOT` ([PR #191](https://github.com/storytold/craft-anything/pull/191)) — claimed: one-bit, branch `bugfix/r1.6-linux-unreal-detection` - [ ] Give Linux-specific Clang guidance in Unreal's guided installer when the bundled compiler is missing (the requirements summary is already platform-specific). - [x] The app's Godot, Unity/Unreal installer and Ghidra flows, and its play, preview and open-folder buttons, call the tools (`CraftApp::act`); inventory reads stay in the UI - [x] Launch, preview and open-folder as tools: `project_play` (needs consent: it runs the builder's code), `project_preview` (loopback; `wait` serves until cancelled), `project_open_folder`; what a build produced lives in `engine::outputs` - [x] Setup and doctor as tools (`setup_plan`, `setup_apply`, `setup_doctor`, `setup_uninstall`, registered from the setup crate; changing other apps' configs asks first) - [x] Tools for the fal.ai key (`assets_key_status`, `assets_key_set`, `assets_key_remove`, from the AI layer; the journal redacts secret-named fields) and a project's asset budget (`project_assets_budget`) - [x] CLI `rebuild --build-with ` (the app's "Build with"; `stacks::resolve`) - [x] Headless runs use the game's own engine: `GameKind::default_stack` detects it (Unreal, Godot) from the target, as the app's Make screen does - [x] CLI: `project-list`, `project-show`, `project-inbox-answer` (from the tools) - [x] CLI: `engines-list`, `engines-install`, `settings-show`, `settings-set` (from the tools) - [x] CLI: `project-play`, `project-preview --wait`, `project-open-folder` (from the tools) - [x] Headless test: every UI action maps to a tool id or CLI command (`ui-egui/tests/headless_parity.rs`: covered, pending with the roadmap reason, or UI-only; every `CraftApp` method must have a row; checked in CI later) - [x] `cargo xtask layers`: `ui-egui` may not use `std::fs` writes or `std::process` directly (`ui_has_no_side_effects`, with a baseline allowlist of today's offenders that can only shrink) - [x] Empty that allowlist: report export (`core_report_save`) and opening Unity Hub or the Epic Games Launcher (`engines_app_open`) go through tools; the UI now writes no files and starts no programs itself ### R1.7 Dynamic tool registry Owner: maintainers · Status: done `ToolSpec` is `&'static` with fn pointers and the registry is fixed at build time. - [x] Higher layers register their own (static) tools: `Engine::shared(session, extra)`, with an `EngineRef` in the session so those tools can reach the engine; the apps pass `craft_anything_ai::tools()` - [x] Tools defined at run time: `registry::RuntimeTool` (owned names, a JSON schema, a function that tells its tools apart by `Ctx::tool`), via `Engine::shared_with`; `Registry::input_schema` serves every client the right schema - [x] Toolsets are open: `Toolset::Custom(id)` (`registry::custom_toolset`), and `Registry::toolsets()`/`toolset(id)` replace `Toolset::ALL` in MCP, the CLI and the agent ### R1.8 Plugins Owner: maintainers · Status: done - [x] Phase 1: external MCP servers as toolsets: `[[plugins.mcp]]` in config.toml, `engines::mcp_client` (stdio JSON-RPC through `platform::process`), cached tool lists (`plugins_refresh`, `plugins_list`), servers started on first call, effects from the server's annotations, consent by default, results as outside-service evidence - [x] Settings → Plugins lists `[[plugins.mcp]]`, turns each on or off and reads its tools (`plugins_refresh`); adding one is still done in config.toml - [x] MCP servers over Streamable HTTP (`url`, `headers`; JSON or event-stream replies, session id); `mcp_client::Transport` for other transports - [x] Adding a plugin from Settings: `plugins_add` and `plugins_remove` (CLI and MCP too); a plugin added this way always asks before each call; the app re-reads the list after the tool runs - [x] **decision**: wasmtime with a WIT interface, behind the off-by-default `wasm-plugins` build feature (it's a large build) - [x] Phase 2, tools: `[[plugins.wasm]]` components exporting `craft-anything:plugin/tools` (`crates/engines/wit/plugin.wit`: `describe`, `call`, JSON strings). They import nothing (no files, network, environment); each call is a fresh instance with a fuel and 256 MiB memory limit, so their tools run without asking - [x] **decision**: plugin checks get the build as JSON (every file's path and size, plus the text of the extensions they ask for); asset providers stay out of plugins (they'd need the network) - [x] Phase 2, checks: `craft-anything:plugin/checks` (`describe` with kinds and extensions, `run` on the build JSON → passed, similarity, summary), run after a kind's own checks; a plugin that fails to run passes with a note instead of failing the build - [x] WASM plugins: Settings → Plugins lists, adds (a `.wasm` path), turns on or off and removes them, `plugins_add`/`plugins_remove`/`plugins_list` take both kinds (`wasm`), and `cargo xtask ci` lints and tests the engine with `--features wasm-plugins` - [x] WASM plugins: `examples/wasm-plugin-wordcount` (Rust, wit-bindgen 0.63, `wasm32-unknown-unknown`, `wasm-tools component new`), built and loaded through the host (opt-in test `CRAFT_ANYTHING_TEST_WASM_PLUGIN`) - [x] Plugin API versions: semver for `package craft-anything:plugin@…`; the host finds a plugin's interfaces by name and accepts any compatible version (same major, and same minor while 0.x), refusing others by name; patch versions only add optional JSON fields (docs/extending.md) - [x] A check plugin in Rust: `examples/wasm-plugin-readme-check` (does the build's README say how to run it), built and loaded for real (opt-in test `CRAFT_ANYTHING_TEST_WASM_CHECK`) - [x] `docs/extending.md`: how to add a tool, a target kind, a check, a builder or a plugin ### R1.9 Versioned files Owner: open · Status: done (branch `feature/r1.9-versioned-files`) - [x] `craft.json`: `version` field, JSON schema (schemars) published in `docs/schemas/` - [x] `config.toml`: version field and migration hook - [x] `.cany` `FORMAT_VERSION`: migration function stub and test --- ## M2 Product completeness ### R2.1 Building with API-key backends Owner: maintainers · Status: in progress (built; live runs need keys) - [x] File and command tools for our own agent (`Build` toolset): `build_files_list`, `build_file_read`, `build_file_write`, `build_file_edit`, `build_command_run`, in the session's workspace, through `platform::process` and the same approval policy as Claude Code builds (`interaction_tools::approve`) - [x] `RebuildRun` accepts Anthropic, OpenAI, OpenRouter and local backends: our agent loop with the Build toolset (`AiHub::start_builder`, up to 400 steps a message), Build tools' consent answered by the run, follow-ups continue the kept conversation (`transcript.json`); the builder is the named backend, else the Builder role, else an agent app, else any enabled AI backend - [ ] A live API-key build (needs a key on the test machine) - [x] Kimi K3 through OpenRouter (`moonshotai/kimi-k3`, offered by Detect with a pasted key, priced at OpenRouter's list price), and choosing who builds: "Build with this" in Settings → AI and `ai builder ` set the Builder role, which the app now honours before an agent app - [x] An extraction step for Deep-mode game rebuilds: a backend chosen for the Extractor role ("Extract with this", `ai extractor `; an API backend such as Kimi K3) takes the game's own files out into `app/Original/` after the study, with the game archive tools, and lists them in `app/Original/MANIFEST.md`; the builder is told to use them. A failed extraction says why and the build goes on - [ ] A live extraction run with Kimi K3 (needs the person's OpenRouter key) - [x] A checklist tool for API builders: `build_checklist` (TodoWrite's shape), shown pinned like Claude Code's checklist - [x] Context management for long builds (`ai::context`): past the budget (150k input tokens, 24k for local models, `LoopConfig::context_tokens`) old tool results are trimmed, then the oldest part is replaced by a summary the model writes; the conversation is kept in `transcript.json` after every step and a follow-up resumes it ### R2.2 More coding agents Owner: open · Status: in progress - [x] `AcpConversation`: chat with agents that speak ACP (`cli_agents::acp`; agents and their ACP arguments in `ACP_AGENTS`: Gemini CLI, Qwen Code, opencode, goose). Opt-in per backend (`acp = true` in its options); one agent process per chat, permission requests become consent questions, sessions continue with `session/load` - [x] Builder runs over ACP: with `acp = true`, rebuild runs use `AcpConversation`; the agent's permission requests go through the approval policy (`interaction_tools::approve_with`: allowed, refused, or asked through the project's inbox; Auto mode honoured) - [ ] Gemini CLI, opencode, goose via ACP or native adapters; each verified live, then turn ACP on by default for the ones that pass ### R2.3 Website coverage and a better visual metric Owner: open · Status: done - [x] Study: up to 4 same-site pages from the start page's links, each captured into `capture/routes//` (`capture/routes.json`); the brief asks for each at the same path - [x] Study: open menus, dialogs and tabs; record states (`web_page_states_capture`: up to 4 on the start page, pictured in capture/states/ and listed in the brief; never form controls) - [x] Study: links more than one level deep (pages linked from the captured pages fill the rest of `build.max_pages`; `pick_deeper_routes`) - [x] Perceptual metric next to tile similarity: `perceptual_similarity` (mean SSIM of brightness, own code) in `web_pixels_compare`; a website check's likeness is the mean of layout and detail, both shown in its note - [x] Check: every captured page compared on the build at the same path; the likeness is the mean over pages, each page's score in the note (live: info.cern.ch, 95% + 83% → 89%) - [x] Check: interaction states (the build's menus, pop-ups and tabs opened like the original's, paired by kind and label, pictures compared) and structure (`structure_likeness`: landmarks, headings, lists, media and controls in order, missing parts named); both in the note, the score stays visual - [x] Check: text/token diff with the original's missing headings and words (`text_likeness`; in the check's note and the builder's refine message; the score stays visual) - [x] Report: per-route likeness on the project page (`CheckResult::pages`; a page is picked to see its pictures) - [x] Stacks and libraries named in a request reach the builder: a Next.js stack (static export to app/out) and a `[[library]]` table in `stacks.toml` (shadcn/ui, Tailwind CSS) whose steps join the stack's; phrases match the request's words, so "Next.js", "nextjs" and "next js" all count - [x] A website whose request names no stack leaves it to the builder ("Builder's choice", told to match the original's); the stack it used is recorded from app/package.json (`stacks::used_in`) - [x] Fidelity at every width: Study and check use the same widths (default 1440, 768, 390); `web_site_capture` records CSS breakpoints, spacing and content widths, hex colours, font files and the layout at each width; menus are also opened at the phone width; the check's note names missing colours, fonts, text sizes and spacing, sideways scrolling, heading sizes and per-section padding, items per row and content column (`design_notes`) - [x] Page health: `web_site_open` reads script, console and log errors and failed requests (400+, failed loads) from the browser's events (`page_health`, no query strings); `web_site_capture` records them with an accessibility outline (language, landmarks, unnamed controls, pictures without alt); the check names what the build adds (`health_notes`, failing paths compared) - [x] Parts that change by themselves aren't compared: Study photographs each width three times at uneven gaps (`web_site_capture` `noise_pass`) and keeps the moving squares as noise-.png; `web_pixels_compare` `ignore` leaves them out of layout and detail (`NoiseMask`, `block_similarity_masked`, `perceptual_similarity_masked`), and the website check passes it - [x] Live clone (2026-10-21) of a fixture page of our own with known values at three breakpoints (type 48/36/28, padding 64/48/32, a 1100 px column, 3/2/1 cards per row), in Next.js + shadcn/ui, 2 rounds: Ready, 96% alike (97/96/95% at 1440/768/390 px), page heights equal. The build matched every measured value exactly, so the thresholds (4 px padding, 2 px type, 8 px column) stay: no note fired from noise. It found two real faults, fixed: sections were paired by heading only, so a section the builder renamed (as it must) was never compared, and now pairs by tag in page order; and a nav's link count read as "items per row" (a link under 40 px was dropped) and gave a false note, so per-row isn't compared for nav or header - [ ] Thresholds under pressure: a clone that's off by a few pixels (a page with fluid type or `clamp()` spacing) to see whether 4 / 2 / 8 px is too strict ### R2.4 Runtime study for apps and games (with consent) Owner: maintainers · Status: in progress - [x] Window screenshots: `platform::window` (xcap) and the `native_window_capture` tool (start, wait, picture the largest window of the process or its app, close; consent required); Study saves `capture/window.png` when the `runtime_study` setting is on (off by default); the screenshot check compares the rebuild with it too; the CLI is DPI-aware on Windows - [x] UI tree on Windows and macOS: `runtime_ui_read` reads UI Automation on Windows (a PowerShell script we ship; role, title, id, value, class, position; never password values) as well as the macOS accessibility tree; `Requires::Os` names the hosts a tool runs on - [ ] UI tree on Linux (AT-SPI) - [x] Scripted UI steps on Windows: `runtime_ui_walkthrough_record` presses, expands, collapses, toggles, selects or focuses controls by title or id through UI Automation (no mouse or keyboard) and records the tree before and after; each step reports its outcome - [x] Study saves the original's UI tree next to `capture/window.png` when runtime study is on (`native_window_capture` `ui_tree`, read just before the program closes; `capture/ui-tree.json`, pointed to in the capture README) - [x] A Settings control for `runtime_study` (Safety → "Study apps by running them") - [ ] Window capture tried on macOS and Linux (only Windows so far); games behind a launcher - [ ] Short scripted input sessions (enigo) to record screens of the original - [x] Same capture of the rebuild: with runtime study on, the screenshot check also starts the build normally and pictures it like the original (`checks/round-/window.png`, `ui-tree.json`); the note says how alike the windows look and names the original's labelled controls the build lacks (`controls_likeness`); advisory, the score is unchanged ### R2.5 Behaviour checks for non-web builds Owner: open · Status: in progress - [x] Standard `--screenshot ` contract in every app and game brief, enforced by `ScreenshotCheck` after the build check (no picture, an unreadable or blank picture fails); checks run in order and a failed one stops the rest - [x] Compare against the person's reference pictures: the closest PNG's likeness is in the note - [x] Let picture likeness decide: with `build.picture_good_enough` set, the screenshot check's likeness has that bar and refining continues until it's met (unset: reported only) - [x] Compare JPEG, WebP and GIF reference pictures too (`web_common::decode_picture`) - [x] `MECHANICS.md` gains a structured twin (`app/mechanics.json`, `engine::mechanics`, schema in `docs/schemas/`): `MechanicsCheck` fails a game without a readable sheet and notes values without a source and numbers not in the code as written - [x] Apps in Deep mode keep the sheet too: the brief asks for it when a program target is decompiled, and `DeepOnly(MechanicsCheck)` checks it only then (`Check::applies`) - [x] Honest game checks (a Deep-mode Unreal rebuild reached Ready as a first slice: a cave, no player, enemies, mission or HUD): every game brief asks for `--self-test ` (play the core loop with scripted input, measure it from the game's own state) and `SelfTestCheck` runs it after the screenshot, failing until `move`, `act` and `hud` all pass; `MechanicsCheck` fails when a sourced number isn't in the code as written (estimates may stay out; Unreal's generated folders aren't read as code); in Deep mode on a program, `DeepOnly(CreditsCheck)` requires `app/CREDITS.md` and `app/README.md` opening with the brief's private-use line - [ ] Behaviour checks that use the sheet: run the build with scripted input and measure values (speed, jump height, fire rate) against it (needs R2.4's scripted input) - [ ] Hold the self-test to what the check can see too: picture the game after its scripted input and require it to differ from the `--screenshot` picture (a report alone is the build's own word) - [x] Publish the self-test report's schema in `docs/schemas/`: `engine::self_test` owns the report's types, `cargo xtask schemas` writes `self-test.schema.json` ### R2.6 Several rebuilds at once Owner: open · Status: done - [x] More than one `RebuildRun` per window: `rebuild::RunSet` runs up to `build.parallel` (Settings → Builds) and queues the rest, for the app and the rebuild tools alike; events come by project folder and a building project's feed is taken from its run (`feed_now`) - [x] Start queued builds from MCP without waiting for the next rebuild tool call: `rebuild::keep_queue_moving` polls the tools' `RunSet` every 2 s while builds wait - [x] A UI test with stand-in runs: `rebuild::StandIns` makes runs without a builder; the smoke test queues a second project and follows it on screen until it starts ### R2.7 The request decides how to build Owner: open · Status: in progress "Build it in Electron" was built in Rust: the brief gave the kind's default stack as a firm instruction after the request, and the app, the build check and Play followed the recorded stack. What the person asks for comes first, in every kind; the stack table is only the default. - [x] The brief puts the request first: a rule says what it names (a language, framework, engine, library or platform) wins, and the kind's stack is given as "How to build it, unless their request says otherwise"; a website's default no longer says to copy the original's stack over the request, and its refinements no longer say `npm run build` - [x] The build check, the recorded stack and Play follow what was built in app/: an engine's own build runs only for its project, a non-engine stack is re-recorded from package.json after each turn, and an Electron app opens in the Electron it installed (and is never a website) - [x] Make: a game engine is required only when the person picks one or the request names none ("Built with Electron, as you asked, so no game engine is needed") - [x] Electron in the stack table (asked by name, recorded from package.json, with steps that fit the build check, Play and --screenshot). Live: "A small notes app in Electron" was built in Electron and reached Ready with both checks passing - [ ] Screenshot check: in that live run the final check once got no picture from the Electron app within 90 s while the same check passed in the builder's turn and on a re-run; not reproduced. The app waited on animation frames, which a window behind others may not get; the Electron steps now say to capture after loading instead - [ ] Make: a game whose request names a technology the stack table doesn't know (Phaser, Bevy…) still needs the default engine installed to start, though the builder is told to use what was named; ask the person, or let such a game start without an engine - [x] CLI: after the tool name, `--project` is the tool's own option when it has one (`rebuild-check --project `, `project-show --project `), as `--format` is; otherwise, and always before the tool name, it's the global investigation folder --- ### R2.8 Clean room means clean room Owner: maintainers · Status: done (branch `feature/r2.8-clean-room`) **decision** (2026-10-23, maintainers): Clean room never looks inside the original. Only Deep mode reads its code, files or page source, with our own engines and with Ghidra when it's installed. Deep mode is its own switch, not tied to Ghidra. - [x] Deep mode as its own setting (`deep_mode`, config.toml version 2 moves `engines.ghidra.enabled` there); Ghidra is used in Deep mode when it's installed - [x] A table of the tools Clean room allows (no target, or watching from outside: the visible website, screenshots, pictures of a window, recorded runs, the screen's controls); every other tool is refused in Clean room, everywhere (app, CLI, MCP, agents), pointing at Deep mode - [x] Clean room website capture keeps what a visitor sees: screenshots, visible text, measured layout, colours, fonts and spacing, menus opened; no HTML, CSS rules, scripts, requests or script errors - [x] Clean room builds: the study reads nothing inside an app or game (pictures, the person's description, and watching it run when Settings allows); the builder can't read the original's files, fetch its pages or run commands on it (its Read, Glob and Grep go through the approval policy); briefs say so - [x] App: Just look inside, Game files and the Advanced workbench ask for Deep mode in Clean room; the mode switch explains both modes - [x] CLI `--deep` for one run; MCP lists only Clean room's tools in Clean room - [x] Deep mode uses every tool: the builder sees them all (`--all-tools`, every toolset for API builders), a website's study also reads its JavaScript and watches its start page (capture/how-it-works.md), and briefs name the tools for each kind of target - [ ] A live Clean room build of a website and of an app, checked end to end - [ ] A remote (SSH) builder needs a craft-anything-cli that knows `--deep` and `--clean-room`: check its version first and say so when it's older - [ ] A remote (SSH) builder doesn't get the time limits or effort set through `Process::env` (`MCP_TOOL_TIMEOUT`, `CLAUDE_CODE_EFFORT_LEVEL`…): they're set on the local `ssh`, which doesn't pass them on. Set them in the remote command - [ ] Failure logs for the other ways an AI stops (API backends, MCP server start-up), like the ones agent CLIs write to `logs/` in the config folder ### R2.9 Everyday use: typing, projects, settings Owner: maintainers · Status: in progress Gaps found while using the app for real projects. - [x] Up at the start of the prompt box takes back the last note still waiting for the builder, to edit it (as the x beside it does) - [x] With nothing waiting, Up and Down in the prompt box cycle through previous prompts, as in a terminal: a project's are read from its feed, so they survive a restart, questions come back starting with `/btw` so they stay questions, Down past the newest returns what was being typed, and the assistant's box does the same - [x] Up and Down only cycle prompts from the very start or end of the text, and move the cursor otherwise, so a prompt of several lines can be walked through line by line - [x] Slash commands in a project's prompt box: `/btw` asks about the build in place of the old `?` prefix; a new one is a `slash::Command` and a row in `COMMANDS`. A mistyped command stays in the box rather than starting a build; a path like `/usr/bin` is a plain message - [ ] Typing `/` lists the commands, and Tab completes one - [x] Questions about a build show in the conversation where they were asked, so a long answer scrolls with it and leaves the prompt box alone; each folds to one line, and they're kept in the project (`questions.json`, the newest 200, `rebuild_questions_list`), in their place among its prompts for Up and Down - [x] A folded sidebar stays lined up with the open one: its button is centred over the row icons, its sections keep their height, so nothing moves when it folds, and a rule takes the place of each heading, with Refresh and the Pinned arrow still there - [ ] A prompt brought back with Up keeps its pictures (only the text comes back now) - [x] The smoke test `a_game_project_shows_its_asset_spending_and_can_raise_the_budget` failed once in a full run and passed in the next 11: wait for `a.facts.assets.0 == 1` instead of a fixed frame step - [x] The prompt box no longer jumps while typing: a frame laid out with its old height is thrown away and laid out again before it's shown - [x] A message sent to a project waiting its turn stays in the box instead of being lost - [x] Several project folders: list projects from any number of folders, and start a project in a folder the person picks, which may already have files; ask whether those files are the app to edit in place or material to start from, with "don't ask again" in Settings (`Workspace::create_at`, `project_folders`, `existing_files`, `rebuild --project-folder --existing`, `rebuild_start`'s `project_folder`; craft.json version 2, config.toml version 3) - [ ] A live build in a picked folder each way (edit in place, start from), checked end to end - [x] Start sits below the fold on the Make screen at common window heights (900 px) - [x] The project list notices projects added, moved or removed on disk (on focus, and a Refresh) - [x] Rename, delete and duplicate a project (`project_rename`, `project_duplicate`, `project_delete`: a delete keeps the person's own files, and a copy leaves out dependencies and build output) - [x] Remove or turn off an AI from Settings (`ai on`/`ai off` in the CLI); a pasted key isn't kept in the app after it's added - [x] Simple mode keeps the Project screen in Advanced mode too, so builds and approvals stay in sight (the workbench is a screen beside the projects: a Workbench row in the sidebar, a Projects button on the workbench) - [x] One answer to "can it build?" (`craft_anything_ai::default_builder`: Make's warning and the builder chosen agree), and a setup dialog with what was found, Look again and API keys instead of "Got it" - [x] "Built by" on Make: every AI that can build, by name, even when one is set up and builds by default; a pick stays with the project (`craft.json` `builder`, also from `rebuild --backend` and `rebuild_start`), and its later changes are built by it while it's on - [ ] Change a project's builder from its page (a tool, so the CLI and MCP can too) - [x] Settings are saved after typing stops (one save at a time), and path fields keep spaces - [x] No file reads while drawing: the project's outcome, reference pictures, screenshots and side panel come from a worker when something changes (at most once a second) - [x] Making a project and copying pictures (of the original, or attached to a message) run on a worker, not on the click - [x] A build repaints the window when it has news instead of a 10-a-second timer; animations run at 15 frames a second, and once a second while the window is in the background - [x] The feed isn't copied every frame and a paragraph is one label, not one per word; screenshot thumbnails are scaled down and dropped when another project opens - [x] The "Working" row's clock counts from the build's start instead of restarting whenever it reappears - [x] Stop on the Ask screen stops only its own answer, not downloads and builds, and "Working on your answer" shows only for it - [x] Opening another target starts a new conversation, so answers about two things don't mix - [x] Closing the Ghidra question without downloading means the project doesn't start later; one that waits for the download starts as it was when Start was pressed - [x] Make's Clean room / Deep choice says it's the app's setting, not the project's - [x] A project that needs an answer says so in words in the sidebar, and the taskbar flashes when a build asks something while the window is behind others - [x] One Pause (in the prompt box); the plan card carries the clock instead of a second Working card; Progress shows where a paused build stopped, and its steps have names for screen readers - [x] App or game is the tile picked (and a project's engine), not whether the path is a folder - [x] The website dialog's field is ready to type into - [x] Typed drafts survive a restart, and each project keeps its own unsent text - [x] Narrow windows: the sidebar folds to icons (by itself below 900 px, or by its button) and the project's side panel moves into "Project details" above the feed below 860 px - [x] Messages float over the bottom of the window instead of pushing the screen up; errors stay until dismissed, so a later message never hides one - [x] Simple-mode text is 12 pt or more (captions, buttons, the title bar, Progress) - [x] The Language menu shows once there's a second language - [x] French UI (`fr.tsv`, Settings → General → Language); a test checks every translation keeps its `{placeholders}`; `--lang fr` on the snapshot example - [x] Simplified and Traditional Chinese UI catalogs, system-locale selection and translated tool-title search; catalog integrity, coverage checks and isolated language-switch tests ([localisation](localization.md)) - [ ] A Simplified Chinese README (`README.zh.md`) translated from `README.md`, linked from the language switcher in `README.md` and `README.ja.md` — claimed: maple323, branch docs/readme-zh - [ ] `README.ja.md` is behind `README.md`: the whole Craft App Suite section is missing, so the Japanese translation needs a catch-up pass - [x] First run sets up who builds, not only who answers, and no longer says the purpose is saved with projects (it stays on the computer); its Back has a name for screen readers - [x] An error whose fix quotes a command (`claude auth login`, `codex login`) offers to copy it, instead of the app matching Claude Code's English wording - [x] A consent card's id is a short number, with the call it stands for kept in memory, not the call serialised into the id - [x] The workbench's list isn't copied every frame (it can hold thousands of rows) - [ ] Simple-mode text sizes checked by a test (no literal sizes under 12 pt outside the workbench) - [x] Typed text no longer blinks in the prompt box; Open folder opens the project's folder - [x] Scroll bars show at rest (6 px, the text colour at 3:1 or more on every surface), not only on hover - [x] Play starts a Rust workspace's window app (from its manifests), not whichever program was built last (a CLI that quit at once) - [x] Play says when what it started quit straight away (with its last output), instead of "Starting…" and nothing, and that it's already open instead of starting it twice - [x] Questions from the builder reach the person: "Builder's questions: Ask me / Decide yourself" under each project's prompt box (`project_questions_set`, craft.json `questions`) and a default in Settings (`questions`, which follows Auto mode until picked), apart from Auto mode's approvals; a builder that decides starts its next message with "I decided:" - [ ] When the builder decides a question, show the question and its choice as an answered card in the feed (the run sees only the tool call today; the builder's words say what it chose) - [x] Ask questions about a build without starting one: "Just ask" under the prompt box (or a message starting with `/btw`), answered as a card, never a round (`rebuild_question_ask`). Claude Code answers from a copy of the builder's session (`--fork-session`, not kept) with Read/Glob/Grep only (none in Clean room) and no tool server; an API AI with the tools that change nothing (`agent::ReadOnlyHost`); other agents are refused - [ ] A question's own cost: a forked Claude Code session reports the whole session's running total, so a forked answer shows none; count questions in the project's usage - [ ] "Make this change" on an answer that suggests one (sends it as a normal message) - [ ] A program started from the CLI (`project_play`) keeps the shell's output pipe open, so the command doesn't return until it closes: start it without inheriting the CLI's handles - [x] The desktop app keeps `desktop.log` beside its settings: startup steps, warnings, a window that couldn't start, panics - [x] The cost says when it's an estimate: Claude Code's figure is its own guess at API prices, so the project page says "AI cost (estimate)" with a note that it isn't the bill, and the title bar says "(estimate)"; what an API key was charged is kept apart (`api_billed_usd` in usage.json; older files count as estimates) - [x] A running build says what it's on ("Now: Running cargo test (42s)", the last action between them), and "More detail" opens its activity as it arrives: stages, every command and file with how it ended, what the builder said and what programs printed outside the conversation, with secrets hidden. Kept in `build-activity.log` ("Open log", `rebuild_activity_show`, and every tool the CLI's `rebuild` prints) - [ ] Show a command's output while it runs: Claude Code reports a tool's result only when it ends, so a long `cargo build` is one quiet line until then. Watching the agent's child processes from outside could say what's running and for how long - [ ] Sometimes starting the app shows nothing: find why from `desktop.log` (a GOG Galaxy Vulkan overlay layer loads into the app; try DX12 first on Windows if that's it) - [x] Try again picks up where a run stopped instead of starting over: a project whose builder has worked isn't studied again or given the first brief; when the builder had finished and only the check didn't (`awaiting_check` in craft.json), only the check runs. A check that returns an error gets a second go, and then the run says the build is finished but couldn't be checked - [ ] Find why a site capture lost its browser ("the WebSocket is closed", 419 ms into `web_site_capture` for a second page, seen once; the same capture passed 41 s earlier) - [ ] Two engine tests fail on a Mac, on `main` too: `real_binary_end_to_end_if_present` expects `/bin/ls` to be Linux, and `auto_mode_keeps_going_but_never_does_the_dangerous_things` is refused `rm -rf` in a temp folder under `/var/folders` ### R2.10 Pinned builds Owner: maintainers · Status: in progress Keep a build that works at hand: pin it, and open it from the sidebar whatever the builder does to the project next. Pins are the engine's (tools), kept in the project's `pinned/` folder. - [x] Pin a build as a frozen copy (the default: the app's files and the program, without what a build makes again, in `pinned//`, with its size shown first) or as a live pin that runs the project's app as it is; open, stop, rename and remove pins; one copy runs per folder (pins and Play alike) unless another is asked for; a start that quits at once says so with its last output (`project_pin_create`, `project_pins_list`, `project_pin_open`, `project_pin_stop`, `project_pin_rename`, `project_pin_delete`; `pin.json` version 1; `platform::Launched`) - [x] A light pin in place of the live one, which only opened what Play opens: the program, the code and anything a program may write to are copied, dependencies and media are hard-linked to the project's (`SHARED_KINDS`), and a file that can't be linked is copied. Replacing or deleting a shared file in the project leaves the pin's as it was; rewriting one in place changes both. Old live pins still open; new ones are refused - [x] Pins leave out what a compiler keeps beside a program that nothing needs to run it (`.pdb` debug symbols, `.lib`, `.d`, lock files): measured on real Rust projects, that took a 291 MB pin to 49 MB, far more than sharing could - [x] Leave out a project's old program copies (`*.running-*.exe`, 22 MB each in one project), which a pin can't tell from the program's helpers by their name alone - [ ] Say when a light pin ended up copying what it meant to share (a drive without hard links) - [ ] Pins of one project sharing with each other, so a third frozen copy of the same assets takes no more space - [ ] Pin a website (a frozen copy of the built site, each served on its own port) - [ ] A pin started before the app restarted shows as running (keep its process id and start time in the pin, and check both), and can be stopped - [ ] Cap a run's log (`last-run.log`, `play.log`): it grows as long as the program prints - [x] A collapsible Pinned section in the sidebar (icons with a running dot when folded; open, open another copy, close, go to the project, rename, remove), Pin this build on the project page with the frozen/light choice and each copy's size; the pin tools are in the command palette with every other tool - [ ] Opening a pin that's already running brings its window to the front, instead of a message - [ ] Show a pin's folder (the right-click menu), and how much space pins take in Settings - [ ] Live check on Windows: rebuild a project while its frozen pin runs, and the build isn't blocked ## M3 Engine depth ### R3.1 More tools (all 22 built) Owner: open · Status: in progress (a live Cutter test is left) - [x] Radio signal decoding: `radio_recording_decode`, `radio_decoders_list`, with rtl_433 run as a program (an optional engine, path in Settings → Engines) - [x] Cutter sessions: `native_cutter_sessions_list`, `native_cutter_command_run`, for sessions the person shares with `=h& 9090` in Cutter's console (read-only commands, 127.0.0.1 only) - [ ] Cutter sessions: try them against a real Cutter, including whether `=h&` keeps Cutter responsive (only a stand-in server so far) - [x] rizin debug sessions: `native_rizin_session_begin` (a program, stopped at its start, or a pid), `_command_run`, `_show`, `_end`, driving `rizin -d` with a marker after each command - [x] Rizin sessions: live test passed again with Rizin 0.9.1 installed from its pin (program arguments reach the program; a `dc` that outlasts its wait keeps the session busy and the next command waits for it; nothing left running) - [x] Every tool is listed in `docs/tools.md` (`cargo xtask tools`) ### R3.2 Known partial tools (see tool-status.md) Owner: open · Status: done - [x] Pseudo-C arguments for imports without prototypes: a table of common libc and Win32 argument counts (`disasm::arity`, plus stdcall `@bytes` names) shows every argument, unset ones as the register - [x] `calls_out` counts tail calls (and `tail_calls` reports them on their own) - [x] Imports as search hits and call-path goals: `native_functions_search` lists matching imports (`kind: "import"`); `native_call_path_find` takes an import as a forward goal (`native::Goal`) - [x] `native_strings_browse` section filter (`section`, any case; an unknown one names those with strings) - [x] Edge vs Electron labelling: `js_electron_pages_list` names the runtime from `/json/version` (`web_common::runtime_of`) and says when a plain Edge or Chrome answered - [x] `core_evidence_exports_compare` tampered record: every record is checked against its own id; edited records are reported and never count as unchanged; duplicate keys are listed - [x] AnyCPU .NET bitness: triage says "any CPU" from the CLI flags (`PeImage::dotnet_arch`, shared with the .NET inventory) and reports `dotnet_arch` - [x] `apple_app_graph_build` on bare Mach-O: only a bundle counts as an app; the result says `shape` (app-bundle, bare-executable, none) - [x] Nested field names in CLI help: fields inside object options (and arrays of objects) are listed as `option.field`, two levels deep - [x] `native_function_pseudocode` on an address no analysis made a function: the hint says so and points to `native_function_find` and xrefs, instead of claiming the built-in decompiler answered - [x] Ghidra: decompile code its quick analysis never made a function (reached only through pointer tables, as seen in a live run): disassemble and create the function on demand in the bridge, in memory only (the analysis stays read-only) - [x] Ghidra: when the bridge stops before it starts, its logs are copied beside the analysis (`.bridge.log`, as the analysis log is) and the error points there, since the runtime folder is removed with the error ### R3.3 Frida sessions for real Owner: open · Status: todo - [ ] Replace the in-memory session bookkeeping with real attach, resume and detach - [ ] `frida_devices_find` with `remote`: `frida-ls-devices` has no `-H`, so listing a remote's devices fails ### R3.4 Decompilers for managed code (external engines) Owner: open · Status: in progress - [x] C#: ILSpy's command-line tool (MIT): `managed_ilspy_decompile` (C# of the whole assembly or one type, the IL, or its types; update check off; native files refused before it runs) - [x] Java: Vineflower (Apache-2.0): `java_vineflower_decompile` (a .class or JAR; its .jar run with the configured Java, or a `vineflower` script; sources read back from a temp folder) - [x] Vineflower: a checked install pin (Settings → Engines or `engines_install`) - [ ] Vineflower: run it once on a real JAR (no Java on the machine it was written on) ### R3.5 Device-backed tools Owner: open · Status: blocked (needs an Android device or emulator) - [ ] Verify the 23 unverified adb tools; record results in tool-status.md ### R3.6 Browser walkthroughs in depth Owner: open · Status: done Scripted web sessions (`web_walkthrough_record`) should be repeatable and see what a person needs to understand a site. - [x] Fixed environment: viewport, scale, locale, time zone, colour scheme, reduced motion, service workers (defaults 1280x720, en-US, UTC, light); the result says what was applied - [x] Start with given cookies and local/session storage (`storage`; every value is treated as a secret and redacted; results name what was set, never values) - [x] Optional request/response bodies and header values per run (`keep_bodies`, off by default; text capped at 64 KiB, digested after redaction; credential headers always redacted) - [x] Locators by role and accessible name (accessibility tree), and by test id, next to CSS selectors; an optional `step_id` per step, echoed in the result - [x] Artifacts at the end or after each step (`capture`): screenshot, DOM (redacted, capped), accessibility tree, history, storage key and cookie names - [x] Event families (`capture.events`): console, page errors, WebSockets, frames, workers, popups, downloads (blocked), each with its step and a sequence number; a goto no longer drops events - [x] Compare two walkthrough runs step by step (`web_snapshots_compare`: steps aligned by `step_id`, network and event-count differences), with plain text `normalization` rules ### R3.7 Program runs you can compare Owner: open · Status: done Running the same app or game twice (`runtime_walkthrough_record`) should only differ where its behaviour differs, so `runtime_captures_compare` can say what changed. - [x] Normalise captures (`normalization`): process ids, loopback ports, working/temp paths, literal patterns and time buckets, with what was replaced recorded - [x] `runtime_captures_compare` reports which terminal lines changed (first changed line, lines only on each side with line numbers), not only their counts - [x] Windows: `runtime_walkthrough_record` runs the program through `platform::process` (its Job Object ends every child) and reads its output through pipes, in the same result shape, saying so (`pty_backend: "pipes"`, a `no_terminal` limitation); typed input, resizes, signals and watched files still need the terminal capture (Linux, macOS) and are refused --- ### R3.8 Game archives Owner: open · Status: done Games keep their files in archives; someone studying a game first wants to see what's inside. - [x] Unreal `.pak`: version, mount point, compression, file list (v1–v11 indexes); an encrypted index or encrypted files are named, never guessed (`game_pak_list`; `formats::unreal_pak`; checked live on large archives) - [x] Unreal `.pak`: extract a file, or a whole folder a page at a time (stored or zlib; `game_pak_extract`, streamed, SHA-256, output folders made, files already there skipped; encrypted and Oodle files refused with the reason; checked live) - [x] Unreal IoStore (`.utoc`/`.ucas`): list the files (`game_iostore_list`, `formats::unreal_iostore`, TOC versions 1–8, signed and partitioned containers; encrypted names counted, never guessed; checked live) - [x] Unity: `*.assets` and level files (serialized files): list the objects by class, with their names (`game_unity_list`, `formats::unity_serialized`, format 14+ (Unity 5 on), either byte order; checked live on files from Unity 2017 to Unity 6) - [x] Unity: UnityFS bundles (`*.bundle`, `data.unity3d`): list the files inside, and the objects by class in each serialized file (`game_unity_bundle_list`, `formats::unity_bundle`, LZ4 and LZMA; checked live) ### R3.9 Game assets out Owner: open · Status: in progress After seeing what a game packs (R3.8), the next step is taking its pictures and sounds out in formats anyone can open. - [x] Unity: textures to PNG (uncompressed formats and DXT1/DXT5, pixels inline or in a `.resS` file, from `*.assets` files and bundles; `game_unity_asset_export`, `formats::unity_texture`; the Texture2D layout of Unity 2017.3–6000 checked live on thousands of textures) - [ ] Unity: sounds out (the stored stream, usually FSB5; PCM ones as WAV) - [x] Unity: BC7 and BC6H textures (every BC7 mode; BC6H unsigned and signed, tone-mapped to 8-bit sRGB, brighter than white clips; `formats::bptc`, written from the Khronos Data Format Specification; Unreal's PF_BC7 and PF_BC6H too; checked on two Unity games: 11 of 11 BC7 textures and 2 BC6H lightmaps saved and looked at) - [ ] Unity: crunched textures (DXT1Crunched, DXT5Crunched; two of the games checked use them) and the float formats (RHalf, RGHalf, RGBAHalf, RFloat, RGFloat, RGBAFloat) - [x] Unreal: textures from `.uasset`/`.uexp` files taken out of a pak, only when the person confirms they may unpack the game (`game_unreal_texture_export`, `permission: true`; `formats::unreal_texture` for Unreal 4.26–4.27 cooked packages, `formats::pixels` shared with Unity, which also gains BC4/BC5; checked live: every texture tried was saved) - [ ] Unreal 5 and Oodle: IoStore packages, unversioned properties and Oodle-compressed paks - [x] App: a Game files screen: pick a game, see its archives, and take its pictures out after the person confirms they have permission (sidebar → Game files; `game_archives_list` finds a folder's paks, IoStore files, Unity files and bundles; the permission question is asked once per game, then a save dialog) - [x] Game files: save pictures from Unity bundles (`game_unity_list` lists a bundle's objects by path id, from every serialized file inside it, each naming its `file`; bundles built without their Unity version use the one in the game's globalgamemanagers; checked on a Unity game's bundles: 27 of 27 sprite atlases saved, and a scene bundle with two serialized files listed) - [ ] Game files: save pictures from IoStore containers (needs the Unreal 5 support above) - [x] The rebuild's Deep-mode brief points the builder at these tools (`enable_toolset` formats, then the `game_*` tools) before any outside converter; a live run had set up a third-party unpacker instead - [ ] Unreal: sounds and meshes out (so a Deep-mode rebuild needs no outside converter for them) ### R3.10 Narrow reads of saved results, and more of an app's set-up Owner: open · Status: in progress A saved analysis should answer a narrow question without running again, and an app's own code should give away more of how it is set up. - [x] `core_evidence_browse` pages keep only the rows that match (`matching`: field → value or list of values, `a.b` inside a row, null for a missing field), return only chosen fields (`fields`) and list the field names of the matching rows (`field_names`); offsets count matching rows. Checked on a saved analysis of a 1,706-file Electron package: 4,472 effects → 12 process starts - [x] `android_resources_read` takes several locales in one call (`only_locales`, added to `locale`), each with its own string table; ones the app lacks are named (`locales_not_found`), and a table cut at 5,000 strings says so - [x] Electron windows whose options sit in a local object (`const opts = {…}; new BrowserWindow(opts)`, or `webPreferences: prefs`) have their webPreferences and preload read, with the object's name and line (`options_from`); an object that is written to, exported or used any other way, anywhere in the module, keeps the window `dynamic` - [x] A spread or computed key in window options or webPreferences makes what it may replace unknown (`dynamic`), instead of "not set": found on an installed app whose preferences spread in another object. Nine installed Electron apps (19 windows) read the same otherwise - [x] An app's file inventory tells a Java class from a universal Mach-O by its contents, not its file name - [ ] Android locales: try `only_locales` on a real APK (only the test fixture so far) - [x] `app_graphs_compare`: chunks whose names differ only by a build hash (`Doctor-DIpf_I7N.js` → `Doctor-DrkWsav4.js`, `main.3f2a9c1b.js`, `chunk-UGNM3QZK.mjs`) are one changed module with the old name (`was`), not removed + added; only when the name without its hash is unique on each side (Vite's several `index-.js` aren't guessed between) - [ ] `app_graphs_compare` on the two real Electron releases again, to count how many of the 162 "removed" now pair (and pair the `index-` chunks by structure if many stay) - [x] Results over Claude Code's tool-output limit (about 25k tokens) come back summary-first: a connection whose client is Claude Code (`clientInfo.name` = `claude-code`, checked against Claude Code 2.1.305) gets an 80 KB response budget instead of 8 MiB, so a bigger evidence record collapses to its summary with `core_evidence_browse` to page it; `CRAFT_ANYTHING_MCP_MAX_RESPONSE_BYTES` still sets it explicitly - [ ] A live chat run with a big comparison result, to see Claude Code page it with `core_evidence_browse` (and whether 80 KB is the right size) ### R3.11 Single-file apps, game files and stand-in backends Owner: open · Status: in progress Read what real apps and games ship as (one file carrying the rest), and let a rebuilt website run against what its original was recorded answering. - [x] One seam for files packed inside a file (`formats::packed`), with `core_packed_files_list` and `core_packed_files_unpack`; a new kind is one reader and one table line - [x] Readers: Bun and Node.js single-file programs, .NET single-file apps, PyInstaller, Godot PCK. A real 279 MB Bun program read and unpacked (2,889 modules); real Node blobs read - [x] `formats_bytecode_show` (Python bytecode), `game_unity_code_names_show` (Unity IL2CPP metadata), `formats_ai_weights_show` (GGUF, safetensors; a real file of each read), `native_publisher_show`, `native_library_calls_list` (delay-load too), `native_offered_symbols_list`, Zstandard ZIP members - [x] `app_folder_explain`: what an install folder is made with, and what to run next, from a table of runtimes; checked against about 25 installed Mac apps - [x] A stand-in backend from a network recording (`web_standin_build`, `_start`, `_stop`, `_show`), GraphQL and tRPC operations in app maps, and `web_walkthrough_check` - [x] `app_builds_line_up` (two real builds of a 276 MB program in 42 s), `native_source_folder_write` (resumable), `android_components_list` - [x] CLI: `--json -` takes parameters from a pipe; `--format csv` and `tsv` write a result's rows, with spreadsheet formulas kept as text. Kimi Code, Factory Droid and WorkBuddy in setup - [x] Real samples built with the real tools and read back (none are kept in the repo): PyInstaller with Python 3.9 and 3.14 (every archive member the same bytes as PyInstaller's own reader, on the 3.14 program), .NET 8 for macOS, Linux and Windows, plain and compressed self-contained, Godot 3.6, 4.3 and 4.5 (formats 1, 2, 3; MD5s match on the files checked), a real APK. 214 real Python modules read the same as CPython's `marshal` - [ ] A real IL2CPP `global-metadata.dat` (needs a Unity build or a shipped game); synthetic so far. Compare a real APK's components against another tool's reading - [ ] Stand-in backend: a browser-made HAR and a real front end talking to it (only a hand-written HAR so far); GraphQL and tRPC on real bundles (only hand-written minified shapes) - [ ] Stand-in backend: writes that change later reads (recorded answers are replayed as they are) - [ ] Tell the builder about the stand-in backend in a website's brief, and start one from a recording - [ ] Stand-in backend: a 304 recorded with no body is replayed as a bare 304, which a fresh browser can't use; say so or leave it out - [ ] Check the Kimi Code, Factory Droid and WorkBuddy registrations in the real apps - [ ] Godot format 4 and IL2CPP metadata 38 and 39 are header-only; read their tables once a real file can be checked - [ ] Bun: take source maps and bytecode out as files; a standalone `.pyz`; PyInstaller scripts carry no Python version of their own, so `formats_bytecode_show` needs `python_version` - [ ] `native_source_folder_write` uses the built-in decompiler only: use Ghidra in Deep mode when it's there - [ ] `native_publisher_show` hashes the file minus the signature fields, not section by section; a file with unusual gaps between sections can read as different - [ ] Two engine tests fail on a Mac, on `main` too: `real_binary_end_to_end_if_present` expects `/bin/ls` to be Linux, and `auto_mode_keeps_going_but_never_does_the_dangerous_things` refuses `rm -rf` of a temp path under `/var/folders` - [ ] Looked at and left for later, as niche for now: firmware image kinds (ESP32, U-Boot, device trees, Intel HEX), Erlang BEAM, GPU code objects, kernel modules, GameCube DOL, Mac crash reports, SAZ and qlog captures, protobuf bodies, R8 mappings, JNI links, MCP over HTTP ## M4 Release (later) Owner: maintainers · Status: in progress (v0.1.0 is released; managed tier and follow-ups remain) - [x] Secret scan: `cargo xtask secrets` (in `cargo xtask ci` and `.githooks/pre-commit`); files and full history scanned clean on 2026-10-19 - [x] **decision**: commit author emails stay as they are (they're public on other repos too) - [x] Before going public: `cargo xtask secrets --history` on every branch: clean on 2026-10-20 (all 16 remote branches); run it again right before the switch if more lands - [x] `CONTRIBUTING.md` and `SECURITY.md` (private reports through GitHub's "Report a vulnerability") Launch checklist (source-only first; signed installers can follow): - [ ] Maintainers: protect `main` (PRs required, squash only) and turn on private vulnerability reporting (Settings → Code security), which SECURITY.md points to - [x] Live website build on current `main` (2026-10-20, Claude Code, `example.com`, 1 round): Ready, 91% alike (95% / 91% / 87% at 1440 / 768 / 390 px); the builder used `rebuild_check` mid-turn (89% → 91%); no processes left. App and game left for after launch (maintainers' choice) - [x] R0.6: the tool-id mapping table and both ids working, before the ids are public - [x] STATUS: a last pass so everything not run live says so (2026-10-20: ACP agents, WASM checks from Rust, Cutter, Vineflower/Apktool, and Ghidra's guided install off Windows are marked) - [x] Pull request and issue templates (`.github/`): the roadmap item, what was and wasn't verified, the checks; security reports go to private reporting - [x] README quick start for someone new: Rust version, Linux packages (as CI installs them), clone, run the app, `cargo install` the CLI, `doctor`, then a first rebuild - [ ] Follow the README quick start on a clean machine (Windows, and Linux or macOS) - [x] CI runs on every pull request and push to `main` once the repository is public (skipped while it's private, so it costs nothing now): fmt, clippy, all tests with `--no-fail-fast`, layers, the tool list, schemas and the secret scan, plus the wasm32 check - [x] CI and release workflows on `main`: Linux builds no longer need screen-capture libraries, so CI passes there; the README lists each download (2026-10-22) - [x] Packaging like the other Crafting Apps (2026-10-22): `packaging/icons.sh` rebuilds every app icon from the SVG (`cargo xtask ico` packs the Windows .ico); the DMG opens on a laid-out window (`packaging/macos/dmg`: background, icon positions) and is built with `hdiutil create`, with the app inside the mounted image checked by `codesign --strict`; the FreeBSD workflow runs on main and packaging PRs (when public) with a package-contents check - [ ] Build the new DMG on a Mac (release dry run) and look at the window once - [x] The first release: v0.1.0 built from `release`, signed (Apple Developer ID and notarization, Azure Trusted Signing) and published on 2026-10-31 - [ ] Move the release workflows off actions that target Node.js 20 (`actions/checkout@v4`, `upload-artifact@v4`, `download-artifact@v4`); GitHub warns on every job and forces them onto Node.js 24 - [x] **decision**: the free, open-source release doesn't wait on Anthropic. Each person runs their own Claude Code under their own account and Anthropic's terms (claude-code.md) - [ ] Managed tier: account backend, usage metering (Claude API under our own key only) - [ ] Before the managed tier or any paid offering: confirm the Claude Code orchestration question with Anthropic (claude-code.md)