|
Download AGENTS.md from SaylorTwift/forgecode: direct link, hf CLI and curl.
- Browser
- Download file 7.33 kB
-
https://huggingface.co/SaylorTwift/forgecode/resolve/main/AGENTS.md
- Command line
-
hf download hf://SaylorTwift/forgecode/AGENTS.md
-
curl -L -o AGENTS.md https://huggingface.co/SaylorTwift/forgecode/resolve/main/AGENTS.md
7.33 kB
| # Agent Guidelines | |
| This document contains guidelines and best practices for AI agents working with this codebase. | |
| ## Error Management | |
| - Use `anyhow::Result` for error handling in services and repositories. | |
| - Create domain errors using `thiserror`. | |
| - Never implement `From` for converting domain errors, manually convert them | |
| ## Writing Tests | |
| - All tests should be written in three discrete steps: | |
| ```rust,ignore | |
| use pretty_assertions::assert_eq; // Always use pretty assertions | |
| fn test_foo() { | |
| let setup = ...; // Instantiate a fixture or setup for the test | |
| let actual = ...; // Execute the fixture to create an output | |
| let expected = ...; // Define a hand written expected result | |
| assert_eq!(actual, expected); // Assert that the actual result matches the expected result | |
| } | |
| ``` | |
| - Use `pretty_assertions` for better error messages. | |
| - Use fixtures to create test data. | |
| - Use `assert_eq!` for equality checks. | |
| - Use `assert!(...)` for boolean checks. | |
| - Use unwraps in test functions and anyhow::Result in fixtures. | |
| - Keep the boilerplate to a minimum. | |
| - Use words like `fixture`, `actual` and `expected` in test functions. | |
| - Fixtures should be generic and reusable. | |
| - Test should always be written in the same file as the source code. | |
| - Use `new`, Default and derive_setters::Setters to create `actual`, `expected` and specially `fixtures`. For example: | |
| **Good:** | |
| ```rust,ignore | |
| User::default().age(12).is_happy(true).name("John") | |
| User::new("Job").age(12).is_happy() | |
| User::test() // Special test constructor | |
| ``` | |
| **Bad:** | |
| ```rust,ignore | |
| User {name: "John".to_string(), is_happy: true, age: 12} | |
| User::with_name("Job") // Bad name, should stick to User::new() or User::test() | |
| ``` | |
| - Use `unwrap()` unless the error information is useful. Use `expect` instead of `panic!` when error message is useful. For example: | |
| **Good:** | |
| ```rust,ignore | |
| users.first().expect("List should not be empty") | |
| ``` | |
| **Bad:** | |
| ```rust,ignore | |
| if let Some(user) = users.first() { | |
| // ... | |
| } else { | |
| panic!("List should not be empty") | |
| } | |
| ``` | |
| - Prefer using `assert_eq` on full objects instead of asserting each field: | |
| **Good:** | |
| ```rust,ignore | |
| assert_eq!(actual, expected); | |
| ``` | |
| **Bad:** | |
| ```rust,ignore | |
| assert_eq!(actual.a, expected.a); | |
| assert_eq!(actual.b, expected.b); | |
| ``` | |
| ## Verification | |
| Always verify changes by running tests and linting the codebase | |
| 1. Run crate specific tests to ensure they pass. | |
| ``` | |
| cargo insta test --accept | |
| ``` | |
| 2. **Build Guidelines**: | |
| - **NEVER** run `cargo build --release` unless absolutely necessary (e.g., performance testing, creating binaries for distribution) | |
| - For verification, use `cargo check` (fastest), `cargo insta test`, or `cargo build` (debug mode) | |
| - Release builds take significantly longer and are rarely needed for development verification | |
| ## Writing Domain Types | |
| - Use `derive_setters` to derive setters and use the `strip_option` and the `into` attributes on the struct types. | |
| ## Documentation | |
| - **Always** write Rust docs (`///`) for all public methods, functions, structs, enums, and traits. | |
| - Document parameters with `# Arguments` and errors with `# Errors` sections when applicable. | |
| - **Do not include code examples** - docs are for LLMs, not humans. Focus on clear, concise functionality descriptions. | |
| ## Refactoring | |
| - If asked to fix failing tests, always confirm whether to update the implementation or the tests. | |
| ## Git Operations | |
| - Safely assume git is pre-installed | |
| - Safely assume github cli (gh) is pre-installed | |
| - Always use `Co-Authored-By: ForgeCode <noreply@forgecode.dev>` for git commits and Github comments | |
| ## Service Implementation Guidelines | |
| Services should follow clean architecture principles and maintain clear separation of concerns: | |
| ### Core Principles | |
| - **No service-to-service dependencies**: Services should never depend on other services directly | |
| - **Infrastructure dependency**: Services should depend only on infrastructure abstractions when needed | |
| - **Single type parameter**: Services should take at most one generic type parameter for infrastructure | |
| - **No trait objects**: Avoid `Box<dyn ...>` - use concrete types and generics instead | |
| - **Constructor pattern**: Implement `new()` without type bounds - apply bounds only on methods that need them | |
| - **Compose dependencies**: Use the `+` operator to combine multiple infrastructure traits into a single bound | |
| - **Arc<T> for infrastructure**: Store infrastructure as `Arc<T>` for cheap cloning and shared ownership | |
| - **Tuple struct pattern**: For simple services with single dependency, use tuple structs `struct Service<T>(Arc<T>)` | |
| ### Examples | |
| #### Simple Service (No Infrastructure) | |
| ```rust,ignore | |
| pub struct UserValidationService; | |
| impl UserValidationService { | |
| pub fn new() -> Self { ... } | |
| pub fn validate_email(&self, email: &str) -> Result<()> { | |
| // Validation logic here | |
| ... | |
| } | |
| pub fn validate_age(&self, age: u32) -> Result<()> { | |
| // Age validation logic here | |
| ... | |
| } | |
| } | |
| ``` | |
| #### Service with Infrastructure Dependency | |
| ```rust,ignore | |
| // Infrastructure trait (defined in infrastructure layer) | |
| pub trait UserRepository { | |
| fn find_by_email(&self, email: &str) -> Result<Option<User>>; | |
| fn save(&self, user: &User) -> Result<()>; | |
| } | |
| // Service with single generic parameter using Arc | |
| pub struct UserService<R> { | |
| repository: Arc<R>, | |
| } | |
| impl<R> UserService<R> { | |
| // Constructor without type bounds, takes Arc<R> | |
| pub fn new(repository: Arc<R>) -> Self { ... } | |
| } | |
| impl<R: UserRepository> UserService<R> { | |
| // Business logic methods have type bounds where needed | |
| pub fn create_user(&self, email: &str, name: &str) -> Result<User> { ... } | |
| pub fn find_user(&self, email: &str) -> Result<Option<User>> { ... } | |
| } | |
| ``` | |
| #### Tuple Struct Pattern for Simple Services | |
| ```rust,ignore | |
| // Infrastructure traits | |
| pub trait FileReader { | |
| async fn read_file(&self, path: &Path) -> Result<String>; | |
| } | |
| pub trait Environment { | |
| fn max_file_size(&self) -> u64; | |
| } | |
| // Tuple struct for simple single dependency service | |
| pub struct FileService<F>(Arc<F>); | |
| impl<F> FileService<F> { | |
| // Constructor without bounds | |
| pub fn new(infra: Arc<F>) -> Self { ... } | |
| } | |
| impl<F: FileReader + Environment> FileService<F> { | |
| // Business logic methods with composed trait bounds | |
| pub async fn read_with_validation(&self, path: &Path) -> Result<String> { ... } | |
| } | |
| ``` | |
| ### Anti-patterns to Avoid | |
| ```rust,ignore | |
| // BAD: Service depending on another service | |
| pub struct BadUserService<R, E> { | |
| repository: R, | |
| email_service: E, // Don't do this! | |
| } | |
| // BAD: Using trait objects | |
| pub struct BadUserService { | |
| repository: Box<dyn UserRepository>, // Avoid Box<dyn> | |
| } | |
| // BAD: Multiple infrastructure dependencies with separate type parameters | |
| pub struct BadUserService<R, C, L> { | |
| repository: R, | |
| cache: C, | |
| logger: L, // Too many generic parameters - hard to use and test | |
| } | |
| impl<R: UserRepository, C: Cache, L: Logger> BadUserService<R, C, L> { | |
| // BAD: Constructor with type bounds makes it hard to use | |
| pub fn new(repository: R, cache: C, logger: L) -> Self { ... } | |
| } | |
| // BAD: Usage becomes cumbersome | |
| let service = BadUserService::<PostgresRepo, RedisCache, FileLogger>::new(...); | |
| ``` | |