red64-cli 0.3.0 → 0.6.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.
Files changed (95) hide show
  1. package/README.md +194 -338
  2. package/dist/cli/parseArgs.d.ts.map +1 -1
  3. package/dist/cli/parseArgs.js +5 -13
  4. package/dist/cli/parseArgs.js.map +1 -1
  5. package/dist/components/init/types.d.ts +0 -2
  6. package/dist/components/init/types.d.ts.map +1 -1
  7. package/dist/components/screens/HelpScreen.d.ts.map +1 -1
  8. package/dist/components/screens/HelpScreen.js +0 -2
  9. package/dist/components/screens/HelpScreen.js.map +1 -1
  10. package/dist/components/screens/InitScreen.d.ts.map +1 -1
  11. package/dist/components/screens/InitScreen.js +5 -8
  12. package/dist/components/screens/InitScreen.js.map +1 -1
  13. package/dist/components/screens/StartScreen.d.ts.map +1 -1
  14. package/dist/components/screens/StartScreen.js +29 -8
  15. package/dist/components/screens/StartScreen.js.map +1 -1
  16. package/dist/components/screens/StatusScreen.d.ts.map +1 -1
  17. package/dist/components/screens/StatusScreen.js +16 -1
  18. package/dist/components/screens/StatusScreen.js.map +1 -1
  19. package/dist/services/AgentInvoker.d.ts.map +1 -1
  20. package/dist/services/AgentInvoker.js +76 -37
  21. package/dist/services/AgentInvoker.js.map +1 -1
  22. package/dist/services/ClaudeErrorDetector.d.ts +1 -1
  23. package/dist/services/ClaudeErrorDetector.d.ts.map +1 -1
  24. package/dist/services/ClaudeErrorDetector.js +1 -0
  25. package/dist/services/ClaudeErrorDetector.js.map +1 -1
  26. package/dist/services/ClaudeHealthCheck.d.ts +7 -0
  27. package/dist/services/ClaudeHealthCheck.d.ts.map +1 -1
  28. package/dist/services/ClaudeHealthCheck.js +76 -12
  29. package/dist/services/ClaudeHealthCheck.js.map +1 -1
  30. package/dist/services/ConfigService.d.ts +1 -0
  31. package/dist/services/ConfigService.d.ts.map +1 -1
  32. package/dist/services/ConfigService.js.map +1 -1
  33. package/dist/services/DockerRunner.js +1 -1
  34. package/dist/services/DockerRunner.js.map +1 -1
  35. package/dist/services/PhaseExecutor.d.ts.map +1 -1
  36. package/dist/services/PhaseExecutor.js +2 -1
  37. package/dist/services/PhaseExecutor.js.map +1 -1
  38. package/dist/services/TaskRunner.d.ts.map +1 -1
  39. package/dist/services/TaskRunner.js +2 -1
  40. package/dist/services/TaskRunner.js.map +1 -1
  41. package/dist/services/index.d.ts +1 -1
  42. package/dist/services/index.d.ts.map +1 -1
  43. package/dist/services/index.js +1 -1
  44. package/dist/services/index.js.map +1 -1
  45. package/dist/types/index.d.ts +4 -3
  46. package/dist/types/index.d.ts.map +1 -1
  47. package/dist/types/index.js.map +1 -1
  48. package/framework/stacks/c/code-quality.md +326 -0
  49. package/framework/stacks/c/coding-style.md +347 -0
  50. package/framework/stacks/c/conventions.md +513 -0
  51. package/framework/stacks/c/error-handling.md +350 -0
  52. package/framework/stacks/c/feedback.md +158 -0
  53. package/framework/stacks/c/memory-safety.md +408 -0
  54. package/framework/stacks/c/tech.md +122 -0
  55. package/framework/stacks/c/testing.md +472 -0
  56. package/framework/stacks/cpp/code-quality.md +282 -0
  57. package/framework/stacks/cpp/coding-style.md +363 -0
  58. package/framework/stacks/cpp/conventions.md +420 -0
  59. package/framework/stacks/cpp/error-handling.md +264 -0
  60. package/framework/stacks/cpp/feedback.md +104 -0
  61. package/framework/stacks/cpp/memory-safety.md +351 -0
  62. package/framework/stacks/cpp/tech.md +160 -0
  63. package/framework/stacks/cpp/testing.md +323 -0
  64. package/framework/stacks/java/code-quality.md +357 -0
  65. package/framework/stacks/java/coding-style.md +400 -0
  66. package/framework/stacks/java/conventions.md +437 -0
  67. package/framework/stacks/java/error-handling.md +408 -0
  68. package/framework/stacks/java/feedback.md +180 -0
  69. package/framework/stacks/java/tech.md +126 -0
  70. package/framework/stacks/java/testing.md +485 -0
  71. package/framework/stacks/javascript/async-patterns.md +216 -0
  72. package/framework/stacks/javascript/code-quality.md +182 -0
  73. package/framework/stacks/javascript/coding-style.md +293 -0
  74. package/framework/stacks/javascript/conventions.md +268 -0
  75. package/framework/stacks/javascript/error-handling.md +216 -0
  76. package/framework/stacks/javascript/feedback.md +80 -0
  77. package/framework/stacks/javascript/tech.md +114 -0
  78. package/framework/stacks/javascript/testing.md +209 -0
  79. package/framework/stacks/loco/code-quality.md +156 -0
  80. package/framework/stacks/loco/coding-style.md +247 -0
  81. package/framework/stacks/loco/error-handling.md +225 -0
  82. package/framework/stacks/loco/feedback.md +35 -0
  83. package/framework/stacks/loco/loco.md +342 -0
  84. package/framework/stacks/loco/structure.md +193 -0
  85. package/framework/stacks/loco/tech.md +129 -0
  86. package/framework/stacks/loco/testing.md +211 -0
  87. package/framework/stacks/rust/code-quality.md +370 -0
  88. package/framework/stacks/rust/coding-style.md +475 -0
  89. package/framework/stacks/rust/conventions.md +430 -0
  90. package/framework/stacks/rust/error-handling.md +399 -0
  91. package/framework/stacks/rust/feedback.md +152 -0
  92. package/framework/stacks/rust/memory-safety.md +398 -0
  93. package/framework/stacks/rust/tech.md +121 -0
  94. package/framework/stacks/rust/testing.md +528 -0
  95. package/package.json +14 -2
