Coding Standards
ClawDesk follows idiomatic Rust conventions with project-specific patterns. All code must pass cargo clippy --workspace and cargo fmt --check before merge.
Rust Edition & Toolchain
- Edition: 2024
- Minimum Rust version: 1.91
- License: MIT
- Author: Sushanth
Error Handling
Use thiserror for library error types and anyhow for application-level (binary) code.
Library Crates
use thiserror::Error;
#[derive(Debug, Error)]
pub enum ChannelError {
#[error("connection failed: {0}")]
ConnectionFailed(String),
#[error("rate limited, retry after {retry_after_secs}s")]
RateLimited { retry_after_secs: u64 },
#[error("authentication failed")]
AuthFailed,
#[error(transparent)]
Storage(#[from] StorageError),
}
Binary Crates
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = load_config()
.context("failed to load configuration")?;
Ok(())
}
Never use unwrap() or expect() in library code. Use ? propagation or explicit error handling. Panics via unwrap are only acceptable in tests and main().
Naming Conventions
| Item | Convention | Example |
|---|---|---|
| Crates | clawdesk-{name} kebab-case | clawdesk-agents |
| Modules | snake_case | rate_limit, context_guard |
| Types | PascalCase | SessionStore, ChannelError |
| Traits | PascalCase, noun/adjective | Channel, Streaming, Threaded |
| Functions | snake_case, verb-first | send_message(), build_context() |
| Constants | SCREAMING_SNAKE_CASE | MAX_RETRIES, DEFAULT_TIMEOUT |
| Type parameters | Single uppercase letter or descriptive | T, S: SessionStore |
Module Structure
Each crate follows a consistent layout:
clawdesk-{name}/
├── Cargo.toml
├── src/
│ ├── lib.rs # Public API re-exports
│ ├── error.rs # Crate-specific error types
│ ├── {module}.rs # Feature modules
│ └── tests/ # Integration tests (if needed)
└── tests/
└── integration.rs # Cross-module integration tests
lib.rs Pattern
//! # clawdesk-channel
//!
//! Channel abstraction layer for ClawDesk.
//! Defines the core `Channel` trait and optional Layer 1 capabilities.
mod error;
mod health;
mod rate_limit;
mod registry;
pub use error::ChannelError;
pub use health::HealthCheck;
pub use rate_limit::RateLimiter;
pub use registry::ChannelRegistry;
Dependency Rules
ClawDesk uses a hexagonal architecture with strict dependency direction:
clawdesk-types— Shared types, depended on by everything- Domain crates (
domain,channel,agents) — Depend only ontypes - Infrastructure crates (
storage,sochdb,providers) — Implement domain traits - Application crates (
gateway,cli,tauri) — Wire everything together
Domain crates must never depend on infrastructure crates. Use trait-based dependency inversion.
Async Patterns
- Use
tokioas the async runtime (multi-threaded) - Prefer
async fnover manualFutureimplementations - Use
async-traitfor async trait methods - Cancellation safety: document whether functions are cancel-safe
use async_trait::async_trait;
#[async_trait]
pub trait SessionStore: Send + Sync {
async fn get(&self, id: &SessionId) -> Result<Option<Session>, StorageError>;
async fn save(&self, session: &Session) -> Result<(), StorageError>;
}
Documentation
- All public items must have doc comments (
///) - Module-level docs use
//! - Include
# Examplessections for non-trivial APIs - Use
# Errorssections to document error conditions
/// Sends a message through the specified channel.
///
/// # Arguments
///
/// * `channel` - The target channel identifier
/// * `message` - The message payload to deliver
///
/// # Errors
///
/// Returns [`ChannelError::ConnectionFailed`] if the channel is unreachable.
/// Returns [`ChannelError::RateLimited`] if the send rate exceeds limits.
pub async fn send_message(
channel: &ChannelId,
message: &Message,
) -> Result<(), ChannelError> {
// ...
}
Clippy Lints
The workspace enables these clippy lint groups:
[workspace.lints.clippy]
pedantic = "warn"
nursery = "warn"
unwrap_used = "deny"
expect_used = "deny"
Common lint fixes:
| Lint | Fix |
|---|---|
clippy::module_name_repetitions | Allow with #[allow()] where clarity requires it |
clippy::missing_errors_doc | Add # Errors doc section |
clippy::must_use_candidate | Add #[must_use] or suppress if appropriate |
Formatting
Run cargo fmt before every commit. The project uses default rustfmt settings with one override:
# rustfmt.toml
max_width = 100
use_field_init_shorthand = true
Imports
Order imports by group, separated by blank lines:
// 1. Standard library
use std::collections::HashMap;
use std::sync::Arc;
// 2. External crates
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. Workspace crates
use clawdesk_types::{ChannelId, Message};
// 4. Local modules
use crate::registry::ChannelRegistry;
Release Profile
The workspace release profile is optimized for production:
[profile.release]
lto = "fat"
strip = true
panic = "abort"
This produces smaller, faster binaries but increases compile times.