development-guidelines
This skill provides guidance on applying SOLID, DRY, KISS, YAGNI, and TDD principles to the ai-cli-syncer Rust project. Should be invoked when planning features, implementing code, reviewing changes, or refactoring to ensure adherence to established design principles and test-driven development practices.
When & Why to Use This Skill
This Claude skill provides comprehensive procedural guidance for applying core software engineering principles—including SOLID, TDD, DRY, KISS, and YAGNI—specifically tailored for Rust development. It streamlines the development lifecycle by offering structured workflows for feature planning, implementation, and refactoring to ensure high code quality, maintainability, and architectural integrity.
Use Cases
- Architecting new modules: Using SOLID principles to design decoupled and extensible Rust components during the initial planning phase to prevent technical debt.
- Test-Driven Development (TDD): Executing the Red-Green-Refactor cycle to ensure reliable functionality and high test coverage for new features within the ai-cli-syncer project.
- Code Quality Audits: Performing systematic code reviews using a standardized checklist to identify principle violations and ensure adherence to engineering best practices.
- Refactoring and Decoupling: Applying Dependency Inversion and Interface Segregation to resolve tight coupling and simplify complex, bloated codebases into manageable units.
| name | development-guidelines |
|---|---|
| description | This skill provides guidance on applying SOLID, DRY, KISS, YAGNI, and TDD principles to the ai-cli-syncer Rust project. Should be invoked when planning features, implementing code, reviewing changes, or refactoring to ensure adherence to established design principles and test-driven development practices. |
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.