lazycodex-ai 5.0.0-beta.85 → 5.0.0-beta.87
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/README.md +1 -1
- package/dist/cli/index.js +68 -41
- package/dist/cli-node/index.js +68 -41
- package/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +2 -0
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-6.md +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +63 -89
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +63 -89
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/define-goal.md +2 -3
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +12 -12
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +8 -2
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +2 -2
- package/packages/omo-codex/plugin/skills/browser/ATTRIBUTION.md +26 -14
- package/packages/omo-codex/plugin/skills/browser/SKILL.md +65 -52
- package/packages/omo-codex/plugin/skills/browser/references/commands.md +81 -66
- package/packages/omo-codex/plugin/skills/browser/references/install.md +31 -34
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/README.md +41 -21
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/ladder.md +24 -22
- package/packages/omo-codex/plugin/skills/browser/references/owned-engine/network.md +35 -15
- package/packages/omo-codex/plugin/skills/browser/references/recipes/1password.md +13 -11
- package/packages/omo-codex/plugin/skills/browser/references/remote.md +5 -4
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/index.js +1534 -0
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/manifest.json +9 -0
- package/packages/omo-codex/plugin/skills/browser/runtime/omowright/page-bundle.js +1395 -0
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-doctor.mjs +37 -32
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-install.mjs +31 -44
- package/packages/omo-codex/plugin/skills/browser/scripts/omowright.mjs +25 -0
- package/packages/omo-codex/plugin/skills/debugging/SKILL.md +2 -2
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/06-fix.md +3 -3
- package/packages/omo-codex/plugin/skills/debugging/references/methodology/08-qa.md +1 -1
- package/packages/omo-codex/plugin/skills/debugging/references/tools/browser-qa.md +104 -0
- package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/SKILL.md +12 -18
- package/packages/omo-codex/plugin/skills/programming/references/rust/README.md +43 -15
- package/packages/omo-codex/plugin/skills/programming/references/rust/api-design.md +81 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/async-tokio.md +60 -28
- package/packages/omo-codex/plugin/skills/programming/references/rust/axum-stack.md +1 -13
- package/packages/omo-codex/plugin/skills/programming/references/rust/cargo-strict.md +44 -8
- package/packages/omo-codex/plugin/skills/programming/references/rust/clap-stack.md +8 -3
- package/packages/omo-codex/plugin/skills/programming/references/rust/concurrency.md +66 -52
- package/packages/omo-codex/plugin/skills/programming/references/rust/libraries.md +35 -25
- package/packages/omo-codex/plugin/skills/programming/references/rust/macros.md +63 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/one-liners.md +5 -3
- package/packages/omo-codex/plugin/skills/programming/references/rust/proptest-insta.md +8 -0
- package/packages/omo-codex/plugin/skills/programming/references/rust/type-state.md +50 -12
- package/packages/omo-codex/plugin/skills/programming/references/rust/unsafe-discipline.md +34 -6
- package/packages/omo-codex/plugin/skills/programming/references/rust/zero-cost-safety.md +62 -52
- package/packages/omo-codex/plugin/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
- package/packages/omo-codex/plugin/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/new-project.py +31 -28
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +27 -20
- package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +5 -11
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
- package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +63 -89
- package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +4 -4
- package/packages/omo-codex/plugin/skills/ulw-loop/references/define-goal.md +2 -3
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +46 -46
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +4 -2
- package/packages/prompts-core/prompts/ultrawork/codex.md +63 -89
- package/packages/shared-skills/skills/browser/ATTRIBUTION.md +26 -14
- package/packages/shared-skills/skills/browser/SKILL.md +65 -52
- package/packages/shared-skills/skills/browser/references/commands.md +81 -66
- package/packages/shared-skills/skills/browser/references/install.md +31 -34
- package/packages/shared-skills/skills/browser/references/owned-engine/README.md +41 -21
- package/packages/shared-skills/skills/browser/references/owned-engine/frames-and-humans.md +33 -20
- package/packages/shared-skills/skills/browser/references/owned-engine/ladder.md +24 -22
- package/packages/shared-skills/skills/browser/references/owned-engine/network.md +35 -15
- package/packages/shared-skills/skills/browser/references/recipes/1password.md +13 -11
- package/packages/shared-skills/skills/browser/references/remote.md +5 -4
- package/packages/shared-skills/skills/browser/runtime/omowright/index.js +1534 -0
- package/packages/shared-skills/skills/browser/runtime/omowright/manifest.json +9 -0
- package/packages/shared-skills/skills/browser/runtime/omowright/page-bundle.js +1395 -0
- package/packages/shared-skills/skills/browser/scripts/browser-doctor.mjs +37 -32
- package/packages/shared-skills/skills/browser/scripts/browser-install.mjs +31 -44
- package/packages/shared-skills/skills/browser/scripts/omowright.mjs +25 -0
- package/packages/shared-skills/skills/debugging/SKILL.md +2 -2
- package/packages/shared-skills/skills/debugging/references/methodology/06-fix.md +3 -3
- package/packages/shared-skills/skills/debugging/references/methodology/08-qa.md +1 -1
- package/packages/shared-skills/skills/debugging/references/tools/browser-qa.md +104 -0
- package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
- package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
- package/packages/shared-skills/skills/programming/SKILL.md +12 -18
- package/packages/shared-skills/skills/programming/references/rust/README.md +43 -15
- package/packages/shared-skills/skills/programming/references/rust/api-design.md +81 -0
- package/packages/shared-skills/skills/programming/references/rust/async-tokio.md +60 -28
- package/packages/shared-skills/skills/programming/references/rust/axum-stack.md +1 -13
- package/packages/shared-skills/skills/programming/references/rust/cargo-strict.md +44 -8
- package/packages/shared-skills/skills/programming/references/rust/clap-stack.md +8 -3
- package/packages/shared-skills/skills/programming/references/rust/concurrency.md +66 -52
- package/packages/shared-skills/skills/programming/references/rust/libraries.md +35 -25
- package/packages/shared-skills/skills/programming/references/rust/macros.md +63 -0
- package/packages/shared-skills/skills/programming/references/rust/one-liners.md +5 -3
- package/packages/shared-skills/skills/programming/references/rust/proptest-insta.md +8 -0
- package/packages/shared-skills/skills/programming/references/rust/type-state.md +50 -12
- package/packages/shared-skills/skills/programming/references/rust/unsafe-discipline.md +34 -6
- package/packages/shared-skills/skills/programming/references/rust/zero-cost-safety.md +62 -52
- package/packages/shared-skills/skills/programming/references/rust-ub/miri-sanitizers-loom.md +1 -1
- package/packages/shared-skills/skills/programming/references/rust-ub/ub-taxonomy.md +6 -3
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.sh +86 -75
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.test.ts +83 -0
- package/packages/shared-skills/skills/programming/scripts/rust/new-project.py +31 -28
- package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +27 -20
- package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +1 -1
- package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +32 -100
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +5 -11
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +20 -37
- package/packages/shared-skills/skills/ulw-execute/SKILL.md +4 -4
- package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
- package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +46 -46
- package/packages/omo-codex/plugin/skills/browser/scripts/browser-env.mjs +0 -41
- package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +0 -112
- package/packages/omo-codex/plugin/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
- package/packages/shared-skills/skills/browser/scripts/browser-env.mjs +0 -41
- package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +0 -112
- package/packages/shared-skills/skills/programming/scripts/rust/check-no-excuse-rules.py +0 -296
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Public API Design
|
|
2
|
+
|
|
3
|
+
What a caller sees: names, conversions, traits, attributes, visibility, and rustdoc. Every rule here exists so a call site reads correctly without opening the implementation.
|
|
4
|
+
|
|
5
|
+
## Naming says cost and ownership
|
|
6
|
+
|
|
7
|
+
| Prefix / form | Meaning | Example |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `as_x(&self) -> &X` | Borrowed view, no allocation | `str::as_bytes`, `PathBuf::as_path` |
|
|
10
|
+
| `to_x(&self) -> X` | Builds a new owned value (may allocate or compute) | `str::to_lowercase`, `Path::to_path_buf` |
|
|
11
|
+
| `into_x(self) -> X` | Consumes `self`, reuses its buffers | `String::into_bytes`, `Vec::into_boxed_slice` |
|
|
12
|
+
| `x(&self)` | Plain accessor, no `get_` prefix | `len()`, `name()`, `user_id()` |
|
|
13
|
+
| `get(&self, key)` | Fallible lookup that returns `Option` | `HashMap::get`, `slice::get` |
|
|
14
|
+
| `iter()` / `iter_mut()` / `IntoIterator` | Borrowing, mutable, and consuming iteration | implement the ones the type actually needs |
|
|
15
|
+
| `is_x()` / `has_x()` / `can_x()` | `bool` query, named for the positive state | `is_empty()`, `has_children()` |
|
|
16
|
+
|
|
17
|
+
Types, traits, and enum variants are `UpperCamelCase` with acronyms as one word (`HttpClient`, `Json`, not `HTTPClient`); functions, modules, and locals `snake_case`; constants and statics `SCREAMING_SNAKE_CASE`; simple generics `T`, `E`, `K`, `V`, lifetimes short (`'a`, `'de`). Return `impl Iterator<Item = T>` instead of a named iterator struct unless callers need to name the type.
|
|
18
|
+
|
|
19
|
+
## Conversions
|
|
20
|
+
|
|
21
|
+
| Conversion | Trait | Rule |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Always succeeds, no invariant can break | `From<T>` | Implement `From`; callers get `Into` for free. Never implement `Into` directly. |
|
|
24
|
+
| Can fail | `TryFrom<T>` | `type Error` is a real error type, never `()` or `String`. |
|
|
25
|
+
| Parses text | `FromStr` | Routes through the same checked constructor as `TryFrom`. |
|
|
26
|
+
| Cheap borrowed view for a generic parameter | `AsRef<T>` | Accept `impl AsRef<Path>` only where callers really pass several types; a plain `&Path` is simpler otherwise. |
|
|
27
|
+
|
|
28
|
+
Validated types expose `TryFrom` / `FromStr` only; an infallible `From` into them bypasses the invariant ([type-state.md](type-state.md)). Units convert through named methods (`to_feet()`), never `From`, so the conversion is visible.
|
|
29
|
+
|
|
30
|
+
Generic parameters that only convert (`fn new(name: impl Into<String>)`) hide an allocation at the call site; take `&str` or `String` explicitly unless the constructor is called with both often.
|
|
31
|
+
|
|
32
|
+
## Trait design
|
|
33
|
+
|
|
34
|
+
- **Generic by default, `dyn` for heterogeneity.** `fn run<R: Renderer>(r: &R)` monomorphizes and inlines; `&dyn Renderer` or `Box<dyn Renderer>` is for a collection of mixed implementors or a deliberate code-size trade. `dyn` needs indirection, not necessarily a heap allocation: `&dyn Trait` borrows.
|
|
35
|
+
- **Associated type vs generic parameter.** One natural output per implementor (`Iterator::Item`, a parser's `Output`) is an associated type. A generic parameter (`From<T>`) is for traits a type implements several times.
|
|
36
|
+
- **Minimal required methods.** Require the few methods that carry the contract; provide the rest as default methods built on them. A default method must be correct for every implementor, not merely convenient.
|
|
37
|
+
- **Blanket impls are a semver commitment.** A public `impl<T: Display> MyTrait for T` forbids downstream crates, and your own future versions, from writing a more specific impl. Add one only when it is permanently right for every `T`.
|
|
38
|
+
- **Orphan rule.** You cannot implement a foreign trait for a foreign type. Wrap the type in a local newtype and implement the trait on the wrapper; do not add `Deref` to the inner type to make it convenient.
|
|
39
|
+
- **Closed sets are sealed.** A public trait whose implementations only you may add uses the sealed-trait pattern ([type-state.md](type-state.md#sealed-traits)).
|
|
40
|
+
- **Bounds live on impls, not structs.** `struct Cache<K, V> { .. }` stays unbounded; `impl<K: Hash + Eq, V> Cache<K, V>` carries the bounds, so every mention of `Cache` does not repeat them.
|
|
41
|
+
- **Callables take the weakest bound.** `FnOnce` if called once, `FnMut` if called repeatedly with mutation, `Fn` for shared repeated calls. Return a closure as `impl Fn(..)`; box it (`Box<dyn Fn(..)>`) only to store closures of different types together.
|
|
42
|
+
|
|
43
|
+
## Attributes that protect callers
|
|
44
|
+
|
|
45
|
+
- **`#[must_use]`** on builder methods that return `Self` (a dropped builder silently loses configuration), on pure constructors of values whose drop is a bug, and on functions whose whole point is the return value. `Result` is already `must_use`; do not blanket-annotate every function.
|
|
46
|
+
- **`#[non_exhaustive]`** on public enums and structs that will grow (error enums, config structs, event types). Downstream matches get a required `_` arm and your next variant is not a breaking change. Keep a public enum exhaustive only when its closed set is part of the contract (`Ordering`). `exhaustive_enums` / `exhaustive_structs` in [cargo-strict.md](cargo-strict.md) flag the choice.
|
|
47
|
+
|
|
48
|
+
## Visibility
|
|
49
|
+
|
|
50
|
+
Start private. Widen one step at a time, only for a caller that exists: private -> `pub(super)` (sibling modules) -> `pub(crate)` -> `pub`. The `unreachable_pub` lint catches `pub` items no one outside the crate can reach. Shape the public surface with deliberate re-exports (`pub use crate::parse::Parser;` in `lib.rs`), never `pub use module::*`, which exports whatever a later edit adds.
|
|
51
|
+
|
|
52
|
+
## Rustdoc sections
|
|
53
|
+
|
|
54
|
+
Every public item has a doc comment (`missing_docs`). Public fallible, panicking, or `unsafe` functions carry the section a caller needs:
|
|
55
|
+
|
|
56
|
+
```rust
|
|
57
|
+
/// Parses a `key=value` line.
|
|
58
|
+
///
|
|
59
|
+
/// # Errors
|
|
60
|
+
///
|
|
61
|
+
/// Returns [`ParseError::MissingSeparator`] when the line has no `=`.
|
|
62
|
+
///
|
|
63
|
+
/// # Examples
|
|
64
|
+
///
|
|
65
|
+
/// ```
|
|
66
|
+
/// # use my_crate::{parse_pair, ParseError};
|
|
67
|
+
/// # fn main() -> Result<(), ParseError> {
|
|
68
|
+
/// let (key, value) = parse_pair("port=8080")?;
|
|
69
|
+
/// assert_eq!((key, value), ("port", "8080"));
|
|
70
|
+
/// # Ok(())
|
|
71
|
+
/// # }
|
|
72
|
+
/// ```
|
|
73
|
+
pub fn parse_pair(line: &str) -> Result<(&str, &str), ParseError> {
|
|
74
|
+
line.split_once('=').ok_or(ParseError::MissingSeparator)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- `# Errors` for every public `Result`-returning function (`missing_errors_doc`), `# Panics` for every reachable panic (`missing_panics_doc`), `# Safety` for every public `unsafe fn` or `unsafe trait`, listing each obligation the caller takes on.
|
|
79
|
+
- Examples use `?`, never `unwrap`; lines starting with `# ` compile but stay hidden, which is where imports and the `fn main() -> Result` wrapper go.
|
|
80
|
+
- Link types with intra-doc links (``[`ParseError`]``). `RUSTDOCFLAGS="-D warnings" cargo doc` fails on a broken link, and `cargo test --doc` runs every example.
|
|
81
|
+
- A crate's front page can be its README: `#![doc = include_str!("../README.md")]` makes the README's code blocks doctests too.
|
|
@@ -5,8 +5,8 @@ Structured concurrency, cancellation, blocking-work isolation, channel selection
|
|
|
5
5
|
## Runtime selection
|
|
6
6
|
|
|
7
7
|
```rust
|
|
8
|
-
// Default for services and CLIs that do real work
|
|
9
|
-
#[tokio::main
|
|
8
|
+
// Default for services and CLIs that do real work: multi-thread, one worker per core
|
|
9
|
+
#[tokio::main]
|
|
10
10
|
async fn main() -> anyhow::Result<()> { ... }
|
|
11
11
|
|
|
12
12
|
// For tiny CLIs or wasm where you measured single-thread is enough
|
|
@@ -14,7 +14,7 @@ async fn main() -> anyhow::Result<()> { ... }
|
|
|
14
14
|
async fn main() -> anyhow::Result<()> { ... }
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Keep the default worker count. Set `worker_threads` only for a measured reason or a deployment constraint (a container CPU quota the runtime cannot see, a desktop tool sharing the machine). CPU-heavy work does not get more workers; it leaves the runtime (see Blocking work).
|
|
18
18
|
|
|
19
19
|
## Spawning
|
|
20
20
|
|
|
@@ -48,13 +48,14 @@ while let Some(joined) = set.join_next().await {
|
|
|
48
48
|
- Dropping the set aborts every still-running task.
|
|
49
49
|
- Lets you handle failures one by one rather than all-or-nothing.
|
|
50
50
|
|
|
51
|
-
For
|
|
51
|
+
For a fixed set of independent futures, `join!` waits for all; for fallible ones, `try_join!` returns on the first `Err`:
|
|
52
52
|
|
|
53
53
|
```rust
|
|
54
|
-
let (a, b, c) = tokio::
|
|
55
|
-
let a = a?; let b = b?; let c = c?;
|
|
54
|
+
let (a, b, c) = tokio::try_join!(load_a(), load_b(), load_c())?;
|
|
56
55
|
```
|
|
57
56
|
|
|
57
|
+
On that first error `try_join!` drops the other futures mid-flight. Dropping is cancellation, not rollback: side effects they already started stay started (see Cancellation).
|
|
58
|
+
|
|
58
59
|
For first-of-many, `select!`:
|
|
59
60
|
|
|
60
61
|
```rust
|
|
@@ -76,19 +77,19 @@ Without `biased`, branches are polled in random order each iteration (good for f
|
|
|
76
77
|
|
|
77
78
|
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
|
|
|
79
|
-
Cancel-safe futures (you can drop without
|
|
80
|
-
- `recv()` on channels
|
|
81
|
-
- `
|
|
82
|
-
- `
|
|
83
|
-
- `read_buf`/`write_all` on streams **only when buffers are owned by the future**, otherwise no
|
|
80
|
+
Cancel-safe futures (you can drop them without losing data):
|
|
81
|
+
- `recv()` on channels, `accept()` on listeners
|
|
82
|
+
- `changed()` / `wait_for` on `watch::Receiver`
|
|
83
|
+
- `AsyncReadExt::read` / `read_buf`: bytes land in your buffer only when the call completes
|
|
84
84
|
|
|
85
|
-
Cancel-unsafe futures (dropping mid-way leaves partial state):
|
|
86
|
-
-
|
|
87
|
-
-
|
|
85
|
+
Cancel-unsafe futures (dropping mid-way loses data or leaves partial state):
|
|
86
|
+
- `read_exact`, `read_to_end`, `read_to_string`: bytes already read are gone
|
|
87
|
+
- `write_all` / `write_all_buf`: an unknown prefix was already written
|
|
88
|
+
- Custom futures that perform side effects before suspending
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
Tokio documents cancel safety per method; check it for every future used as a `select!` branch in a loop. When an operation is cancel-unsafe, keep its progress outside the future (a buffer that lives across loop iterations) or move it into its own task. If your own function is cancel-unsafe, document it in a rustdoc `# Cancel Safety` section.
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
Cooperative cancellation of a whole task tree uses `tokio_util::sync::CancellationToken`: tasks watch the token and exit at a point they choose, so cleanup runs.
|
|
92
93
|
|
|
93
94
|
```rust
|
|
94
95
|
use tokio_util::sync::CancellationToken;
|
|
@@ -135,7 +136,7 @@ let result = tokio::task::spawn_blocking(|| {
|
|
|
135
136
|
}).await?;
|
|
136
137
|
```
|
|
137
138
|
|
|
138
|
-
|
|
139
|
+
Sustained CPU parallelism → a dedicated pool (`rayon`), not tokio's blocking pool, which is sized for many short blocking calls. The same rule covers the blocking std APIs that look harmless inside `async fn`: `std::thread::sleep` (use `tokio::time::sleep`), `std::fs::*` (use `tokio::fs`, or batch it into one `spawn_blocking`), and `blocking_recv` / `blocking_lock` (they panic inside a runtime).
|
|
139
140
|
|
|
140
141
|
## Channels
|
|
141
142
|
|
|
@@ -199,21 +200,31 @@ serve_sse(stream).await
|
|
|
199
200
|
```rust
|
|
200
201
|
use tokio::signal;
|
|
201
202
|
|
|
203
|
+
/// Resolves on Ctrl-C or SIGTERM. A signal source that cannot be installed is
|
|
204
|
+
/// logged and never fires, so the other source still works.
|
|
202
205
|
async fn shutdown_signal() {
|
|
203
|
-
let ctrl_c = async {
|
|
206
|
+
let ctrl_c = async {
|
|
207
|
+
if let Err(error) = signal::ctrl_c().await {
|
|
208
|
+
tracing::error!(%error, "cannot listen for ctrl-c");
|
|
209
|
+
std::future::pending::<()>().await;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
204
212
|
#[cfg(unix)]
|
|
205
213
|
let terminate = async {
|
|
206
|
-
signal::unix::signal(signal::unix::SignalKind::terminate())
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
214
|
+
match signal::unix::signal(signal::unix::SignalKind::terminate()) {
|
|
215
|
+
Ok(mut sigterm) => { sigterm.recv().await; }
|
|
216
|
+
Err(error) => {
|
|
217
|
+
tracing::error!(%error, "cannot listen for SIGTERM");
|
|
218
|
+
std::future::pending::<()>().await;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
210
221
|
};
|
|
211
222
|
#[cfg(not(unix))]
|
|
212
223
|
let terminate = std::future::pending::<()>();
|
|
213
224
|
|
|
214
225
|
tokio::select! {
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
() = ctrl_c => {},
|
|
227
|
+
() = terminate => {},
|
|
217
228
|
}
|
|
218
229
|
tracing::info!("shutdown signal received");
|
|
219
230
|
}
|
|
@@ -224,7 +235,10 @@ async fn main() -> anyhow::Result<()> {
|
|
|
224
235
|
let server = tokio::spawn(run_server(token.child_token()));
|
|
225
236
|
shutdown_signal().await;
|
|
226
237
|
token.cancel();
|
|
227
|
-
|
|
238
|
+
match tokio::time::timeout(Duration::from_secs(10), server).await {
|
|
239
|
+
Ok(joined) => joined??, // JoinError (panic) and the server's own error both surface
|
|
240
|
+
Err(_elapsed) => tracing::warn!("server did not stop within 10s"),
|
|
241
|
+
}
|
|
228
242
|
Ok(())
|
|
229
243
|
}
|
|
230
244
|
```
|
|
@@ -235,14 +249,16 @@ Pattern: catch signal → cancel a token shared with the server → server's `se
|
|
|
235
249
|
|
|
236
250
|
- `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
251
|
- `tokio::sync::RwLock` — async read-write lock. Same caveat.
|
|
238
|
-
- `
|
|
252
|
+
- `std::sync::Mutex` / `parking_lot::Mutex` — sync mutex for a critical section that never spans an `.await` (see [concurrency.md](concurrency.md) for choosing between them).
|
|
253
|
+
- Nothing guard-like lives across an `.await`: not a sync lock guard, not a `watch` borrow ([concurrency.md](concurrency.md)), not a `span.enter()` guard. Instrument the future instead: `fut.instrument(span).await`.
|
|
239
254
|
- `tokio::sync::Semaphore` — bound concurrent operations. Perfect for "max 10 in-flight HTTP requests" or "max 3 DB writers".
|
|
240
255
|
|
|
241
256
|
```rust
|
|
242
257
|
let sem = Arc::new(tokio::sync::Semaphore::new(10));
|
|
258
|
+
let mut set = JoinSet::new();
|
|
243
259
|
for url in urls {
|
|
244
|
-
let permit =
|
|
245
|
-
|
|
260
|
+
let permit = Arc::clone(&sem).acquire_owned().await?;
|
|
261
|
+
set.spawn(async move {
|
|
246
262
|
let _permit = permit; // released on task end
|
|
247
263
|
fetch(&url).await
|
|
248
264
|
});
|
|
@@ -278,6 +294,8 @@ async fn fetches_and_parses() {
|
|
|
278
294
|
async fn parallel_work() { ... }
|
|
279
295
|
```
|
|
280
296
|
|
|
297
|
+
`current_thread` is the default test runtime; it chooses a scheduler, it does not make a test deterministic. Wait on the exact event (a channel message, a `Notify`, a `watch` change) or on paused virtual time below, never on a real `sleep`.
|
|
298
|
+
|
|
281
299
|
For time-sensitive tests, advance virtual time:
|
|
282
300
|
|
|
283
301
|
```rust
|
|
@@ -290,6 +308,20 @@ async fn time_travel() {
|
|
|
290
308
|
}
|
|
291
309
|
```
|
|
292
310
|
|
|
311
|
+
## Async traits and async closures
|
|
312
|
+
|
|
313
|
+
`async fn` in traits is stable (1.75) and dispatches statically:
|
|
314
|
+
|
|
315
|
+
```rust
|
|
316
|
+
trait Store {
|
|
317
|
+
async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, StoreError>;
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
Two limits: callers cannot require the returned future to be `Send` (spawning a generic `S: Store` call fails), and the trait is not `dyn`-compatible. For `Send` futures, declare the method as `fn get(&self, key: &str) -> impl Future<Output = ...> + Send;`. For `dyn Store`, box the future (`Pin<Box<dyn Future<Output = T> + Send + '_>>`) or use `async-trait`; pick `dyn` only for runtime heterogeneity.
|
|
322
|
+
|
|
323
|
+
A parameter that is an async callback borrowing from its caller takes `F: AsyncFn(&Request) -> Response` (1.85, `std::ops::AsyncFn`), not `F: Fn(&Request) -> Fut`: the `Fut` form cannot name a future that borrows its argument.
|
|
324
|
+
|
|
293
325
|
## When NOT to use async
|
|
294
326
|
|
|
295
327
|
- Single-threaded CPU-heavy code that does no I/O — plain `fn` + `rayon` is simpler and often faster.
|
|
@@ -307,19 +307,7 @@ fn init_tracing() {
|
|
|
307
307
|
fmt().with_env_filter(filter).with_target(false).json().init();
|
|
308
308
|
}
|
|
309
309
|
|
|
310
|
-
|
|
311
|
-
let ctrl_c = async { tokio::signal::ctrl_c().await.expect("ctrl_c handler"); };
|
|
312
|
-
#[cfg(unix)]
|
|
313
|
-
let terminate = async {
|
|
314
|
-
tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate())
|
|
315
|
-
.expect("signal handler").recv().await;
|
|
316
|
-
};
|
|
317
|
-
#[cfg(not(unix))]
|
|
318
|
-
let terminate = std::future::pending::<()>();
|
|
319
|
-
|
|
320
|
-
tokio::select! { _ = ctrl_c => {}, _ = terminate => {} }
|
|
321
|
-
tracing::info!("shutting down");
|
|
322
|
-
}
|
|
310
|
+
// shutdown_signal(): the Ctrl-C / SIGTERM future from async-tokio.md#graceful-shutdown
|
|
323
311
|
```
|
|
324
312
|
|
|
325
313
|
## Middleware: bearer auth example
|
|
@@ -32,6 +32,8 @@ non_ascii_idents = "deny"
|
|
|
32
32
|
trivial_numeric_casts = "warn"
|
|
33
33
|
unused_lifetimes = "warn"
|
|
34
34
|
single_use_lifetimes = "warn"
|
|
35
|
+
# every custom cfg is declared here; a typo like cfg(feture = "x") fails the build
|
|
36
|
+
unexpected_cfgs = { level = "deny", check-cfg = ["cfg(loom)"] }
|
|
35
37
|
|
|
36
38
|
[lints.clippy]
|
|
37
39
|
# Groups
|
|
@@ -66,7 +68,9 @@ mutex_atomic = "warn"
|
|
|
66
68
|
rc_buffer = "warn"
|
|
67
69
|
rc_mutex = "warn"
|
|
68
70
|
exit = "warn"
|
|
69
|
-
|
|
71
|
+
allow_attributes = "deny" # silence with #[expect(lint, reason)], never #[allow]
|
|
72
|
+
allow_attributes_without_reason = "deny"
|
|
73
|
+
let_underscore_must_use = "deny" # `let _ = fallible()` drops the error
|
|
70
74
|
dbg_macro = "warn"
|
|
71
75
|
print_stderr = "warn"
|
|
72
76
|
print_stdout = "warn"
|
|
@@ -75,10 +79,8 @@ use_debug = "warn"
|
|
|
75
79
|
# Stylistic relaxations (project-wide opinions only)
|
|
76
80
|
module_name_repetitions = "allow"
|
|
77
81
|
must_use_candidate = "allow"
|
|
78
|
-
missing_errors_doc = "allow" # we use anyhow::Result with .context() everywhere; doc rule is noisy
|
|
79
82
|
|
|
80
83
|
# Restriction lints - opt-in soundness rails
|
|
81
|
-
unreachable = "deny"
|
|
82
84
|
mod_module_files = "warn" # prefer foo.rs over foo/mod.rs
|
|
83
85
|
empty_drop = "warn"
|
|
84
86
|
empty_structs_with_brackets = "warn"
|
|
@@ -89,6 +91,8 @@ exhaustive_structs = "warn"
|
|
|
89
91
|
|
|
90
92
|
The `priority = -1` trick: group-level levels are weak; specific lints below them win. This lets us deny `unwrap_used` while still allowing `pedantic` group warnings instead of denies.
|
|
91
93
|
|
|
94
|
+
A lint is silenced only with `#[expect(clippy::lint_name, reason = "...")]` on the smallest item that needs it. Unlike `#[allow]`, `#[expect]` fails the build once the lint stops firing, so stale exceptions cannot pile up; `allow_attributes` and `allow_attributes_without_reason` enforce both halves. `missing_errors_doc` / `missing_panics_doc` stay on (pedantic): public fallible APIs document their `# Errors`.
|
|
95
|
+
|
|
92
96
|
## `Cargo.toml` — release profile
|
|
93
97
|
|
|
94
98
|
```toml
|
|
@@ -116,6 +120,8 @@ opt-level = 1
|
|
|
116
120
|
overflow-checks = true
|
|
117
121
|
```
|
|
118
122
|
|
|
123
|
+
`-C target-cpu=native` and PGO are deployment decisions, not defaults: a `native` binary faults on older CPUs, and PGO only pays with a representative training workload. Apply either only with a before/after measurement.
|
|
124
|
+
|
|
119
125
|
## `Cargo.toml` — workspace level
|
|
120
126
|
|
|
121
127
|
```toml
|
|
@@ -124,15 +130,23 @@ resolver = "3"
|
|
|
124
130
|
|
|
125
131
|
[workspace.package]
|
|
126
132
|
edition = "2024"
|
|
127
|
-
rust-version = "1.
|
|
133
|
+
rust-version = "1.85" # the 2024 edition floor; raise only when a needed feature lands
|
|
128
134
|
license = "Apache-2.0 OR MIT"
|
|
129
135
|
|
|
130
136
|
[workspace.lints]
|
|
131
137
|
# Then in each member crate:
|
|
132
138
|
# [lints]
|
|
133
139
|
# workspace = true
|
|
140
|
+
|
|
141
|
+
[workspace.dependencies]
|
|
142
|
+
# one version per dependency; members write `serde = { workspace = true }`
|
|
134
143
|
```
|
|
135
144
|
|
|
145
|
+
## Features and build scripts
|
|
146
|
+
|
|
147
|
+
- **Features are additive.** Enabling a feature never removes an API or changes another feature's behavior, and `--all-features` always builds. Optional dependencies use `dep:` (`serde = ["dep:serde"]`) so no implicit feature leaks. Truly exclusive backends fail loudly: `#[cfg(all(feature = "a", feature = "b"))] compile_error!("features `a` and `b` are mutually exclusive");`.
|
|
148
|
+
- **`build.rs` is deterministic.** Declare every input with `cargo::rerun-if-changed=` / `cargo::rerun-if-env-changed=`, write only under `OUT_DIR`, and never touch the network. A build script that reads undeclared inputs produces stale builds that pass locally and fail in CI.
|
|
149
|
+
|
|
136
150
|
## `rustfmt.toml`
|
|
137
151
|
|
|
138
152
|
```toml
|
|
@@ -156,11 +170,11 @@ Most options come from stable rustfmt. `imports_granularity` and `group_imports`
|
|
|
156
170
|
# Reduce cognitive load thresholds.
|
|
157
171
|
cognitive-complexity-threshold = 25
|
|
158
172
|
type-complexity-threshold = 250
|
|
159
|
-
too-many-arguments-threshold =
|
|
173
|
+
too-many-arguments-threshold = 3 # SKILL.md Smell 2: more than 3 parameters is a smell
|
|
160
174
|
too-many-lines-threshold = 100
|
|
161
175
|
|
|
162
176
|
# msrv - keeps clippy from suggesting features past our MSRV
|
|
163
|
-
msrv = "1.
|
|
177
|
+
msrv = "1.85"
|
|
164
178
|
|
|
165
179
|
# Avoid `panic` lint complaining about derived Debug impls calling unreachable_unchecked etc.
|
|
166
180
|
allow-unwrap-in-tests = true
|
|
@@ -169,7 +183,7 @@ allow-panic-in-tests = true
|
|
|
169
183
|
allow-dbg-in-tests = true
|
|
170
184
|
allow-print-in-tests = true
|
|
171
185
|
|
|
172
|
-
#
|
|
186
|
+
# At most 4 single-character bindings in scope
|
|
173
187
|
single-char-binding-names-threshold = 4
|
|
174
188
|
```
|
|
175
189
|
|
|
@@ -257,6 +271,25 @@ jobs:
|
|
|
257
271
|
- uses: Swatinem/rust-cache@v2
|
|
258
272
|
- uses: taiki-e/install-action@nextest
|
|
259
273
|
- run: cargo nextest run --all-targets --all-features --workspace
|
|
274
|
+
- run: cargo test --doc --all-features --workspace # nextest does not run doctests
|
|
275
|
+
|
|
276
|
+
doc:
|
|
277
|
+
runs-on: ubuntu-latest
|
|
278
|
+
steps:
|
|
279
|
+
- uses: actions/checkout@v4
|
|
280
|
+
- uses: dtolnay/rust-toolchain@stable
|
|
281
|
+
- uses: Swatinem/rust-cache@v2
|
|
282
|
+
- env:
|
|
283
|
+
RUSTDOCFLAGS: "-D warnings" # broken intra-doc links fail the build
|
|
284
|
+
run: cargo doc --no-deps --all-features --workspace
|
|
285
|
+
|
|
286
|
+
msrv:
|
|
287
|
+
runs-on: ubuntu-latest
|
|
288
|
+
steps:
|
|
289
|
+
- uses: actions/checkout@v4
|
|
290
|
+
- uses: dtolnay/rust-toolchain@1.85 # = rust-version
|
|
291
|
+
- uses: Swatinem/rust-cache@v2
|
|
292
|
+
- run: cargo check --all-features --workspace
|
|
260
293
|
|
|
261
294
|
miri:
|
|
262
295
|
runs-on: ubuntu-latest
|
|
@@ -270,6 +303,9 @@ jobs:
|
|
|
270
303
|
- env:
|
|
271
304
|
MIRIFLAGS: "-Zmiri-strict-provenance -Zmiri-symbolic-alignment-check"
|
|
272
305
|
run: cargo +nightly miri nextest run --all-features --workspace
|
|
306
|
+
- env:
|
|
307
|
+
MIRIFLAGS: "-Zmiri-tree-borrows -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check"
|
|
308
|
+
run: cargo +nightly miri nextest run --all-features --workspace
|
|
273
309
|
|
|
274
310
|
machete:
|
|
275
311
|
runs-on: ubuntu-latest
|
|
@@ -312,6 +348,6 @@ After every change:
|
|
|
312
348
|
```bash
|
|
313
349
|
cargo fmt --all -- --check && \
|
|
314
350
|
cargo clippy --all-targets --all-features -- -D warnings && \
|
|
315
|
-
cargo nextest run && \
|
|
351
|
+
cargo nextest run && cargo test --doc && \
|
|
316
352
|
cargo +nightly miri nextest run # only if unsafe is involved
|
|
317
353
|
```
|
|
@@ -280,14 +280,19 @@ For automated tests, expose a `--non-interactive` flag and gate all prompts behi
|
|
|
280
280
|
## Structured output
|
|
281
281
|
|
|
282
282
|
```rust
|
|
283
|
+
use std::io::Write as _;
|
|
284
|
+
|
|
285
|
+
// `println!` panics when stdout is closed (`mytool | head -1`); writing to a
|
|
286
|
+
// locked handle returns the error instead, so a broken pipe exits cleanly.
|
|
287
|
+
let mut out = std::io::stdout().lock();
|
|
283
288
|
match cli.format {
|
|
284
289
|
OutputFormat::Json => {
|
|
285
|
-
serde_json::to_writer(
|
|
286
|
-
|
|
290
|
+
serde_json::to_writer(&mut out, &result)?;
|
|
291
|
+
writeln!(out)?;
|
|
287
292
|
}
|
|
288
293
|
OutputFormat::Plain => {
|
|
289
294
|
for row in &result.rows {
|
|
290
|
-
|
|
295
|
+
writeln!(out, "{}\t{}\t{}", row.a, row.b, row.c)?;
|
|
291
296
|
}
|
|
292
297
|
}
|
|
293
298
|
OutputFormat::Pretty => {
|