The std Plugin for runvil, built as a dynamic library. It depends on runvil-plugin-sdk, from cppalliance/runvil-plugin-api, and on no Engine, Harness, or Host crate.
These terms have exactly one meaning each, everywhere in this repository, as in the Engine's repository. Write them capitalized.
- Engine: the crates that parse a prompt and step a run. They never see a Plugin.
- Harness: the crates that step the Engine and perform its effects. The Harness's loader is the only code that calls this Plugin's entry point in production.
- Host: an application that runs prompts through the Harness, such as Workshop. The Host chooses which Plugins load.
- Pack: code which installs an optional native prelude, an optional Lua prelude, and zero or more tools.
- Plugin: an entry point which installs zero or more Packs. A library Plugin is a dynamic library that exports
runvil_plugin.
Anything else gets a qualified lowercase name, such as Rust's built-in test harness. Names defined outside this repository are used exactly as defined.
- Design for human review. A reviewer reads one crate at a time: its code, its surface, and the items it names from other crates. Keep all three small.
- Do more with less. Before building, check whether an existing facility subsumes the work: reuse one, else make the smallest improvement that enables the feature, else add a new facility, which needs a material benefit beyond tidiness.
- Error and status messages assume a model reads them: concise, factual, and self-contained, naming what is missing or unmet with required versus actual.
- There are no users and no stored data to preserve, so backward compatibility does not matter: change formats, schemas, and APIs freely.
- Prefer types and compiler checks, then behavior tests. Gate on yes/no properties, never on a score threshold that ordinary work edits.
- Behavior changes ship with tests in the same change. Preserve behavior tests during refactors.
- The Plugin crate depends on
runvil-plugin-sdkand outside libraries alone, and writes nounsafe. The SDK owns the C boundary. - A tool reads files only through
Source(crates/plugin-std/src/source.rs), which the call'sToolCallimplements, so every read goes through the Host and the run's policy applies to it. A test runs a tool's own code over the SDK'stesting::Filestree, or overFakeSource(crates/plugin-std/src/fake.rs) for what that tree cannot hold. - A tool's description and parameter schema come from its JSON file under
crates/plugin-std/schemas.EDITS.mdbeside them records every difference from Cursor's definition, and a change to a JSON file changesEDITS.mdwith it. - A tool that does a long job awaits
next_step()after each bounded slice of work, as Grep does every 32 files or directories and every 2 MiB read, so that no step of its call works for long and the Host can abandon the call between two steps. - A Plugin shares its process with the Host, so nothing here installs process-global state, such as a panic hook (
clippy.tomlbansstd::panic::set_hook), and nothing starts a thread that outlives the Plugin's unload. What every run shares lives in the Plugin instance and never in astatic, so an unload frees it. - Comments explain a non-obvious constraint, ordering requirement, or workaround. A platform or external-bug workaround cites its upstream issue URL.
- Tests, the structural checks among them:
cargo test --locked --workspace --all-features. - Lint:
cargo clippy --locked --workspace --all-targets --all-features, withCARGO_BUILD_WARNINGS=deny. - Format:
cargo fmt --all --check. - Docs:
cargo doc --locked --workspace --no-deps --all-features --document-private-items, withRUSTDOCFLAGS="-D warnings". Almost every item here is private, so the flag is what makes rustdoc check their docs. Clippy does not run rustdoc lints, so never skip this. - The library Plugin:
cargo build --locked -p plugin-std --release. - Coverage, to find a branch no test takes:
cargo llvm-cov --locked --workspace --all-features --show-missing-lines, which needscargo-llvm-cov. It is a report and never a gate.
- A file holds one concept and has no length limit. Split a file only when it holds a second unrelated concept.
- Size limits sit on functions and types. Clippy holds a function to 100 lines and 7 arguments (
clippy.toml).cargo test -p build-checksholds a type to 25 inherent methods, summed across everyimplblock in its crate, and a struct to 12 named fields. Split an oversized type by responsibility; a new entry in the allowlist incrates/build-checks/src/type_size.rsis a design decision with a reason, never a way to make a change pass. - Production code uses the standard module layout:
foo.rs, withfoo/bar.rsfor its children. No#[path], and no inlinemodblocks. A module other than the crate root never has exactly one child file (tests aside): merge it, or make it a peer modulefoo_bar.rsdeclared besidefoo.rs. - Test code goes at the bottom of the file under test in a
#[cfg(test)] mod tests { }block when practical. Otherwise it goes beside that file, infoo-tests.rs, wired with#[path = "foo-tests.rs"] mod tests;, or infoo/tests.rs, with one file per topic underfoo/tests/when the tests are many. Afoo-tests.rsfile is the only kind wired with#[path], so a hyphen in a file name means test code. A fake that the tests of several modules share is a module of its own, declared under#[cfg(test)]in the crate root. A top-leveltests/tree follows Cargo's conventions. - The source code of each tool goes in its own module, named after the last segment of the tool's Lua id. A stub has no module until it is implemented.
- Pseudo-random test input comes only from a fixed seed that the failure prints.