design-patterns — Claude Code skill: what it does, how to install
Provides Rust architectural patterns for RTK CLI filter modules. Helps select Newtype, Builder, State Machine, and RAII during module design and refactoring.
npx skills add rtk-ai/rtk --skill design-patterns -a claude-codeWhat design-patterns does
This skill guides Rust module architecture for command-line filter tools in RTK. It documents targeted design patterns: Newtype for type safety around primitives, Builder for structs with four or more optional fields, and State Machine for multi-step test or build output parsing.
The instructions explicitly flag over-engineering anti-patterns. Creating a Builder for structs with only one to three fields is discouraged, and match-based static dispatch in main.rs is preferred over trait objects until the command count passes 20. The skill operates using Read, Grep, and Glob tools.
What it helps with
- prevent primitive argument mix-ups using Newtype wrappers
- parse multi-phase command output with State Machine
- structure structs with 4+ optional flags using Builder
- manage temporary file lifecycles with RAII
Example prompts for the agent
- Which design pattern should I use to parse test runner output?
- How to structure a filter configuration struct with multiple optional flags?
- Refactor to pattern: convert raw string wrappers to Newtype
How to install design-patterns
1Skills CLI (Vercel)
npx skills add rtk-ai/rtk --skill design-patterns -a claude-code2Manually: put the files in place
~/.claude/skills/design-patterns/SKILL.mdCopy the whole skill folder; for one project use .claude/skills/design-patterns/
1Skills CLI (Vercel)
npx skills add rtk-ai/rtk --skill design-patterns -a codex2Manually: put the files in place
.agents/skills/design-patterns/SKILL.mdFor all projects use ~/.agents/skills/
1Skills CLI (Vercel)
npx skills add rtk-ai/rtk --skill design-patterns -a cursor2Manually: put the files in place
.cursor/skills/design-patterns/SKILL.mdCursor also reads .agents/skills/ and .claude/skills/
1GitHub CLI
gh skill install rtk-ai/rtk design-patterns2Manually: put the files in place
.github/skills/design-patterns/SKILL.md1Skills CLI (Vercel)
npx skills add rtk-ai/rtk --skill design-patterns -a gemini-cli2Platform CLI
gemini skills install https://github.com/rtk-ai/rtk --path .claude/skills/design-patternsManually: put the files in place
.devin/skills/design-patterns/SKILL.mdThe legacy .windsurf/skills/ path still works
Skills CLI (Vercel)
npx skills add rtk-ai/rtk --skill design-patterns -a opencodeClaude.ai
Zip the folder and upload it in Settings → Capabilities → Skills
From the same set4
The whole set →Similar
Questions about design-patterns
How to install design-patterns in Claude Code?
Run npx skills add rtk-ai/rtk --skill design-patterns -a claude-code in your terminal. You can also place the skill file directly at ~/.claude/skills/design-patterns/SKILL.md.
What does the design-patterns skill do?
It provides practical Rust patterns tailored for CLI filter tools, covering Newtype, Builder, State Machine, Trait Objects, and RAII. It guides architecture decisions and flags over-engineering.
When should trait objects be avoided according to the skill?
Trait objects should not replace match-based dispatch in main.rs unless the number of commands exceeds 20, preserving zero-overhead static dispatch.
Source text
SKILL.md · Apache-2.0
---
name: design-patterns
description: Rust design patterns for RTK. Newtype, Builder, RAII, Trait Objects, State Machine. Applied to CLI filter modules. Use when designing new modules or refactoring existing ones.
triggers:
- "design pattern"
- "how to structure"
- "best pattern for"
- "refactor to pattern"
allowed-tools:
- Read
- Grep
- Glob
effort: medium
tags: [rust, design-patterns, architecture, newtype, builder, rtk]
---
# RTK Rust Design Patterns
Patterns that apply to RTK's filter module architecture. Focused on CLI tool patterns, not web/service patterns.
## Pattern 1: Newtype (Type Safety)
Use when: wrapping primitive types to prevent misuse (command names, paths, token counts).
```rust
// Without Newtype — easy to mix up
fn track(input_tokens: usize, output_tokens: usize) { ... }
track(output_tokens, input_tokens); // Silent bug!
// With Newtype — compile error on swap
pub struct InputTokens(pub usize);
pub struct OutputTokens(pub usize);
fn track(input: InputTokens, output: OutputTokens) { ... }
track(OutputTokens(100), InputTokens(400)); // Compile error ✅
```
```rust
// Practical RTK example: command name validation
pub struct CommandName(String);
impl CommandName {
pub fn new(s: &str) -> Result<Self> {
if s.contains(';') || s.contains('|') || s.contains('`') {
anyhow::bail!("Invalid command name: shell metacharacters");
}
Ok(Self(s.to_string()))
}
pub fn as_str(&self) -> &str { &self.0 }
}
```
## Pattern 2: Builder (Complex Configuration)
Use when: a struct has 4+ optional fields, many with defaults.
```rust
#[derive(Default)]
pub struct FilterConfig {
max_lines: Option<usize>,
strip_ansi: bool,
show_warnings: bool,
truncate_at: Option<usize>,
}
impl FilterConfig {
pub fn new() -> Self { Self::default() }
pub fn max_lines(mut self, n: usize) -> Self { self.max_lines = Some(n); self }
pub fn strip_ansi(mut self, v: bool) -> Self { self.strip_ansi = v; self }
pub fn show_warnings(mut self, v: bool) -> Self { self.show_warnings = v; self }
}
// Usage — readable, no positional arg confusion
let config = FilterConfig::new()
.max_lines(50)
.strip_ansi(true)
.show_warnings(false);
```
When NOT to use Builder: if the struct has 1-3 fields with obvious meaning. Over-engineering for simple cases.
## Pattern 3: State Machine (Parser/Filter Flows)
Use when: parsing multi-section output (test results, build output) where context changes behavior.
```rust
// RTK example: pytest output parsing
#[derive(Debug, PartialEq)]
enum ParseState {
LookingForTests,
InTestOutput,
InFailureSummary,
Done,
}
fn parse_pytest(input: &str) -> String {
let mut state = ParseState::LookingForTests;
let mut failures = Vec::new();
for line in input.lines() {
match state {
ParseState::LookingForTests => {
if line.contains("FAILED") || line.contains("ERROR") {
state = ParseState::InFailureSummary;
failures.push(line);
}
}
ParseState::InFailureSummary => {
if line.starts_with("=====") { state = ParseState::Done; }
else { failures.push(line); }
}
ParseState::Done => break,
_ => {}
}
}
failures.join("\n")
}
```
## Pattern 4: Trait Object (Command Dispatch)
Use when: different command families need the same interface. Avoids massive match arms.
```rust
// Define a common interface for filters
pub trait OutputFilter {
fn filter(&self, input: &str) -> Result<String>;
fn command_name(&self) -> &str;
}
pub struct GitFilter;
pub struct CargoFilter;
impl OutputFilter for GitFilter {
fn filter(&self, input: &str) -> Result<String> { filter_git(input) }
fn command_name(&self) -> &str { "git" }
}
// RTK currently uses match-based dispatch in main.rs (simpler, no dynamic dispatch overhead)
// Trait objects are useful if filter registry becomes dynamic (e.g., TOML-loaded plugins)
```
Note: RTK's current `match` dispatch in `main.rs` is intentional — static dispatch, zero overhead. Only move to trait objects if the match arm count exceeds ~20 commands.
## Pattern 5: RAII (Resource Management)
Use when: managing resources that need cleanup (temp files, SQLite connections).
```rust
// RTK tee.rs — RAII for temp output files
pub struct TeeFile {
path: PathBuf,
}
impl TeeFile {
pub fn create(content: &str) -> Result<Self> {
let path = tee_path()?;
fs::write(&path, content)
.with_context(|| format!("Failed to write tee file: {}", path.display()))?;
Ok(Self { path })
}
pub fn path(&self) -> &Path { &self.path }
}
// No explicit cleanup needed — file persists intentionally (rotation handled separately)
// If cleanup were needed: impl Drop { fn drop(&mut self) { let _ = fs::remove_file(&self.path); } }
```
## Pattern 6: Strategy (Swappable Filter Logic)
Use when: a command has multiple filtering modes (e.g., compact vs. verbose).
```rust
pub enum FilterMode {
Compact, // Show only failures/errors
Summary, // Show counts + top errors
Full, // Pass through unchanged
}
pub fn apply_filter(input: &str, mode: FilterMode) -> String {
match mode {
FilterMode::Compact => filter_compact(input),
FilterMode::Summary => filter_summary(input),
FilterMode::Full => input.to_string(),
}
}
```
## Pattern 7: Extension Trait (Add Methods to External Types)
Use when: you need to add methods to types you don't own (like `&str` for RTK-specific parsing).
```rust
pub trait RtkStrExt {
fn is_error_line(&self) -> bool;
fn is_warning_line(&self) -> bool;
fn token_count(&self) -> usize;
}
impl RtkStrExt for str {
fn is_error_line(&self) -> bool {
self.starts_with("error") || self.contains("[E")
}
fn is_warning_line(&self) -> bool {
self.starts_with("warning")
}
fn token_count(&self) -> usize {
self.split_whitespace().count()
}
}
// Usage
if line.is_error_line() { ... }
let tokens = output.token_count();
```
## RTK Pattern Selection Guide
| Situation | Pattern | Avoid |
|-----------|---------|-------|
| New `*_cmd.rs` filter module | Standard module pattern (see CLAUDE.md) | Over-abstracting |
| 4+ optional config fields | Builder | Struct literal |
| Multi-phase output parsing | State Machine | Nested if/else |
| Type-safe wrapper around string | Newtype | Raw `String` |
| Adding methods to `&str` | Extension Trait | Free functions |
| Resource with cleanup | RAII / Drop | Manual cleanup |
| Dynamic filter registry | Trait Object | Match sprawl |
## Anti-Patterns in RTK Context
```rust
// ❌ Generic over-engineering for one command
pub trait Filterable<T: CommandArgs + Send + Sync + 'static> { ... }
// ✅ Just write the function
pub fn filter_git_log(input: &str) -> Result<String> { ... }
// ❌ Singleton registry with global state
static FILTER_REGISTRY: Mutex<HashMap<String, Box<dyn Filter>>> = ...;
// ✅ Match in main.rs — simple, zero overhead, easy to trace
// ❌ Async traits for "future-proofing"
#[async_trait]
pub trait Filter { async fn apply(&self, input: &str) -> Result<String>; }
// ✅ Synchronous — RTK is single-threaded by design
pub trait Filter { fn apply(&self, input: &str) -> Result<String>; }
```
© rtk-ai · Apache-2.0 · rtk-ai/rtk