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.
Files changed (156) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/LICENSE +21 -0
  3. package/README.md +369 -0
  4. package/README_ko-KR.md +374 -0
  5. package/RELEASE_CHECKLIST.md +165 -0
  6. package/bin/litclaude-ai.js +643 -0
  7. package/cover.png +0 -0
  8. package/docs/agents.md +67 -0
  9. package/docs/hooks.md +134 -0
  10. package/docs/lsp.md +40 -0
  11. package/docs/migration.md +209 -0
  12. package/docs/workflow-compatibility-audit.md +119 -0
  13. package/generate_cover.py +123 -0
  14. package/package.json +48 -0
  15. package/plugins/litclaude/.claude-plugin/plugin.json +25 -0
  16. package/plugins/litclaude/.lsp.json +13 -0
  17. package/plugins/litclaude/.mcp.json +9 -0
  18. package/plugins/litclaude/agents/boulder-executor.md +12 -0
  19. package/plugins/litclaude/agents/librarian-researcher.md +15 -0
  20. package/plugins/litclaude/agents/oracle-verifier.md +16 -0
  21. package/plugins/litclaude/agents/prometheus-planner.md +13 -0
  22. package/plugins/litclaude/agents/qa-runner.md +16 -0
  23. package/plugins/litclaude/agents/quality-reviewer.md +17 -0
  24. package/plugins/litclaude/bin/litclaude-hook.js +110 -0
  25. package/plugins/litclaude/bin/litclaude-hud.js +271 -0
  26. package/plugins/litclaude/bin/litclaude-lsp-doctor.js +15 -0
  27. package/plugins/litclaude/bin/litclaude-mcp.js +70 -0
  28. package/plugins/litclaude/commands/deep-interview.md +21 -0
  29. package/plugins/litclaude/commands/dynamic-workflow.md +36 -0
  30. package/plugins/litclaude/commands/lit-loop.md +40 -0
  31. package/plugins/litclaude/commands/lit-plan.md +35 -0
  32. package/plugins/litclaude/commands/litgoal.md +30 -0
  33. package/plugins/litclaude/commands/review-work.md +35 -0
  34. package/plugins/litclaude/commands/start-work.md +36 -0
  35. package/plugins/litclaude/hooks/hooks.json +54 -0
  36. package/plugins/litclaude/lib/context-pressure.mjs +25 -0
  37. package/plugins/litclaude/lib/hud-accent-palette.mjs +58 -0
  38. package/plugins/litclaude/lib/litgoal/cli.mjs +266 -0
  39. package/plugins/litclaude/lib/litgoal/ledger.mjs +16 -0
  40. package/plugins/litclaude/lib/litgoal/paths.mjs +7 -0
  41. package/plugins/litclaude/lib/litgoal/state.mjs +67 -0
  42. package/plugins/litclaude/lib/mutated-file-paths.mjs +63 -0
  43. package/plugins/litclaude/lib/start-work-continuation.mjs +99 -0
  44. package/plugins/litclaude/lib/workflow-check.mjs +83 -0
  45. package/plugins/litclaude/skills/ai-slop-remover/SKILL.md +142 -0
  46. package/plugins/litclaude/skills/comment-checker/SKILL.md +55 -0
  47. package/plugins/litclaude/skills/debugging/SKILL.md +70 -0
  48. package/plugins/litclaude/skills/debugging/references/methodology/00-setup.md +108 -0
  49. package/plugins/litclaude/skills/debugging/references/methodology/02-investigate.md +126 -0
  50. package/plugins/litclaude/skills/debugging/references/methodology/04-oracle-triple.md +106 -0
  51. package/plugins/litclaude/skills/debugging/references/methodology/05-escalate.md +69 -0
  52. package/plugins/litclaude/skills/debugging/references/methodology/06-fix.md +116 -0
  53. package/plugins/litclaude/skills/debugging/references/methodology/08-qa.md +94 -0
  54. package/plugins/litclaude/skills/debugging/references/methodology/09-cleanup.md +164 -0
  55. package/plugins/litclaude/skills/debugging/references/methodology/partial-runtime-evidence.md +228 -0
  56. package/plugins/litclaude/skills/debugging/references/runtimes/bundled-js-binary.md +415 -0
  57. package/plugins/litclaude/skills/debugging/references/runtimes/go.md +252 -0
  58. package/plugins/litclaude/skills/debugging/references/runtimes/native-binary.md +484 -0
  59. package/plugins/litclaude/skills/debugging/references/runtimes/node.md +260 -0
  60. package/plugins/litclaude/skills/debugging/references/runtimes/python.md +248 -0
  61. package/plugins/litclaude/skills/debugging/references/runtimes/rust.md +234 -0
  62. package/plugins/litclaude/skills/debugging/references/tools/ghidra.md +212 -0
  63. package/plugins/litclaude/skills/debugging/references/tools/playwright-cli.md +194 -0
  64. package/plugins/litclaude/skills/debugging/references/tools/pwndbg.md +263 -0
  65. package/plugins/litclaude/skills/debugging/references/tools/pwntools.md +265 -0
  66. package/plugins/litclaude/skills/deep-interview/SKILL.md +323 -0
  67. package/plugins/litclaude/skills/deep-interview/scripts/render_progress.py +193 -0
  68. package/plugins/litclaude/skills/frontend-ui-ux/SKILL.md +62 -0
  69. package/plugins/litclaude/skills/lit-loop/SKILL.md +144 -0
  70. package/plugins/litclaude/skills/lit-plan/SKILL.md +125 -0
  71. package/plugins/litclaude/skills/litgoal/SKILL.md +219 -0
  72. package/plugins/litclaude/skills/lsp/SKILL.md +63 -0
  73. package/plugins/litclaude/skills/programming/SKILL.md +106 -0
  74. package/plugins/litclaude/skills/programming/references/go/README.md +90 -0
  75. package/plugins/litclaude/skills/programming/references/go/backend-stack.md +641 -0
  76. package/plugins/litclaude/skills/programming/references/go/bootstrap.md +328 -0
  77. package/plugins/litclaude/skills/programming/references/go/bubbletea-v2.md +360 -0
  78. package/plugins/litclaude/skills/programming/references/go/cobra-stack.md +468 -0
  79. package/plugins/litclaude/skills/programming/references/go/concurrency.md +362 -0
  80. package/plugins/litclaude/skills/programming/references/go/data-modeling.md +329 -0
  81. package/plugins/litclaude/skills/programming/references/go/error-handling.md +359 -0
  82. package/plugins/litclaude/skills/programming/references/go/golangci-strict.md +236 -0
  83. package/plugins/litclaude/skills/programming/references/go/grpc-connect.md +375 -0
  84. package/plugins/litclaude/skills/programming/references/go/libraries.md +337 -0
  85. package/plugins/litclaude/skills/programming/references/go/one-liners.md +202 -0
  86. package/plugins/litclaude/skills/programming/references/go/sqlc-pgx.md +471 -0
  87. package/plugins/litclaude/skills/programming/references/go/testing.md +467 -0
  88. package/plugins/litclaude/skills/programming/references/go/type-patterns.md +298 -0
  89. package/plugins/litclaude/skills/programming/references/python/README.md +314 -0
  90. package/plugins/litclaude/skills/programming/references/python/async-anyio.md +442 -0
  91. package/plugins/litclaude/skills/programming/references/python/data-modeling.md +233 -0
  92. package/plugins/litclaude/skills/programming/references/python/data-processing.md +133 -0
  93. package/plugins/litclaude/skills/programming/references/python/error-handling.md +218 -0
  94. package/plugins/litclaude/skills/programming/references/python/fastapi-stack.md +316 -0
  95. package/plugins/litclaude/skills/programming/references/python/httpx2-optimization.md +360 -0
  96. package/plugins/litclaude/skills/programming/references/python/libraries.md +307 -0
  97. package/plugins/litclaude/skills/programming/references/python/one-liners.md +268 -0
  98. package/plugins/litclaude/skills/programming/references/python/orjson-stack.md +378 -0
  99. package/plugins/litclaude/skills/programming/references/python/pydantic-ai.md +285 -0
  100. package/plugins/litclaude/skills/programming/references/python/pyproject-strict.md +232 -0
  101. package/plugins/litclaude/skills/programming/references/python/textual-tui.md +201 -0
  102. package/plugins/litclaude/skills/programming/references/python/type-patterns.md +176 -0
  103. package/plugins/litclaude/skills/programming/references/rust/README.md +317 -0
  104. package/plugins/litclaude/skills/programming/references/rust/async-tokio.md +299 -0
  105. package/plugins/litclaude/skills/programming/references/rust/axum-stack.md +467 -0
  106. package/plugins/litclaude/skills/programming/references/rust/cargo-strict.md +317 -0
  107. package/plugins/litclaude/skills/programming/references/rust/clap-stack.md +409 -0
  108. package/plugins/litclaude/skills/programming/references/rust/concurrency.md +375 -0
  109. package/plugins/litclaude/skills/programming/references/rust/libraries.md +439 -0
  110. package/plugins/litclaude/skills/programming/references/rust/one-liners.md +291 -0
  111. package/plugins/litclaude/skills/programming/references/rust/proptest-insta.md +429 -0
  112. package/plugins/litclaude/skills/programming/references/rust/type-state.md +354 -0
  113. package/plugins/litclaude/skills/programming/references/rust/unsafe-discipline.md +250 -0
  114. package/plugins/litclaude/skills/programming/references/rust/zero-cost-safety.md +527 -0
  115. package/plugins/litclaude/skills/programming/references/rust-ub/README.md +289 -0
  116. package/plugins/litclaude/skills/programming/references/rust-ub/miri-sanitizers-loom.md +411 -0
  117. package/plugins/litclaude/skills/programming/references/rust-ub/ub-taxonomy.md +269 -0
  118. package/plugins/litclaude/skills/programming/references/typescript/README.md +195 -0
  119. package/plugins/litclaude/skills/programming/references/typescript/backend-hono.md +672 -0
  120. package/plugins/litclaude/skills/programming/references/typescript/bootstrap.md +199 -0
  121. package/plugins/litclaude/skills/programming/references/typescript/data-modeling.md +202 -0
  122. package/plugins/litclaude/skills/programming/references/typescript/error-handling.md +169 -0
  123. package/plugins/litclaude/skills/programming/references/typescript/tsconfig-strict.md +152 -0
  124. package/plugins/litclaude/skills/programming/references/typescript/type-patterns.md +196 -0
  125. package/plugins/litclaude/skills/programming/scripts/go/check-no-excuse-rules.sh +173 -0
  126. package/plugins/litclaude/skills/programming/scripts/go/new-project.py +138 -0
  127. package/plugins/litclaude/skills/programming/scripts/go/templates/.editorconfig +13 -0
  128. package/plugins/litclaude/skills/programming/scripts/go/templates/.golangci.yml +95 -0
  129. package/plugins/litclaude/skills/programming/scripts/go/templates/AGENTS.md.tmpl +24 -0
  130. package/plugins/litclaude/skills/programming/scripts/go/templates/README.md.tmpl +12 -0
  131. package/plugins/litclaude/skills/programming/scripts/go/templates/Taskfile.yml +40 -0
  132. package/plugins/litclaude/skills/programming/scripts/go/templates/ci.yml +37 -0
  133. package/plugins/litclaude/skills/programming/scripts/go/templates/config.go +24 -0
  134. package/plugins/litclaude/skills/programming/scripts/go/templates/gitignore +15 -0
  135. package/plugins/litclaude/skills/programming/scripts/go/templates/main.go.tmpl +22 -0
  136. package/plugins/litclaude/skills/programming/scripts/go/templates/run.go +15 -0
  137. package/plugins/litclaude/skills/programming/scripts/python/check-no-excuse-rules.py +687 -0
  138. package/plugins/litclaude/skills/programming/scripts/python/new-project.py +172 -0
  139. package/plugins/litclaude/skills/programming/scripts/python/new-script.py +116 -0
  140. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.py +296 -0
  141. package/plugins/litclaude/skills/programming/scripts/rust/check-no-excuse-rules.sh +158 -0
  142. package/plugins/litclaude/skills/programming/scripts/rust/new-project.py +175 -0
  143. package/plugins/litclaude/skills/programming/scripts/typescript/check-no-excuse-rules.ts +282 -0
  144. package/plugins/litclaude/skills/programming/scripts/typescript/new-project.ts +177 -0
  145. package/plugins/litclaude/skills/refactor/SKILL.md +73 -0
  146. package/plugins/litclaude/skills/remove-ai-slops/SKILL.md +52 -0
  147. package/plugins/litclaude/skills/review-work/SKILL.md +331 -0
  148. package/plugins/litclaude/skills/rules/SKILL.md +66 -0
  149. package/plugins/litclaude/skills/start-work/SKILL.md +132 -0
  150. package/scripts/audit-plan-checkboxes.mjs +37 -0
  151. package/scripts/doctor.mjs +41 -0
  152. package/scripts/inspect-agent-tools.mjs +27 -0
  153. package/scripts/postinstall.mjs +50 -0
  154. package/scripts/qa-claude-plugin-smoke.sh +60 -0
  155. package/scripts/qa-portable-install.sh +136 -0
  156. package/scripts/validate-plugin.mjs +72 -0
