This document defines the code style and linting standards for the StarForge project. All contributors must follow these guidelines to maintain code consistency, quality, and readability across the codebase.
- Overview
- Rust Formatting Standards
- Clippy Lint Rules
- Pre-Commit Developer Checklist
- CI Pipeline Expectations
- IDE/Editor Integration
- Automated Enforcement
- Quick Reference
StarForge uses industry-standard Rust tooling to enforce code quality:
- rustfmt: Automatic code formatting (enforced in CI)
- Clippy: Linter for catching common mistakes and idioms
- cargo deny: Security and license compliance checking
- Custom lint rules: Project-specific suppressions for known limitations
Philosophy: We automate all style enforcement so developers can focus on logic, not formatting. When in doubt, run the tools—they are the source of truth.
All Rust code must be formatted using rustfmt, Rust's official formatter. This is non-negotiable and enforced in CI.
What rustfmt does:
- Normalizes indentation (4 spaces per level)
- Aligns imports and use statements
- Wraps long lines consistently
- Formats comments and doc strings
- Enforces brace placement and spacing
# Format all code in the project
cargo fmt --all
# Check formatting without modifying (useful in CI)
cargo fmt --all --check
# Format a specific file
rustfmt src/main.rs
# Format with specific edition (2021)
cargo fmt -- --edition 2021These are the standards enforced by rustfmt in this project (Rust 2021 edition):
- 4 spaces per indentation level (not tabs)
- No trailing whitespace
// ✅ Correct
fn my_function(x: i32) -> i32 {
if x > 0 {
x + 1
} else {
x - 1
}
}
// ❌ Incorrect (tabs or 2 spaces)
fn my_function(x: i32) -> i32 {
→ if x > 0 {
→ → x + 1- Prefer lines under 100 characters (soft limit)
- Lines may exceed 100 chars if breaking them makes code less readable
- rustfmt will break long lines intelligently
// ✅ Long line that's clear
let result = perform_complex_calculation_with_meaningful_name(arg1, arg2, arg3)?;
// ✅ Broken into multiple lines for clarity
let result = perform_complex_calculation_with_meaningful_name(
very_long_argument_one,
very_long_argument_two,
very_long_argument_three,
)?;
// ❌ Artificially broken for no reason
let result =
perform_calculation(arg1, arg2, arg3)?;- Group imports in this order: std, external crates, internal crates
- Separate groups with blank lines
- Use paths over wildcard imports (except for prelude)
// ✅ Correct grouping
use std::fs;
use std::path::Path;
use anyhow::Result;
use serde::{Deserialize, Serialize};
use crate::utils::config;
use crate::utils::print;
// ❌ Wrong: mixed groups
use anyhow::Result;
use std::fs;
use crate::utils::config;
use serde::Deserialize;
// ⚠️ Minimize wildcard imports (except std/prelude)
use anyhow::*; // Avoid—be explicit| Item | Convention | Example |
|---|---|---|
| Functions | snake_case |
create_wallet() |
| Types/Structs | PascalCase |
WalletConfig |
| Enums | PascalCase |
NetworkType::Testnet |
| Constants | SCREAMING_SNAKE_CASE |
MAX_RETRIES = 3 |
| Lifetimes | 'lowercase |
'a, 'static |
| Modules | snake_case |
mod wallet_manager; |
| Type Parameters | PascalCase |
<T, U> |
// ✅ Correct naming
const MAX_WALLET_SIZE: usize = 100;
struct WalletConfig {
name: String,
encrypted: bool,
}
fn create_wallet(config: WalletConfig) -> Result<Wallet> {
// ...
}
enum NetworkType {
Testnet,
Mainnet,
Development,
}
// ❌ Incorrect naming
const max_wallet_size: usize = 100; // Should be SCREAMING_SNAKE_CASE
struct walletConfig {} // Should be PascalCase
fn CreateWallet() {} // Should be snake_case- Spaces around operators:
let x = a + b;(nota+b) - No space before colons in type annotations:
x: i32(notx : i32) - Space after keywords:
if condition {(notif(condition) {) - Closing braces on same line as opening (K&R style for functions)
// ✅ Correct spacing
fn process(data: Vec<String>) -> Result<Output> {
let x = 5 + 3;
if x > 0 {
process_positive(x)
} else {
process_negative(x)
}
}
// ❌ Incorrect
fn process(data:Vec<String>)->Result<Output>{
let x=5+3;
if(x>0){
process_positive(x);
}else{
process_negative(x);
}
}- Use
//for comments (not/*...*/for line comments) - Use
///for public function/type documentation - Use
//!for module-level documentation - Doc comments must precede items they document
// ✅ Correct doc comment format
/// Fetches account information from the Horizon API.
///
/// This function queries the Stellar Horizon service for the given public key
/// and returns detailed account balance and sequence information.
///
/// # Arguments
///
/// * `public_key` - Stellar public key starting with 'G'
/// * `network` - Network identifier: "testnet" or "mainnet"
///
/// # Returns
///
/// Returns `Ok(AccountData)` with account details, or error if:
/// - Account doesn't exist
/// - Network is unreachable
/// - Response parsing fails
///
/// # Example
///
/// ```
/// let account = fetch_account("GAB...", "testnet")?;
/// println!("Balance: {}", account.balance);
/// ```
pub fn fetch_account(public_key: &str, network: &str) -> Result<AccountData> {
// Implementation...
}
// ✅ Module-level doc comment
//! Network operations for Stellar integration.
//!
//! This module provides abstractions over the Horizon HTTP API
//! for querying account and transaction data.
// ✅ Inline comments explain WHY, not WHAT
// Use shallow clone to minimize memory footprint
git_clone("--depth", "1", &url);
// ❌ Don't state the obvious
// Clone the repository
git_clone(&url);
// ❌ Multiple-line comments for code (use // for consistency)
/* This does something */- Braces always on same line as control structure (Allman style not used in Rust)
- No space before opening brace
- Closing brace on its own line for multi-line blocks
// ✅ Correct
if condition {
do_something();
} else {
do_other_thing();
}
for item in items {
process(item)?;
}
match value {
Some(x) => println!("{}", x),
None => println!("Nothing"),
}
// ❌ Wrong: Allman style not used in Rust
if condition
{
do_something();
}
// ❌ Wrong: no space before brace
if condition{
do_something();
}- Space after generic bounds with
where - Align multiple bounds for readability
// ✅ Correct generic/trait syntax
impl<T: Clone, U: Default> MyType<T, U> {
fn method(&self) -> T {
self.value.clone()
}
}
impl<T> Iterator for MyIterator<T>
where
T: Clone + Debug,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
None
}
}
// ❌ Wrong
impl<T:Clone,U:Default>MyType<T,U>{
fn method(&self)->T{
self.value.clone()
}
}# See what would change (dry-run)
cargo fmt --all -- --check
# Apply all changes
cargo fmt --all
# Force a specific edition (2021)
cargo fmt --edition 2021
# Verbose mode (shows files being formatted)
cargo fmt --all --verboseClippy is the Rust linter that catches common mistakes, suggests idiomatic patterns, and improves performance. In CI, clippy runs with all warnings treated as errors (-D warnings), so clippy violations block merges.
Key principle: Write idiomatic Rust. When clippy complains, it's usually right.
This is a selection of clippy rules you'll encounter in code review:
| Rule | Problem | Fix |
|---|---|---|
must_use_candidate |
Function result often needs checking | Add #[must_use] or document why not |
result_unit_err |
Error type is () |
Use meaningful error types |
unwrap_used |
Called unwrap() in library code |
Use ? operator instead |
expect_used |
Called expect() |
Use ? operator; only use expect() for programmer errors |
// ✅ Good: Using ? operator
pub fn process_file(path: &str) -> Result<Data> {
let contents = std::fs::read_to_string(path)?;
parse_data(&contents)
}
// ❌ Bad: Using unwrap
pub fn process_file(path: &str) -> Result<Data> {
let contents = std::fs::read_to_string(path).unwrap(); // CLIPPY: unwrap_used
Ok(parse_data(&contents).unwrap())
}
// ✅ Good: expect() only for programmer errors
let config = config::load()
.expect("Config should be initialized in main()");
// ✅ Good: add #[must_use] to functions whose return value matters
#[must_use]
pub fn validate_key(key: &str) -> bool {
// ...
}
// ❌ Bad: silently ignoring Result
validate_key(&input); // CLIPPY: must_use_candidate| Rule | Problem | Fix |
|---|---|---|
needless_clone |
Cloning when not needed | Use references instead |
needless_return |
Explicit return on last line |
Remove; let value be implicit |
too_many_arguments |
Function has >7 parameters | Group into struct |
type_complexity |
Type is too complex to read | Extract into type alias |
redundant_closure |
Closure just forwards to function | Use function pointer directly |
// ✅ Good: implicit return
fn calculate(x: i32) -> i32 {
x + 1
}
// ❌ Bad: explicit return on last line
fn calculate(x: i32) -> i32 {
return x + 1; // CLIPPY: needless_return
}
// ✅ Good: no unnecessary cloning
fn process(items: &[String]) {
for item in items {
println!("{}", item);
}
}
// ❌ Bad: clone when not needed
fn process(items: &[String]) {
for item in items.clone() { // CLIPPY: needless_clone
println!("{}", item);
}
}
// ✅ Good: group many parameters
struct Config {
host: String,
port: u16,
timeout: u64,
retries: u32,
}
fn connect(config: &Config) -> Result<()> { }
// ❌ Bad: too many parameters
fn connect(
host: &str,
port: u16,
timeout: u64,
retries: u32,
) -> Result<()> { } // CLIPPY: too_many_arguments| Rule | Problem | Fix |
|---|---|---|
manual_string_new |
Creating empty string inefficiently | Use String::new() |
filter_next |
Using .filter().next() |
Use .find() instead |
map_flatten |
Using .map().flatten() |
Use .flat_map() instead |
comparison_to_empty |
Comparing to empty string/vec | Use .is_empty() |
// ✅ Good: idiomatic
let empty = String::new();
let found = items.find(|x| x.is_valid());
let flattened: Vec<_> = items.flat_map(|x| x.children()).collect();
if name.is_empty() { }
// ❌ Bad: non-idiomatic
let empty = String::from(""); // CLIPPY: manual_string_new
let found = items.filter(|x| x.is_valid()).next(); // CLIPPY: filter_next
let flattened: Vec<_> = items.map(|x| x.children()).flatten().collect(); // CLIPPY: map_flatten
if name == "" { } // CLIPPY: comparison_to_empty# Run clippy with warnings only (non-blocking)
cargo clippy
# Run clippy and deny all warnings (what CI does)
cargo clippy -- -D warnings
# Run clippy on tests
cargo clippy --tests -- -D warnings
# Run clippy with all features
cargo clippy --all-features -- -D warnings
# Auto-fix some issues (when possible)
cargo clippy --fix
# Check specific lint
cargo clippy -- -W clippy::needless_cloneThe StarForge project allows these clippy rules in specific circumstances. See src/main.rs for the global allowlist:
#![allow(
dead_code, // Some plugin infrastructure code is unused until plugins load it
clippy::needless_range_loop, // Sometimes more readable than alternatives
clippy::redundant_closure, // Used intentionally for clarity in some cases
clippy::too_many_arguments, // Complex CLI commands require many arguments
clippy::type_complexity, // Some type definitions are inherently complex
clippy::unnecessary_lazy_evaluations // Some expressions are evaluated for side effects
)]When to add to this allowlist:
- Only for unavoidable patterns
- Document why with a comment
- Discuss with maintainers before merging
#![allow(clippy::too_many_arguments)] // Contract CLI requires many parameters for optimization contextWhen NOT to add:
- "I don't want to refactor" — do the refactor
- "This is faster" — measure it; clippy suggestions are usually equivalent
- "It's more readable" — it probably isn't; clippy catches real issues
When clippy complains, read the full message—it includes:
- The rule name (in brackets):
[clippy::rule_name] - Why it matters: "This is inefficient" or "This is not idiomatic"
- The suggestion: What to change
warning: using `clone` on type `String` which implements `Copy`
--> src/main.rs:42:15
|
42 | let x = s.clone();
| ^^^^^^^^^^ help: try dereferencing: `*s`
|
= note: `#[warn(clippy::clone_on_copy)]` on by default
What to do:
- Read the help text
- Understand WHY it's suggested
- Apply the fix or add
#[allow(clippy::rule_name)]with a comment - Run
cargo clippyagain to verify
Before pushing code, run this checklist locally. It mirrors what CI will run.
#!/bin/bash
# Pre-commit checks before git push
echo "Checking code format..."
cargo fmt --all --check || {
echo "❌ Format check failed. Run: cargo fmt --all"
exit 1
}
echo "Running clippy..."
cargo clippy --all-targets -- -D warnings || {
echo "❌ Clippy failed. Fix warnings or run: cargo clippy --fix"
exit 1
}
echo "Running tests..."
cargo test --locked || {
echo "❌ Tests failed"
exit 1
}
echo "Running smoke tests..."
./scripts/e2e-smoke.sh || {
echo "⚠️ Smoke tests failed (optional, but recommended)"
exit 0
}
echo "✅ All checks passed! Ready to push."# Format everything
cargo fmt --all
# Verify formatting
cargo fmt --all --checkAfter this step, all formatting violations should be fixed. rustfmt is non-negotiable.
# Run clippy and fix what you can automatically
cargo clippy --fix
# Run clippy and report remaining issues
cargo clippy --all-targets -- -D warningsIf clippy reports warnings after --fix, you need to manually address them:
- Read the warning message carefully
- Refactor if possible
- Add
#[allow(...)]with a comment if absolutely necessary - Re-run clippy to confirm
# Run unit and integration tests
cargo test --locked
# Run with verbose output to see failures
cargo test --locked -- --nocapture
# Run tests sequentially if debugging
cargo test --locked -- --test-threads=1All tests must pass locally before pushing. If tests fail in CI, the PR is blocked.
# Run cargo-deny (dependency security/license check)
cargo deny check
# If missing, install it first:
cargo install cargo-deny
cargo deny init # Creates deny.tomlBefore committing:
- Code follows naming conventions (functions:
snake_case, types:PascalCase) - Error messages are clear and actionable
- No
unwrap()orpanic!()in library code (only inmain.rsfor exceptional cases) - Public functions have doc comments
- No debug
println!()statements left in - No commented-out code blocks
- No
TODO/FIXMEwithout context (should reference an issue) - Tests cover normal cases, error cases, and edge cases
# Use semantic commit messages
git commit -m "feat: add wallet encryption support"
# Format: <type>(<scope>): <message>
# Types: feat, fix, docs, style, refactor, test, chore
# Scope: Optional, but recommended (e.g., wallet, network, contract)
# Message: Lowercase, imperative, no periodGood examples:
feat(wallet): add hardware wallet support
fix(deploy): handle large contract files correctly
docs(plugin): update plugin development guide
refactor(config): simplify configuration loading
test(network): add integration tests for Horizon API
chore(deps): update clap to 4.5.0
git push origin feat/your-feature
# Watch CI at:
# https://github.com/Nanle-code/StarForge/actionsCI will re-run:
- rustfmt check (all code must be formatted)
- clippy check (all warnings are errors)
- cargo test (all tests must pass)
- cargo deny (dependency audit)
- smoke tests (E2E command checks)
If any step fails, you'll see a detailed error message. Fix locally and push again.
You can set up a local git hook to run checks automatically:
# Create .git/hooks/pre-commit
#!/bin/bash
set -e
echo "Running pre-commit checks..."
cargo fmt --all --check || {
echo "Format check failed. Run: cargo fmt --all"
exit 1
}
cargo clippy -- -D warnings || exit 1
cargo test --locked || exit 1
echo "✅ Pre-commit checks passed"Make it executable:
chmod +x .git/hooks/pre-commitNow git commit will automatically run these checks. Add --no-verify to skip (not recommended).
This section describes what happens in CI and what will block a PR merge.
File: .github/workflows/ci.yml
The CI pipeline runs on every push and pull request. It consists of these jobs:
name: Rustfmt
runs-on: ubuntu-latest
- cargo fmt --all --checkWhat it checks: All Rust code is formatted according to rustfmt rules.
Failure conditions:
- Any file has formatting differences
- Imports are not grouped correctly
- Lines exceed rustfmt's wrapping preferences
How to fix: cargo fmt --all locally and push again.
cargo fmt --all
git add .
git commit -m "style: apply rustfmt"
git pushname: Cargo Deny
runs-on: ubuntu-latest
- cargo deny checkWhat it checks: Dependencies for:
- Known security vulnerabilities (advisory database)
- Copyleft/restricted licenses (GPL, AGPL)
- Duplicate dependencies
- Unused dependencies
Failure conditions:
- Any dependency has a known CVE
- Any dependency uses a banned license
- Dependency tree is broken or incomplete
How to fix:
- Check the deny.toml file for rules
- Update vulnerable dependencies:
cargo update <crate> - If a license is problematic, discuss with maintainers
- If a denial is incorrect, add an exception to
deny.toml
# View deny configuration
cat deny.toml
# Update specific crate
cargo update clap
# Check again locally
cargo deny checkname: Build, Test & Clippy
runs-on: ubuntu-latest
- cargo build --locked
- cargo test --locked
- cargo clippy --locked -- -D warningsWhat it checks:
- Build: Project compiles without errors
- Tests: All unit and integration tests pass
- Clippy: No clippy warnings (all treated as errors with
-D warnings)
Failure conditions:
- Compilation errors
- Any test fails
- Any clippy warning appears
How to fix:
# Fix compilation
cargo build --locked
# Fix tests
cargo test --locked -- --nocapture # See output
# Fix clippy
cargo clippy --locked -- -D warnings
cargo clippy --fix # Auto-fix when possiblename: CLI Smoke Tests
runs-on: ubuntu-latest
- cargo test --test cli_smoke --locked
- ./scripts/e2e-smoke.shWhat it checks:
- CLI smoke tests (
tests/cli_smoke.rs): Quick checks that main CLI commands work - E2E smoke script (
scripts/e2e-smoke.sh): End-to-end verification of common workflows
Failure conditions:
- Any command returns unexpected exit code
- Any command output is missing expected text
- Script exits with code 1
How to fix:
# Run locally to see what's failing
cargo test --test cli_smoke --locked -- --nocapture
./scripts/e2e-smoke.sh
# Debug with environment variable
STARFORGE_E2E=1 ./scripts/e2e-smoke.sh # Includes network testsPR Status Requirements:
All four CI jobs must pass before a PR can be merged:
- ✅ Rustfmt
- ✅ Cargo Deny
- ✅ Build, Test & Clippy
- ✅ Smoke Tests
If any job fails, you'll see:
- A red ❌ on the PR
- A comment with the failure log
- A link to the CI run
View CI logs:
PR → "Checks" tab → Click job name → Scroll to see failure
Re-running CI:
- Push a new commit to update the PR
- Click "Re-run jobs" button if the failure was environmental (e.g., network timeout)
Common CI failures and fixes:
| Failure | Cause | Fix |
|---|---|---|
cargo fmt --all --check |
Code not formatted | cargo fmt --all |
clippy warnings |
Non-idiomatic code | cargo clippy --fix or refactor manually |
cargo test fails |
Test assertion failed | Debug with cargo test -- --nocapture |
cargo deny fails |
Vulnerable/restricted dependency | Update dependency or add exception |
| Smoke test fails | Command output unexpected | Run locally with ./scripts/e2e-smoke.sh |
| Timeout (>10 min) | Slow test or network issue | Can click "Re-run jobs" |
CI uses --locked flag for deterministic builds:
# Uses exact versions from Cargo.lock
cargo build --locked
cargo test --locked
cargo clippy --lockedThis ensures:
- Same versions across environments
- No surprise breaking changes from dependencies
- Reproducible builds
When to update Cargo.lock:
# Update specific dependency
cargo update clap
# Or update all
cargo update
# Commit the updated Cargo.lock
git add Cargo.lock
git commit -m "chore: update dependencies"-
Install Rust Analyzer extension:
- Open VS Code Extensions (Ctrl+Shift+X)
- Search for "Rust-analyzer"
- Click "Install"
-
Install rustfmt and clippy (if not already installed):
rustup component add rustfmt clippy
-
Configure settings (
.vscode/settings.jsonin project root):
{
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.clippy": "explicit"
}
},
"rust-analyzer.checkOnSave.command": "clippy",
"rust-analyzer.checkOnSave.extraArgs": ["--all-targets", "--", "-D", "warnings"],
"rust-analyzer.diagnostics.enableExperimental": true,
"rust-analyzer.inlayHints.maxLength": 80,
"rust-analyzer.assist.emitMustUse": true
}This configuration:
- Formats on save using rustfmt
- Runs clippy on save and shows violations
- Enables experimental diagnostics
- Shows type hints inline
- Format:
Shift+Alt+F(or manualcargo fmt) - Hover for diagnostics: Hover over red squiggly lines
- Quick fixes:
Ctrl+.when cursor is on warning - Go to definition:
F12orCtrl+Click - Find usages:
Shift+Alt+F12
{
"recommendations": [
"rust-lang.rust-analyzer",
"serayuzgur.crates",
"bungcip.better-toml",
"usernamehw.errorlens",
"vadimcn.vscode-lldb"
]
}Save as .vscode/extensions.json, then use "Extensions: Show Recommended" command.
-
Install Rust plugin:
- Settings → Plugins → Search "Rust"
- Install "Rust" by JetBrains
-
Enable formatters and linters:
- Settings → Languages & Frameworks → Rust → Rustfmt
- Check "Run rustfmt on Save"
-
Enable Clippy:
- Settings → Languages & Frameworks → Rust → Clippy
- Check "Run external linter"
- Check "Show warnings"
-
Configure code style:
- Settings → Editor → Code Style → Rust
- Ensure 4-space indentation is set
- Match inspection settings to clippy
- Format:
Ctrl+Alt+L(orCmd+Alt+Lon macOS) - Run inspections:
Ctrl+Alt+I - Fix with intention:
Alt+Enteron error - Run Clippy: Tools → Run External Tools → Clippy
Install rust-analyzer (if not already via rustup):
rustup component add rust-analyzerFor vim-lsp users:
" .vimrc or init.vim
if executable('rust-analyzer')
augroup lsp
autocmd!
autocmd User lsp_setup call lsp#register_server({
\ 'name': 'rust-analyzer',
\ 'cmd': {server_info->['rust-analyzer']},
\ 'workspace_config': {'rust': {'checkOnSave': {'command': 'clippy'}}},
\ 'allowlist': ['rust'],
\ })
augroup end
endifFor Neovim with nvim-lspconfig:
-- init.lua
local nvim_lsp = require('lspconfig')
nvim_lsp.rust_analyzer.setup {
settings = {
['rust-analyzer'] = {
checkOnSave = {
command = 'clippy',
extraArgs = { '--all-targets', '--', '-D', 'warnings' }
}
}
}
}
-- Auto format on save
vim.api.nvim_create_autocmd('BufWritePre', {
pattern = '*.rs',
callback = function()
vim.lsp.buf.format { async = false }
end
})Configure Vim/Neovim to run cargo fmt and cargo clippy as external tools:
" Format with :Fmt
command! Fmt execute '!cargo fmt --all' | edit
" Lint with :Lint
command! Lint execute '!cargo clippy -- -D warnings'
" Run tests with :Test
command! Test execute '!cargo test'For Emacs users with rustic-mode:
(use-package rustic
:ensure t
:init
(setq rustic-linter 'clippy
rustic-format-on-save t
rustic-lsp-client 'lsp-mode))
;; Optional: keybindings
(define-key rustic-mode-map (kbd "M-f") #'rustic-format-buffer)
(define-key rustic-mode-map (kbd "C-c C-c l") #'rustic-compile)To prevent committing code that fails checks, set up git hooks:
# Create pre-commit hook
mkdir -p .git/hooks
cat > .git/hooks/pre-commit << 'EOF'
#!/bin/bash
set -e
echo "Checking format..."
cargo fmt --all --check || {
echo "Format check failed. Run: cargo fmt --all"
exit 1
}
echo "Running clippy..."
cargo clippy -- -D warnings || {
echo "Clippy violations found."
exit 1
}
echo "Running unit tests..."
cargo test --lib || exit 1
echo "Pre-commit checks passed!"
EOF
chmod +x .git/hooks/pre-commitNow git commit will automatically run checks. To skip (not recommended):
git commit --no-verifyIf CI fails on a flaky test or timeout:
- Go to the PR on GitHub
- Click "Checks" or "Details" on a failed job
- Click "Re-run jobs" button
This re-runs all CI jobs without requiring a new commit.
Before pushing, run all CI checks locally:
#!/bin/bash
# Run all CI checks locally
echo "Simulating CI pipeline..."
echo "1/4: Checking format..."
cargo fmt --all --check || exit 1
echo "2/4: Running cargo deny..."
cargo deny check || exit 1
echo "3/4: Building, testing, and clippy..."
cargo build --locked || exit 1
cargo test --locked || exit 1
cargo clippy --locked -- -D warnings || exit 1
echo "4/4: Running smoke tests..."
cargo test --test cli_smoke --locked || exit 1
./scripts/e2e-smoke.sh || exit 1
echo "All CI checks passed locally!"Save as scripts/ci-check.sh and run before pushing:
chmod +x scripts/ci-check.sh
./scripts/ci-check.sh# Format all code
cargo fmt --all
# Check if formatted (no changes)
cargo fmt --all --check
# Run linter with auto-fix
cargo clippy --fix
# Run linter (deny warnings)
cargo clippy -- -D warnings
# Run all tests
cargo test --locked
# Run specific test
cargo test wallet_create -- --nocapture
# Build everything
cargo build --locked
# Security/license check
cargo deny check
# Run pre-commit checklist
cargo fmt --all --check && cargo clippy -- -D warnings && cargo test --locked
# Full CI simulation
./scripts/ci-check.sh| Issue | Solution |
|---|---|
| Code not formatted in CI | cargo fmt --all and commit |
| Clippy warnings block PR | cargo clippy --fix or manually refactor |
| Tests fail locally but pass in CI | Run with --locked: cargo test --locked |
| Slow compilation | Use sccache: export RUSTC_WRAPPER=sccache |
| "denied by Cargo.lock" | Run cargo update and commit Cargo.lock |
| IDE not showing errors | Reload Rust-analyzer: Ctrl+Shift+P → "Reload Window" |
| Aspect | Standard | Enforced By |
|---|---|---|
| Formatting | 4-space indent, rustfmt rules | rustfmt + CI |
| Naming | snake_case functions, PascalCase types |
Clippy + code review |
| Line length | ~100 chars (soft) | Manual review |
| Error handling | Result<T> and ? operator |
Clippy + code review |
| Doc comments | /// on public items |
Manual review |
| Imports | Grouped (std, external, internal) | rustfmt + manual review |
| Tests | Unit + integration, all must pass | CI test job |
| Dependencies | No vulnerable/copyleft crates | Cargo Deny |
StarForge Code Standards at a Glance:
- Run
cargo fmt --allafter every change (non-negotiable) - Run
cargo clippy -- -D warningsand fix warnings before pushing - Run
cargo test --lockedto ensure tests pass - Check your IDE is configured to format and lint on save
- Read CI failure messages carefully—they tell you exactly what to fix
- Use the pre-commit checklist before pushing to avoid CI failures
Philosophy: We automate everything so you can focus on logic, not style. When in doubt, run the tools—they're the source of truth.
For questions or suggestions about these standards, open a GitHub issue or discussion.
Last Updated: 2025-06-01
Maintainer: StarForge Core Team
Related Documents: CONTRIBUTING.md, DEVELOPER_GUIDE.md, ARCHITECTURE.md