@@ -0,0 +1,399 @@
1
+ # Error Handling Patterns
2
+
3
+ Structured error handling for Rust applications with `thiserror` for libraries, `anyhow` for applications, and the `?` operator everywhere.
4
+
5
+ ---
6
+
7
+ ## Philosophy
8
+
9
+ - **`Result<T, E>` everywhere**: No panics in production code, no silent failures
10
+ - **Typed errors for libraries**: Use `thiserror` so callers can match on variants
11
+ - **Ergonomic errors for applications**: Use `anyhow` at the application boundary
12
+ - **Context propagation**: Always add context when crossing abstraction boundaries
13
+ - **Fail fast**: Validate inputs early, return errors immediately with `?`
14
+
15
+ ---
16
+
17
+ ## Error Strategy: `thiserror` vs `anyhow`
18
+
19
+ | Layer | Crate | Purpose |
20
+ |---|---|---|
21
+ | Library / shared crates | `thiserror` | Custom error enums callers can match on |
22
+ | Application / binary | `anyhow` | Ergonomic error propagation with context |
23
+ | HTTP boundary (Axum) | `thiserror` + `IntoResponse` | Map domain errors to HTTP status codes |
24
+
25
+ ```rust
26
+ // GOOD: thiserror for domain/library errors
27
+ #[derive(Debug, thiserror::Error)]
28
+ pub enum UserError {
29
+ #[error("User not found: {0}")]
30
+ NotFound(i64),
31
+
32
+ #[error("Email already registered: {0}")]
33
+ DuplicateEmail(String),
34
+
35
+ #[error("Invalid email format: {0}")]
36
+ InvalidEmail(String),
37
+
38
+ #[error(transparent)]
39
+ Database(#[from] sqlx::Error),
40
+ }
41
+
42
+ // GOOD: anyhow for application-level code
43
+ use anyhow::{Context, Result};
44
+
45
+ async fn run_migration(pool: &PgPool) -> Result<()> {
46
+ sqlx::migrate!("./migrations")
47
+ .run(pool)
48
+ .await
49
+ .context("Failed to run database migrations")?;
50
+ Ok(())
51
+ }
52
+
53
+ // BAD: Using anyhow in a library crate
54
+ pub fn parse_config(path: &str) -> anyhow::Result<Config> {
55
+ // Callers cannot match on specific error types
56
+ }
57
+
58
+ // BAD: Using thiserror for top-level CLI error handling
59
+ fn main() -> Result<(), AppError> {
60
+ // Unnecessarily verbose for a binary
61
+ }
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Custom Error Enums with `thiserror`
67
+
68
+ ### Domain Errors
69
+
70
+ ```rust
71
+ use thiserror::Error;
72
+
73
+ #[derive(Debug, Error)]
74
+ pub enum AppError {
75
+ #[error("Resource not found: {resource} ({id})")]
76
+ NotFound {
77
+ resource: &'static str,
78
+ id: String,
79
+ },
80
+
81
+ #[error("Conflict: {0}")]
82
+ Conflict(String),
83
+
84
+ #[error("Validation failed: {0}")]
85
+ Validation(String),
86
+
87
+ #[error("Authentication required")]
88
+ Unauthenticated,
89
+
90
+ #[error("Insufficient permissions")]
91
+ Forbidden,
92
+
93
+ #[error("External service error ({service}): {message}")]
94
+ ExternalService {
95
+ service: String,
96
+ message: String,
97
+ },
98
+
99
+ #[error(transparent)]
100
+ Database(#[from] sqlx::Error),
101
+
102
+ #[error(transparent)]
103
+ Internal(#[from] anyhow::Error),
104
+ }
105
+ ```
106
+
107
+ ### Axum `IntoResponse` Integration
108
+
109
+ ```rust
110
+ use axum::http::StatusCode;
111
+ use axum::response::{IntoResponse, Response};
112
+ use axum::Json;
113
+ use serde_json::json;
114
+
115
+ impl IntoResponse for AppError {
116
+ fn into_response(self) -> Response {
117
+ let (status, code, message) = match &self {
118
+ AppError::NotFound { resource, id } => (
119
+ StatusCode::NOT_FOUND,
120
+ "NOT_FOUND",
121
+ format!("{resource} not found: {id}"),
122
+ ),
123
+ AppError::Conflict(msg) => (
124
+ StatusCode::CONFLICT,
125
+ "CONFLICT",
126
+ msg.clone(),
127
+ ),
128
+ AppError::Validation(msg) => (
129
+ StatusCode::UNPROCESSABLE_ENTITY,
130
+ "VALIDATION_ERROR",
131
+ msg.clone(),
132
+ ),
133
+ AppError::Unauthenticated => (
134
+ StatusCode::UNAUTHORIZED,
135
+ "UNAUTHENTICATED",
136
+ "Authentication required".to_string(),
137
+ ),
138
+ AppError::Forbidden => (
139
+ StatusCode::FORBIDDEN,
140
+ "FORBIDDEN",
141
+ "Insufficient permissions".to_string(),
142
+ ),
143
+ AppError::ExternalService { service, message } => (
144
+ StatusCode::BAD_GATEWAY,
145
+ "EXTERNAL_SERVICE_ERROR",
146
+ format!("External service error ({service}): {message}"),
147
+ ),
148
+ AppError::Database(e) => {
149
+ tracing::error!(error = %e, "Database error");
150
+ (
151
+ StatusCode::INTERNAL_SERVER_ERROR,
152
+ "INTERNAL_ERROR",
153
+ "An internal error occurred".to_string(),
154
+ )
155
+ }
156
+ AppError::Internal(e) => {
157
+ tracing::error!(error = %e, "Internal error");
158
+ (
159
+ StatusCode::INTERNAL_SERVER_ERROR,
160
+ "INTERNAL_ERROR",
161
+ "An internal error occurred".to_string(),
162
+ )
163
+ }
164
+ };
165
+
166
+ let body = Json(json!({
167
+ "error": {
168
+ "code": code,
169
+ "message": message,
170
+ }
171
+ }));
172
+
173
+ (status, body).into_response()
174
+ }
175
+ }
176
+ ```
177
+
178
+ ---
179
+
180
+ ## The `?` Operator
181
+
182
+ ### Propagation with `?`
183
+
184
+ ```rust
185
+ // GOOD: Clean propagation with ?
186
+ async fn get_user_posts(pool: &PgPool, user_id: i64) -> Result<Vec<Post>, AppError> {
187
+ let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
188
+ .fetch_optional(pool)
189
+ .await?
190
+ .ok_or(AppError::NotFound { resource: "User", id: user_id.to_string() })?;
191
+
192
+ let posts = sqlx::query_as!(Post, "SELECT * FROM posts WHERE user_id = $1", user.id)
193
+ .fetch_all(pool)
194
+ .await?;
195
+
196
+ Ok(posts)
197
+ }
198
+
199
+ // BAD: Manual matching instead of ?
200
+ async fn get_user_posts(pool: &PgPool, user_id: i64) -> Result<Vec<Post>, AppError> {
201
+ let user = match sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", user_id)
202
+ .fetch_optional(pool)
203
+ .await
204
+ {
205
+ Ok(Some(u)) => u,
206
+ Ok(None) => return Err(AppError::NotFound { resource: "User", id: user_id.to_string() }),
207
+ Err(e) => return Err(AppError::Database(e)),
208
+ };
209
+ // ... more nested matches
210
+ }
211
+ ```
212
+
213
+ ---
214
+
215
+ ## Error Context with `anyhow`
216
+
217
+ ### Adding Context
218
+
219
+ ```rust
220
+ use anyhow::{Context, Result};
221
+
222
+ // GOOD: Context explains what was happening when the error occurred
223
+ async fn load_config(path: &str) -> Result<Config> {
224
+ let content = std::fs::read_to_string(path)
225
+ .with_context(|| format!("Failed to read config file: {path}"))?;
226
+
227
+ let config: Config = toml::from_str(&content)
228
+ .with_context(|| format!("Failed to parse config file: {path}"))?;
229
+
230
+ Ok(config)
231
+ }
232
+
233
+ // BAD: No context -- error message is just "No such file or directory"
234
+ async fn load_config(path: &str) -> Result<Config> {
235
+ let content = std::fs::read_to_string(path)?;
236
+ let config: Config = toml::from_str(&content)?;
237
+ Ok(config)
238
+ }
239
+ ```
240
+
241
+ ### Context in Async Chains
242
+
243
+ ```rust
244
+ // GOOD: Context on each fallible step
245
+ async fn sync_user(pool: &PgPool, external_id: &str) -> Result<User> {
246
+ let external_user = fetch_external_user(external_id)
247
+ .await
248
+ .context("Failed to fetch user from external service")?;
249
+
250
+ let user = upsert_user(pool, &external_user)
251
+ .await
252
+ .context("Failed to upsert user in database")?;
253
+
254
+ send_sync_notification(&user)
255
+ .await
256
+ .context("Failed to send sync notification")?;
257
+
258
+ Ok(user)
259
+ }
260
+ ```
261
+
262
+ ---
263
+
264
+ ## `From` Implementations
265
+
266
+ ### Automatic with `thiserror`
267
+
268
+ ```rust
269
+ #[derive(Debug, thiserror::Error)]
270
+ pub enum AppError {
271
+ // #[from] auto-generates From<sqlx::Error> for AppError
272
+ #[error(transparent)]
273
+ Database(#[from] sqlx::Error),
274
+
275
+ // #[from] auto-generates From<serde_json::Error> for AppError
276
+ #[error("Serialization error: {0}")]
277
+ Serialization(#[from] serde_json::Error),
278
+ }
279
+
280
+ // Now ? works automatically:
281
+ async fn get_user(pool: &PgPool, id: i64) -> Result<User, AppError> {
282
+ let user = sqlx::query_as!(User, "SELECT * FROM users WHERE id = $1", id)
283
+ .fetch_one(pool)
284
+ .await?; // sqlx::Error -> AppError via From
285
+ Ok(user)
286
+ }
287
+ ```
288
+
289
+ ### Manual `From` When Needed
290
+
291
+ ```rust
292
+ // When you need custom conversion logic
293
+ impl From<reqwest::Error> for AppError {
294
+ fn from(err: reqwest::Error) -> Self {
295
+ if err.is_timeout() {
296
+ AppError::ExternalService {
297
+ service: "http_client".to_string(),
298
+ message: "Request timed out".to_string(),
299
+ }
300
+ } else if err.is_connect() {
301
+ AppError::ExternalService {
302
+ service: "http_client".to_string(),
303
+ message: "Connection failed".to_string(),
304
+ }
305
+ } else {
306
+ AppError::Internal(err.into())
307
+ }
308
+ }
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ## No `unwrap()` in Production
315
+
316
+ ### Alternatives to `unwrap()`
317
+
318
+ ```rust
319
+ // GOOD: Propagate with ?
320
+ let user = get_user(pool, id).await?;
321
+
322
+ // GOOD: Provide default
323
+ let port = env::var("PORT").unwrap_or_else(|_| "8080".to_string());
324
+
325
+ // GOOD: Convert to Result with ok_or
326
+ let user = users.get(0).ok_or(AppError::NotFound {
327
+ resource: "User",
328
+ id: "first".to_string(),
329
+ })?;
330
+
331
+ // GOOD: expect() in tests and initialization (with justification)
332
+ let pool = PgPool::connect(&database_url)
333
+ .await
334
+ .expect("DATABASE_URL must point to a valid Postgres instance");
335
+
336
+ // BAD: unwrap in production code
337
+ let user = get_user(pool, id).await.unwrap(); // PANICS if None/Err
338
+ let config = std::fs::read_to_string("config.toml").unwrap(); // PANICS if file missing
339
+ ```
340
+
341
+ ### When `expect()` Is Acceptable
342
+
343
+ | Context | Acceptable? | Reason |
344
+ |---|---|---|
345
+ | Tests | Yes | Test failure is the correct behavior |
346
+ | `main()` initialization | Yes | Cannot proceed without config/db |
347
+ | After validation guard | Cautiously | Document why it cannot fail |
348
+ | Production request handling | Never | Panics crash the server |
349
+
350
+ ---
351
+
352
+ ## Error Logging with `tracing`
353
+
354
+ ### Log at the Boundary
355
+
356
+ ```rust
357
+ // GOOD: Log once at the boundary, not at every layer
358
+ async fn create_user_handler(
359
+ State(pool): State<PgPool>,
360
+ Json(data): Json<CreateUserRequest>,
361
+ ) -> Result<Json<UserResponse>, AppError> {
362
+ let user = create_user(&pool, &data).await.map_err(|e| {
363
+ tracing::error!(
364
+ error = %e,
365
+ email = %data.email,
366
+ "Failed to create user"
367
+ );
368
+ e
369
+ })?;
370
+ Ok(Json(UserResponse::from(user)))
371
+ }
372
+
373
+ // BAD: Logging at every layer
374
+ async fn create_user(pool: &PgPool, data: &CreateUserRequest) -> Result<User, AppError> {
375
+ let hash = hash_password(&data.password).map_err(|e| {
376
+ tracing::error!("hash failed: {}", e); // Too noisy
377
+ e
378
+ })?;
379
+ // ...
380
+ }
381
+ ```
382
+
383
+ ---
384
+
385
+ ## Anti-Patterns
386
+
387
+ | Anti-Pattern | Problem | Correct Approach |
388
+ |---|---|---|
389
+ | `.unwrap()` in handlers | Panics crash the server | Use `?` with proper error types |
390
+ | `Box<dyn Error>` in libraries | Callers cannot match on variants | Use `thiserror` enums |
391
+ | Stringly-typed errors | No programmatic handling | Use typed error enums |
392
+ | Catching and ignoring errors | Hides bugs | Log and propagate or handle explicitly |
393
+ | `anyhow` in library crates | Callers lose type information | Reserve for application code |
394
+ | Error messages with internal details | Security risk in API responses | Return safe messages, log details |
395
+ | Re-implementing `From` that `thiserror` provides | Boilerplate | Use `#[from]` attribute |
396
+
397
+ ---
398
+
399
+ _Errors are values in Rust. Classify them with `thiserror`, propagate them with `?`, enrich them with `.context()`, and handle them at the boundary._
@@ -0,0 +1,152 @@
1
+ # Feedback Configuration
2
+
3
+ Project-specific commands for automated feedback during Rust implementation.
4
+
5
+ ---
6
+
7
+ ## Test Commands
8
+
9
+ Commands to run tests during implementation. The agent will use these to verify code changes.
10
+
11
+ ```yaml
12
+ # Primary test command (REQUIRED)
13
+ test: cargo nextest run
14
+
15
+ # Test with standard cargo test (fallback)
16
+ test_fallback: cargo test
17
+
18
+ # Test with coverage report
19
+ test_coverage: cargo llvm-cov nextest --fail-under-lines 80
20
+
21
+ # Run specific test by name
22
+ test_pattern: cargo nextest run --filter-expr 'test({pattern})'
23
+
24
+ # Run tests for specific crate in workspace
25
+ test_crate: cargo nextest run -p {crate}
26
+
27
+ # Run doctests (nextest does not run these)
28
+ test_doc: cargo test --doc
29
+
30
+ # Run only integration tests
31
+ test_integration: cargo nextest run --filter-expr 'kind(test)'
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Linting Commands
37
+
38
+ Commands for code quality checks.
39
+
40
+ ```yaml
41
+ # Primary lint command (Clippy with denied warnings)
42
+ lint: cargo clippy --all-targets --all-features -- -D warnings
43
+
44
+ # Lint specific crate
45
+ lint_crate: cargo clippy -p {crate} -- -D warnings
46
+
47
+ # Auto-fix Clippy suggestions
48
+ lint_fix: cargo clippy --fix --allow-dirty
49
+
50
+ # Format check
51
+ format_check: cargo fmt -- --check
52
+
53
+ # Format fix
54
+ format_fix: cargo fmt
55
+
56
+ # Security audit
57
+ audit: cargo audit
58
+
59
+ # Dependency policy check (licenses, advisories, bans)
60
+ deny: cargo deny check
61
+ ```
62
+
63
+ ---
64
+
65
+ ## Build Commands
66
+
67
+ Commands for building the project.
68
+
69
+ ```yaml
70
+ # Debug build
71
+ build: cargo build
72
+
73
+ # Release build
74
+ build_release: cargo build --release
75
+
76
+ # Check without building (faster feedback)
77
+ check: cargo check --all-targets --all-features
78
+
79
+ # Build documentation
80
+ doc: cargo doc --no-deps
81
+
82
+ # Clean build artifacts
83
+ clean: cargo clean
84
+ ```
85
+
86
+ ---
87
+
88
+ ## Development Server
89
+
90
+ Commands for starting the development server (Axum application).
91
+
92
+ ```yaml
93
+ # Start dev server
94
+ dev_server: cargo run
95
+
96
+ # Start dev server with auto-reload (requires cargo-watch)
97
+ dev_watch: cargo watch -x run
98
+
99
+ # Dev server port
100
+ dev_port: 8080
101
+
102
+ # Dev server base URL
103
+ dev_url: http://localhost:8080
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Database Commands
109
+
110
+ Commands for database operations with SQLx.
111
+
112
+ ```yaml
113
+ # Run migrations
114
+ migrate: sqlx migrate run
115
+
116
+ # Create new migration
117
+ migrate_create: sqlx migrate add {name}
118
+
119
+ # Prepare offline query data (for CI without database)
120
+ sqlx_prepare: cargo sqlx prepare
121
+
122
+ # Check offline query data is up to date
123
+ sqlx_check: cargo sqlx prepare --check
124
+ ```
125
+
126
+ ---
127
+
128
+ ## CI Pipeline Commands
129
+
130
+ Full quality check sequence for continuous integration.
131
+
132
+ ```yaml
133
+ # Complete CI check (run in order)
134
+ ci_format: cargo fmt -- --check
135
+ ci_lint: cargo clippy --all-targets --all-features -- -D warnings
136
+ ci_test: cargo nextest run --all-features
137
+ ci_doctest: cargo test --doc
138
+ ci_audit: cargo audit
139
+ ci_deny: cargo deny check
140
+ ci_doc: cargo doc --no-deps
141
+ ```
142
+
143
+ ---
144
+
145
+ ## Notes
146
+
147
+ - Uses `cargo-nextest` as the primary test runner for faster parallel execution
148
+ - Doctests must be run separately with `cargo test --doc` (nextest does not support them)
149
+ - `cargo clippy` with `-D warnings` treats all warnings as errors in CI
150
+ - `cargo audit` checks against the RustSec advisory database
151
+ - `cargo deny` enforces license compliance, advisory checks, and duplicate detection
152
+ - SQLx offline mode (`cargo sqlx prepare`) enables CI builds without a live database