# AGENTS.md: guide for AI agents and contributors NotesCraft is a native, Obsidian-compatible notes app written in **Rust only** (egui/eframe, no JavaScript, no Tauri/Electron/webview). It is a Craft-suite sibling of PhotoCraft and VectorCraft and follows their conventions. Product name is always **NotesCraft** in user-facing text; machine names are lowercase (`notescraft-*`). Read this file, then `SPEC.md` (the contract), `ROADMAP.md` (status) and `docs/ARCHITECTURE.md` (crate map and APIs). ## Orientation | Read | Why | |---|---| | `SPEC.md` | Authoritative requirements, formats, contracts and acceptance table | | `ROADMAP.md` | Phase status, what is done, what is next | | `docs/ARCHITECTURE.md` | Layers, per-crate public API and status, design decisions | ## Workspace rules - Edition 2024, resolver 3, `rust-version = "1.95"`. Use rustup stable (`PATH="$HOME/.cargo/bin:$PATH"`), not an older system cargo. - One crate per concern in `crates/`, package `notescraft-`, `[lints] workspace = true`, registered in the root `[workspace.dependencies]` and in `xtask/src/layers.rs`. - **Layering is enforced** by `cargo xtask layers`: a crate may depend only on strictly lower layers; nothing below L5 may use egui, eframe, winit or rfd. Layers: L0 `vault` `markdown`; L1 `index` `canvas` `diagrams` `whiteboard`; L2 `backlinks` `graph` `templates`; L3 `plugins` `themes`; L4 `engine`; L5 `editor` `mcp` `i18n`; L6 `ui-egui`. - Shared crates come from craft-libs (`craft-atomic`, `craft-settings`, `craft-ui`, `craft-quit`, `craft-print`), pinned by git rev in the root `Cargo.toml`. Do not change their API; ask first. - A new third-party dependency needs a written reason and owner approval. Already approved: `notify`, `pulldown-cmark`, `toml`, `wasmi`, `wat` (dev), `thiserror`, `serde`, `serde_json`, and for sync (2026-10-19): `sha2`, `axum`, `tokio`, `ureq` (rustls), `rusqlite`, and for paste (2026-10-20): `arboard` (image-data), `image` (png feature only), and for the registry (2026-10-21): `ring` (ed25519 verify, already linked through rustls). ## Never crash (outranks features) - No `unwrap()`, `expect()`, `panic!`, `todo!`, `unreachable!`, `unimplemented!` in non-test code. Every crate keeps `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`. `unsafe` is forbidden. - Input-derived numbers are hostile: use `get()` / `split_at_checked`, never `[i]` or `[a..b]` (even when guarded); `checked_*` / `saturating_*`; cap allocations; bound recursion (depth limits or iteration). - Invalid input returns `Err`. Never silently clamp, truncate or "fix" it. - Reserved vault paths `.obsidian/` and `.notescraft/` are never written through public APIs (except the explicit `write_private` for `.notescraft/`). - No silent panics across the plugin boundary: traps, fuel exhaustion and memory limits become `Error`s. ## Code style - One `thiserror` enum `Error` and `pub type Result` per crate. Libraries do not use `anyhow`. - Every public item has rustdoc, every module has a `//!` comment. `clippy::pedantic` must be clean; no blanket allows. - Files stay under about 400 lines; split by concern. Smallest design that works; no speculative abstractions. - **Source folders stay shallow and grouped**: no directory under a crate's `src/` (or an app's) holds more than 12 loose `.rs` files (`mod.rs` and `lib.rs` do not count). Group files into sub-folders by concern (`foo.rs` + `foo/`), and nest a group inside a parent folder when it grows. Moving files must not change public paths: re-export from `lib.rs`. Enforced by `cargo xtask tree` (part of `cargo xtask ci`); directories still waiting for their restructure are listed in `xtask/src/tree.rs` and in ROADMAP.md. - Named exports, `import type`-style discipline, no magic numbers (named constants), `get` = derived, `fetch` = IO, `is`/`has`/`can` = predicate. - Tests: unit in `src`, integration in `tests/`, benches/perf in `perf/` or `#[ignore]`d tests, examples in `examples/`. Write the failing test first. Every crate has at least one. - Every write to disk goes through `craft-atomic`. ## Before you finish a task ```sh export PATH="$HOME/.cargo/bin:$PATH" cargo test -p notescraft- cargo clippy -p notescraft- --all-targets -- -D warnings cargo fmt -p notescraft- cargo xtask ci # fmt --check, clippy -D warnings, test, layers: must be green ``` run `cargo xtask i18n` and do not add hardcoded user strings. Check that every module is declared in `lib.rs` and that its tests actually run. After finishing, update `ROADMAP.md` and `docs/ARCHITECTURE.md` (status, public API, numbers). Commit only when the owner asks. ## Build hygiene (disk is shared and finite) - Build only in an absolute `target/agent-` dir, keep one scratch copy per agent with anchored excludes (`target*/` for directories), and delete both when you finish; the full rules are in `.claude/rules/build-hygiene.md`. `scripts/clean-agent-builds.sh --check` reports free space; without `--check` it deletes `target/agent-*`, stray `crates/*/target` and scratch copies (`--keep name` to spare a running agent). - Under 20 GiB free on the system volume or `/Volumes/Storage`: stop and clean before building. A full system volume stops every agent and the harness. ## Everything is a command User-visible behaviour is an engine command (id, label, params, `enabled`, `run`) so the UI, CLI, control channel, MCP and plugins share one surface (SPEC 10). The `engine` crate does not exist yet; until it does, keep logic in pure library crates that a command can call. ## Design Check UI changes visually: `cargo run -p notescraft-ui-egui --example snapshot -- --vault --out ui.png --size 1440x900 --open "folder/note.md"` renders offscreen; read the PNG and compare with the PaperCraft design (also `--theme catppuccin-latte`, `--scale 2`). Design reference: PaperCraft project `NotesCraft` (tokens, workspace, graph, palette, settings, whiteboard screens). Panels are rounded floating cards on the `canvas` colour and separate by tone, not borders; Catppuccin Mocha/Latte are the default themes (SPEC 8, 8.1, 8.2).