Development Guidelines
Overview
This skill provides procedural guidance for applying core software engineering principles to the ai-cli-syncer Rust project. Apply SOLID design patterns, Test-Driven Development (TDD), and simplicity principles (DRY, KISS, YAGNI) to maintain high code quality and maintainability.
When to Use This Skill
This skill should be invoked when:
- •Planning new features - Designing modules following SOLID principles before implementation
- •Writing code - Applying SRP, DIP, and other patterns to maintain clean architecture
- •Implementing tests - Following TDD cycle (Red-Green-Refactor) for all functionality
- •Code review - Verifying adherence to development principles and best practices
- •Refactoring - Identifying and eliminating principle violations (bloated classes, tight coupling, repeated code)
- •Resolving design questions - Determining proper abstraction levels and module responsibilities
Core Development Principles
SOLID Principles
Apply these five design principles to Rust code:
- •
Single Responsibility (SRP) - Each module/struct has one clear purpose
- •Separate file I/O operations from business logic
- •Keep repository structs focused on data operations only
- •Isolate validation logic into dedicated validator modules
- •Example: Split
ConfigManagerintoConfigReaderandConfigValidator - •Benefits: Easier testing, clearer code purpose, simpler maintenance
- •
Open/Closed (OCP) - Design for extension through traits, not modification
- •Define trait abstractions for contracts
- •Extend functionality by implementing new types
- •Example: Create
SyncStrategytrait, then implementFileSyncStrategy,McpSyncStrategy - •Benefits: Reduces bugs in existing code, safer to add features
- •
Liskov Substitution (LSP) - Trait implementations must be substitutable
- •Ensure all implementations honor trait contracts
- •Don't throw unexpected errors in trait methods
- •Example: All
ConfigLoaderimplementations must handle missing files consistently - •Benefits: Predictable behavior, safer inheritance hierarchies
- •
Interface Segregation (ISP) - Create focused traits, not monolithic ones
- •Split large traits into smaller, specific traits
- •Structs implement only the traits they need
- •Example: Split
FileHandlerintoFileReader,FileWriter,FileValidator - •Benefits: More flexible code, easier to implement and test
- •
Dependency Inversion (DIP) - Depend on trait abstractions, inject implementations
- •High-level modules depend on trait abstractions
- •Use dependency injection for concrete implementations
- •Example:
SyncEnginedepends ontrait ConfigLoader, notJsonConfigLoader - •Benefits: Easier testing with mocks, flexible architecture, decoupled code
TDD (Test-Driven Development)
Follow the Red-Green-Refactor cycle for all feature development:
- •
Red - Write a failing test for the desired functionality
- •Test should compile but fail assertion
- •Verify test actually fails before proceeding
- •
Green - Implement minimum code to make the test pass
- •Focus on making it work, not making it perfect
- •Don't add functionality beyond what the test requires
- •
Refactor - Improve code while keeping tests green
- •Apply SOLID principles to clean up implementation
- •Remove duplication, improve names, simplify logic
- •Run tests after each refactor to ensure behavior preserved
Test Structure:
- •Place unit tests in
mod testswithin each module - •Place integration tests in
tests/directory at project root - •Follow Arrange-Act-Assert (AAA) pattern
- •Use descriptive test names:
test_config_loader_returns_error_for_missing_file
Benefits: Catches bugs early, provides living documentation, makes refactoring safer, builds confidence when making changes
Simplicity Principles
- •
DRY (Don't Repeat Yourself) - Extract repeated patterns into reusable functions or modules
- •Create utility modules for common operations
- •Use Rust traits to share functionality across types
- •Avoid copy-pasting code blocks
- •Benefits: Less code to maintain, fewer bugs, better testing
- •
KISS (Keep It Simple) - Favor simple solutions over complex ones
- •Use standard library features before adding external crates
- •Write self-explanatory code with clear naming
- •Break complex functions into smaller, focused ones
- •Benefits: Easier to understand, faster to debug, lower cognitive load
- •
YAGNI (You Aren't Gonna Need It) - Implement only what's needed now
- •Don't create abstractions until multiple implementations exist
- •Avoid premature optimization
- •Don't build features for hypothetical future requirements
- •Benefits: Faster delivery, less code to maintain, lower complexity, easier to change
Development Workflow
1. Understand the Requirement
Before coding, clarify:
- •What specific problem needs solving?
- •What are the acceptance criteria?
- •What's the simplest solution that could work?
2. Plan the Design
Apply SOLID principles to planning:
- •Which module should own this responsibility? (SRP)
- •Is a trait abstraction needed? (DIP, OCP)
- •Should existing traits be split? (ISP)
- •Can implementations be substituted? (LSP)
3. Write the Test First (Red)
Follow TDD:
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_feature_behavior() {
// Arrange: Set up test data
let input = "test data";
// Act: Call the function (this doesn't exist yet!)
let result = my_function(input);
// Assert: Verify expected behavior
assert_eq!(result, expected_value);
}
}
Run cargo test - it should fail with "function not found".
4. Implement Minimal Solution (Green)
Write the simplest code to pass the test:
pub fn my_function(input: &str) -> ReturnType {
// Minimum implementation to pass test
expected_value
}
Run cargo test - it should now pass.
5. Refactor
Now improve the code while keeping tests green:
- •Apply SOLID principles
- •Remove duplication (DRY)
- •Simplify complex logic (KISS)
- •Add error handling with
anyhow::Context - •Improve variable and function names
Run cargo test after each change to ensure behavior preserved.
6. Verify Principles
Before committing, check:
- •✓ SRP: Does each module/struct have one clear responsibility?
- •✓ Tests: Do all tests pass? Is behavior tested?
- •✓ Simplicity: Is this the simplest solution? Any unnecessary complexity?
- •✓ DRY: Any duplicated code that should be extracted?
- •✓ Documentation: Are public APIs documented with
///comments?
Code Review Checklist
When reviewing code, verify:
SOLID Compliance
- • Single Responsibility - Each module/struct has one purpose
- • Open/Closed - Extensions use traits, not modifications
- • Liskov Substitution - Implementations properly substitute traits
- • Interface Segregation - Traits are focused, not bloated
- • Dependency Inversion - Depends on traits, not concrete types
Test Coverage
- • Tests exist for new functionality
- • Tests follow AAA pattern (Arrange-Act-Assert)
- • Test names describe behavior being tested
- • Tests are independent and isolated
- • TDD cycle was followed (Red-Green-Refactor)
Simplicity
- • DRY - No repeated code blocks
- • KISS - Solution is as simple as possible
- • YAGNI - No unnecessary abstractions or features
Code Quality
- • Error handling uses
anyhow::Context - • Public APIs have
///doc comments - • Variable and function names are clear and descriptive
- • No compiler warnings or clippy violations
Common Refactoring Patterns
Violation: Bloated Struct (SRP)
Before:
struct ConfigManager {
// Too many responsibilities!
}
impl ConfigManager {
fn read_file() -> Result<String> { ... }
fn parse_json() -> Result<Config> { ... }
fn validate() -> Result<()> { ... }
fn sync_to_agents() -> Result<()> { ... }
}
After (Apply SRP):
struct ConfigReader;
impl ConfigReader {
fn read(&self, path: &Path) -> Result<String> { ... }
}
struct ConfigParser;
impl ConfigParser {
fn parse(&self, content: &str) -> Result<Config> { ... }
}
struct ConfigValidator;
impl ConfigValidator {
fn validate(&self, config: &Config) -> Result<()> { ... }
}
struct ConfigSyncer;
impl ConfigSyncer {
fn sync(&self, config: &Config) -> Result<()> { ... }
}
Violation: Tight Coupling (DIP)
Before:
struct SyncEngine {
loader: JsonConfigLoader, // Concrete type!
}
After (Apply DIP):
trait ConfigLoader {
fn load(&self, path: &Path) -> Result<Config>;
}
struct SyncEngine<L: ConfigLoader> {
loader: L, // Depends on abstraction
}
Violation: Repeated Code (DRY)
Before:
fn sync_cursor() -> Result<()> {
let path = home_dir().join(".cursor/config");
let content = fs::read_to_string(&path)?;
// ... sync logic
}
fn sync_windsurf() -> Result<()> {
let path = home_dir().join(".windsurf/config");
let content = fs::read_to_string(&path)?;
// ... same sync logic
}
After (Apply DRY):
fn sync_agent(agent_name: &str) -> Result<()> {
let path = home_dir().join(format!(".{}/config", agent_name));
let content = fs::read_to_string(&path)?;
// ... reusable sync logic
}
Remember: Good code is simple, clear, and purposeful.