agentic-team-templates 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/index.js +8 -0
- package/src/index.test.js +2 -0
- package/templates/golang-expert/.cursorrules/concurrency.md +290 -0
- package/templates/golang-expert/.cursorrules/error-handling.md +199 -0
- package/templates/golang-expert/.cursorrules/interfaces-and-types.md +255 -0
- package/templates/golang-expert/.cursorrules/overview.md +139 -0
- package/templates/golang-expert/.cursorrules/performance.md +234 -0
- package/templates/golang-expert/.cursorrules/production-patterns.md +320 -0
- package/templates/golang-expert/.cursorrules/stdlib-and-tooling.md +276 -0
- package/templates/golang-expert/.cursorrules/testing.md +326 -0
- package/templates/golang-expert/CLAUDE.md +361 -0
- package/templates/rust-expert/.cursorrules/concurrency.md +250 -0
- package/templates/rust-expert/.cursorrules/ecosystem-and-tooling.md +299 -0
- package/templates/rust-expert/.cursorrules/error-handling.md +190 -0
- package/templates/rust-expert/.cursorrules/overview.md +142 -0
- package/templates/rust-expert/.cursorrules/ownership-and-borrowing.md +204 -0
- package/templates/rust-expert/.cursorrules/performance-and-unsafe.md +256 -0
- package/templates/rust-expert/.cursorrules/testing.md +300 -0
- package/templates/rust-expert/.cursorrules/traits-and-generics.md +236 -0
- package/templates/rust-expert/CLAUDE.md +283 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Rust Traits and Generics
|
|
2
|
+
|
|
3
|
+
Traits are Rust's mechanism for abstraction, polymorphism, and code reuse. Combined with generics, they enable zero-cost abstractions that rival hand-written specialized code.
|
|
4
|
+
|
|
5
|
+
## Trait Design
|
|
6
|
+
|
|
7
|
+
### Small, Focused Traits
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
// Good: Single-purpose traits
|
|
11
|
+
pub trait Validate {
|
|
12
|
+
fn validate(&self) -> Result<(), ValidationError>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
pub trait Serialize {
|
|
16
|
+
fn serialize(&self, writer: &mut dyn Write) -> Result<(), SerializeError>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Bad: God trait
|
|
20
|
+
pub trait Entity {
|
|
21
|
+
fn validate(&self) -> Result<(), Error>;
|
|
22
|
+
fn serialize(&self) -> Vec<u8>;
|
|
23
|
+
fn save(&self, db: &Database) -> Result<(), Error>;
|
|
24
|
+
fn render(&self) -> Html;
|
|
25
|
+
// Too many responsibilities — split by concern
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Extension Traits
|
|
30
|
+
|
|
31
|
+
```rust
|
|
32
|
+
// Add methods to existing types via extension traits
|
|
33
|
+
pub trait StrExt {
|
|
34
|
+
fn is_blank(&self) -> bool;
|
|
35
|
+
fn truncate_to(&self, max_len: usize) -> &str;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
impl StrExt for str {
|
|
39
|
+
fn is_blank(&self) -> bool {
|
|
40
|
+
self.trim().is_empty()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
fn truncate_to(&self, max_len: usize) -> &str {
|
|
44
|
+
if self.len() <= max_len {
|
|
45
|
+
self
|
|
46
|
+
} else {
|
|
47
|
+
// Find a char boundary to avoid panic
|
|
48
|
+
let mut end = max_len;
|
|
49
|
+
while !self.is_char_boundary(end) {
|
|
50
|
+
end -= 1;
|
|
51
|
+
}
|
|
52
|
+
&self[..end]
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Sealed Traits
|
|
59
|
+
|
|
60
|
+
```rust
|
|
61
|
+
// Prevent external implementations when your trait is part of an internal contract
|
|
62
|
+
mod private {
|
|
63
|
+
pub trait Sealed {}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub trait Backend: private::Sealed {
|
|
67
|
+
fn execute(&self, query: &str) -> Result<Rows>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Only types in your crate can implement Sealed, so only they can implement Backend
|
|
71
|
+
impl private::Sealed for PostgresBackend {}
|
|
72
|
+
impl Backend for PostgresBackend { ... }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### The Newtype Pattern
|
|
76
|
+
|
|
77
|
+
```rust
|
|
78
|
+
// Implement foreign traits on foreign types via newtype
|
|
79
|
+
struct Meters(f64);
|
|
80
|
+
struct Seconds(f64);
|
|
81
|
+
|
|
82
|
+
impl fmt::Display for Meters {
|
|
83
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
84
|
+
write!(f, "{:.2}m", self.0)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Also prevents mixing units at compile time
|
|
89
|
+
fn speed(distance: Meters, time: Seconds) -> f64 {
|
|
90
|
+
distance.0 / time.0
|
|
91
|
+
}
|
|
92
|
+
// speed(Seconds(1.0), Meters(5.0)) — compile error! Arguments are swapped.
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Generics
|
|
96
|
+
|
|
97
|
+
### Trait Bounds
|
|
98
|
+
|
|
99
|
+
```rust
|
|
100
|
+
// Prefer impl Trait for simple cases
|
|
101
|
+
fn print_all(items: &[impl Display]) {
|
|
102
|
+
for item in items {
|
|
103
|
+
println!("{item}");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Use where clauses for complex bounds
|
|
108
|
+
fn merge<T, U>(left: T, right: U) -> Merged
|
|
109
|
+
where
|
|
110
|
+
T: IntoIterator<Item = Record>,
|
|
111
|
+
U: IntoIterator<Item = Record>,
|
|
112
|
+
T::IntoIter: ExactSizeIterator,
|
|
113
|
+
{
|
|
114
|
+
// ...
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Use explicit generic parameters when the caller needs to specify the type
|
|
118
|
+
fn parse<T: FromStr>(input: &str) -> Result<T, T::Err> {
|
|
119
|
+
input.parse()
|
|
120
|
+
}
|
|
121
|
+
let n: i32 = parse("42")?;
|
|
122
|
+
let f: f64 = parse("3.14")?;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Associated Types vs Generic Parameters
|
|
126
|
+
|
|
127
|
+
```rust
|
|
128
|
+
// Associated types: one implementation per type
|
|
129
|
+
trait Iterator {
|
|
130
|
+
type Item; // Each iterator has exactly one Item type
|
|
131
|
+
fn next(&mut self) -> Option<Self::Item>;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Generic parameters: multiple implementations per type
|
|
135
|
+
trait Convert<T> {
|
|
136
|
+
fn convert(&self) -> T;
|
|
137
|
+
}
|
|
138
|
+
// A single type can implement Convert<String>, Convert<i32>, etc.
|
|
139
|
+
|
|
140
|
+
// Rule of thumb: If there should be only one implementation
|
|
141
|
+
// for a given Self type, use an associated type.
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Phantom Types
|
|
145
|
+
|
|
146
|
+
```rust
|
|
147
|
+
use std::marker::PhantomData;
|
|
148
|
+
|
|
149
|
+
// Type-state pattern: encode state in the type system
|
|
150
|
+
struct Validated;
|
|
151
|
+
struct Unvalidated;
|
|
152
|
+
|
|
153
|
+
struct Form<State> {
|
|
154
|
+
data: FormData,
|
|
155
|
+
_state: PhantomData<State>,
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
impl Form<Unvalidated> {
|
|
159
|
+
fn new(data: FormData) -> Self {
|
|
160
|
+
Form { data, _state: PhantomData }
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
fn validate(self) -> Result<Form<Validated>, ValidationError> {
|
|
164
|
+
// validation logic...
|
|
165
|
+
Ok(Form { data: self.data, _state: PhantomData })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
impl Form<Validated> {
|
|
170
|
+
fn submit(self) -> Result<(), SubmitError> {
|
|
171
|
+
// Only validated forms can be submitted
|
|
172
|
+
send(self.data)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Form::new(data).submit() — compile error! Must validate first.
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Dynamic Dispatch
|
|
180
|
+
|
|
181
|
+
```rust
|
|
182
|
+
// Static dispatch (monomorphization) — zero cost, larger binary
|
|
183
|
+
fn process(handler: impl Handler) { handler.handle(); }
|
|
184
|
+
|
|
185
|
+
// Dynamic dispatch (trait objects) — vtable indirection, smaller binary
|
|
186
|
+
fn process(handler: &dyn Handler) { handler.handle(); }
|
|
187
|
+
|
|
188
|
+
// Use trait objects when:
|
|
189
|
+
// - You need a heterogeneous collection
|
|
190
|
+
// - You want to reduce binary size (e.g., embedded)
|
|
191
|
+
// - The type is determined at runtime
|
|
192
|
+
|
|
193
|
+
// Trait object safety: a trait is object-safe if:
|
|
194
|
+
// - No methods return Self
|
|
195
|
+
// - No methods have generic type parameters
|
|
196
|
+
// - No associated functions (no &self receiver)
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Derive and Common Traits
|
|
200
|
+
|
|
201
|
+
```rust
|
|
202
|
+
// Derive what you can — it's correct and free
|
|
203
|
+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
204
|
+
pub struct UserId(String);
|
|
205
|
+
|
|
206
|
+
// Standard trait hierarchy to consider:
|
|
207
|
+
// Debug — always derive, essential for diagnostics
|
|
208
|
+
// Clone — when values need to be duplicated
|
|
209
|
+
// PartialEq/Eq — when values need comparison
|
|
210
|
+
// Hash — when used as map keys (requires Eq)
|
|
211
|
+
// Default — when a meaningful zero value exists
|
|
212
|
+
// Display — for user-facing output (implement manually)
|
|
213
|
+
// Serialize/Deserialize — for serde (derive with feature flag)
|
|
214
|
+
|
|
215
|
+
// Don't derive Copy unless the type is truly trivially copyable
|
|
216
|
+
// and you want implicit copies. Copy types can't have Drop.
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Anti-Patterns
|
|
220
|
+
|
|
221
|
+
```rust
|
|
222
|
+
// Never: Trait objects everywhere when generics would work
|
|
223
|
+
// Box<dyn Fn()> is fine for callbacks stored in structs
|
|
224
|
+
// impl Fn() is better for function parameters
|
|
225
|
+
|
|
226
|
+
// Never: Overusing generics for types that will only ever have one concrete type
|
|
227
|
+
fn process<T: Into<String>>(name: T) { } // Just take String or &str
|
|
228
|
+
|
|
229
|
+
// Never: Unused type parameters
|
|
230
|
+
struct Wrapper<T> { // T is not used — won't compile without PhantomData
|
|
231
|
+
data: Vec<u8>,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Never: Implementing traits for types you don't own without a newtype
|
|
235
|
+
// Orphan rule prevents this anyway — respect it
|
|
236
|
+
```
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# Rust Expert Development Guide
|
|
2
|
+
|
|
3
|
+
Principal-level guidelines for Rust engineering. Ownership, zero-cost abstractions, and fearless concurrency — wielded with precision.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
This guide applies to:
|
|
10
|
+
- Systems programming and embedded targets
|
|
11
|
+
- Web services and async runtimes (Tokio, async-std)
|
|
12
|
+
- CLI tools and developer utilities
|
|
13
|
+
- Libraries and crates published to crates.io
|
|
14
|
+
- WebAssembly targets
|
|
15
|
+
- FFI and interop with C/C++
|
|
16
|
+
|
|
17
|
+
### Core Philosophy
|
|
18
|
+
|
|
19
|
+
Rust gives you power without sacrificing safety. The compiler is your closest collaborator.
|
|
20
|
+
|
|
21
|
+
- **If it compiles, it's probably correct.** The borrow checker catches bugs that would be CVEs elsewhere.
|
|
22
|
+
- **Zero-cost abstractions are the point.** Never choose between expressiveness and performance.
|
|
23
|
+
- **Make illegal states unrepresentable.** Encode invariants in the type system.
|
|
24
|
+
- **Explicit over implicit.** Lifetimes, ownership, error handling — surfaced, not hidden.
|
|
25
|
+
- **Unsafe is a scalpel, not a sledgehammer.** Every `unsafe` block is a proof obligation.
|
|
26
|
+
- **If you don't know, say so.** Admitting uncertainty is better than guessing about soundness.
|
|
27
|
+
|
|
28
|
+
### Project Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
project/
|
|
32
|
+
├── Cargo.toml
|
|
33
|
+
├── src/
|
|
34
|
+
│ ├── main.rs / lib.rs
|
|
35
|
+
│ ├── error.rs
|
|
36
|
+
│ └── domain/
|
|
37
|
+
├── tests/ # Integration tests
|
|
38
|
+
├── benches/ # Benchmarks
|
|
39
|
+
└── examples/
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Ownership and Borrowing
|
|
45
|
+
|
|
46
|
+
### The Three Rules
|
|
47
|
+
|
|
48
|
+
1. Each value has exactly one owner
|
|
49
|
+
2. When the owner goes out of scope, the value is dropped
|
|
50
|
+
3. Either one `&mut T` OR any number of `&T` — never both
|
|
51
|
+
|
|
52
|
+
### Lifetimes
|
|
53
|
+
|
|
54
|
+
```rust
|
|
55
|
+
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
|
|
56
|
+
if x.len() > y.len() { x } else { y }
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Smart Pointers
|
|
61
|
+
|
|
62
|
+
- `Box<T>` — heap allocation, single owner
|
|
63
|
+
- `Rc<T>` — reference-counted, single-threaded
|
|
64
|
+
- `Arc<T>` — atomic reference-counted, thread-safe
|
|
65
|
+
- `Cow<'_, T>` — clone-on-write, avoid allocation when possible
|
|
66
|
+
|
|
67
|
+
### Interior Mutability
|
|
68
|
+
|
|
69
|
+
- `Cell<T>` — Copy types, single-threaded, zero overhead
|
|
70
|
+
- `RefCell<T>` — runtime borrow checking, single-threaded
|
|
71
|
+
- `Mutex<T>` / `RwLock<T>` — thread-safe
|
|
72
|
+
- `Atomic*` — lock-free primitives
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Error Handling
|
|
77
|
+
|
|
78
|
+
### thiserror (Libraries)
|
|
79
|
+
|
|
80
|
+
```rust
|
|
81
|
+
#[derive(Debug, Error)]
|
|
82
|
+
pub enum AppError {
|
|
83
|
+
#[error("database query failed")]
|
|
84
|
+
Database(#[from] sqlx::Error),
|
|
85
|
+
#[error("record not found: {entity} with id {id}")]
|
|
86
|
+
NotFound { entity: &'static str, id: String },
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### anyhow (Applications)
|
|
91
|
+
|
|
92
|
+
```rust
|
|
93
|
+
fn load_config(path: &Path) -> Result<Config> {
|
|
94
|
+
let contents = fs::read_to_string(path)
|
|
95
|
+
.with_context(|| format!("reading config from {}", path.display()))?;
|
|
96
|
+
Ok(toml::from_str(&contents).context("parsing config")?)
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Panic Policy
|
|
101
|
+
|
|
102
|
+
Panics are for bugs, not expected errors. No `unwrap()` in library code without justification. `todo!()` never ships.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Traits and Generics
|
|
107
|
+
|
|
108
|
+
### Small, Focused Traits
|
|
109
|
+
|
|
110
|
+
```rust
|
|
111
|
+
pub trait Validate {
|
|
112
|
+
fn validate(&self) -> Result<(), ValidationError>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Define Interfaces at the Consumer
|
|
117
|
+
|
|
118
|
+
The consumer decides what it needs — don't force a God-trait on callers.
|
|
119
|
+
|
|
120
|
+
### Phantom Types for State Machines
|
|
121
|
+
|
|
122
|
+
```rust
|
|
123
|
+
struct Form<State> { data: FormData, _state: PhantomData<State> }
|
|
124
|
+
// Form<Unvalidated> can't call submit() — only Form<Validated> can
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Generics Rules
|
|
128
|
+
|
|
129
|
+
- `impl Trait` for simple bounds in function parameters
|
|
130
|
+
- `where` clauses for complex bounds
|
|
131
|
+
- Associated types when there's one implementation per Self type
|
|
132
|
+
- Generic parameters when multiple implementations per Self type
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Concurrency
|
|
137
|
+
|
|
138
|
+
### Threads
|
|
139
|
+
|
|
140
|
+
```rust
|
|
141
|
+
// Scoped threads (1.63+) — safe borrows from parent stack
|
|
142
|
+
thread::scope(|s| {
|
|
143
|
+
s.spawn(|| process(&data));
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Async/Await
|
|
148
|
+
|
|
149
|
+
```rust
|
|
150
|
+
// Concurrent execution
|
|
151
|
+
let (users, posts) = tokio::join!(fetch_users(), fetch_posts());
|
|
152
|
+
|
|
153
|
+
// Timeout
|
|
154
|
+
tokio::select! {
|
|
155
|
+
result = fetch_data() => handle(result),
|
|
156
|
+
_ = tokio::time::sleep(Duration::from_secs(5)) => bail!("timeout"),
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Key Rules
|
|
161
|
+
|
|
162
|
+
- Never hold a `MutexGuard` across an `.await` point
|
|
163
|
+
- Never block in async context — use `spawn_blocking` for CPU work
|
|
164
|
+
- Use `tokio::sync::Mutex` when you must hold across awaits
|
|
165
|
+
- Every spawned task needs a cancellation/shutdown strategy
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Testing
|
|
170
|
+
|
|
171
|
+
### Table-Style Tests
|
|
172
|
+
|
|
173
|
+
```rust
|
|
174
|
+
#[test]
|
|
175
|
+
fn test_parse_age() {
|
|
176
|
+
let cases = [("25", Ok(25)), ("-1", Err(..)), ("abc", Err(..))];
|
|
177
|
+
for (input, expected) in cases {
|
|
178
|
+
assert_eq!(parse_age(input), expected, "input: {input}");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Trait-Based Dependency Injection
|
|
184
|
+
|
|
185
|
+
```rust
|
|
186
|
+
trait Clock { fn now(&self) -> DateTime<Utc>; }
|
|
187
|
+
struct FakeClock(DateTime<Utc>);
|
|
188
|
+
impl Clock for FakeClock { fn now(&self) -> DateTime<Utc> { self.0 } }
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Property-Based Testing
|
|
192
|
+
|
|
193
|
+
```rust
|
|
194
|
+
proptest! {
|
|
195
|
+
#[test]
|
|
196
|
+
fn round_trips(name in "[a-z]{1,50}", age in 0u32..150) {
|
|
197
|
+
let user = User { name, age };
|
|
198
|
+
let json = serde_json::to_string(&user)?;
|
|
199
|
+
let decoded: User = serde_json::from_str(&json)?;
|
|
200
|
+
assert_eq!(user, decoded);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Always Run
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
cargo test --all-features
|
|
209
|
+
cargo clippy -- -D warnings
|
|
210
|
+
cargo +nightly miri test # For crates with unsafe
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
## Performance and Unsafe
|
|
216
|
+
|
|
217
|
+
### Measure First
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
cargo bench
|
|
221
|
+
cargo flamegraph
|
|
222
|
+
cargo build --timings
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Key Patterns
|
|
226
|
+
|
|
227
|
+
- Preallocate with `Vec::with_capacity`
|
|
228
|
+
- Iterators over collect-then-iterate
|
|
229
|
+
- `Cow` for conditional allocation
|
|
230
|
+
- `SmallVec` for small stack-allocated collections
|
|
231
|
+
- `String::with_capacity` and `std::fmt::Write`
|
|
232
|
+
|
|
233
|
+
### Unsafe Rules
|
|
234
|
+
|
|
235
|
+
- Every `unsafe` block gets a `// SAFETY:` comment
|
|
236
|
+
- Wrap unsafe in safe abstractions with narrow interfaces
|
|
237
|
+
- Run Miri in CI for any crate with unsafe code
|
|
238
|
+
- `unsafe` doesn't fix design problems — it hides them
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
242
|
+
## Ecosystem and Tooling
|
|
243
|
+
|
|
244
|
+
### Essential Crates
|
|
245
|
+
|
|
246
|
+
| Domain | Crate |
|
|
247
|
+
|--------|-------|
|
|
248
|
+
| Serialization | serde, serde_json |
|
|
249
|
+
| Async | tokio |
|
|
250
|
+
| Web | axum, reqwest, tonic |
|
|
251
|
+
| Database | sqlx, diesel |
|
|
252
|
+
| Errors | thiserror, anyhow |
|
|
253
|
+
| CLI | clap |
|
|
254
|
+
| Observability | tracing, metrics |
|
|
255
|
+
| Testing | proptest, criterion, mockall, insta |
|
|
256
|
+
|
|
257
|
+
### CI Essentials
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
cargo fmt -- --check
|
|
261
|
+
cargo clippy --all-targets --all-features -- -D warnings
|
|
262
|
+
cargo test --all-features
|
|
263
|
+
cargo audit
|
|
264
|
+
cargo deny check
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## Definition of Done
|
|
270
|
+
|
|
271
|
+
A Rust feature is complete when:
|
|
272
|
+
|
|
273
|
+
- [ ] `cargo build` compiles with zero warnings
|
|
274
|
+
- [ ] `cargo clippy -- -D warnings` passes
|
|
275
|
+
- [ ] `cargo test` passes (including doc tests)
|
|
276
|
+
- [ ] `cargo fmt -- --check` passes
|
|
277
|
+
- [ ] No `unwrap()` in library code without justification
|
|
278
|
+
- [ ] All `unsafe` blocks have `// SAFETY:` comments
|
|
279
|
+
- [ ] Error types implement `std::error::Error`
|
|
280
|
+
- [ ] Public API has doc comments with examples
|
|
281
|
+
- [ ] No unnecessary allocations in hot paths
|
|
282
|
+
- [ ] `cargo deny check` passes
|
|
283
|
+
- [ ] Code reviewed and approved
|