@@ -0,0 +1,299 @@
1
+ # Async with Tokio
2
+
3
+ Structured concurrency, cancellation, blocking-work isolation, channel selection. The patterns the agent should reach for by default.
4
+
5
+ ## Runtime selection
6
+
7
+ ```rust
8
+ // Default for services and CLIs that do real work
9
+ #[tokio::main(flavor = "multi_thread", worker_threads = 8)]
10
+ async fn main() -> anyhow::Result<()> { ... }
11
+
12
+ // For tiny CLIs or wasm where you measured single-thread is enough
13
+ #[tokio::main(flavor = "current_thread")]
14
+ async fn main() -> anyhow::Result<()> { ... }
15
+ ```
16
+
17
+ Pick worker count explicitly. The default (`num_cpus`) is fine for servers; for desktop tools you usually want 2-4.
18
+
19
+ ## Spawning
20
+
21
+ `tokio::spawn` returns a `JoinHandle<T>`. The future runs to completion even if the handle is dropped (detached). To enforce structured concurrency, use `JoinSet`:
22
+
23
+ ```rust
24
+ use tokio::task::JoinSet;
25
+
26
+ let mut set = JoinSet::new();
27
+ for url in urls {
28
+ let client = client.clone();
29
+ set.spawn(async move { fetch(&client, &url).await });
30
+ }
31
+
32
+ let mut results = Vec::new();
33
+ while let Some(joined) = set.join_next().await {
34
+ match joined {
35
+ Ok(Ok(body)) => results.push(body),
36
+ Ok(Err(error)) => tracing::warn!(%error, "fetch failed"),
37
+ Err(panicked) if panicked.is_panic() => {
38
+ tracing::error!(?panicked, "worker panicked");
39
+ // Choose: re-raise, or continue with degraded result set.
40
+ }
41
+ Err(other) => tracing::error!(?other, "worker join error"),
42
+ }
43
+ }
44
+ ```
45
+
46
+ `JoinSet`:
47
+ - Knows when all spawned tasks finish.
48
+ - Dropping the set aborts every still-running task.
49
+ - Lets you handle failures one by one rather than all-or-nothing.
50
+
51
+ For wait-for-all semantics with one type, `join!`:
52
+
53
+ ```rust
54
+ let (a, b, c) = tokio::join!(load_a(), load_b(), load_c());
55
+ let a = a?; let b = b?; let c = c?;
56
+ ```
57
+
58
+ For first-of-many, `select!`:
59
+
60
+ ```rust
61
+ tokio::select! {
62
+ biased; // bias to top-to-bottom checking when ordering matters
63
+ _ = shutdown.recv() => {
64
+ tracing::info!("shutdown signal");
65
+ return Ok(());
66
+ }
67
+ request = listener.accept() => {
68
+ handle_request(request?).await?;
69
+ }
70
+ }
71
+ ```
72
+
73
+ Without `biased`, branches are polled in random order each iteration (good for fairness). Use `biased` only when you need deterministic priority (shutdown signal first, etc).
74
+
75
+ ## Cancellation
76
+
77
+ A future is cancelled when it is dropped (e.g., the `select!` arm wins another branch). **Always think: if this future is dropped mid-await, what state is left behind?**
78
+
79
+ Cancel-safe futures (you can drop without lasting effect):
80
+ - `recv()` on channels
81
+ - `accept()` on listeners
82
+ - `wait_for` on `watch::Receiver`
83
+ - `read_buf`/`write_all` on streams **only when buffers are owned by the future**, otherwise no
84
+
85
+ Cancel-unsafe futures (dropping mid-way leaves partial state):
86
+ - Manual `read_exact` into an external buffer
87
+ - Custom futures that perform partial side effects before suspending
88
+
89
+ If a function is cancel-unsafe, document it in a rustdoc `# Cancel Safety` section.
90
+
91
+ To explicitly opt out of cancellation, use `tokio_util::sync::CancellationToken`:
92
+
93
+ ```rust
94
+ use tokio_util::sync::CancellationToken;
95
+
96
+ let token = CancellationToken::new();
97
+ let child = token.child_token();
98
+ tokio::spawn(async move {
99
+ tokio::select! {
100
+ _ = child.cancelled() => { /* clean up */ }
101
+ result = work() => { /* normal */ }
102
+ }
103
+ });
104
+ // later
105
+ token.cancel();
106
+ ```
107
+
108
+ Pass child tokens down the call tree so the whole tree can be cancelled together.
109
+
110
+ ## Timeouts
111
+
112
+ ```rust
113
+ use tokio::time::{timeout, Duration};
114
+
115
+ match timeout(Duration::from_secs(5), fetch(url)).await {
116
+ Ok(Ok(body)) => Ok(body),
117
+ Ok(Err(error)) => Err(error.into()),
118
+ Err(_elapsed) => Err(anyhow::anyhow!("timed out fetching {url}")),
119
+ }
120
+ ```
121
+
122
+ Set timeouts on every external I/O boundary. Defaults of "wait forever" are bugs.
123
+
124
+ ## Blocking work
125
+
126
+ NEVER block inside an async task. Symptoms: deadlock, every future stalled, latency cliffs.
127
+
128
+ Heavy CPU or sync I/O → `spawn_blocking`:
129
+
130
+ ```rust
131
+ let result = tokio::task::spawn_blocking(|| {
132
+ // CPU-bound: parsing, hashing, image processing
133
+ // Or sync I/O: rusqlite, OS APIs without async wrappers
134
+ expensive_pure_computation()
135
+ }).await?;
136
+ ```
137
+
138
+ Long-running blocking jobs (more than ~1 second of CPU) → use a dedicated thread pool (`rayon`), not tokio's blocking pool which is sized for short bursts.
139
+
140
+ ## Channels
141
+
142
+ | Need | Use |
143
+ |---|---|
144
+ | 1-many producers → 1 consumer, async | `tokio::sync::mpsc::channel(cap)` |
145
+ | Same as above, both sync + async | `flume::bounded(cap)` |
146
+ | 1 → many fan-out, latest-value semantics | `tokio::sync::watch::channel(initial)` |
147
+ | 1 → many fan-out, queued | `tokio::sync::broadcast::channel(cap)` |
148
+ | One-shot reply | `tokio::sync::oneshot::channel()` |
149
+ | Backpressure-driven stream of items | `tokio::sync::mpsc::Receiver` + `ReceiverStream` |
150
+
151
+ Mpsc pattern:
152
+
153
+ ```rust
154
+ let (tx, mut rx) = tokio::sync::mpsc::channel::<Job>(256);
155
+
156
+ tokio::spawn(async move {
157
+ while let Some(job) = rx.recv().await {
158
+ if let Err(error) = process(job).await {
159
+ tracing::warn!(%error, "job failed");
160
+ }
161
+ }
162
+ tracing::info!("queue closed, shutting down worker");
163
+ });
164
+
165
+ tx.send(Job { ... }).await?; // blocks if full, applies backpressure
166
+ ```
167
+
168
+ Always bound channels. Unbounded channels are a memory leak waiting to happen.
169
+
170
+ ## Streams
171
+
172
+ `futures::Stream` is the async analogue of `Iterator`. Use it for paginated fetches, long-poll responses, file lines.
173
+
174
+ ```rust
175
+ use futures::stream::{StreamExt, TryStreamExt};
176
+
177
+ let urls: Vec<String> = ...;
178
+ let bodies: Vec<String> = futures::stream::iter(urls)
179
+ .map(|url| async move { fetch(&url).await })
180
+ .buffer_unordered(8) // up to 8 in flight
181
+ .try_collect()
182
+ .await?;
183
+ ```
184
+
185
+ `buffer_unordered(n)` is the throttle. Use it instead of spawning N tasks manually.
186
+
187
+ For producing a stream from a channel:
188
+
189
+ ```rust
190
+ use tokio_stream::wrappers::ReceiverStream;
191
+
192
+ let (tx, rx) = tokio::sync::mpsc::channel::<Event>(64);
193
+ let stream = ReceiverStream::new(rx);
194
+ serve_sse(stream).await
195
+ ```
196
+
197
+ ## Graceful shutdown
198
+
199
+ ```rust
200
+ use tokio::signal;
201
+
202
+ async fn shutdown_signal() {
203
+ let ctrl_c = async { signal::ctrl_c().await.expect("ctrl_c handler") };
204
+ #[cfg(unix)]
205
+ let terminate = async {
206
+ signal::unix::signal(signal::unix::SignalKind::terminate())
207
+ .expect("install signal handler")
208
+ .recv()
209
+ .await;
210
+ };
211
+ #[cfg(not(unix))]
212
+ let terminate = std::future::pending::<()>();
213
+
214
+ tokio::select! {
215
+ _ = ctrl_c => {},
216
+ _ = terminate => {},
217
+ }
218
+ tracing::info!("shutdown signal received");
219
+ }
220
+
221
+ #[tokio::main]
222
+ async fn main() -> anyhow::Result<()> {
223
+ let token = CancellationToken::new();
224
+ let server = tokio::spawn(run_server(token.child_token()));
225
+ shutdown_signal().await;
226
+ token.cancel();
227
+ let _ = tokio::time::timeout(Duration::from_secs(10), server).await;
228
+ Ok(())
229
+ }
230
+ ```
231
+
232
+ Pattern: catch signal → cancel a token shared with the server → server's `select!` arms see the cancel and exit cleanly → wait with a timeout so a hung worker can't deadlock shutdown.
233
+
234
+ ## Concurrency primitives
235
+
236
+ - `tokio::sync::Mutex` — async mutex. Use for state shared between async tasks. **Do not hold across `.await` without thinking** (you'll serialize the whole system).
237
+ - `tokio::sync::RwLock` — async read-write lock. Same caveat.
238
+ - `parking_lot::Mutex` — sync mutex, faster than `std::sync::Mutex`, no poisoning. Use when the lock is held briefly and you do not need to `.await` while holding it.
239
+ - `tokio::sync::Semaphore` — bound concurrent operations. Perfect for "max 10 in-flight HTTP requests" or "max 3 DB writers".
240
+
241
+ ```rust
242
+ let sem = Arc::new(tokio::sync::Semaphore::new(10));
243
+ for url in urls {
244
+ let permit = sem.clone().acquire_owned().await?;
245
+ tokio::spawn(async move {
246
+ let _permit = permit; // released on task end
247
+ fetch(&url).await
248
+ });
249
+ }
250
+ ```
251
+
252
+ ## Common mistakes
253
+
254
+ 1. **Holding a sync mutex across `.await`.** Compiles and runs, deadlocks at scale. Solution: refactor to release before await, or use `tokio::sync::Mutex`.
255
+ 2. **Forgetting `?` on `JoinHandle`.** A panicked task returns `Err(JoinError)`; if you `.await` and ignore, panics are silently swallowed.
256
+ 3. **`tokio::spawn` instead of `JoinSet`.** Detached tasks survive past their parent, causing leaks. Default to `JoinSet` for structured concurrency.
257
+ 4. **Unbounded channels.** Always set a capacity.
258
+ 5. **`block_on` inside an async context.** Causes deadlock under `current_thread` runtime, performance cliff under `multi_thread`.
259
+ 6. **CPU-heavy work in async fn.** Move to `spawn_blocking` or `rayon`.
260
+ 7. **No timeout on external I/O.** Every `await` that touches the network or filesystem needs `tokio::time::timeout` wrapping.
261
+
262
+ ## Testing async code
263
+
264
+ ```rust
265
+ #[tokio::test]
266
+ async fn fetches_and_parses() {
267
+ let server = wiremock::MockServer::start().await;
268
+ wiremock::Mock::given(wiremock::matchers::method("GET"))
269
+ .respond_with(wiremock::ResponseTemplate::new(200).set_body_string("{\"id\":1}"))
270
+ .mount(&server)
271
+ .await;
272
+
273
+ let result = my_client::fetch(&server.uri()).await.unwrap();
274
+ assert_eq!(result.id, 1);
275
+ }
276
+
277
+ #[tokio::test(flavor = "multi_thread", worker_threads = 4)]
278
+ async fn parallel_work() { ... }
279
+ ```
280
+
281
+ For time-sensitive tests, advance virtual time:
282
+
283
+ ```rust
284
+ #[tokio::test(start_paused = true)]
285
+ async fn time_travel() {
286
+ let start = tokio::time::Instant::now();
287
+ tokio::time::sleep(Duration::from_secs(3600)).await;
288
+ assert!(start.elapsed() >= Duration::from_secs(3600));
289
+ // Real wallclock elapsed: ~0ms.
290
+ }
291
+ ```
292
+
293
+ ## When NOT to use async
294
+
295
+ - Single-threaded CPU-heavy code that does no I/O — plain `fn` + `rayon` is simpler and often faster.
296
+ - Trivial scripts that do one HTTP call — `ureq` (sync) is simpler.
297
+ - FFI heavy code where the FFI side is sync.
298
+
299
+ Async pays off when you have many concurrent I/O operations or need cancellation as a first-class primitive.