MCP.Market

design-patterns skill

by rtk-ai·rtk-ai/rtk·87k stars·Apache-2.0

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.

A100/100content scan

Is the design-patterns skill safe?

Clean: nothing in its files matched our rules. We read 1 file in the folder on 2026-11-10.

No findings.

Install the design-patterns skill

A skill is a folder. Copy it into your agent's skills folder and the agent loads it when the task matches its description.

git clone --depth 1 https://github.com/rtk-ai/rtk.git /tmp/rtk
mkdir -p ~/.claude/skills
cp -r /tmp/rtk/.claude/skills/design-patterns ~/.claude/skills/design-patterns
available in every project

In the Claude apps, zip the folder and upload it from the Skills settings. The folder on GitHub

The instructions your agent would load

SKILL.md as published, without the frontmatter. Read it on GitHub

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).

// 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 ✅
// 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.

#[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.

// 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.

// 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).

// 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).

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).

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

Anti-Patterns in RTK Context

// ❌ 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>; }

More skills from rtk-ai/rtk

  • Acode-simplifierReview RTK Rust code for idiomatic simplification. Detects over-engineering, unnecessary allocations, verbose patterns. Applies Rust idioms without changing behavior.
  • Aissue-triageIssue triage: audit open issues, categorize, detect duplicates, cross-ref PRs, risk assessment, post comments. Args: "all" for deep analysis of all, issue numbers to focus (e.g. "42 57"), "en"/"fr" for language, no arg = audit only in French.
  • AperformanceCLI performance optimization - startup time, memory usage, token savings benchmarking
  • Apr-reviewBatch review des PRs RTK par ordre de complexité croissante (XS → S → M → L). Pour chaque PR : vérifie l'état (conflits, CLA, reviews), lit le diff complet, analyse le code en contexte, présente un résumé avec lien + taille + recommandation. Attend validation explicite avant tout merge. Poste des commentaires boldguy-adapt sur les PRs bloquées (conflit, CLA, CHANGES_REQUESTED). Args: "triage" pour lancer un triage complet avant la review. "from:<num>" pour reprendre à partir d'un numéro de PR spécifique.
  • Apr-triagePR triage: audit open PRs, deep review selected ones, draft and post review comments. Args: "all" to review all, PR numbers to focus (e.g. "42 57"), "en"/"fr" for language, no arg = audit only in French.
  • Arepo-recapGenerate a comprehensive repo recap (PRs, issues, releases) for sharing with team. Pass "en" or "fr" as argument for language (default fr).
  • Drtk-tddEnforces TDD (Red-Green-Refactor) for Rust development. Auto-triggers on implementation, testing, refactoring, and bug fixing tasks. Provides Rust-idiomatic testing patterns with anyhow/thiserror, cfg(test), and Arrange-Act-Assert workflow.
  • Artk-triageTriage complet RTK : exécute issue-triage + pr-triage en parallèle, puis croise les données pour détecter doubles couvertures, trous sécurité, P0 sans PR, et conflits internes. Sauvegarde dans claudedocs/RTK-YYYY-MM-DD.md. Args: "en"/"fr" pour la langue (défaut: fr), "save" pour forcer la sauvegarde.
  • Fsecurity-guardianCLI security expert for RTK - command injection, shell escaping, hook security
  • AshipBuild, commit, push & version bump workflow - automates the complete release cycle
  • Atdd-rustTDD workflow for RTK filter development. Red-Green-Refactor with Rust idioms. Real fixtures, token savings assertions, snapshot tests with insta. Auto-triggers on new filter implementation.

All agent skills → · MCP servers