litclaude-ai 0.2.2
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/CHANGELOG.md +155 -0
- package/LICENSE +21 -0
- package/README.md +369 -0
- package/README_ko-KR.md +374 -0
- package/RELEASE_CHECKLIST.md +165 -0
- package/bin/litclaude-ai.js +643 -0
- package/cover.png +0 -0
- package/docs/agents.md +67 -0
- package/docs/hooks.md +134 -0
- package/docs/lsp.md +40 -0
- package/docs/migration.md +209 -0
- package/docs/workflow-compatibility-audit.md +119 -0
- package/generate_cover.py +123 -0
- package/package.json +48 -0
- package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
- package/plugins/litclaude/.lsp.json +13 -0
- package/plugins/litclaude/.mcp.json +9 -0
- package/plugins/litclaude/agents/boulder-executor.md +12 -0
- package/plugins/litclaude/agents/librarian-researcher.md +15 -0
- package/plugins/litclaude/agents/oracle-verifier.md +16 -0
- package/plugins/litclaude/agents/prometheus-planner.md +13 -0
- package/plugins/litclaude/agents/qa-runner.md +16 -0
- package/plugins/litclaude/agents/quality-reviewer.md +17 -0
- package/plugins/litclaude/bin/litclaude-hook.js +110 -0
- package/plugins/litclaude/bin/litclaude-hud.js +271 -0
- package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
- package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
- package/plugins/litclaude/commands/deep-interview.md +21 -0
- package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
- package/plugins/litclaude/commands/lit-loop.md +40 -0
- package/plugins/litclaude/commands/lit-plan.md +35 -0
- package/plugins/litclaude/commands/litgoal.md +30 -0
- package/plugins/litclaude/commands/review-work.md +35 -0
- package/plugins/litclaude/commands/start-work.md +36 -0
- package/plugins/litclaude/hooks/hooks.json +54 -0
- package/plugins/litclaude/lib/context-pressure.mjs +25 -0
- package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
- package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
- package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
- package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
- package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
- package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
- package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
- package/plugins/litclaude/lib/workflow-check.mjs +83 -0
- package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
- package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
- package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
- package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
- package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
- package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
- package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
- package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
- package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
- package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
- package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
- package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
- package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
- package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
- package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
- package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
- package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
- package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
- package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
- package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
- package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
- package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
- package/plugins/litclaude/skills/programming/SKILL.md +106 -0
- package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
- package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
- package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
- package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
- package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
- package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
- package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
- package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
- package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
- package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
- package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
- package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
- package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
- package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
- package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
- package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
- package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
- package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
- package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
- package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
- package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
- package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
- package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
- package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
- package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
- package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
- package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
- package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
- package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
- package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
- package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
- package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
- package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
- package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
- package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
- package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
- package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
- package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
- package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
- package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
- package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
- package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
- package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
- package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
- package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
- package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
- package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
- package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
- package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
- package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
- package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
- package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
- package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
- package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
- package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
- package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
- package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
- package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
- package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
- package/plugins/litclaude/skills/rules/SKILL.md +66 -0
- package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
- package/scripts/audit-plan-checkboxes.mjs +37 -0
- package/scripts/doctor.mjs +41 -0
- package/scripts/inspect-agent-tools.mjs +27 -0
- package/scripts/postinstall.mjs +50 -0
- package/scripts/qa-claude-plugin-smoke.sh +60 -0
- package/scripts/qa-portable-install.sh +136 -0
- package/scripts/validate-plugin.mjs +72 -0
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
# Library Defaults — Full Decision Tree
|
|
2
|
+
|
|
3
|
+
The opinionated, audited-in-prod stack for 2026 Rust. Every entry has a one-line rationale and a canonical code snippet so the agent does not have to relearn each library's idioms.
|
|
4
|
+
|
|
5
|
+
## Async runtime — `tokio`
|
|
6
|
+
|
|
7
|
+
The default. Use `tokio` for new work. Multi-thread runtime unless you have a measured reason to go single-thread.
|
|
8
|
+
|
|
9
|
+
```rust
|
|
10
|
+
#[tokio::main(flavor = "multi_thread", worker_threads = 8)]
|
|
11
|
+
async fn main() -> anyhow::Result<()> {
|
|
12
|
+
tracing_subscriber::fmt::init();
|
|
13
|
+
run().await
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Avoid:
|
|
18
|
+
- `async-std` — unmaintained, last release ages ago. crates.io download counts are misleading because of historical inertia.
|
|
19
|
+
- `smol` — fine for embedded-ish niches; outside that, the ecosystem is on tokio.
|
|
20
|
+
- Mixing runtimes in one binary. Pick one and stay.
|
|
21
|
+
|
|
22
|
+
## Errors — `anyhow` (apps) + `thiserror` (libs)
|
|
23
|
+
|
|
24
|
+
Application boundaries get `anyhow::Error` with `.context("...")` at every layer that adds meaning. Libraries expose `#[derive(thiserror::Error)]` enums with `#[non_exhaustive]`.
|
|
25
|
+
|
|
26
|
+
```rust
|
|
27
|
+
// Application code
|
|
28
|
+
use anyhow::Context as _;
|
|
29
|
+
|
|
30
|
+
pub async fn load_config(path: &Path) -> anyhow::Result<Config> {
|
|
31
|
+
let text = tokio::fs::read_to_string(path)
|
|
32
|
+
.await
|
|
33
|
+
.with_context(|| format!("reading config from {}", path.display()))?;
|
|
34
|
+
let cfg: Config = toml::from_str(&text)
|
|
35
|
+
.with_context(|| format!("parsing config at {}", path.display()))?;
|
|
36
|
+
Ok(cfg)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Library code
|
|
40
|
+
#[derive(Debug, thiserror::Error)]
|
|
41
|
+
#[non_exhaustive]
|
|
42
|
+
pub enum ParseError {
|
|
43
|
+
#[error("expected {expected}, found {found} at position {position}")]
|
|
44
|
+
Mismatch { expected: &'static str, found: String, position: usize },
|
|
45
|
+
#[error("unexpected end of input after {context}")]
|
|
46
|
+
UnexpectedEof { context: &'static str },
|
|
47
|
+
#[error(transparent)]
|
|
48
|
+
Io(#[from] std::io::Error),
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`#[non_exhaustive]` on enums prevents downstream `match` from breaking when you add variants. `#[error(transparent)]` on a wrapper variant forwards Display + cause to the inner error.
|
|
53
|
+
|
|
54
|
+
## CLI — `clap` with derive
|
|
55
|
+
|
|
56
|
+
```rust
|
|
57
|
+
use clap::{Parser, Subcommand};
|
|
58
|
+
|
|
59
|
+
#[derive(Parser, Debug)]
|
|
60
|
+
#[command(author, version, about, long_about = None)]
|
|
61
|
+
struct Cli {
|
|
62
|
+
/// Path to the config file
|
|
63
|
+
#[arg(short, long, env = "MYAPP_CONFIG", default_value = "config.toml")]
|
|
64
|
+
config: PathBuf,
|
|
65
|
+
|
|
66
|
+
/// Enable verbose output (-v, -vv, -vvv)
|
|
67
|
+
#[arg(short, long, action = clap::ArgAction::Count)]
|
|
68
|
+
verbose: u8,
|
|
69
|
+
|
|
70
|
+
#[command(subcommand)]
|
|
71
|
+
command: Command,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#[derive(Subcommand, Debug)]
|
|
75
|
+
enum Command {
|
|
76
|
+
/// Run the server
|
|
77
|
+
Serve {
|
|
78
|
+
#[arg(short, long, default_value_t = 8080)]
|
|
79
|
+
port: u16,
|
|
80
|
+
},
|
|
81
|
+
/// Migrate the database
|
|
82
|
+
Migrate {
|
|
83
|
+
#[arg(long)]
|
|
84
|
+
dry_run: bool,
|
|
85
|
+
},
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Avoid `structopt` (deprecated, merged into clap), `argh` (less ergonomic), `pico-args` (only when binary size matters more than DX).
|
|
90
|
+
|
|
91
|
+
## Logging — `tracing` + `tracing-subscriber`
|
|
92
|
+
|
|
93
|
+
Not `log` + `env_logger`. `tracing` supports spans (structured context that follows async tasks) and structured fields - `log` cannot.
|
|
94
|
+
|
|
95
|
+
```rust
|
|
96
|
+
use tracing::{info, instrument, warn, Level};
|
|
97
|
+
use tracing_subscriber::{fmt, EnvFilter};
|
|
98
|
+
|
|
99
|
+
fn init_tracing() {
|
|
100
|
+
let filter = EnvFilter::try_from_default_env()
|
|
101
|
+
.unwrap_or_else(|_| EnvFilter::new("info,sqlx=warn,hyper=warn"));
|
|
102
|
+
fmt()
|
|
103
|
+
.with_env_filter(filter)
|
|
104
|
+
.with_target(false)
|
|
105
|
+
.with_thread_ids(true)
|
|
106
|
+
.with_line_number(true)
|
|
107
|
+
.compact()
|
|
108
|
+
.init();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[instrument(skip(db), fields(user_id = %user.id))]
|
|
112
|
+
async fn process_user(db: &Pool, user: &User) -> anyhow::Result<()> {
|
|
113
|
+
info!("processing user");
|
|
114
|
+
if user.is_banned() {
|
|
115
|
+
warn!(reason = "banned", "skipping");
|
|
116
|
+
return Ok(());
|
|
117
|
+
}
|
|
118
|
+
// ... body ...
|
|
119
|
+
Ok(())
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Replace `println!` with `info!`/`warn!`/`error!`. Replace `eprintln!` with `tracing::error!`.
|
|
124
|
+
|
|
125
|
+
## Error reporting (binaries) — `color-eyre`
|
|
126
|
+
|
|
127
|
+
For binary `main()`, hook `color-eyre` to give pretty panics + nice `Result` printing:
|
|
128
|
+
|
|
129
|
+
```rust
|
|
130
|
+
fn main() -> color_eyre::Result<()> {
|
|
131
|
+
color_eyre::install()?;
|
|
132
|
+
tracing_subscriber::fmt::init();
|
|
133
|
+
real_main()
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Library code stays on `anyhow`/`thiserror`. `color-eyre` is purely a display layer for the binary.
|
|
138
|
+
|
|
139
|
+
## Serialization — `serde` + `serde_json`
|
|
140
|
+
|
|
141
|
+
The default for any data crossing a process boundary (file, network, IPC, database column).
|
|
142
|
+
|
|
143
|
+
```rust
|
|
144
|
+
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
145
|
+
#[serde(deny_unknown_fields, rename_all = "snake_case")]
|
|
146
|
+
pub struct ApiResponse {
|
|
147
|
+
pub user_id: UserId,
|
|
148
|
+
pub created_at: jiff::Timestamp,
|
|
149
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
150
|
+
pub display_name: Option<String>,
|
|
151
|
+
#[serde(flatten)]
|
|
152
|
+
pub extra: HashMap<String, serde_json::Value>,
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`deny_unknown_fields` catches typos in inputs. `rename_all = "snake_case"` aligns with REST/JSON conventions while keeping idiomatic Rust field names. `#[serde(flatten)]` for forward-compatible extra fields.
|
|
157
|
+
|
|
158
|
+
Alternatives:
|
|
159
|
+
- `serde_yaml` (YAML — note: YAML's "deserialize anything" surface is a security trap; prefer JSON/TOML where possible)
|
|
160
|
+
- `toml` (config files)
|
|
161
|
+
- `rmp-serde` (MessagePack — binary, fast)
|
|
162
|
+
- `ciborium` (CBOR)
|
|
163
|
+
- `bincode 2` (binary, smaller; no serde required in v2 but interop fine)
|
|
164
|
+
|
|
165
|
+
## HTTP client — `reqwest`
|
|
166
|
+
|
|
167
|
+
```rust
|
|
168
|
+
let client = reqwest::Client::builder()
|
|
169
|
+
.timeout(std::time::Duration::from_secs(30))
|
|
170
|
+
.user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))
|
|
171
|
+
.https_only(true)
|
|
172
|
+
.pool_max_idle_per_host(8)
|
|
173
|
+
.build()?;
|
|
174
|
+
|
|
175
|
+
#[derive(serde::Deserialize)]
|
|
176
|
+
struct Repo { full_name: String, stargazers_count: u64 }
|
|
177
|
+
|
|
178
|
+
let repo: Repo = client
|
|
179
|
+
.get("https://api.github.com/repos/rust-lang/rust")
|
|
180
|
+
.send().await?
|
|
181
|
+
.error_for_status()?
|
|
182
|
+
.json().await?;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
`error_for_status()?` turns 4xx/5xx into `Err`. Always include a User-Agent. `https_only(true)` is a soundness toggle - prevents accidental http:// downgrade.
|
|
186
|
+
|
|
187
|
+
## Web framework — `axum`
|
|
188
|
+
|
|
189
|
+
```rust
|
|
190
|
+
use axum::{Router, routing::get, extract::State, response::Json};
|
|
191
|
+
use std::sync::Arc;
|
|
192
|
+
|
|
193
|
+
#[derive(Clone)]
|
|
194
|
+
struct AppState { db: sqlx::PgPool }
|
|
195
|
+
|
|
196
|
+
async fn health(State(state): State<Arc<AppState>>) -> Json<serde_json::Value> {
|
|
197
|
+
let ok = sqlx::query_scalar!("SELECT 1::int4").fetch_one(&state.db).await.is_ok();
|
|
198
|
+
Json(serde_json::json!({ "ok": ok }))
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[tokio::main]
|
|
202
|
+
async fn main() -> anyhow::Result<()> {
|
|
203
|
+
tracing_subscriber::fmt::init();
|
|
204
|
+
let state = Arc::new(AppState { db: sqlx::PgPool::connect(&env_db()).await? });
|
|
205
|
+
let app = Router::new()
|
|
206
|
+
.route("/health", get(health))
|
|
207
|
+
.with_state(state)
|
|
208
|
+
.layer(tower_http::trace::TraceLayer::new_for_http())
|
|
209
|
+
.layer(tower_http::compression::CompressionLayer::new());
|
|
210
|
+
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
|
|
211
|
+
axum::serve(listener, app).await?;
|
|
212
|
+
Ok(())
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Avoid `actix-web` (legacy patterns, separate runtime model), `warp` (filter explosion in non-trivial apps), `rocket` (slow release cadence). Pair `axum` with `tower-http` for middleware (trace, compression, CORS, timeout, request-id).
|
|
217
|
+
|
|
218
|
+
## Database — `sqlx` (compile-time checked SQL)
|
|
219
|
+
|
|
220
|
+
```rust
|
|
221
|
+
use sqlx::PgPool;
|
|
222
|
+
|
|
223
|
+
#[derive(Debug, sqlx::FromRow)]
|
|
224
|
+
pub struct User { pub id: uuid::Uuid, pub email: String, pub created_at: jiff::Timestamp }
|
|
225
|
+
|
|
226
|
+
pub async fn find_user(pool: &PgPool, email: &str) -> Result<Option<User>, sqlx::Error> {
|
|
227
|
+
sqlx::query_as!(
|
|
228
|
+
User,
|
|
229
|
+
r#"SELECT id, email, created_at as "created_at: jiff::Timestamp"
|
|
230
|
+
FROM users WHERE email = $1"#,
|
|
231
|
+
email
|
|
232
|
+
)
|
|
233
|
+
.fetch_optional(pool)
|
|
234
|
+
.await
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`query_as!` checks the SQL against the live database at compile time. To work without a live DB during builds, generate offline metadata: `cargo sqlx prepare`. Commit the resulting `.sqlx/` directory.
|
|
239
|
+
|
|
240
|
+
Avoid `diesel` (sync-first, heavy DSL), raw `tokio-postgres` (loses type checks), `sea-orm` (more magic, less control).
|
|
241
|
+
|
|
242
|
+
For migrations: `sqlx migrate add <name>` + `sqlx::migrate!("./migrations").run(&pool).await?`.
|
|
243
|
+
|
|
244
|
+
## Time — `jiff`
|
|
245
|
+
|
|
246
|
+
The 2025+ choice. Single crate, sane defaults, civil time / instant / span distinction.
|
|
247
|
+
|
|
248
|
+
```rust
|
|
249
|
+
use jiff::{Timestamp, Span, ToSpan, Zoned};
|
|
250
|
+
|
|
251
|
+
let now: Timestamp = Timestamp::now();
|
|
252
|
+
let in_one_hour = now.checked_add(1.hour())?;
|
|
253
|
+
let local: Zoned = now.in_tz("Asia/Seoul")?;
|
|
254
|
+
let span: Span = local - some_earlier.in_tz("Asia/Seoul")?;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Avoid `chrono` (old API, generic-heavy, time zone story still painful), `time` crate (split ecosystem, weaker docs). `jiff` is the post-`chrono` consolidation.
|
|
258
|
+
|
|
259
|
+
## UUID — `uuid` with v7
|
|
260
|
+
|
|
261
|
+
```rust
|
|
262
|
+
use uuid::Uuid;
|
|
263
|
+
|
|
264
|
+
// v7 for IDs (sortable, time-ordered, monotonic-ish, RFC 9562)
|
|
265
|
+
let id = Uuid::now_v7();
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
v4 is fine for nonces, v7 for primary keys (better index locality). Never v1 (leaks MAC). Cargo features: `uuid = { version = "1", features = ["v4", "v7", "serde"] }`.
|
|
269
|
+
|
|
270
|
+
## DataFrames / analytics — `polars`
|
|
271
|
+
|
|
272
|
+
For columnar data, joins, group-by, lazy plans:
|
|
273
|
+
|
|
274
|
+
```rust
|
|
275
|
+
use polars::prelude::*;
|
|
276
|
+
|
|
277
|
+
let df = LazyCsvReader::new("events.csv")
|
|
278
|
+
.finish()?
|
|
279
|
+
.group_by([col("user_id")])
|
|
280
|
+
.agg([col("amount").sum().alias("total")])
|
|
281
|
+
.sort(["total"], Default::default())
|
|
282
|
+
.collect()?;
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
The Rust API mirrors the Python one. Use the lazy API by default; materialize with `.collect()` at the end.
|
|
286
|
+
|
|
287
|
+
## Channels
|
|
288
|
+
|
|
289
|
+
- Single-producer single-consumer or bounded MPSC → `tokio::sync::mpsc` (async) or `flume` (sync + async).
|
|
290
|
+
- Broadcast → `tokio::sync::broadcast`.
|
|
291
|
+
- Watch (latest-value pubsub) → `tokio::sync::watch`.
|
|
292
|
+
- Oneshot → `tokio::sync::oneshot`.
|
|
293
|
+
|
|
294
|
+
Avoid raw `std::sync::mpsc` (sync only, fewer features), `crossbeam-channel` (good but heavier; use only if you need rendezvous semantics).
|
|
295
|
+
|
|
296
|
+
## Coordinate spaces / 2D math — `euclid`
|
|
297
|
+
|
|
298
|
+
```rust
|
|
299
|
+
use euclid::{Point2D, Size2D, default::Box2D};
|
|
300
|
+
struct ScreenSpace;
|
|
301
|
+
struct WorldSpace;
|
|
302
|
+
|
|
303
|
+
type ScreenPoint = Point2D<f32, ScreenSpace>;
|
|
304
|
+
type WorldPoint = Point2D<f32, WorldSpace>;
|
|
305
|
+
|
|
306
|
+
let cursor: ScreenPoint = Point2D::new(120.0, 240.0);
|
|
307
|
+
let player: WorldPoint = Point2D::new(3.5, 1.2);
|
|
308
|
+
|
|
309
|
+
// let mistake = cursor + player; // ❌ type error
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
Generalize the pattern to your own domains (see `references/type-state.md`).
|
|
313
|
+
|
|
314
|
+
## Property tests — `proptest`
|
|
315
|
+
|
|
316
|
+
```rust
|
|
317
|
+
use proptest::prelude::*;
|
|
318
|
+
|
|
319
|
+
proptest! {
|
|
320
|
+
#[test]
|
|
321
|
+
fn parse_roundtrips(s in r"[a-zA-Z0-9_-]{1,50}") {
|
|
322
|
+
let parsed = parse(&s).unwrap();
|
|
323
|
+
let back = parsed.to_string();
|
|
324
|
+
prop_assert_eq!(back, s);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
Avoid `quickcheck` (older, less ergonomic). proptest gives shrinking + regression corpus + integration with `criterion`.
|
|
330
|
+
|
|
331
|
+
## Snapshot tests — `insta`
|
|
332
|
+
|
|
333
|
+
```rust
|
|
334
|
+
#[test]
|
|
335
|
+
fn renders_help() {
|
|
336
|
+
let output = render(&example_input());
|
|
337
|
+
insta::assert_snapshot!(output);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
#[test]
|
|
341
|
+
fn serializes_well() {
|
|
342
|
+
insta::assert_json_snapshot!(serializable_value());
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
`cargo insta review` (after `cargo install cargo-insta`) — interactive review of changed snapshots.
|
|
347
|
+
|
|
348
|
+
## Benchmarks — `criterion`
|
|
349
|
+
|
|
350
|
+
Stable Rust friendly (no nightly `#[bench]`).
|
|
351
|
+
|
|
352
|
+
```rust
|
|
353
|
+
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
|
354
|
+
|
|
355
|
+
fn bench_parse(c: &mut Criterion) {
|
|
356
|
+
let input = std::fs::read_to_string("samples/large.txt").unwrap();
|
|
357
|
+
c.bench_function("parse_large", |b| b.iter(|| parse(black_box(&input))));
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
criterion_group!(benches, bench_parse);
|
|
361
|
+
criterion_main!(benches);
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
Run with `cargo bench`. HTML reports under `target/criterion/`. Pair with `cargo bench -- --save-baseline main` then `--baseline main` for comparison.
|
|
365
|
+
|
|
366
|
+
## Concurrency model — `loom`
|
|
367
|
+
|
|
368
|
+
For lock-free or atomic-heavy code (channels, refcounts, hazard pointers). See `references/concurrency.md` for the full pattern.
|
|
369
|
+
|
|
370
|
+
## Arena allocator — `bumpalo`
|
|
371
|
+
|
|
372
|
+
```rust
|
|
373
|
+
use bumpalo::Bump;
|
|
374
|
+
|
|
375
|
+
let bump = Bump::new();
|
|
376
|
+
let node = bump.alloc(Node { value: 42, next: None });
|
|
377
|
+
let s: &str = bump.alloc_str("hello");
|
|
378
|
+
// All allocations freed at once when `bump` drops.
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
For parser nodes, AST construction, per-request scratch. Outperforms heap allocation for short-lived owned data by an order of magnitude.
|
|
382
|
+
|
|
383
|
+
## Web client (browser, WASM-bound) — `gloo` ecosystem
|
|
384
|
+
|
|
385
|
+
If targeting WASM browser, use `gloo-net` for fetch and `gloo-storage` for localStorage; not `web-sys` directly unless you need DOM-level APIs.
|
|
386
|
+
|
|
387
|
+
## Lazy statics — `std::sync::LazyLock` (since 1.80)
|
|
388
|
+
|
|
389
|
+
```rust
|
|
390
|
+
use std::sync::LazyLock;
|
|
391
|
+
static CONFIG: LazyLock<Config> = LazyLock::new(|| Config::load_from_env().unwrap());
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Avoid `lazy_static!` (macro-heavy, predates std), `once_cell` (now in std as `LazyLock`/`OnceLock`).
|
|
395
|
+
|
|
396
|
+
## Hash maps — `std::collections::HashMap` + `ahash` for hot paths
|
|
397
|
+
|
|
398
|
+
```rust
|
|
399
|
+
use std::collections::HashMap;
|
|
400
|
+
use ahash::RandomState;
|
|
401
|
+
|
|
402
|
+
type FastMap<K, V> = HashMap<K, V, RandomState>;
|
|
403
|
+
let mut counters: FastMap<String, u64> = FastMap::default();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`HashMap` defaults to SipHash (DoS-resistant). For internal hot loops where you trust the keys, `ahash` is 2-5x faster.
|
|
407
|
+
|
|
408
|
+
For sorted iteration, use `BTreeMap`. For small keys with known small N, `Vec<(K, V)>` may beat both.
|
|
409
|
+
|
|
410
|
+
## File I/O — `tokio::fs` (async) or `std::fs` (sync utility)
|
|
411
|
+
|
|
412
|
+
```rust
|
|
413
|
+
let contents = tokio::fs::read_to_string("data.json").await?;
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
For large files: `tokio::fs::File` + `tokio::io::BufReader`. For random access, `memmap2` (with the unsafe-discipline wrappers).
|
|
417
|
+
|
|
418
|
+
## Decision tree
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
Need to ship the thing?
|
|
422
|
+
├── HTTP server → axum + sqlx + tracing + jiff + tokio
|
|
423
|
+
├── HTTP client → reqwest (+ tokio)
|
|
424
|
+
├── CLI → clap + color-eyre + tracing + indicatif (progress) + dialoguer (prompts)
|
|
425
|
+
├── TUI → ratatui + crossterm
|
|
426
|
+
├── Background worker → tokio + ETL → polars + duckdb
|
|
427
|
+
├── Game / graphics → wgpu + winit + euclid (or bevy if you want the engine)
|
|
428
|
+
├── WASM front-end → leptos (or dioxus / yew) + wasm-bindgen + gloo
|
|
429
|
+
├── Embedded → embassy (async on bare metal)
|
|
430
|
+
├── FFI to C / Python → cxx (C++) / pyo3 (Python) / cbindgen (header gen)
|
|
431
|
+
└── Just a script → rust-script (see one-liners.md)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
When in doubt, search crates.io for the latest version, then check:
|
|
435
|
+
1. Is it maintained? (`cargo deny check` will scream if it's yanked or unmaintained)
|
|
436
|
+
2. Does it have `serde` feature? (boundary types should always serde)
|
|
437
|
+
3. Does it have `tokio` integration? (avoid runtime mixing)
|
|
438
|
+
4. Is it on `tokio::io::AsyncRead`/`AsyncWrite` (the std for async I/O)?
|
|
439
|
+
5. Are there safety-critical `unsafe` regions? If yes, has the author shipped miri proofs?
|