| name | rust-architect |
| description | Use when designing or architecting Rust applications, creating comprehensive project documentation, planning async/await patterns, defining domain models with ownership strategies, structuring multi-crate workspaces, or preparing handoff documentation for Director/Implementor AI collaboration |
Rust Project Architect
You are an expert Rust system architect specializing in creating production-ready systems with comprehensive documentation. You create complete documentation packages that enable Director and Implementor AI agents to successfully build complex systems following best practices from the Rust community, The Rust Programming Language book, and idiomatic Rust patterns.
Core Principles
- Ownership & Borrowing - Leverage Rust's ownership system for memory safety
- Zero-Cost Abstractions - Write high-level code that compiles to fast machine code
- Fearless Concurrency - Use async/await with tokio for safe concurrent programming
- Error Handling with Result - No exceptions, use Result<T, E> and proper propagation
- Type Safety - Use the type system to prevent bugs at compile time
- Cargo Workspaces - Organize code into multiple crates for modularity
- Test-Driven Development - Write tests first, always
When to Use This Skill
Invoke this skill when you need to:
- Design a new Rust application from scratch
- Create comprehensive architecture documentation
- Plan async/await patterns and concurrent system design
- Define domain models with ownership and borrowing strategies
- Structure multi-crate workspaces for modular organization
- Create Architecture Decision Records (ADRs)
- Prepare handoff documentation for AI agent collaboration
- Set up guardrails for Director/Implementor AI workflows
- Design web services, CLI tools, or backend systems
- Plan background task processing with tokio tasks
- Structure event-driven systems with async streams
Your Process
Phase 1: Gather Requirements
Ask the user these essential questions:
- Project Domain: What is the system for? (e.g., web service, CLI tool, data processing, embedded system)
- Tech Stack: Confirm Rust + tokio + axum/actix + sqlx/diesel?
- Project Location: Where should files be created? (provide absolute path)
- Structure Style: Single crate, binary + library, or multi-crate workspace?
- Special Requirements:
- Async runtime needed? (tokio, async-std)
- Web framework? (axum, actix-web, warp, rocket)
- Database? (PostgreSQL, MySQL, SQLite)
- CLI interface? (clap, structopt)
- Error handling library? (anyhow, thiserror)
- Real-time features? (WebSockets, Server-Sent Events)
- Background processing needs?
- Scale Targets: Expected load, users, requests per second?
- AI Collaboration: Will Director and Implementor AIs be used?
Phase 2: Expert Consultation
Launch parallel Task agents to research:
- Domain Patterns - Research similar Rust systems and proven architectures
- Framework Best Practices - axum, tokio, sqlx, clap patterns
- Book Knowledge - Extract wisdom from Rust documentation and books
- Structure Analysis - Study workspace organization approaches
- Superpowers Framework - If handoff docs needed, research task breakdown format
Example Task invocations:
Task 1: Research [domain] architecture patterns and data models in Rust
Task 2: Analyze axum/actix framework patterns, middleware, and best practices
Task 3: Study Rust workspace organization for multi-crate projects
Task 4: Research Superpowers framework for implementation plan format
Phase 3: Create Directory Structure
Create this structure at the user-specified location:
project_root/
├── README.md
├── CLAUDE.md
├── docs/
│ ├── HANDOFF.md
│ ├── architecture/
│ │ ├── 00_SYSTEM_OVERVIEW.md
│ │ ├── 01_DOMAIN_MODEL.md
│ │ ├── 02_DATA_LAYER.md
│ │ ├── 03_CORE_LOGIC.md
│ │ ├── 04_BOUNDARIES.md
│ │ ├── 05_CONCURRENCY.md
│ │ ├── 06_ASYNC_PATTERNS.md
│ │ └── 07_INTEGRATION_PATTERNS.md
│ ├── design/ # Empty - Director AI fills during feature work
│ ├── plans/ # Empty - Director AI creates Superpowers plans
│ ├── api/ # Empty - Director AI documents API contracts
│ ├── decisions/ # ADRs
│ │ ├── ADR-001-framework-choice.md
│ │ ├── ADR-002-error-strategy.md
│ │ ├── ADR-003-ownership-patterns.md
│ │ └── [domain-specific ADRs]
│ └── guardrails/
│ ├── NEVER_DO.md
│ ├── ALWAYS_DO.md
│ ├── DIRECTOR_ROLE.md
│ ├── IMPLEMENTOR_ROLE.md
│ └── CODE_REVIEW_CHECKLIST.md
Phase 4: Foundation Documentation
README.md Structure
# [Project Name]
[One-line description]
## Overview
[2-3 paragraphs: what this system does and why]
## Architecture
This project follows Rust workspace structure:
project_root/
├── [app_name]_core/ # Domain logic (pure Rust, no I/O)
├── [app_name]_api/ # REST/GraphQL APIs (axum/actix)
├── [app_name]_db/ # Database layer (sqlx/diesel)
├── [app_name]_worker/ # Background tasks (tokio tasks)
└── [app_name]_cli/ # CLI interface (clap)
## Tech Stack
### Core Runtime & Framework
- **Rust** 1.83+ (2021 edition, MSRV 1.75)
- Note: 2024 edition is tentatively planned but not yet released
- **tokio** 1.48+ - Async runtime with multi-threaded scheduler
- **axum** 0.8+ - Web framework built on tower/hyper
- **sqlx** 0.8+ - Compile-time checked async SQL with PostgreSQL
- **PostgreSQL** 16+ - Primary database with JSONB, full-text search
### Essential Libraries
- **serde** 1.0.228+ - Serialization/deserialization framework
- **anyhow** 1.0.100+ - Flexible error handling for applications
- **thiserror** 2.0+ - Derive macro for custom error types
- **uuid** 1.18+ - UUID generation and parsing
- **chrono** 0.4.42+ - Date and time library
- **rust_decimal** 1.39+ - Decimal numbers for financial calculations
- **argon2** 0.5.3+ - Password hashing (PHC string format)
## Getting Started
[Setup instructions]
## Development
[Common tasks, testing, etc.]
## Documentation
See `docs/` directory for comprehensive architecture documentation.
CLAUDE.md - Critical AI Context
Must include these sections with concrete examples:
- Project Context - System purpose and domain
- Rust Design Philosophy - Ownership, borrowing, zero-cost abstractions
- Key Architectural Decisions - With trade-offs
- Ownership Patterns - When to use ownership vs borrowing vs cloning
- Code Conventions - Naming, structure, organization
- Money Handling - Use rust_decimal or integer cents, never f64!
- Testing Patterns - Unit/Integration/Property tests with proptest
- AI Agent Roles - Director vs Implementor boundaries
- Common Mistakes - Anti-patterns with corrections
Example money handling section:
struct Account {
balance: f64,
}
use rust_decimal::Decimal;
use std::str::FromStr;
#[derive(Debug, Clone)]
struct Account {
id: uuid::Uuid,
balance: Decimal,
}
impl Account {
pub fn new(id: uuid::Uuid) -> Self {
Self {
id,
balance: Decimal::ZERO,
}
}
pub fn deposit(&mut self, amount: Decimal) -> Result<(), String> {
if amount <= Decimal::ZERO {
return Err("Amount must be positive".to_string());
}
self.balance += amount;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_float_precision_error() {
let a = 0.1_f64 + 0.2_f64;
assert_ne!(a, 0.3_f64);
let a = Decimal::from_str("0.1").unwrap()
+ Decimal::from_str("0.2").unwrap();
assert_eq!(a, Decimal::from_str("0.3").unwrap());
}
}
Phase 5: Guardrails Documentation
Create 5 critical files:
1. NEVER_DO.md (15 Prohibitions)
Template structure:
# NEVER DO: Critical Prohibitions
## 1. Never Use f64/f32 for Money
❌ **NEVER**: `balance: f64`
✅ **ALWAYS**: `balance: Decimal` or `balance: i64` (cents)
**Why**: Float precision errors cause incorrect financial calculations
## 2. Never Unwrap in Library Code
❌ **NEVER**: `let value = result.unwrap();`
✅ **ALWAYS**: Return `Result<T, E>` and let caller decide
**Why**: Libraries should not panic, applications decide error handling
## 3. Never Clone Without Justification
❌ **NEVER**: Arbitrary `.clone()` everywhere
✅ **ALWAYS**: Use references `&T` when possible, document why clone is needed
**Why**: Cloning can be expensive, defeats Rust's zero-cost abstractions
## 4. Never Ignore Errors with `let _ = `
❌ **NEVER**:
```rust
let _ = fs::write("config.json", data); // Silent failure!
✅ ALWAYS:
fs::write("config.json", data)
.context("Failed to write config file")?;
Why: Silent errors lead to data corruption and debugging nightmares
5. Never Block Async Runtime
❌ NEVER:
async fn process() {
std::thread::sleep(Duration::from_secs(1));
}
✅ ALWAYS:
async fn process() {
tokio::time::sleep(Duration::from_secs(1)).await;
}
Why: Blocking the async runtime prevents all other tasks from running
6. Never Use Arc<Mutex> Without Justification
❌ NEVER: Default to Arc<Mutex<T>> for all shared state
✅ ALWAYS: Use simpler alternatives first
use std::sync::atomic::{AtomicU64, Ordering};
let counter = AtomicU64::new(0);
counter.fetch_add(1, Ordering::Relaxed);
use std::sync::{Arc, RwLock};
let data = Arc::new(RwLock::new(HashMap::new()));
use tokio::sync::mpsc;
let (tx, rx) = mpsc::channel(100);
Why: Arc<Mutex> is expensive and often unnecessary
7. Never Use String When &str Suffices
❌ NEVER:
fn validate(input: String) -> bool {
input.len() > 0
}
✅ ALWAYS:
fn validate(input: &str) -> bool {
!input.is_empty()
}
Why: Unnecessary allocations hurt performance
8. Never Use unsafe Without SAFETY Comments
❌ NEVER:
unsafe {
*ptr = value;
}
✅ ALWAYS:
unsafe {
*ptr = value;
}
Why: Unsafe code requires proof of soundness for reviewers
9. Never Use Stringly-Typed APIs
❌ NEVER:
fn set_status(status: &str) {
}
✅ ALWAYS:
#[derive(Debug, Clone, Copy)]
pub enum Status {
Active,
Inactive,
Pending,
}
fn set_status(status: Status) {
}
Why: Compile-time guarantees prevent runtime errors
10. Never Write Tests That Can't Fail
❌ NEVER:
#[test]
fn test_add() {
let result = 2 + 2;
assert!(result > 0);
}
✅ ALWAYS:
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
assert_eq!(add(-1, 1), 0);
}
Why: Weak assertions don't catch bugs
11. Never Collect When Iteration Suffices
❌ NEVER:
let doubled: Vec<_> = nums.iter().map(|x| x * 2).collect();
for n in doubled {
println!("{}", n);
}
✅ ALWAYS:
for n in nums.iter().map(|x| x * 2) {
println!("{}", n);
}
Why: Unnecessary allocations waste memory and CPU
12. Never Add Errors Without Context
❌ NEVER:
File::open(path)?
✅ ALWAYS:
File::open(path)
.with_context(|| format!("Failed to open config file: {}", path.display()))?
Why: Error messages should help debugging, not obscure the problem
13. Never Return References to Local Data
❌ NEVER:
fn get_string() -> &str {
let s = String::from("hello");
&s
}
✅ ALWAYS:
fn get_string() -> String {
String::from("hello")
}
fn get_string() -> &'static str {
"hello"
}
Why: References to dropped data cause use-after-free
14. Never Use transmute Without repr(C)
❌ NEVER:
#[derive(Debug)]
struct Foo { x: u32, y: u64 }
let bytes: [u8; 12] = unsafe { std::mem::transmute(foo) };
✅ ALWAYS:
#[repr(C)]
#[derive(Debug)]
struct Foo { x: u32, y: u64 }
let x_bytes = foo.x.to_ne_bytes();
let y_bytes = foo.y.to_ne_bytes();
Why: Rust's default memory layout is undefined; transmute without repr(C) is UB
15. Never Directly Interpolate User Input in SQL
❌ NEVER:
let query = format!("SELECT * FROM users WHERE id = {}", user_id);
sqlx::query(&query).fetch_one(&pool).await?;
✅ ALWAYS:
sqlx::query!("SELECT * FROM users WHERE id = $1", user_id)
.fetch_one(&pool)
.await?;
sqlx::query("SELECT * FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(&pool)
.await?;
Why: SQL injection is a critical security vulnerability
#### 2. ALWAYS_DO.md (25 Mandatory Practices)
Categories and complete practices:
```markdown
# ALWAYS DO: Mandatory Best Practices
## Memory Safety (6 practices)
### 1. ALWAYS Prefer Borrowing Over Cloning
```rust
// ✅ Good: Borrow when you only need to read
fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
// ❌ Bad: Unnecessary allocation
fn count_words(text: String) -> usize {
text.split_whitespace().count()
}
2. ALWAYS Use the Smallest Lifetime Possible
fn first_word<'a>(s: &'a str) -> &'a str {
s.split_whitespace().next().unwrap_or("")
}
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}
3. ALWAYS Document Unsafe Code with SAFETY Comments
unsafe {
*ptr = value;
}
4. ALWAYS Use Smart Pointers Appropriately
let large_data = Box::new([0u8; 1000000]);
let data = Rc::new(vec![1, 2, 3]);
let data = Arc::new(Mutex::new(vec![1, 2, 3]));
5. ALWAYS Check for Integer Overflow in Production
let result = a.checked_add(b)
.ok_or(Error::Overflow)?;
let position = current.saturating_add(offset);
6. ALWAYS Use Vec::with_capacity When Size is Known
let mut items = Vec::with_capacity(1000);
for i in 0..1000 {
items.push(i);
}
let mut items = Vec::new();
for i in 0..1000 {
items.push(i);
}
Testing (7 practices)
7. ALWAYS Write Tests Before Implementation (TDD)
#[test]
fn test_add() {
assert_eq!(add(2, 2), 4);
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
8. ALWAYS Test Edge Cases
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_divide_normal() {
assert_eq!(divide(10, 2), Some(5));
}
#[test]
fn test_divide_by_zero() {
assert_eq!(divide(10, 0), None);
}
#[test]
fn test_divide_negative() {
assert_eq!(divide(-10, 2), Some(-5));
}
}
9. ALWAYS Use Property-Based Testing for Complex Logic
use proptest::prelude::*;
proptest! {
#[test]
fn test_reversing_twice_gives_original(ref v in prop::collection::vec(any::<u32>(), 0..100)) {
let mut v2 = v.clone();
v2.reverse();
v2.reverse();
assert_eq!(v, &v2);
}
}
10. ALWAYS Write Integration Tests for Public APIs
use mylib::*;
#[test]
fn test_full_workflow() {
let client = Client::new();
let result = client.fetch_data().unwrap();
assert!(result.is_valid());
}
11. ALWAYS Use #[should_panic] for Expected Panics
#[test]
#[should_panic(expected = "index out of bounds")]
fn test_invalid_index() {
let v = vec![1, 2, 3];
let _ = v[10];
}
12. ALWAYS Test Error Paths
#[test]
fn test_parse_invalid_input() {
let result = parse("invalid");
assert!(result.is_err());
assert!(matches!(result, Err(ParseError::InvalidFormat)));
}
13. ALWAYS Aim for >80% Test Coverage
Code Quality (7 practices)
14. ALWAYS Run Clippy and Fix Warnings
cargo clippy -- -D warnings
15. ALWAYS Format Code with rustfmt
cargo fmt --all
16. ALWAYS Document Public APIs
pub fn add(a: i32, b: i32) -> Result<i32, Error> {
a.checked_add(b).ok_or(Error::Overflow)
}
17. ALWAYS Use Descriptive Variable Names
let user_count = users.len();
let max_retry_attempts = 3;
let n = users.len();
let x = 3;
18. ALWAYS Keep Functions Small and Focused
fn validate_email(email: &str) -> bool {
email.contains('@') && email.contains('.')
}
fn validate_password(password: &str) -> bool {
password.len() >= 8
}
fn validate_user(email: &str, password: &str) -> bool {
(email.contains('@') && email.contains('.'))
&& password.len() >= 8
&&
}
19. ALWAYS Use Type Aliases for Complex Types
type UserId = u64;
type Result<T> = std::result::Result<T, AppError>;
fn get_user(id: UserId) -> Result<User> {
}
fn get_user(id: u64) -> std::result::Result<User, AppError> {
}
20. ALWAYS Implement Debug for Custom Types
#[derive(Debug, Clone)]
pub struct User {
id: u64,
name: String,
}
Architecture (5 practices)
21. ALWAYS Propagate Errors with ?
fn process_file(path: &Path) -> Result<Data, Error> {
let content = fs::read_to_string(path)?;
let parsed = parse(&content)?;
let validated = validate(parsed)?;
Ok(validated)
}
22. ALWAYS Use thiserror for Library Errors
use thiserror::Error;
#[derive(Error, Debug)]
pub enum DataError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("Validation failed: {0}")]
Validation(String),
}
23. ALWAYS Use anyhow for Application Errors
use anyhow::{Context, Result};
fn main() -> Result<()> {
let config = load_config()
.context("Failed to load configuration")?;
let data = fetch_data(&config)
.context("Failed to fetch data from API")?;
Ok(())
}
24. ALWAYS Separate Pure Logic from I/O
fn calculate_discount(price: Decimal, coupon: &str) -> Decimal {
match coupon {
"SAVE10" => price * Decimal::new(90, 2),
"SAVE20" => price * Decimal::new(80, 2),
_ => price,
}
}
async fn apply_discount(order_id: Uuid, coupon: &str) -> Result<Order> {
let order = fetch_order(order_id).await?;
let discounted = calculate_discount(order.total, coupon);
update_order_total(order_id, discounted).await?;
Ok(order)
}
25. ALWAYS Use Builder Pattern for Complex Constructors
#[derive(Debug)]
pub struct HttpClient {
timeout: Duration,
retries: u32,
user_agent: String,
}
impl HttpClient {
pub fn builder() -> HttpClientBuilder {
HttpClientBuilder::default()
}
}
#[derive(Default)]
pub struct HttpClientBuilder {
timeout: Option<Duration>,
retries: Option<u32>,
user_agent: Option<String>,
}
impl HttpClientBuilder {
pub fn timeout(mut self, timeout: Duration) -> Self {
self.timeout = Some(timeout);
self
}
pub fn retries(mut self, retries: u32) -> Self {
self.retries = Some(retries);
self
}
pub fn build(self) -> HttpClient {
HttpClient {
timeout: self.timeout.unwrap_or(Duration::from_secs(30)),
retries: self.retries.unwrap_or(3),
user_agent: self.user_agent.unwrap_or_else(|| "rust-client".to_string()),
}
}
}
let client = HttpClient::builder()
.timeout(Duration::from_secs(10))
.retries(5)
.build();
#### 3. DIRECTOR_ROLE.md
Complete template with communication protocols:
```markdown
# Director AI Role & Responsibilities
## Core Mission
Architect the system, design features, plan implementation, and ensure quality through design review.
## What Director CAN Do
### ✅ Architecture & Design
- Make architectural decisions (frameworks, patterns, structure)
- Create design documents in `docs/design/`
- Write Architecture Decision Records (ADRs)
- Define domain models and entity relationships
- Design API contracts and data schemas
### ✅ Planning & Documentation
- Create Superpowers implementation plans in `docs/plans/`
- Break features into 2-5 minute atomic tasks
- Define acceptance criteria and test strategies
- Document system architecture in `docs/architecture/`
- Write technical specifications
### ✅ Quality Assurance
- Review implemented code against design
- Verify adherence to guardrails (NEVER_DO, ALWAYS_DO)
- Validate test coverage and quality
- Approve or request changes to implementations
## What Director CANNOT Do
### ❌ Implementation
- Write production code (that's Implementor's job)
- Execute cargo commands (build, test, run)
- Modify existing code directly
- Create git commits
### ❌ Tactical Decisions
- Choose variable names (Implementor decides)
- Select specific algorithms (unless architecturally significant)
- Optimize performance details (unless architectural)
## Decision Authority Matrix
| Decision Type | Director | Implementor | Requires Approval |
|--------------|----------|-------------|-------------------|
| Framework choice | ✅ Decides | ❌ No input | User approval |
| Architecture pattern | ✅ Decides | Consults | User approval |
| API contract | ✅ Decides | ❌ No input | No (internal) |
| Error handling strategy | ✅ Decides | ❌ No input | No |
| Domain model design | ✅ Decides | Provides feedback | No |
| Variable naming | ❌ N/A | ✅ Decides | No |
| Algorithm choice | Consults | ✅ Decides | No |
| Test approach | ✅ Decides | ✅ Implements | No |
| File structure | ✅ Decides | ❌ No input | No |
| Code formatting | ❌ N/A | ✅ (cargo fmt) | No |
## Communication Protocol
### Template 1: Feature Assignment to Implementor
```markdown
## Feature Assignment: [Feature Name]
**Feature ID**: FEAT-XXX
**Priority**: High | Medium | Low
**Estimated Hours**: X
### Design Documents
- Design: `docs/design/FEAT-XXX-[feature-name].md`
- Implementation Plan: `docs/plans/PLAN-XXX-[feature-name].md`
- Related ADRs: ADR-XXX, ADR-YYY
### Implementation Plan Location
`docs/plans/PLAN-XXX-[feature-name].md`
### Key Architectural Constraints
1. Must use Repository pattern for data access
2. All errors must use thiserror for domain layer
3. Follow existing naming conventions in `user` module
### Success Criteria
- [ ] All tasks in implementation plan completed
- [ ] cargo test passes (≥80% coverage)
- [ ] cargo clippy clean (no warnings)
- [ ] Follows NEVER_DO and ALWAYS_DO guidelines
### Questions or Blockers?
Please report any issues or questions back to Director before proceeding with workarounds.
---
**Next Step**: Review implementation plan, execute tasks in TDD manner, report completion.
Template 2: Progress Check Request
## Progress Check: [Feature Name]
**Feature ID**: FEAT-XXX
**Assigned**: [Date]
### Status Update Requested
Please provide:
1. **Completed Tasks**: List task numbers from plan
2. **Current Task**: What you're working on now
3. **Blockers**: Any issues preventing progress
4. **Questions**: Architecture or design clarifications needed
5. **ETA**: Estimated completion date
### Format
- Completed: Tasks 1, 2, 3
- Current: Task 4 (Password hashing)
- Blockers: None | [Describe blocker]
- Questions: [Any questions]
- ETA: [Date] | [X hours remaining]
---
**Response Expected**: Within 24 hours or when blocked
Template 3: Code Review Feedback
## Code Review: [Feature Name]
**Feature ID**: FEAT-XXX
**Review Date**: [Date]
**Status**: ✅ Approved | ⚠️ Changes Requested | ❌ Rejected
### Review Against Design
- [ ] Implementation matches design document
- [ ] All planned tasks completed
- [ ] API contracts followed
- [ ] Domain model correctly implemented
### Guardrails Compliance
- [ ] No NEVER_DO violations detected
- [ ] ALWAYS_DO practices followed
- [ ] Error handling strategy correct (thiserror/anyhow)
- [ ] No blocking operations in async code
### Code Quality
- [ ] Tests pass (cargo test)
- [ ] Clippy clean (cargo clippy)
- [ ] Formatted (cargo fmt)
- [ ] Test coverage ≥80%
### Feedback
#### ✅ Strengths
1. [Positive observation]
2. [Good practice noticed]
#### ⚠️ Changes Requested
1. **Issue**: [Description]
**Location**: `src/path/file.rs:123`
**Required Change**: [What needs to change]
**Reason**: [Why this matters architecturally]
2. [Additional issues...]
#### 💡 Suggestions (Optional)
1. [Nice-to-have improvements]
---
**Next Step**:
- If Approved: Feature complete, merge approved
- If Changes Requested: Address issues, resubmit for review
- If Rejected: Schedule design discussion
Template 4: Architecture Question Response
## Architecture Question Response
**Question ID**: Q-XXX
**Feature**: [Feature Name]
**Asked By**: Implementor
**Date**: [Date]
### Question
[Exact question from Implementor]
### Answer
[Clear, specific answer]
### Reasoning
[Why this approach is chosen]
### Example
```rust
// Demonstrate the approach
[Code example if applicable]
Related Documentation
- ADR-XXX: [Related decision]
- Design Doc:
docs/design/FEAT-XXX.md
Action: Proceed with answered approach, update plan if needed
## Quality Gates
### Before Creating Implementation Plan
- [ ] Feature request is clear and complete
- [ ] Architecture documents reviewed
- [ ] Domain model defined
- [ ] ADRs created for new decisions
- [ ] Design document complete
### Before Assigning to Implementor
- [ ] Superpowers plan created and validated
- [ ] All tasks are 2-5 minutes and atomic
- [ ] Acceptance criteria are testable
- [ ] Prerequisites clearly defined
- [ ] Rollback plan documented
### Before Approving Implementation
- [ ] All design requirements met
- [ ] Guardrails compliance verified
- [ ] Code quality standards met
- [ ] Tests comprehensive and passing
- [ ] Documentation updated
## Escalation Protocol
### When to Escalate to User
1. **Major Architecture Changes**: Framework swap, data model redesign
2. **Contradictory Requirements**: User requirements conflict
3. **Technical Limitations**: Can't meet requirements with current stack
4. **Security Concerns**: Potential vulnerability in design
5. **Timeline Impact**: Implementation will take significantly longer
### Escalation Template
```markdown
## Escalation: [Issue]
**Severity**: Critical | High | Medium
**Impact**: [What's affected]
### Issue Description
[Clear explanation of the problem]
### Options Considered
1. **Option A**: [Description]
- Pros: [List]
- Cons: [List]
- Timeline: [Impact]
2. **Option B**: [Description]
- Pros: [List]
- Cons: [List]
- Timeline: [Impact]
### Recommendation
[Director's recommended approach]
### Reasoning
[Why this recommendation]
---
**Decision Needed**: [What user needs to decide]
#### 4. IMPLEMENTOR_ROLE.md
Complete template with TDD workflow:
```markdown
# Implementor AI Role & Responsibilities
## Core Mission
Execute implementation plans through test-driven development, maintain code quality, and deliver working features.
## What Implementor CAN Do
### ✅ Implementation
- Write production Rust code following the implementation plan
- Create and modify source files in src/ directories
- Implement domain logic, API handlers, repository patterns
- Write SQL migrations with sqlx
- Execute cargo commands (build, test, clippy, fmt)
- Create git commits with meaningful messages
### ✅ Testing
- Write unit tests, integration tests, property tests
- Use TDD: write test first, implement, refactor
- Ensure ≥80% test coverage
- Test edge cases and error paths
### ✅ Tactical Decisions
- Choose variable and function names
- Select algorithms and data structures
- Decide implementation details
- Optimize code performance (within design constraints)
- Format code with cargo fmt
## What Implementor CANNOT Do
### ❌ Architecture Changes
- Change frameworks or major dependencies
- Modify domain model structure
- Redesign API contracts
- Change error handling strategy
- Alter project structure
### ❌ Design Decisions
- Skip tasks in the implementation plan
- Add features not in the plan
- Change acceptance criteria
- Modify architectural patterns
## When to Stop and Ask Director
### 🛑 Immediate Stop Scenarios
1. **Implementation Plan Unclear**: Task description is ambiguous
2. **Design Contradiction**: Code requirements conflict with architecture docs
3. **Missing Information**: Don't have data needed to proceed (API keys, schemas, etc.)
4. **Architectural Decision Needed**: Need to choose between architectural alternatives
5. **Guardrail Violation**: Following plan would violate NEVER_DO rules
### 📝 Question Template
```markdown
## Implementation Question
**Plan**: PLAN-XXX
**Task**: Task X
**Status**: Blocked
### Question
[Clear, specific question]
### Context
[What you were trying to do]
### Options Considered
1. **Option A**: [Description]
- Aligns with: [Architecture doc reference]
- Concern: [Why you're asking]
2. **Option B**: [Description]
- Aligns with: [Different consideration]
- Concern: [Trade-off]
### Waiting For
Director's decision before proceeding with implementation.
TDD Workflow (Red-Green-Refactor)
Complete Example: Adding Password Validation
Step 1: RED - Write Failing Test
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_password_too_short() {
let result = validate_password("short");
assert!(result.is_err());
assert!(matches!(result, Err(PasswordError::TooShort)));
}
#[test]
fn test_validate_password_no_number() {
let result = validate_password("password");
assert!(result.is_err());
assert!(matches!(result, Err(PasswordError::NoNumber)));
}
#[test]
fn test_validate_password_valid() {
let result = validate_password("password123");
assert!(result.is_ok());
}
}
Run: cargo test → Tests fail (function doesn't exist yet) ✅ RED
Step 2: GREEN - Minimum Implementation
use thiserror::Error;
#[derive(Error, Debug, PartialEq)]
pub enum PasswordError {
#[error("Password must be at least 8 characters")]
TooShort,
#[error("Password must contain at least one number")]
NoNumber,
}
pub fn validate_password(password: &str) -> Result<(), PasswordError> {
if password.len() < 8 {
return Err(PasswordError::TooShort);
}
if !password.chars().any(|c| c.is_numeric()) {
return Err(PasswordError::NoNumber);
}
Ok(())
}
Run: cargo test → Tests pass ✅ GREEN
Step 3: REFACTOR - Improve Code
const MIN_PASSWORD_LENGTH: usize = 8;
pub fn validate_password(password: &str) -> Result<(), PasswordError> {
validate_length(password)?;
validate_contains_number(password)?;
Ok(())
}
fn validate_length(password: &str) -> Result<(), PasswordError> {
if password.len() < MIN_PASSWORD_LENGTH {
return Err(PasswordError::TooShort);
}
Ok(())
}
fn validate_contains_number(password: &str) -> Result<(), PasswordError> {
if !password.chars().any(char::is_numeric) {
return Err(PasswordError::NoNumber);
}
Ok(())
}
Run: cargo test → Tests still pass ✅ REFACTOR COMPLETE
Step 4: Quality Checks
cargo test
cargo clippy -- -D warnings
cargo fmt --all
Step 5: Commit
git add src/domain/password.rs
git commit -m "feat: add password validation
- Validate minimum length (8 characters)
- Require at least one numeric character
- Return typed errors for validation failures
Tests: Added unit tests for validation logic
Coverage: 100% for password module"
Code Quality Checklist
Before Marking Task Complete
Before Requesting Review
Progress Reporting
Daily Progress Template
## Progress Update: [Feature Name]
**Date**: [Date]
**Plan**: PLAN-XXX
### Completed Today
- ✅ Task 1: Database schema (3 min actual)
- ✅ Task 2: User domain model (4 min actual)
- ✅ Task 3: Password hashing (6 min actual)
### Currently Working On
- 🔄 Task 4: Repository implementation
### Blockers
- None | [Describe blocker and question to Director]
### Next Up
- Task 5: Integration tests
### Notes
- All tests passing, coverage at 85%
- Found edge case in email validation, added test
Common Mistakes to Avoid
❌ Don't: Skip Tests
fn calculate_discount(price: Decimal) -> Decimal {
price * Decimal::new(90, 2)
}
✅ Do: Test First
#[test]
fn test_calculate_discount_10_percent() {
assert_eq!(calculate_discount(Decimal::new(100, 0)), Decimal::new(90, 0));
}
fn calculate_discount(price: Decimal) -> Decimal {
price * Decimal::new(90, 2)
}
❌ Don't: Commit Failing Code
Always ensure cargo test && cargo clippy passes before commit.
✅ Do: Commit Working Code Only
cargo test && cargo clippy -- -D warnings && git commit
❌ Don't: Change Architecture
If you find an issue with the design, ask Director—don't fix it yourself.
✅ Do: Report Design Issues
Use the question template to escalate architectural concerns.
#### 5. CODE_REVIEW_CHECKLIST.md
**Use this checklist before marking any task as complete or requesting code review.**
---
### ✅ Correctness
**Logic & Control Flow**
- [ ] All code paths handle both success and failure cases
- [ ] No unwrap() or expect() in production code (use proper error handling)
- [ ] Pattern matching is exhaustive (no wildcard `_` on critical enums)
- [ ] Loop termination conditions are correct (no infinite loops)
- [ ] Edge cases are explicitly tested (empty collections, boundary values, None/Some)
**Error Handling**
- [ ] All errors have proper context using `.context()` or `.with_context()`
- [ ] Library code uses `thiserror` for custom error types
- [ ] Application code uses `anyhow::Result` for error propagation
- [ ] No errors are silently discarded (all Result/Option properly handled)
- [ ] Error messages include actionable information (what failed, why, how to fix)
**Ownership & Borrowing**
- [ ] No unnecessary `.clone()` calls (prefer borrowing)
- [ ] Lifetime annotations are minimal and necessary
- [ ] No dangling references or use-after-free scenarios
- [ ] Smart pointers (Arc, Rc, Box) are used appropriately, not by default
---
### 💰 Financial Integrity (if applicable)
**Decimal Types**
- [ ] All money calculations use `rust_decimal::Decimal` or `i64` (never f32/f64)
- [ ] Currency conversions preserve precision
- [ ] Rounding is explicit and documented with business justification
- [ ] Database columns use `NUMERIC` or `BIGINT`, never `REAL`/`DOUBLE`
**Audit Trail**
- [ ] All financial transactions are logged with timestamp, user, amount
- [ ] Immutable audit log (append-only, never delete/update)
- [ ] Transaction IDs are unique and traceable
- [ ] Balance changes include before/after snapshots
**Idempotency**
- [ ] Financial operations are idempotent (safe to retry)
- [ ] Duplicate transaction detection is in place
- [ ] Distributed transactions use proper isolation levels
---
### 🛡️ Memory Safety
**Unsafe Code**
- [ ] No `unsafe` blocks unless absolutely necessary
- [ ] Every `unsafe` block has a `// SAFETY:` comment explaining invariants
- [ ] Unsafe code is isolated in smallest possible scope
- [ ] Alternative safe solutions were considered and documented
**Lifetime Correctness**
- [ ] No lifetime parameters unless necessary for API design
- [ ] Lifetime elision is used where possible
- [ ] References don't outlive the data they point to
- [ ] Self-referential structs use `Pin` if needed
**Smart Pointer Usage**
- [ ] `Vec::with_capacity()` for known-size collections
- [ ] `Arc<T>` only for shared ownership across threads
- [ ] `Rc<T>` only for single-threaded shared ownership
- [ ] `Box<T>` for heap allocation or trait objects
- [ ] Mutex/RwLock used appropriately (prefer message passing)
---
### 🔐 Security
**Input Validation**
- [ ] All user input is validated before processing
- [ ] String length limits are enforced
- [ ] Numeric inputs check min/max ranges
- [ ] Email/URL validation uses proper libraries
- [ ] File uploads check MIME type and size limits
**SQL Injection Prevention**
- [ ] All database queries use parameterized queries (sqlx macros or `query!`)
- [ ] No string concatenation for SQL
- [ ] Input sanitization for LIKE clauses
- [ ] Database user has minimum necessary privileges
**Authentication & Authorization**
- [ ] Passwords are hashed with bcrypt/argon2 (never plaintext)
- [ ] JWT tokens have expiration times
- [ ] Authorization checks happen on every protected endpoint
- [ ] Session tokens are cryptographically random
- [ ] Sensitive operations require re-authentication
**Secrets Management**
- [ ] No secrets in source code (use environment variables or secret manager)
- [ ] API keys rotate regularly
- [ ] Database credentials stored securely
- [ ] Secrets never logged or exposed in error messages
**HTTPS & Transport Security**
- [ ] All HTTP traffic uses TLS in production
- [ ] Certificate validation is enabled
- [ ] No self-signed certificates in production
- [ ] CORS configuration is restrictive (not `allow_origin("*")`)
---
### 🧪 Testing
**Test Coverage**
- [ ] Minimum 80% code coverage (run `cargo tarpaulin`)
- [ ] All public functions have tests
- [ ] Critical business logic has >95% coverage
- [ ] Edge cases are explicitly tested (empty, null, boundary values)
**Test Types**
- [ ] Unit tests for pure logic (no I/O)
- [ ] Integration tests for database/HTTP interactions
- [ ] Property-based tests for invariants (using `proptest` or `quickcheck`)
- [ ] `#[should_panic(expected = "...")]` for expected failures
**Test Quality**
- [ ] Tests have descriptive names (test_user_registration_fails_with_weak_password)
- [ ] Tests are independent (no shared mutable state)
- [ ] Tests clean up resources (temp files, database transactions)
- [ ] Error paths are tested (not just happy path)
- [ ] Async tests use `#[tokio::test]` not `#[test]`
**Performance Tests**
- [ ] Benchmarks exist for performance-critical code (using `criterion`)
- [ ] Load tests validate scalability targets
- [ ] Database query performance measured (no N+1 queries)
---
### 📝 Code Quality
**Linting & Formatting**
- [ ] `cargo clippy` passes with no warnings
- [ ] `cargo fmt --check` passes (code is formatted)
- [ ] No `#[allow(clippy::...)]` without justification
- [ ] Compiler warnings are treated as errors in CI
**Naming Conventions**
- [ ] Types are `PascalCase` (struct User)
- [ ] Functions/variables are `snake_case` (get_user_by_id)
- [ ] Constants are `SCREAMING_SNAKE_CASE` (MAX_RETRIES)
- [ ] Names are descriptive (not `tmp`, `data`, `info`)
**Function Design**
- [ ] Functions are <50 lines (prefer smaller)
- [ ] Functions do one thing well (Single Responsibility)
- [ ] Function names start with verbs (get_, create_, validate_)
- [ ] Nested blocks are <3 levels deep
**Type Safety**
- [ ] Type aliases used for domain concepts (`type UserId = Uuid`)
- [ ] Newtypes for distinct domains (`struct Email(String)`)
- [ ] Enums for exclusive states (not bool flags)
- [ ] Structs implement `Debug` derive
---
### 📚 Documentation
**Module Documentation**
- [ ] Every module has `//!` doc comment explaining purpose
- [ ] Public API has rustdoc comments (`///`)
- [ ] Code examples in docs compile (use `cargo test --doc`)
- [ ] Complex algorithms have implementation notes
**Function Documentation**
- [ ] Public functions document parameters and return values
- [ ] Error cases are documented
- [ ] Examples provided for non-obvious usage
- [ ] Panics are documented with `# Panics` section
**Inline Comments**
- [ ] Comments explain WHY, not WHAT (code explains what)
- [ ] Complex logic has explanatory comments
- [ ] TODO comments have GitHub issue numbers
- [ ] Magic numbers are explained or replaced with constants
---
### ⚡ Performance
**Allocations**
- [ ] Hot paths avoid allocations (use references, slices, iterators)
- [ ] Unnecessary `String` allocations removed (use `&str` where possible)
- [ ] `.collect()` only used when necessary
- [ ] Clone-on-write (`Cow`) for conditional ownership
**Async Performance**
- [ ] No `.await` inside loops (collect futures, join_all)
- [ ] Blocking operations use `spawn_blocking`
- [ ] Database connection pooling configured (min/max connections)
- [ ] HTTP client reused (not created per request)
**Database Performance**
- [ ] Indexes exist for all WHERE/JOIN columns
- [ ] Queries are analyzed with EXPLAIN ANALYZE
- [ ] Batch inserts used for multiple records
- [ ] Pagination implemented for large result sets
- [ ] No N+1 queries (use eager loading)
**Caching**
- [ ] Expensive computations are cached
- [ ] Cache invalidation strategy is correct
- [ ] TTL set appropriately for cached data
---
### 🏗️ Architecture
**Layering**
- [ ] Domain logic is pure (no I/O in business rules)
- [ ] Infrastructure code separated from domain code
- [ ] API handlers are thin (delegate to services)
- [ ] No database queries in handlers
**Separation of Concerns**
- [ ] Each module has a single responsibility
- [ ] Dependencies flow inward (domain ← services ← handlers)
- [ ] No circular dependencies between crates/modules
**Design Patterns**
- [ ] Builder pattern for complex construction
- [ ] Repository pattern for data access
- [ ] Error types follow thiserror/anyhow conventions
- [ ] Traits used for abstraction (not concrete types)
**API Design**
- [ ] Public API is minimal (principle of least privilege)
- [ ] Breaking changes follow semantic versioning
- [ ] Deprecated items have replacement suggestions
- [ ] Generics have clear trait bounds
---
### ✅ Final Checks
Before marking task complete:
- [ ] All checklist items above are checked
- [ ] `cargo test` passes
- [ ] `cargo clippy` has no warnings
- [ ] `cargo fmt` applied
- [ ] Code compiles without warnings
- [ ] Git commit message follows conventional commits
Before requesting code review:
- [ ] Self-review performed (read your own code)
- [ ] Edge cases tested and documented
- [ ] Performance implications considered
- [ ] Security implications considered
- [ ] Breaking changes documented
- [ ] Migration guide provided (if needed)
### Phase 6: Architecture Documentation (8 Files)
#### 00_SYSTEM_OVERVIEW.md
- Vision and goals
- High-level architecture diagram (ASCII art is fine)
- Component overview (crates and their purposes)
- Data flow diagrams
- Technology justification (why axum, why tokio, why sqlx)
- Scalability strategy (connection pooling, caching, load balancing)
- Security approach (authentication, authorization, secrets)
- Performance targets with specific metrics
#### 01_DOMAIN_MODEL.md
- All domain entities with complete field definitions
- Relationships between entities
- Business rules and constraints
- State machines (if applicable, with ASCII diagrams)
- Use cases with concrete code examples
- Entity lifecycle explanations
Example entity:
```rust
use chrono::{DateTime, NaiveDate, Utc};
use uuid::Uuid;
#[derive(Debug, Clone)]
pub struct Task {
pub id: Uuid, // Or use ULID: Ulid
pub project_id: Uuid,
pub title: String,
pub description: Option<String>,
pub status: TaskStatus, // Enum: Todo | InProgress | Blocked | Review | Done
pub priority: Priority, // Enum: Low | Medium | High | Urgent
pub assignee_id: Option<Uuid>,
pub due_date: Option<NaiveDate>,
pub estimated_hours: Option<u32>,
pub version: i32, // For optimistic locking
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl Default for Task {
fn default() -> Self {
Self {
id: Uuid::new_v4(),
project_id: Uuid::new_v4(),
title: String::new(),
description: None,
status: TaskStatus::default(),
priority: Priority::default(),
assignee_id: None,
due_date: None,
estimated_hours: None,
version: 0,
created_at: Utc::now(),
updated_at: Utc::now(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[default = Todo]
pub enum TaskStatus {
Todo,
InProgress,
Blocked,
Review,
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
#[default = Medium]
pub enum Priority {
Low,
Medium,
High,
Urgent,
}
02_DATA_LAYER.md
- Complete sqlx query patterns for all entities
- PostgreSQL table schemas
- Indexes and their justifications
- Optimistic locking implementation (version fields)
- Performance considerations (connection pooling, prepared statements)
- Migration strategy
Example sqlx pattern:
use sqlx::{PgPool, query_as, Type};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Type)]
#[sqlx(type_name = "task_status")]
#[sqlx(rename_all = "lowercase")]
#[default = Todo]
pub enum TaskStatus {
Todo,
InProgress,
Blocked,
Review,
Done,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default, Type)]
#[sqlx(type_name = "priority")]
#[sqlx(rename_all = "lowercase")]
#[default = Medium]
pub enum Priority {
Low,
Medium,
High,
Urgent,
}
pub struct TaskRepository {
pool: PgPool,
}
impl TaskRepository {
pub async fn find_by_id(&self, id: Uuid) -> Result<Option<Task>, sqlx::Error> {
query_as!(
Task,
r#"
SELECT id, project_id, title, description,
status as "status: TaskStatus",
priority as "priority: Priority",
assignee_id, due_date, estimated_hours,
version, created_at, updated_at
FROM tasks
WHERE id = $1
"#,
id
)
.fetch_optional(&self.pool)
.await
}
pub async fn update_with_version(
&self,
task: &Task,
old_version: i32,
) -> Result<Task, TaskError> {
let updated = query_as!(
Task,
r#"
UPDATE tasks
SET title = $1, description = $2, status = $3,
priority = $4, assignee_id = $5, due_date = $6,
version = version + 1, updated_at = NOW()
WHERE id = $7 AND version = $8
RETURNING *
"#,
task.title,
task.description,
task.status as TaskStatus,
task.priority as Priority,
task.assignee_id,
task.due_date,
task.id,
old_version
)
.fetch_optional(&self.pool)
.await?
.ok_or(TaskError::VersionConflict)?;
Ok(updated)
}
}
03_CORE_LOGIC.md
- Pure business logic patterns (no I/O, no side effects)
- Core calculations (priorities, estimates, metrics)
- Validation logic (state transitions, constraints)
- Testing patterns for pure functions
- Property test examples with proptest
Example:
pub mod task_logic {
use super::*;
pub fn can_transition(from: TaskStatus, to: TaskStatus) -> bool {
use TaskStatus::*;
match (from, to) {
(Todo, InProgress | Blocked) => true,
(InProgress, Blocked | Review | Done) => true,
(Blocked, Todo | InProgress) => true,
(Review, InProgress | Done) => true,
(Done, _) => false,
_ => false,
}
}
pub fn calculate_priority_score(task: &Task) -> i32 {
let base_score = priority_value(task.priority);
let urgency_bonus = days_until_due(task.due_date);
let blocker_penalty = if task.status == TaskStatus::Blocked { -10 } else { 0 };
base_score + urgency_bonus + blocker_penalty
}
fn priority_value(priority: Priority) -> i32 {
match priority {
Priority::Urgent => 100,
Priority::High => 75,
Priority::Medium => 50,
Priority::Low => 25,
}
}
fn days_until_due(due_date: Option<NaiveDate>) -> i32 {
let Some(due) = due_date else { return 0 };
let today = Utc::now().date_naive();
let diff = (due - today).num_days();
match diff {
d if d < 0 => 50,
d if d <= 3 => 30,
d if d <= 7 => 15,
_ => 0,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_transitions() {
assert!(can_transition(TaskStatus::Todo, TaskStatus::InProgress));
assert!(!can_transition(TaskStatus::Done, TaskStatus::InProgress));
}
use proptest::prelude::*;
proptest! {
#[test]
fn priority_score_never_negative(
priority in prop::sample::select(&[
Priority::Low, Priority::Medium, Priority::High, Priority::Urgent
])
) {
let task = Task {
priority,
status: TaskStatus::Todo,
due_date: None,
..Task::default()
};
assert!(calculate_priority_score(&task) >= 0);
}
}
}
}
04_BOUNDARIES.md
- Service orchestration layer
- Transaction patterns (database transactions with sqlx)
- Error handling strategies (anyhow for app, thiserror for libs)
- Service composition patterns
Example:
use anyhow::{Context, Result};
use sqlx::PgPool;
pub struct TaskService {
repo: TaskRepository,
activity_logger: ActivityLogger,
notifier: Notifier,
}
impl TaskService {
pub async fn transition_task(
&self,
task_id: Uuid,
new_status: TaskStatus,
notify: bool,
) -> Result<Task> {
let task = self.repo
.find_by_id(task_id)
.await
.context("Failed to load task")?
.ok_or_else(|| anyhow::anyhow!("Task not found: {}", task_id))?;
if !task_logic::can_transition(task.status, new_status) {
return Err(anyhow::anyhow!(
"Invalid transition from {:?} to {:?}",
task.status,
new_status
));
}
let mut tx = self.repo.pool.begin().await?;
let mut updated_task = task.clone();
updated_task.status = new_status;
let updated = self.repo
.update_with_version(&updated_task, task.version)
.await
.context("Failed to update task")?;
self.activity_logger
.log(&mut tx, task_id, "status_changed", json!({
"from": task.status,
"to": new_status,
}))
.await?;
tx.commit().await?;
if notify {
if let Some(assignee_id) = updated.assignee_id {
let notifier = self.notifier.clone();
let task_clone = updated.clone();
tokio::spawn(async move {
let _ = notifier.send_notification(assignee_id, task_clone).await;
});
}
}
Ok(updated)
}
}
05_CONCURRENCY.md
- Async/await patterns with tokio
- Shared state management (Arc, RwLock, Mutex)
- Channel patterns (mpsc, oneshot, broadcast)
- Concurrent task spawning
- Cancellation and timeouts
Example:
use tokio::sync::{RwLock, mpsc};
use std::sync::Arc;
pub struct AppState {
pub config: Arc<RwLock<Config>>,
pub request_count: Arc<AtomicU64>,
pub db: PgPool,
}
pub async fn process_batch(tasks: Vec<Task>) -> Vec<Result<()>> {
let handles: Vec<_> = tasks
.into_iter()
.map(|task| {
tokio::spawn(async move {
process_single_task(task).await
})
})
.collect();
let mut results = Vec::new();
for handle in handles {
results.push(handle.await.unwrap());
}
results
}
pub async fn worker_pool(rx: mpsc::Receiver<Task>) {
while let Some(task) = rx.recv().await {
if let Err(e) = process_task(&task).await {
log::error!("Task processing failed: {}", e);
}
}
}
06_ASYNC_PATTERNS.md
- Background task patterns with tokio
- Retry strategies with exponential backoff
- Circuit breaker implementation
- Health checks and graceful shutdown
- Async streams and futures
Example:
use tokio::time::{sleep, Duration};
pub async fn retry_with_backoff<F, T, E>(
operation: F,
max_attempts: u32,
) -> Result<T, E>
where
F: Fn() -> futures::future::BoxFuture<'static, Result<T, E>>,
{
let mut attempt = 0;
loop {
match operation().await {
Ok(result) => return Ok(result),
Err(e) if attempt >= max_attempts - 1 => return Err(e),
Err(_) => {
attempt += 1;
let delay = Duration::from_millis(100 * 2_u64.pow(attempt));
sleep(delay).await;
}
}
}
}
pub async fn periodic_task<F, Fut>(
interval: Duration,
mut task: F,
) -> Result<()>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<()>> + Send,
{
let mut interval_timer = tokio::time::interval(interval);
loop {
interval_timer.tick().await;
if let Err(e) = task().await {
log::error!("Periodic task failed: {}", e);
}
}
}
Health Check Example:
use axum::{
extract::State,
http::StatusCode,
response::{IntoResponse, Response},
routing::get,
Json, Router,
};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use std::sync::Arc;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthStatus {
pub status: String,
pub version: String,
pub checks: HealthChecks,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HealthChecks {
pub database: CheckResult,
pub redis: CheckResult,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CheckResult {
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub message: Option<String>,
pub response_time_ms: u64,
}
#[derive(Clone)]
pub struct AppState {
pub db_pool: PgPool,
pub version: String,
}
pub async fn liveness() -> StatusCode {
StatusCode::OK
}
pub async fn readiness(
State(state): State<Arc<AppState>>,
) -> Response {
let start = std::time::Instant::now();
let db_check = match sqlx::query("SELECT 1")
.execute(&state.db_pool)
.await
{
Ok(_) => CheckResult {
status: "healthy".to_string(),
message: None,
response_time_ms: start.elapsed().as_millis() as u64,
},
Err(e) => CheckResult {
status: "unhealthy".to_string(),
message: Some(e.to_string()),
response_time_ms: start.elapsed().as_millis() as u64,
},
};
let redis_check = CheckResult {
status: "healthy".to_string(),
message: None,
response_time_ms: 5,
};
let overall_healthy = db_check.status == "healthy"
&& redis_check.status == "healthy";
let health_status = HealthStatus {
status: if overall_healthy {
"healthy".to_string()
} else {
"unhealthy".to_string()
},
version: state.version.clone(),
checks: HealthChecks {
database: db_check,
redis: redis_check,
},
};
let status_code = if overall_healthy {
StatusCode::OK
} else {
StatusCode::SERVICE_UNAVAILABLE
};
(status_code, Json(health_status)).into_response()
}
pub fn health_routes(state: Arc<AppState>) -> Router {
Router::new()
.route("/health/liveness", get(liveness))
.route("/health/readiness", get(readiness))
.with_state(state)
}
Graceful Shutdown Example:
use axum::Router;
use std::sync::Arc;
use tokio::{
signal,
sync::watch,
time::{sleep, Duration},
};
use tracing::{info, warn};
pub struct ShutdownCoordinator {
shutdown_tx: watch::Sender<bool>,
}
impl ShutdownCoordinator {
pub fn new() -> (Self, watch::Receiver<bool>) {
let (shutdown_tx, shutdown_rx) = watch::channel(false);
(Self { shutdown_tx }, shutdown_rx)
}
pub fn trigger_shutdown(&self) {
let _ = self.shutdown_tx.send(true);
}
}
async fn shutdown_signal() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install SIGTERM handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {
info!("Received SIGINT (Ctrl+C), initiating graceful shutdown");
}
_ = terminate => {
info!("Received SIGTERM, initiating graceful shutdown");
}
}
}
pub async fn run_with_graceful_shutdown(
app: Router,
port: u16,
state: Arc<AppState>,
) -> anyhow::Result<()> {
let (coordinator, mut shutdown_rx) = ShutdownCoordinator::new();
let background_task = tokio::spawn({
let mut shutdown_rx = shutdown_rx.clone();
async move {
info!("Background task started");
loop {
tokio::select! {
_ = sleep(Duration::from_secs(60)) => {
info!("Background task running...");
}
_ = shutdown_rx.changed() => {
info!("Background task received shutdown signal");
break;
}
}
}
info!("Background task cleanup complete");
}
});
let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port))
.await?;
info!("Server listening on {}", listener.local_addr()?);
axum::serve(listener, app)
.with_graceful_shutdown(async move {
shutdown_signal().await;
coordinator.trigger_shutdown();
})
.await?;
info!("HTTP server stopped, waiting for background tasks...");
tokio::select! {
_ = background_task => {
info!("All background tasks completed");
}
_ = sleep(Duration::from_secs(30)) => {
warn!("Shutdown timeout exceeded, forcing exit");
}
}
state.db_pool.close().await;
info!("Database connections closed");
info!("Graceful shutdown complete");
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt::init();
let db_pool = sqlx::PgPool::connect("postgresql://localhost/mydb").await?;
let state = Arc::new(AppState {
db_pool,
version: env!("CARGO_PKG_VERSION").to_string(),
});
let app = Router::new()
.nest("/api", health_routes(state.clone()))
.with_state(state.clone());
run_with_graceful_shutdown(app, 3000, state).await?;
Ok(())
}
Key Points:
- Liveness Probe: Simple endpoint that returns 200 if process is alive
- Readiness Probe: Checks dependencies (database, cache) before accepting traffic
- Signal Handling: Catches SIGTERM/SIGINT for graceful shutdown
- Connection Draining: HTTP server stops accepting new connections but finishes existing requests
- Background Task Coordination: Uses
watch channel to notify all tasks
- Timeout Protection: Forceful shutdown after 30s if tasks don't complete
- Resource Cleanup: Explicitly close database pools and other resources
07_INTEGRATION_PATTERNS.md
- HTTP client patterns with reqwest
- Circuit breaker implementation
- Retry logic with exponential backoff
- Webhook handling (incoming and outgoing)
- Event streaming patterns
- External service integration patterns
Example:
use reqwest::Client;
use serde::de::DeserializeOwned;
use std::time::Duration;
use anyhow::{Context, Result};
use tokio::time::sleep;
pub struct HttpClient {
client: Client,
timeout: Duration,
}
impl HttpClient {
pub fn new(timeout: Duration) -> Result<Self> {
let client = Client::builder()
.timeout(timeout)
.build()
.context("Failed to build HTTP client")?;
Ok(Self { client, timeout })
}
pub async fn request_with_retry<T: DeserializeOwned>(
&self,
url: &str,
max_retries: u32,
) -> Result<T> {
let mut attempt = 0;
loop {
match self.client
.get(url)
.timeout(self.timeout)
.send()
.await
{
Ok(resp) if resp.status().is_success() => {
return resp.json().await.context("Failed to parse response");
}
Ok(resp) if resp.status().is_server_error() && attempt < max_retries => {
attempt += 1;
let backoff = Duration::from_millis(100 * 2_u64.pow(attempt));
sleep(backoff).await;
continue;
}
Ok(resp) => {
return Err(anyhow::anyhow!(
"HTTP error: status {}",
resp.status()
));
}
Err(e) if attempt < max_retries => {
attempt += 1;
let backoff = Duration::from_millis(100 * 2_u64.pow(attempt));
sleep(backoff).await;
continue;
}
Err(e) => return Err(e.into()),
}
}
}
}
Phase 7: Architecture Decision Records
Create ADRs for major decisions. Template:
# ADR-XXX: [Decision Title]
**Status:** Accepted
**Date:** YYYY-MM-DD
**Deciders:** [Role]
**Context:** [Brief context]
## Context
[Detailed explanation of the situation requiring a decision]
## Decision
[Clear statement of what was decided]
## Rationale
[Why this decision was made - include code examples, metrics, trade-offs]
## Alternatives Considered
### Alternative 1: [Name]
**Implementation:**
```rust
// Example code
Pros:
Cons:
- Disadvantage 1
- Disadvantage 2
Why Rejected: [Clear explanation]
Alternative 2: [Name]
[Same structure]
Consequences
Positive
- Benefit with explanation
- Another benefit
Negative
- Trade-off with mitigation strategy
- Another trade-off
Implementation Guidelines
DO: [Pattern]
DON'T: [Anti-pattern]
Validation
[How we'll verify this was the right choice]
- Metric 1: Target value
- Metric 2: Target value
References
Related ADRs
- ADR-XXX: Related Decision
Review Schedule
Last Reviewed: YYYY-MM-DD
Next Review: YYYY-MM-DD
**Minimum ADRs to create:**
1. **ADR-001: Framework Choice** (axum vs actix-web vs warp vs rocket)
2. **ADR-002: Error Strategy** (anyhow vs thiserror usage patterns)
3. **ADR-003: Ownership Patterns** (When to use owned data vs references vs cloning)
4. **Domain-specific ADRs** based on requirements
### Phase 8: Handoff Documentation
Create HANDOFF.md with:
1. **Overview** - Project status, location, ready state
2. **Project Structure** - Annotated directory tree
3. **Documentation Index** - What each file contains
4. **Workflow** - Director → Implementor → Review → Iterate cycle
5. **Implementation Phases** - Break project into 4-week phases
6. **Key Architectural Principles** - DO/DON'T examples
7. **Testing Strategy** - Unit/Integration/Property test patterns
8. **Commit Message Format** - Conventional commits structure
9. **Communication Protocol** - Message templates between Director/Implementor
10. **Troubleshooting** - Common issues and solutions
11. **Success Metrics** - Specific performance targets
12. **Next Steps** - Immediate actions for Director AI
Example workflow section:
```markdown
## Workflow
### Phase 1: Director Creates Design & Plan
1. Read feature request from user
2. Review architecture documents
3. Create design document in `docs/design/`
4. Create implementation plan in `docs/plans/` (Superpowers format)
5. Commit design + plan
6. Hand off to Implementor with plan path
### Phase 2: Implementor Executes Plan
1. Read implementation plan
2. For each task:
- Write test first (TDD)
- Implement minimum code
- Refactor
- Run tests (cargo test)
- Check clippy (cargo clippy)
- Format code (cargo fmt)
- Commit
3. Report completion to Director
### Phase 3: Director Reviews
1. Review committed code
2. Check against design
3. Verify guardrails followed
4. Either approve or request changes
### Phase 4: Iterate Until Approved
[Loop until feature is complete]
Superpowers Implementation Plan Format
Superpowers plans are structured Markdown documents with YAML frontmatter that break down features into atomic, testable tasks of 2-5 minutes each.
File Structure
---
plan_id: "PLAN-001-user-authentication"
feature: "User Authentication System"
created: "2024-01-15"
author: "Director AI"
status: "approved"
estimated_hours: 8
priority: "high"
dependencies: []
---
# Implementation Plan: User Authentication System
## Overview
Brief description of what this plan achieves and why it's necessary.
## Context
- **Related ADRs**: ADR-001 (JWT Strategy), ADR-002 (Error Handling)
- **Related Docs**: `docs/architecture/04_BOUNDARIES.md`
- **Dependencies**: PostgreSQL 16+, argon2 crate for password hashing
## Tasks
### Task 1: Database Schema (2-5 min)
**Type**: database
**Estimated**: 3 minutes
**Prerequisites**: None
**Objective**: Create users table with security best practices
**Steps**:
1. Create migration file: `sqlx migrate add create_users_table`
2. Define schema with email, password_hash, created_at, updated_at
3. Add unique constraint on email for login uniqueness
4. Add index on email for login performance
**Acceptance Criteria**:
- [ ] Migration file created in migrations/ directory
- [ ] `sqlx migrate run` succeeds without errors
- [ ] Can insert test user with email and password_hash
**Code Location**: `migrations/YYYYMMDDHHMMSS_create_users_table.sql`
---
### Task 2: User Domain Model (2-5 min)
**Type**: implementation
**Estimated**: 4 minutes
**Prerequisites**: Task 1
**Objective**: Define User entity with validation logic
**Steps**:
1. Create `myapp_core/src/domain/user.rs`
2. Define User struct with proper types (email: String, password_hash: String, etc.)
3. Implement email validation (regex for email format)
4. Add methods: `new()`, `verify_password()`
**Acceptance Criteria**:
- [ ] User struct defined with all required fields
- [ ] Email validation works (test with invalid emails)
- [ ] Password verification works (test with valid/invalid passwords)
- [ ] Unit tests pass: `cargo test user::tests`
**Code Location**: `myapp_core/src/domain/user.rs`
---
### Task 3: Password Hashing (2-5 min)
**Type**: implementation
**Estimated**: 5 minutes
**Prerequisites**: Task 2
**Objective**: Implement secure password hashing with argon2
**Steps**:
1. Add argon2 to Cargo.toml: `argon2 = "0.5.3"`
2. Create `myapp_core/src/domain/password.rs`
3. Implement `hash_password(password: &str) -> Result<String>`
4. Implement `verify_password(password: &str, hash: &str) -> Result<bool>`
5. Write unit tests for both functions
**Acceptance Criteria**:
- [ ] Passwords hashed with argon2 (verify config: memory=19MB, iterations=2)
- [ ] Same password produces different hashes (salt working correctly)
- [ ] Verification succeeds for valid passwords
- [ ] Verification fails for invalid passwords
- [ ] All tests pass: `cargo test password`
**Code Location**: `myapp_core/src/domain/password.rs`
---
## Testing Strategy
- **Unit Tests**: Each task includes its own isolated tests
- **Integration Tests**: Final end-to-end test in `myapp_api/tests/auth_flow.rs`
- **Coverage Target**: ≥80% for authentication code (critical security component)
## Rollback Plan
If any task fails or needs to be reverted:
1. Revert migrations: `sqlx migrate revert`
2. Delete created files and restore from git
3. Restore to last commit: `git reset --hard HEAD~1`
4. Re-plan if fundamental issues discovered
## Success Criteria
- [ ] All tasks completed and individually tested
- [ ] `cargo test` passes (all unit and integration tests)
- [ ] `cargo clippy` clean (no warnings)
- [ ] Integration test demonstrates full auth flow works end-to-end
- [ ] Documentation updated in HANDOFF.md
## Notes
- Use `thiserror` for domain errors (library code following DDD)
- Use `anyhow` for application errors (API layer convenience)
- Never log passwords (even hashed ones in production logs)
- Follow OWASP authentication guidelines
Superpowers Plan Principles
- Atomic Tasks: Each task is independently completable in 2-5 minutes
- Clear Prerequisites: Explicit task dependencies prevent blocking
- Testable Acceptance: Every task has verifiable completion criteria
- TDD Workflow: Write test first, minimum implementation, then refactor
- Rollback Safety: Each task can be independently reverted if needed
Task Types
database: Schema definitions, migrations, query optimization
implementation: Core logic, domain models, business rules
api: HTTP endpoints, handlers, middleware
testing: Test files, integration tests, property tests
documentation: Docs, inline comments, examples, README updates
Task Metadata
- Type: Categorizes the work for filtering and reporting
- Estimated: Time estimate in minutes (2-5 minute range)
- Prerequisites: Task IDs that must complete first
- Objective: One-sentence goal of this task
- Steps: Ordered list of concrete actions
- Acceptance Criteria: Checkboxes for verification
- Code Location: Where the changes will be made
Phase 9: Validate and Summarize
Before finishing, verify:
- ✅ All directories created
- ✅ 20+ documentation files present
- ✅ All cross-references between docs work
- ✅ All code examples are valid Rust syntax
- ✅ Every architectural principle has concrete example
- ✅ ADRs include alternatives with rationale
- ✅ Guardrails have DO/DON'T code examples
- ✅ Domain-specific adaptations included
Present summary:
## Project Architecture Complete! 🚀
**Location:** /path/to/project
**Created:**
- ✅ Complete directory structure
- ✅ Foundation docs (README, CLAUDE.md)
- ✅ 5 guardrail documents
- ✅ 8 architecture documents (~6,000 lines)
- ✅ X Architecture Decision Records
- ✅ Handoff documentation
**Ready For:**
- Director AI to create first design + plan
- Implementor AI to execute implementation
- Iterative feature development
**Next Step:**
Director AI should begin by creating the first feature design.
Domain-Specific Adaptations
For Web Services (axum/actix-web)
Add emphasis on:
-
NEVER_DO.md additions:
- Never block async runtime with std::thread::sleep (use tokio::time::sleep)
- Never use Arc<Mutex> without justification (prefer message passing)
- Never unwrap in request handlers (return proper HTTP errors)
- Never store sessions in memory without justification (use database)
-
Domain Model inclusions:
- HTTP request/response types
- Middleware patterns
- Authentication/authorization models
- State management with Arc
-
ADRs to add:
- Web framework choice (axum vs actix-web)
- State sharing strategy
- Error response format (JSON API spec)
- Authentication method (JWT, sessions, OAuth)
-
Use Cases examples:
- Handle HTTP request with validation
- Middleware for authentication
- Database query with connection pooling
- Background job spawning
For CLI Tools (clap)
Add emphasis on:
-
Domain Model additions:
- Command structure with clap
- Configuration file handling
- Progress indicators
- Error reporting to terminal
-
ADRs to add:
- CLI argument parsing library choice
- Configuration file format (TOML, YAML, JSON)
- Error reporting strategy
- Output formatting approach
-
Use Cases examples:
- Parse command-line arguments
- Read configuration file
- Execute subcommands
- Report progress and errors
For Backend Services
Add emphasis on:
-
Domain Model additions:
- Background job patterns with tokio
- Event sourcing patterns
- CQRS implementation
- Message queue integration
-
Workers to document:
- Background job processing
- Periodic tasks
- Event handlers
- Cleanup tasks
-
Integration Patterns:
- Message queue clients (RabbitMQ, Kafka)
- Cache integration (Redis)
- External API clients
Critical Patterns and Best Practices
Ownership Patterns
fn count_words(text: &str) -> usize {
text.split_whitespace().count()
}
fn to_uppercase(mut s: String) -> String {
s.make_ascii_uppercase();
s
}
fn store_in_cache(key: String, value: Data) {
CACHE.insert(key.clone(), value);
log::info!("Stored {}", key);
}
Error Handling Patterns
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TaskError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("Task not found: {0}")]
NotFound(Uuid),