Contributing

Guidelines for contributing to SystemPrompt

Thank you for your interest in contributing to SystemPrompt. This guide covers our development process and coding standards.

Getting Started

Prerequisites

  • Rust 1.75+
  • Docker and Docker Compose
  • Git
  • Just command runner (optional)

Setup

# Clone the repository
git clone --recursive https://github.com/systempromptio/systemprompt-core
cd systemprompt-core

# Start database
docker-compose up -d

# Run migrations
cargo run -p systemprompt-cli -- infra db migrate

# Build all crates
cargo build

# Run tests
cargo test

Code Organization

Crate Structure

SystemPrompt uses a layered crate architecture:

crates/
├── shared/          # Core types (models, traits, identifiers)
├── infrastructure/  # Database, logging, config, events
├── domain/          # Business logic (users, agents, mcp)
├── application/     # Runtime, scheduler, generator
├── entry/           # API server, CLI
└── facade/          # Public API facade

Dependency Rules

  1. Dependencies flow downward only
  2. Same-layer crates communicate via traits
  3. No circular dependencies
  4. Extensions in downstream projects, not core

Coding Standards

Rust Guidelines

Required:

// Use explicit error types
fn process() -> Result<Output, ProcessError> { ... }

// Handle all errors
let value = operation().map_err(|e| ProcessError::Operation(e))?;

// Document public items
/// Processes the input and returns formatted output.
///
/// # Errors
/// Returns `ProcessError::InvalidInput` if input is malformed.
pub fn process(input: &str) -> Result<String, ProcessError> { ... }

Forbidden (lint denies):

// Never use unwrap
let value = option.unwrap();  // DENIED

// Never panic
panic!("unexpected");  // DENIED

// Never use todo
todo!("implement later");  // DENIED

SQL Guidelines

Required:

  • All SQL in .sql files
  • Compile-time verification via SQLx
  • Parameterized queries only
// Correct: SQL in file, compile-time verified
let users = sqlx::query_file_as!(User, "queries/get_users.sql")
    .fetch_all(&pool)
    .await?;

// Wrong: Inline SQL string
let users = sqlx::query_as!(User, "SELECT * FROM users")  // AVOID

Error Handling

Use thiserror for error types:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ServiceError {
    #[error("database error: {0}")]
    Database(#[from] sqlx::Error),

    #[error("not found: {0}")]
    NotFound(String),

    #[error("validation failed: {0}")]
    Validation(String),
}

Pull Request Process

Before Submitting

  1. Run all checks:

    cargo fmt --all
    cargo clippy --all-targets --all-features
    cargo test --all
    
  2. Update documentation if changing public API

  3. Add tests for new functionality

  4. Update CHANGELOG with your changes

PR Guidelines

  • Keep PRs focused on a single change
  • Write descriptive commit messages
  • Reference related issues
  • Respond to review feedback promptly

Commit Messages

Follow conventional commits:

type(scope): description

feat(mcp): add tool timeout configuration
fix(auth): handle expired refresh tokens
docs(readme): update installation instructions
refactor(database): simplify connection pooling
test(agents): add integration tests for messaging

Types: feat, fix, docs, refactor, test, chore

Testing

Unit Tests

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validation() {
        let input = "valid";
        assert!(validate(input).is_ok());
    }

    #[test]
    fn test_invalid_input() {
        let input = "";
        assert!(matches!(
            validate(input),
            Err(ValidationError::Empty)
        ));
    }
}

Integration Tests

#[tokio::test]
async fn test_user_creation() {
    let pool = test_database().await;
    let service = UserService::new(pool);

    let user = service.create("test@example.com").await.unwrap();

    assert_eq!(user.email, "test@example.com");
}

Running Tests

# All tests
cargo test

# Specific crate
cargo test -p systemprompt-database

# With output
cargo test -- --nocapture

# Integration tests only
cargo test --test '*'

Documentation

Code Documentation

Document all public items:

/// A user in the system.
///
/// Users can authenticate via OAuth or WebAuthn and have
/// associated roles that determine their permissions.
///
/// # Example
///
/// ```rust
/// let user = User::new("user@example.com");
/// assert!(user.is_active());
/// ```
pub struct User {
    /// Unique identifier.
    pub id: UserId,
    /// Email address (unique).
    pub email: String,
    /// Display name.
    pub name: Option<String>,
}

Playbooks

Update playbooks when changing workflows:

# View playbook
systemprompt core playbooks show build_extension-checklist

# Sync after changes
systemprompt core playbooks sync --direction to-db -y

Extension Development

When building extensions:

  1. Create crate in extensions/ directory
  2. Implement required traits
  3. Register with register_extension!
  4. Add to workspace Cargo.toml

See Building Extensions for details.

Getting Help

License

By contributing, you agree that your contributions will be licensed under the FSL-1.1-ALv2 license.