diff --git a/AGENTS.md b/AGENTS.md index b7c56fc3..c6df0637 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -67,6 +67,35 @@ reproduce an image (for example, point lists traced from someone else's icon). ## 3. Engineering rules +### 3.1 Never panic: fail with `Result` + +FilmCraft must not crash. A user losing unsaved work, or an agent's session dying, because of a bad file +or an unexpected input is a bug as serious as wrong output. + +1. **No panics in production code.** Don't use `unwrap()`, `expect()`, `panic!`, `unreachable!`, + `todo!` or `unimplemented!` outside tests. Return `Result` with the crate's error type and + propagate with `?`; in the UI, report the error (status bar, error dialog) and carry on. +2. **Avoid implicit panics too.** Index and slice with `get()` (or validate the bounds once, up front, + where a hot loop needs plain indexing); use `checked_*` / `saturating_*` arithmetic where values come + from outside; never divide by a value that can be zero (frame rates, timescales, sample rates, sizes); + don't call `clamp` with bounds that can cross; bound recursion and loop counts. +3. **All input is untrusted:** media files, project and interchange files, presets, fonts, command + parameters from the CLI / MCP / control channel, and UI state. A malformed or hostile input must give an + error, never a panic, a hang, or an allocation sized by the input without a sane limit. +4. **Background work catches panics.** Every thread or job (export, previews, proxies, decoding workers…) + runs its body under `catch_unwind` and reports a failure as an error: a dead worker leaves monitors + blank or jobs "running" forever. +5. **Lock poisoning is not fatal:** use `lock().unwrap_or_else(|e| e.into_inner())`. +6. **Enforced:** crates deny `clippy::unwrap_used`, `clippy::expect_used`, `clippy::panic` and + `clippy::unreachable` outside tests. Where an invariant truly cannot be violated and restructuring is + unreasonable, a single-item `#[allow(...)]` with a one-line justification comment is acceptable; keep + these rare and reviewable. +7. **Test the failure paths:** parsers and decoders get mutation-fuzz tests (truncation, bit flips, + corrupt sizes) run under `catch_unwind`; new commands get hostile-parameter tests. Fix every panic a + fuzzer finds and keep its reproducer as a test. + +### 3.2 The rest + See `CLAUDE.md`: pure Rust, dependency layering (`cargo xtask layers`), exact `Tick` time, everything is a command, everything is agent-drivable, and the quality gates (`cargo xtask ci`) before every commit. diff --git a/CLAUDE.md b/CLAUDE.md index 4c14d0e8..14fac8bf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,6 +13,7 @@ FilmCraft is a clean-room, open-source, pure-Rust non-linear video editor target - **Clean-room.** Never read/disassemble anything inside Adobe app bundles (names/listings only). Never copy Adobe icons, presets, LUTs, fonts. Never copy GPL/LGPL/AGPL code (FFmpeg, x264, x265, MLT, Kdenlive, Shotcut, Olive, LAME…). ffmpeg/ffprobe run only as external test oracles / fixture generators — never linked or shipped. - **Pure Rust** in the product. OS media APIs only via Rust bindings in `crates/platform` (planned; not created yet), optional, behind traits. - **Layering** ([`docs/architecture.md`](docs/architecture.md) §1, enforced by `cargo xtask layers`): nothing below L5 depends on egui/eframe/winit/rfd/cpal. L0 codec/container crates depend only on `filmcraft-bitstream`. +- **Never panic** ([`AGENTS.md`](AGENTS.md) §3.1): no `unwrap`/`expect`/`panic!`/`unreachable!` outside tests; return `Result` and propagate with `?`; no unchecked indexing, division by possibly-zero values or input-sized allocations on untrusted data (media, project files, command params); background threads catch panics and report errors. Parsers and decoders get mutation-fuzz tests. - **Exact time:** all time is `filmcraft_time::Tick` (254 016 000 000/s). Never use f64 seconds for edit math. - **Everything is a command** (`crates/engine`): id, label, menu path, shortcut, params, enabled(), run(). UI, CLI, control channel and MCP all dispatch by id. - **Everything is agent-drivable:** every interactive widget registers an automation id; UI state is serde so the control channel can read/set it. diff --git a/docs/contributing.md b/docs/contributing.md index 6dee3e97..dc380b75 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -58,6 +58,15 @@ Every commit must pass all of these: `cargo xtask` is an alias in `.cargo/config.toml` for `cargo run -p xtask --`. +### No panics + +Production code must never panic (rules in [AGENTS.md](../AGENTS.md) §3.1). In short: return +`Result` instead of `unwrap()` / `expect()` / `panic!` / `unreachable!`; use `get()`, +`checked_*` / `saturating_*` and validated bounds on anything that came from a file, a command parameter +or the UI; catch panics in background threads and report them as errors. Clippy denies `unwrap_used`, +`expect_used`, `panic` and `unreachable` outside tests. Parsers, decoders and commands get fuzz / +hostile-input tests run under `catch_unwind`. + ## 4. Commits - One task per commit. The subject starts with the task id: `M3.2: trim mode engine — …`,