Contributing
Wayland Core is Apache-2.0. Contributions follow the rules in AGENTS.md at the repo root, which is the operative spec for coding agents and human contributors alike. The notes below highlight the architectural contracts most likely to cause a CI failure if you miss them.
Getting oriented
Section titled “Getting oriented”Before writing code, read the files you will touch and the files that call them. The crate map in Architecture Overview describes where new functionality belongs. The rule is: place it in the lowest crate where it semantically belongs. Do not create a new crate for a single shared function.
The full workflow is:
cargo build # verify it compilescargo nextest run # run tests (preferred over cargo test)cargo clippy # must pass zero warningscargo fmt --all # must produce no diffjust push # lint-fix + fmt + auto-commit-fixes + test + git pushCI runs on macOS, Linux, and Windows. A change that compiles only on one platform will fail on the other two.
The NO-STUBS contract
Section titled “The NO-STUBS contract”Tools in wcore-tools are registered at agent startup in wcore-agent/src/bootstrap.rs. A tool that is registered is expected to be fully functional. Tools that depend on external credentials or system capabilities (API keys, a running sidecar, a platform-specific binary) are hidden when those dependencies are absent rather than registered as stubs that return errors.
Concretely: if you add a tool that requires SOME_API_KEY, gate its registration on std::env::var("SOME_API_KEY").is_ok() in bootstrap.rs. Do not register it unconditionally and return NOT_IMPLEMENTED from the handler. The agent’s tool list should reflect only what is actually callable in the current environment.
The same contract applies to MCP server tools. The default_tool_set() path in wcore-mcp/src/server.rs currently returns an empty set; see the status matrix for the current state of MCP server tool dispatch.
Structural Clippy guards
Section titled “Structural Clippy guards”Two structural lints are enforced in clippy.toml and fail cargo clippy -D warnings in CI.
Egress chokepoint (B1)
Section titled “Egress chokepoint (B1)”Every outbound HTTP request must go through wcore_egress::EgressClient. Constructing a raw reqwest::Client anywhere else in the workspace is banned via disallowed-methods:
disallowed-methods = [ { path = "reqwest::Client::new", reason = "construct via wcore_egress::EgressClient" }, { path = "reqwest::Client::builder", reason = "construct via wcore_egress::EgressClient::builder" }, { path = "reqwest::ClientBuilder::new", reason = "construct via wcore_egress::EgressClient::builder" }, { path = "reqwest::get", reason = "construct via wcore_egress::EgressClient" },]The lint matches by resolved path, so it catches aliased imports (use reqwest::Client as HttpClient) that a text grep would miss. The only sanctioned exceptions are wcore-egress/src/client.rs (which implements the chokepoint) and one self-test in wcore-providers/src/retry.rs; both carry #[allow(clippy::disallowed_methods)] with an explanatory comment.
If you add a new crate that makes outbound HTTP calls, depend on wcore-egress and use EgressClient::builder(). Do not add a new egress exception without a review comment explaining why the egress policy cannot apply.
Shell injection prevention
Section titled “Shell injection prevention”The workspace uses wcore_config::shell helpers for all process spawning. Direct use of Command::new("sh"), Command::new("bash"), or Command::new("cmd") is forbidden. For LLM-supplied arguments, use shell_command_argv(program, &[args]) (argv mode: no shell interpreter, metacharacters in arguments are never interpreted). Use shell_command(str) only for cases where the shell semantics are genuinely required (pipeline, redirection) and never interpolate LLM-supplied data into the string.
Provider quirks: use ProviderCompat, not conditionals
Section titled “Provider quirks: use ProviderCompat, not conditionals”The most important architectural rule in the codebase: do not add hardcoded conditionals that detect a specific provider by URL or name. Instead, add a field to ProviderCompat in wcore-config/src/compat.rs, set its default in the appropriate preset function (e.g. openai_defaults()), and read it in the provider implementation via self.compat.field_name. This keeps all provider differences as config data rather than code branches.
Gating and flagging partial features
Section titled “Gating and flagging partial features”When a capability exists in the codebase but is not yet fully wired to production call sites, it must be gated rather than shipped as a quietly non-functional path.
The patterns used in the codebase:
- Cargo feature flags. Capabilities with significant compile-time deps (OTLP, Landlock, seccomp, chromiumoxide) are off by default and require an explicit
--featuresflag. The feature flag name and its effect must be documented. - Env-gated runtime availability. Tools and channel adapters that need credentials check for them at startup and are absent from the tool list if the credential is missing. This is the NO-STUBS contract applied at runtime.
:::cautionin docs. Any capability that is partial, has a known gap, or behaves differently from its description in some configurations gets a Starlight:::cautioncallout in its documentation page stating the real status. The status matrix is the canonical list.- Source-level
// TODOcomments with tracking references. Stubs in progress reference the task ID (e.g.// W4-TODO: wire to production call sites). A baretodo!()macro in non-test production code is a build failure (the codebase convention is to avoid it; use a typed error return instead).
Dependency graph discipline
Section titled “Dependency graph discipline”Run cargo metadata before adding a new dependency to verify the change fits the dependency graph. The rules:
- Bottom-layer crates (
wcore-types,wcore-compact) must not gain internalwcore-*deps. - Plugin crates (
wayland-*) must not depend onwcore-agent,wcore-tools,wcore-browser, orwcore-cua. Use the mirror types inwcore-plugin-apiinstead. - Circular dependencies fail the build. The
wcore-plugin-apibuild.rslint enforces the plugin isolation boundary.
Unit tests live in #[cfg(test)] blocks in the same file as the code they test. Integration tests live in crates/<crate>/tests/. Write tests from the spec, not from reading the implementation. Every test must check a meaningful behavior or edge case; tests that assert only the happy path without checking boundaries or error conditions are not useful.
Tests that require live external processes (real LLM API, running Chromium, Docker) must be gated behind a feature flag and excluded from the default cargo test run. See the browser-live-tests, live-docker, and harness-failure-injection features in their respective Cargo.toml files for examples of this pattern.
Commit style
Section titled “Commit style”Commit message subject under 72 characters. Body explains the why, not the what. No “update file” or “fix bug” messages. The existing history in the repo shows the expected style; match it. Do not add Co-Authored-By: lines unless the existing history uses them.
Use just push instead of git push to run the full lint-fix + fmt + test cycle before the push lands in CI.