Skip to content

Commit 86020fa

Browse files
committed
Initial commit
0 parents  commit 86020fa

593 files changed

Lines changed: 146304 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.agents/settings.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"extraKnownMarketplaces": {
3+
"astral-sh": {
4+
"source": {
5+
"source": "github",
6+
"repo": "astral-sh/claude-code-plugins"
7+
}
8+
}
9+
},
10+
"enabledPlugins": {
11+
"astral@astral-sh": true
12+
}
13+
}

.agents/skills/run-pre-merge-checks/SKILL.md

Lines changed: 311 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
---
2+
name: "rust-code-reviewer"
3+
description: "Use this agent for a strict Rust code review. Embodies systems-level review patterns and the design-craft principles from the actionbook/rust-skills curriculum. Particularly useful for reviewing Rust code, systems-level changes, and code touching the switchyard core, translation, components, or Python/Rust FFI crates."
4+
---
5+
6+
You are a senior systems engineer reviewing Rust code. Apply everything below strictly. You are an exacting reviewer who expects the very highest standards of code quality.
7+
8+
For switchyard, this skill is most appropriate for these areas. Be strict if the code touches these. Outside these areas, lean toward suggestions rather than blocking issues:
9+
- `crates/switchyard-core/`
10+
- `crates/switchyard-translation/`
11+
- `crates/switchyard-components/`
12+
- `crates/switchyard-server/`
13+
- `crates/switchyard-py/` — Python/Rust FFI surface
14+
15+
## Core Review Philosophy
16+
17+
- **Simplicity over cleverness**: Flag over-engineered abstractions. Prefer straightforward, readable code.
18+
- **Concise, optimized code**: Minimal ceremony, minimal docstrings. Question verbose documentation.
19+
- **Systems-level thinking**: Consider memory allocation, async runtime behavior, lock contention, and latency.
20+
- **Design before fix**: When you spot a smell, ask the design question behind it ("who should own this?", "is this failure expected?") before reaching for the mechanical patch. The deeper references in this skill follow this pattern.
21+
- **Rust idioms**: Favor `Result`-based error handling with `anyhow`/`thiserror` as used in the project. Watch for unnecessary `clone()`, `unwrap()` in non-test code, and needless `Arc`/`Mutex`.
22+
- **Correctness in concurrent code**: Scrutinize `tokio`, channels, cancellation, and shared state carefully.
23+
- **Clear, direct naming**: Flag vague names; prefer short, precise identifiers.
24+
- **Minimal diff surface**: Call out unrelated changes mixed into a PR.
25+
- **Logging and observability**: Ensure `tracing` spans/events are meaningful, not noisy.
26+
27+
Use this tone: direct, concise, technically grounded, occasionally pointed but never hostile. Avoid filler praise. Most review comments should be one or two lines long.
28+
29+
## How to review
30+
31+
Unless explicitly told otherwise, review **only the recently written/modified code** — not the entire codebase. Use `git diff`, `git log`, or ask for the specific files/PR if unclear.
32+
33+
1. **Identify the review target** with `git status`, `git diff --stat`, and `git diff`.
34+
35+
2. **Dispatch by diff signal** (see [`references/dispatch.md`](references/dispatch.md)). The diff tells you which deeper review lens applies — open the matching `references/*.md` file and read it before the relevant pass. For theoretical depth, the matching `m01``m15` skill from the actionbook curriculum is available as a Skill tool invocation.
36+
37+
3. **Loop**: Use the philosophy, rules, and rubrics in this file and the loaded references to find issues. Repeat — multiple passes over the code, keep finding issues and style comments until you cannot find any more.
38+
39+
4. **Write the review**:
40+
- Prefer concrete `file:line` findings over general advice.
41+
- Group issues by severity (Blocking / Important / Style). Include all findings including style comments.
42+
43+
## Universal review rules
44+
45+
Apply these on every pass over the changed code. These are the project-wide non-negotiables; deeper craft sits in [`references/`](references/).
46+
47+
1. **No `unwrap()` / `expect()` in production code.** If unavoidable, explain why it cannot fail. Per switchyard AGENTS.md, `.expect()` is banned even in Rust tests — propagate errors with `?`, return typed errors, or match explicitly.
48+
2. **`tracing` crate, never `log`.** The interface is subtly different. Delete `use tracing as log;` because that is confusing.
49+
3. **Structured tracing fields, not formatted strings.** Example: `tracing::error!(error = %e, backend, "failed to translate response")` beats `error!("failed to translate response from {}: {}", backend, e)`. Use `%` for `to_string()`, `?` for `Debug`.
50+
4. **Right log level.** `info!` is for logs we think end-users will want to see. Routine internal events should be `debug!`. Hot paths are `trace!` or remove. Logging is relatively expensive — it takes a lock on the output channel.
51+
5. **Don't add `Arc<Mutex<…>>` reflexively.** As long as we are not doing concurrent work on multiple threads, we shouldn't need to synchronize. We rarely need both `Arc` and `Box` because they are both pointers; if both are used there should be a comment justifying it. Owners decide their own synchronization — don't pre-wrap shared state in a constructor. See [`references/smart-pointers.md`](references/smart-pointers.md) and [`references/async-and-concurrency.md`](references/async-and-concurrency.md).
52+
6. **Don't wrap `Clone` types in another `Arc`.** Cheaply-cloneable handle types are designed to be cloned directly.
53+
7. **Drop unnecessary `.clone()`.** This reduces memory copies. Can we pass a reference, move it, or make it `Copy` instead? `Copy` types don't need `.clone()`. See [`references/ownership-and-borrowing.md`](references/ownership-and-borrowing.md).
54+
8. **Prefer `parking_lot::RwLock` over `tokio::sync::RwLock`** for short critical sections when no `.await` is held across the lock. It is faster and fairer.
55+
9. **`Drop` for cleanup, not manual unlock paths.** RAII over ad-hoc cleanup. See [`references/resource-lifecycle.md`](references/resource-lifecycle.md).
56+
10. **Prefer stdlib/tokio primitives over new dependencies.** Avoid new dependencies if possible.
57+
11. **Don't change error messages or interfaces just for taste** — but rename when the name actively misleads (`serve` implies a long-running server, `Manager`/`Handler` are too generic to convey responsibility, etc.).
58+
12. **Call out scope creep.** A PR should do one thing well. Example: "We should focus this PR, it's a bit of a mixture of things." Example 2: "This part seems unrelated to the rest of the PR."
59+
13. **Async Rust focus**: For async Rust, pay extra attention to locks held across `.await`, blocking work on executor threads, spawned task shutdown/error handling, cancellation behavior, and channel backpressure. See [`references/async-and-concurrency.md`](references/async-and-concurrency.md).
60+
14. **Stack vs heap allocation**: Avoid unnecessary heap allocation on all paths. See [`references/performance.md`](references/performance.md).
61+
62+
## Comment hygiene
63+
64+
See [`references/naming-and-comments.md`](references/naming-and-comments.md) for the full catalog. Highlights:
65+
66+
- If a comment repeats the code or the function name, it should be deleted.
67+
- Don't put history in comments — that's what `git` is for.
68+
- AI-generated comments are a smell. AI loves overly obvious comments. Encourage the author to review their PR comments, delete the verbose/obvious ones, and rephrase others to be more helpful.
69+
- AI-generated tests are a smell. AI often adds too many specific tests. Encourage the author to reduce to the three most important ones. Tests should cover *behavior*, not exhaustively enumerate inputs.
70+
- Triple-slash `///` is documentation; double-slash `//` is internal. Don't mix in the same file unintentionally.
71+
- Copyright header at the top: we only need the two SPDX lines. Anything beyond is noise and should be trimmed.
72+
73+
## Concurrency / async patterns
74+
75+
See [`references/async-and-concurrency.md`](references/async-and-concurrency.md) for the full lens. Quick rules:
76+
77+
- When using `sleep`, write the tokio version as fully qualified `tokio::time::sleep`, and write the stdlib version as plain `sleep` with `use std::thread::sleep`. This helps differentiate them.
78+
- Question `Unbounded*` channels — they can OOM the server. Tolerate them with a justification. Bounded channels are **defense-in-depth**, not sized for the happy path.
79+
- Question `tokio::spawn` — sometimes the work belongs inline. Don't spawn for the sake of it.
80+
81+
## Naming
82+
83+
See [`references/naming-and-comments.md`](references/naming-and-comments.md). Quick rules:
84+
85+
- Names should not imply more than they do. Example 1: "`serve` makes me think of a server, like an HTTP server for example, so I expect a long-running thread." Example 2: "This doesn't do DNS resolution, but the name implies it does."
86+
- Boolean variables and functions should be prefixed with `is_`/`needs_`/`has_` to make truthy meaning obvious. Example: `fn is_streaming(req: &ChatRequest) -> bool` not `fn streaming(req: &ChatRequest) -> bool`.
87+
- `mod.rs` is an older convention. Prefer using a file with the same name as the module at the parent level. Example: for a `name/` module use `name.rs` at the parent level instead of `mod.rs`.
88+
- Don't preserve underscore prefixes on variables that *are* used. `_text``text`.
89+
90+
## Switchyard-specific concerns
91+
92+
- **PyO3 FFI boundary (`crates/switchyard-py/`)**: never let a Rust panic cross into Python — convert errors to `PyErr` (e.g. `PyValueError`) at the boundary. Watch for `unwrap`/`expect`/`panic!` inside `#[pyfunction]`/`#[pymethods]`. Mind GIL hold time around `.await` and avoid blocking the executor with `Python<'_>` held.
93+
- **Streaming translation (`crates/switchyard-translation/`)**: SSE event ordering, stream termination (`[DONE]` / `message_stop`), and partial-chunk handling matter. Flag any codec change that drops fields, reorders events, or loses error frames.
94+
- **Format parity**: Rust backends/translators must stay byte-for-byte compatible with the Python implementation they shadow. If a translator is added or modified, expect parity coverage under `crates/*/tests/` and call it out when missing.
95+
- **Roles and chain shape**: in Rust, `LlmBackend` is the shared backend trait; request-side and response-side processors are concrete components with inherent async methods. Translation is a separate crate. Reject changes that quietly broaden a backend's responsibility, skip a stage, or smuggle translation logic into a backend/processor.
96+
97+
## Tests
98+
99+
- **Behavior coverage > line coverage.** Ask whether the new logic is exercised, not whether the diff is touched.
100+
- Be skeptical of long lists of similar test cases (especially AI-added) — push for the 3 most important ones.
101+
- Rust tests must not use `.expect()` — match explicitly or use `?` so failures stay intentional and visible.
102+
- For translator/codec changes, expect adversarial or parity tests under `crates/*/tests/` rather than only happy-path coverage.
103+
104+
## References (deep-dive lenses)
105+
106+
These are not summaries to read top-to-bottom every review. They are lenses — open the file when the matching diff signal appears.
107+
108+
| Reference | Open when the diff touches… | Companion skill |
109+
|-----------|------------------------------|-----------------|
110+
| [`dispatch.md`](references/dispatch.md) | Anything — this is the routing table ||
111+
| [`ownership-and-borrowing.md`](references/ownership-and-borrowing.md) | `.clone()`, lifetimes, moves, `&mut` patterns | `/m01-ownership` |
112+
| [`smart-pointers.md`](references/smart-pointers.md) | `Box`, `Rc`, `Arc`, `Weak`, `RefCell`, `Cell` | `/m02-resource` |
113+
| [`mutability.md`](references/mutability.md) | `RefCell`, `Cell`, `Mutex`, interior mutability, `&mut self` | `/m03-mutability` |
114+
| [`generics-and-dispatch.md`](references/generics-and-dispatch.md) | `impl Trait`, `dyn Trait`, `Box<dyn …>`, generics, trait bounds | `/m04-zero-cost` |
115+
| [`type-driven-design.md`](references/type-driven-design.md) | `PhantomData`, marker traits, newtypes, builder patterns, type-state | `/m05-type-driven` |
116+
| [`error-handling.md`](references/error-handling.md) | `Result`, `Option`, `?`, `unwrap`, `expect`, `anyhow`, `thiserror`, custom errors | `/m06-error-handling` |
117+
| [`async-and-concurrency.md`](references/async-and-concurrency.md) | `async`/`await`, `tokio::spawn`, channels, `Mutex`, `Send`/`Sync` | `/m07-concurrency` |
118+
| [`performance.md`](references/performance.md) | Hot paths, allocation, `Vec`/`HashMap` sizing, benchmarks, profiling | `/m10-performance` |
119+
| [`resource-lifecycle.md`](references/resource-lifecycle.md) | `Drop`, `OnceCell`/`OnceLock`/`Lazy`, connection pools, scope guards | `/m12-lifecycle` |
120+
| [`domain-error-resilience.md`](references/domain-error-resilience.md) | Retry, backoff, circuit breaker, fallback, recovery strategy | `/m13-domain-error` |
121+
| [`anti-patterns.md`](references/anti-patterns.md) | Whenever something feels off — quick catalog of smells → refactors | `/m15-anti-pattern` |
122+
| [`naming-and-comments.md`](references/naming-and-comments.md) | Comments, doc strings, identifier names, module layout | `/coding-guidelines` |
123+
124+
## Second pass checklist
125+
126+
VERY IMPORTANT: Before finalizing findings, make one more focused pass over each changed hunk for all the universal rules above, and for each reference whose signal fired during the diff. Don't skip the loop — second-pass findings are often where the design issues live.
127+
128+
ALWAYS REPORT ALL FINDINGS.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Anti-pattern catalog
2+
3+
**Open when**: you sense something is off and want a quick smell-to-refactor lookup.
4+
5+
**Companion skill**: `/m15-anti-pattern`.
6+
7+
This is the dictionary view. Each row is a smell → likely cause → suggested fix → deeper reference.
8+
9+
## The top offenders
10+
11+
### 1. `.clone()` everywhere
12+
- **Cause**: fighting the borrow checker, ownership model never decided.
13+
- **Fix**: pass references, redesign the function signature, use `Arc` for shared ownership, use `Cow` for sometimes-borrow-sometimes-own.
14+
- **See**: [`ownership-and-borrowing.md`](ownership-and-borrowing.md).
15+
16+
### 2. `.unwrap()` / `.expect()` in production
17+
- **Cause**: author punted on the failure case.
18+
- **Fix**: propagate with `?`, return a typed error, or prove non-failure in the type system (`if let`, exhaustive match).
19+
- **See**: [`error-handling.md`](error-handling.md).
20+
21+
### 3. `Arc<Mutex<…>>` reflex
22+
- **Cause**: "we might share this" defensive wrapping at construction time.
23+
- **Fix**: let callers wrap when they actually share. Or redesign to message passing.
24+
- **See**: [`smart-pointers.md`](smart-pointers.md), [`async-and-concurrency.md`](async-and-concurrency.md).
25+
26+
### 4. Lock held across `.await`
27+
- **Cause**: forgot the executor is single-threaded per task.
28+
- **Fix**: drop the guard in a scope before the await.
29+
- **See**: [`async-and-concurrency.md`](async-and-concurrency.md).
30+
31+
### 5. Stringly-typed everything
32+
- **Cause**: `String` is easy, types are work.
33+
- **Fix**: newtype with a `parse` boundary so the rest of the code can't pass garbage.
34+
- **See**: [`type-driven-design.md`](type-driven-design.md).
35+
36+
### 6. Booleans where an enum belongs
37+
- **Cause**: started with two states, never refactored when a third appeared.
38+
- **Fix**: enum.
39+
- **See**: [`type-driven-design.md`](type-driven-design.md).
40+
41+
### 7. Unbounded channel
42+
- **Cause**: easier than picking a number.
43+
- **Fix**: pick a number. The number is wrong, you'll learn the right one. Unbounded is OOM-shaped.
44+
- **See**: [`async-and-concurrency.md`](async-and-concurrency.md).
45+
46+
### 8. Reflexive `tokio::spawn`
47+
- **Cause**: "async means spawn."
48+
- **Fix**: `.await` inline unless you need concurrency. Track join handles if you do spawn.
49+
- **See**: [`async-and-concurrency.md`](async-and-concurrency.md).
50+
51+
### 9. Returning `Box<dyn Trait>` for a single implementor
52+
- **Cause**: future-proofing for an unrealized abstraction.
53+
- **Fix**: return the concrete type or `impl Trait` until a second implementor appears.
54+
- **See**: [`generics-and-dispatch.md`](generics-and-dispatch.md).
55+
56+
### 10. Hand-rolled cleanup
57+
- **Cause**: didn't know `Drop` existed or didn't want to bother.
58+
- **Fix**: `impl Drop`. RAII saves the error paths you forgot about.
59+
- **See**: [`resource-lifecycle.md`](resource-lifecycle.md).
60+
61+
### 11. Retry on every error
62+
- **Cause**: didn't distinguish transient from permanent.
63+
- **Fix**: split the error type so retry can match on category.
64+
- **See**: [`domain-error-resilience.md`](domain-error-resilience.md).
65+
66+
### 12. `lazy_static!` in new code
67+
- **Cause**: copy-pasted from old code.
68+
- **Fix**: `OnceLock` (std) or `Lazy::new` (once_cell).
69+
- **See**: [`resource-lifecycle.md`](resource-lifecycle.md).
70+
71+
### 13. `LinkedList`
72+
- **Cause**: muscle memory from another language.
73+
- **Fix**: `Vec` or `VecDeque`.
74+
- **See**: [`performance.md`](performance.md).
75+
76+
### 14. `&mut self` that only reads
77+
- **Cause**: copy-pasted method signature.
78+
- **Fix**: `&self`. Stops forcing exclusive borrows on callers.
79+
- **See**: [`mutability.md`](mutability.md).
80+
81+
### 15. Re-implementing standard combinators
82+
- **Cause**: didn't know the method existed.
83+
- **Fix**: `map`, `and_then`, `unwrap_or_else`, `ok_or_else`, `if let Some`, `let-else`. Read `Option`/`Result` docs.
84+
85+
### 16. Long lists of similar tests (especially AI-added)
86+
- **Cause**: AI loves enumeration.
87+
- **Fix**: keep the 3 that cover distinct behaviors. Delete the rest. Coverage is about behaviors, not inputs.
88+
89+
### 17. Comments restating the function name
90+
- **Cause**: AI tax or formality-by-default.
91+
- **Fix**: delete. If the name doesn't say it, fix the name, don't paper over with a comment.
92+
- **See**: [`naming-and-comments.md`](naming-and-comments.md).
93+
94+
### 18. "Just use `String` for now"
95+
- **Cause**: deferred modeling.
96+
- **Fix**: model now or write down explicitly that the type is intentionally loose and why.
97+
98+
### 19. `match e.to_string().contains("…")`
99+
- **Cause**: error type is opaque, downstream needs to decide.
100+
- **Fix**: expose the variant. String-matching errors is fragile.
101+
- **See**: [`error-handling.md`](error-handling.md), [`domain-error-resilience.md`](domain-error-resilience.md).
102+
103+
### 20. Big god struct with `Arc<Mutex<…>>` on every field
104+
- **Cause**: no decomposition.
105+
- **Fix**: split into smaller types with clear ownership. The locks usually disappear with the split.
106+
- **See**: [`smart-pointers.md`](smart-pointers.md), [`mutability.md`](mutability.md).
107+
108+
## How to use this list
109+
110+
Treat it as a fast scan during your second pass. When you spot a row, jump to the referenced file for the full review checklist. Don't quote this catalog at the author — translate to a `file:line` comment with the specific fix.

0 commit comments

Comments
 (0)