diff --git a/.claude/agents/rust-rtk.md b/.claude/agents/rust-rtk.md index 8efe67f0e..dc5b71345 100644 --- a/.claude/agents/rust-rtk.md +++ b/.claude/agents/rust-rtk.md @@ -1,7 +1,7 @@ --- name: rust-rtk description: Expert Rust developer for RTK - CLI proxy patterns, filter design, performance optimization -model: claude-sonnet-4-5-20250929 +model: sonnet tools: Read, Write, Edit, MultiEdit, Bash, Grep, Glob --- diff --git a/.claude/commands/diagnose.md b/.claude/commands/diagnose.md index f91422769..78366c848 100644 --- a/.claude/commands/diagnose.md +++ b/.claude/commands/diagnose.md @@ -199,7 +199,7 @@ options: ### Fix 1 : Installer RTK localement ```bash -cd /Users/florianbruniaux/Sites/rtk-ai/rtk +# Depuis la racine du repo RTK cargo install --path . # Vérifier installation which rtk && rtk --version @@ -345,7 +345,7 @@ chmod +x .claude/hooks/*.sh **Upgrade recommendation**: If running v0.15.x or older, upgrade to v0.16.x: ```bash -cd /Users/florianbruniaux/Sites/rtk-ai/rtk +# From the RTK repo root git pull origin main cargo install --path . --force rtk --version # Should show 0.16.x or newer diff --git a/.claude/commands/tech/codereview.md b/.claude/commands/tech/codereview.md index fb0813fc3..35e92360d 100644 --- a/.claude/commands/tech/codereview.md +++ b/.claude/commands/tech/codereview.md @@ -1,6 +1,7 @@ --- model: sonnet description: RTK Code Review — Review locale pre-PR avec auto-fix +argument-hint: "[--fix] [file-pattern]" --- # RTK Code Review diff --git a/.claude/commands/tech/remove-worktree.md b/.claude/commands/tech/remove-worktree.md index edc5802da..90a63632d 100644 --- a/.claude/commands/tech/remove-worktree.md +++ b/.claude/commands/tech/remove-worktree.md @@ -1,6 +1,7 @@ --- model: haiku description: Remove a specific worktree (directory + git reference + branch) +argument-hint: "" --- # Remove Worktree diff --git a/.claude/commands/tech/worktree-status.md b/.claude/commands/tech/worktree-status.md index 9b2194eb9..faa5ca49d 100644 --- a/.claude/commands/tech/worktree-status.md +++ b/.claude/commands/tech/worktree-status.md @@ -1,6 +1,7 @@ --- model: haiku description: Worktree Cargo Check Status +argument-hint: "" --- # Worktree Status Check diff --git a/.claude/commands/tech/worktree.md b/.claude/commands/tech/worktree.md index 1f5df7afb..69dfc04d8 100644 --- a/.claude/commands/tech/worktree.md +++ b/.claude/commands/tech/worktree.md @@ -1,6 +1,7 @@ --- model: haiku description: Git Worktree Setup for RTK +argument-hint: "" --- # Git Worktree Setup diff --git a/.claude/commands/worktree-status.md b/.claude/commands/worktree-status.md index fb423f5f7..0de86d248 100644 --- a/.claude/commands/worktree-status.md +++ b/.claude/commands/worktree-status.md @@ -1,6 +1,7 @@ --- model: haiku description: Check background cargo check status for a git worktree +argument-hint: "" --- # Worktree Status Check diff --git a/.claude/commands/worktree.md b/.claude/commands/worktree.md index 17666b014..eabdff07e 100644 --- a/.claude/commands/worktree.md +++ b/.claude/commands/worktree.md @@ -1,6 +1,7 @@ --- model: haiku description: Git Worktree Setup for RTK (Rust project) +argument-hint: "" --- # Git Worktree Setup diff --git a/.claude/skills/issue-triage/SKILL.md b/.claude/skills/issue-triage/SKILL.md index fb798d87c..635167757 100644 --- a/.claude/skills/issue-triage/SKILL.md +++ b/.claude/skills/issue-triage/SKILL.md @@ -162,7 +162,16 @@ Si toujours ambigu, demander à l'utilisateur via `AskUserQuestion`. Après affichage du tableau de triage, copier dans le presse-papier : ```bash -pbcopy <<'EOF' +# Cross-platform clipboard +clip() { + if command -v pbcopy &>/dev/null; then pbcopy + elif command -v xclip &>/dev/null; then xclip -selection clipboard + elif command -v wl-copy &>/dev/null; then wl-copy + else cat + fi +} + +clip <<'EOF' {tableau de triage complet} EOF ``` diff --git a/.claude/skills/performance.md b/.claude/skills/performance.md deleted file mode 100644 index 30c90b0eb..000000000 --- a/.claude/skills/performance.md +++ /dev/null @@ -1,435 +0,0 @@ ---- -description: CLI performance optimization - startup time, memory usage, token savings benchmarking ---- - -# Performance Optimization Skill - -Systematic performance analysis and optimization for RTK CLI tool, focusing on **startup time (<10ms)**, **memory usage (<5MB)**, and **token savings (60-90%)**. - -## When to Use - -- **Automatically triggered**: After filter changes, regex modifications, or dependency additions -- **Manual invocation**: When performance degradation suspected or before release -- **Proactive**: After any code change that could impact startup time or memory - -## RTK Performance Targets - -| Metric | Target | Verification Method | Failure Threshold | -|--------|--------|---------------------|-------------------| -| **Startup time** | <10ms | `hyperfine 'rtk '` | >15ms = blocker | -| **Memory usage** | <5MB resident | `/usr/bin/time -l rtk ` (macOS) | >7MB = blocker | -| **Token savings** | 60-90% | Tests with `count_tokens()` | <60% = blocker | -| **Binary size** | <5MB stripped | `ls -lh target/release/rtk` | >8MB = investigate | - -## Performance Analysis Workflow - -### 1. Establish Baseline - -Before making any changes, capture current performance: - -```bash -# Startup time baseline -hyperfine 'rtk git status' --warmup 3 --export-json /tmp/baseline_startup.json - -# Memory usage baseline (macOS) -/usr/bin/time -l rtk git status 2>&1 | grep "maximum resident set size" > /tmp/baseline_memory.txt - -# Memory usage baseline (Linux) -/usr/bin/time -v rtk git status 2>&1 | grep "Maximum resident set size" > /tmp/baseline_memory.txt - -# Binary size baseline -ls -lh target/release/rtk | tee /tmp/baseline_binary_size.txt -``` - -### 2. Make Changes - -Implement optimization or feature changes. - -### 3. Rebuild and Measure - -```bash -# Rebuild with optimizations -cargo build --release - -# Measure startup time -hyperfine 'target/release/rtk git status' --warmup 3 --export-json /tmp/after_startup.json - -# Measure memory usage -/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident set size" > /tmp/after_memory.txt - -# Check binary size -ls -lh target/release/rtk | tee /tmp/after_binary_size.txt -``` - -### 4. Compare Results - -```bash -# Startup time comparison -hyperfine 'rtk git status' 'target/release/rtk git status' --warmup 3 - -# Example output: -# Benchmark 1: rtk git status -# Time (mean ± σ): 6.2 ms ± 0.3 ms [User: 4.1 ms, System: 1.8 ms] -# Benchmark 2: target/release/rtk git status -# Time (mean ± σ): 7.8 ms ± 0.4 ms [User: 5.2 ms, System: 2.1 ms] -# -# Summary -# 'rtk git status' ran 1.26 times faster than 'target/release/rtk git status' - -# Memory comparison -diff /tmp/baseline_memory.txt /tmp/after_memory.txt - -# Binary size comparison -diff /tmp/baseline_binary_size.txt /tmp/after_binary_size.txt -``` - -### 5. Identify Regressions - -**Startup time regression** (>15% increase or >2ms absolute): -```bash -# Profile with flamegraph -cargo install flamegraph -cargo flamegraph -- target/release/rtk git status - -# Open flamegraph.svg -open flamegraph.svg -# Look for: -# - Regex compilation (should be in lazy_static init) -# - Excessive allocations -# - File I/O on startup (should be zero) -``` - -**Memory regression** (>20% increase or >1MB absolute): -```bash -# Profile allocations (requires nightly) -cargo +nightly build --release -Z build-std -RUSTFLAGS="-C link-arg=-fuse-ld=lld" cargo +nightly build --release - -# Use DHAT for heap profiling -cargo install dhat -# Add to main.rs: -# #[global_allocator] -# static ALLOC: dhat::Alloc = dhat::Alloc; -``` - -**Token savings regression** (<60% savings): -```bash -# Run token accuracy tests -cargo test test_token_savings - -# Example failure output: -# Git log filter: expected ≥60% savings, got 52.3% - -# Fix: Improve filter condensation logic -``` - -## Common Performance Issues - -### Issue 1: Regex Recompilation - -**Symptom**: Startup time >20ms, flamegraph shows regex compilation in hot path - -**Detection**: -```bash -# Flamegraph shows Regex::new() calls during execution -cargo flamegraph -- target/release/rtk git log -10 -# Look for "regex::Regex::new" in non-lazy_static sections -``` - -**Fix**: -```rust -// ❌ WRONG: Recompiled on every call -fn filter_line(line: &str) -> Option<&str> { - let re = Regex::new(r"pattern").unwrap(); // RECOMPILED! - re.find(line).map(|m| m.as_str()) -} - -// ✅ RIGHT: Compiled once with lazy_static -use lazy_static::lazy_static; - -lazy_static! { - static ref LINE_PATTERN: Regex = Regex::new(r"pattern").unwrap(); -} - -fn filter_line(line: &str) -> Option<&str> { - LINE_PATTERN.find(line).map(|m| m.as_str()) -} -``` - -### Issue 2: Excessive Allocations - -**Symptom**: Memory usage >5MB, many small allocations in flamegraph - -**Detection**: -```bash -# DHAT heap profiling -cargo +nightly build --release -valgrind --tool=dhat target/release/rtk git status -``` - -**Fix**: -```rust -// ❌ WRONG: Allocates Vec for every line -fn filter_lines(input: &str) -> String { - input.lines() - .map(|line| line.to_string()) // Allocates String - .collect::>() - .join("\n") -} - -// ✅ RIGHT: Borrow slices, single allocation -fn filter_lines(input: &str) -> String { - input.lines() - .collect::>() // Vec of &str (no String allocation) - .join("\n") -} -``` - -### Issue 3: Startup I/O - -**Symptom**: Startup time varies wildly (5ms to 50ms), flamegraph shows file reads - -**Detection**: -```bash -# strace on Linux -strace -c target/release/rtk git status 2>&1 | grep -E "open|read" - -# dtrace on macOS (requires SIP disabled) -sudo dtrace -n 'syscall::open*:entry { @[execname] = count(); }' & -target/release/rtk git status -sudo pkill dtrace -``` - -**Fix**: -```rust -// ❌ WRONG: File I/O on startup -fn main() { - let config = load_config().unwrap(); // Reads ~/.config/rtk/config.toml - // ... -} - -// ✅ RIGHT: Lazy config loading (only if needed) -fn main() { - // No I/O on startup - // Config loaded on-demand when first accessed -} -``` - -### Issue 4: Dependency Bloat - -**Symptom**: Binary size >5MB, many unused dependencies in `Cargo.toml` - -**Detection**: -```bash -# Analyze dependency tree -cargo tree - -# Find heavy dependencies -cargo install cargo-bloat -cargo bloat --release --crates - -# Example output: -# File .text Size Crate -# 0.5% 2.1% 42.3KB regex -# 0.4% 1.8% 36.1KB clap -# ... -``` - -**Fix**: -```toml -# ❌ WRONG: Full feature set (bloat) -[dependencies] -clap = { version = "4", features = ["derive", "color", "suggestions"] } - -# ✅ RIGHT: Minimal features -[dependencies] -clap = { version = "4", features = ["derive"], default-features = false } -``` - -## Optimization Techniques - -### Technique 1: Lazy Static Initialization - -**Use case**: Regex patterns, static configuration, one-time allocations - -**Implementation**: -```rust -use lazy_static::lazy_static; -use regex::Regex; - -lazy_static! { - static ref COMMIT_HASH: Regex = Regex::new(r"[0-9a-f]{7,40}").unwrap(); - static ref AUTHOR_LINE: Regex = Regex::new(r"^Author: (.+)$").unwrap(); - static ref DATE_LINE: Regex = Regex::new(r"^Date: (.+)$").unwrap(); -} - -// All regex compiled once at startup, reused forever -``` - -**Impact**: ~5-10ms saved per regex pattern (if compiled at runtime) - -### Technique 2: Zero-Copy String Processing - -**Use case**: Filter output without allocating intermediate Strings - -**Implementation**: -```rust -// ❌ WRONG: Allocates String for every line -fn filter(input: &str) -> String { - input.lines() - .filter(|line| !line.is_empty()) - .map(|line| line.to_string()) // Allocates! - .collect::>() - .join("\n") -} - -// ✅ RIGHT: Borrow slices, single final allocation -fn filter(input: &str) -> String { - input.lines() - .filter(|line| !line.is_empty()) - .collect::>() // Vec<&str> (no String alloc) - .join("\n") // Single allocation for joined result -} -``` - -**Impact**: ~1-2MB memory saved, ~1-2ms startup saved - -### Technique 3: Minimal Dependencies - -**Use case**: Reduce binary size and compile time - -**Implementation**: -```toml -# Only include features you actually use -[dependencies] -clap = { version = "4", features = ["derive"], default-features = false } -serde = { version = "1", features = ["derive"], default-features = false } - -# Avoid heavy dependencies -# ❌ Avoid: tokio (adds 5-10ms startup overhead) -# ❌ Avoid: full regex (use regex-lite if possible) -# ✅ Use: anyhow (lightweight error handling) -# ✅ Use: lazy_static (zero runtime overhead) -``` - -**Impact**: ~1-2MB binary size reduction, ~2-5ms startup saved - -## Performance Testing Checklist - -Before committing filter changes: - -### Startup Time -- [ ] Benchmark with `hyperfine 'rtk ' --warmup 3` -- [ ] Verify <10ms mean time -- [ ] Check variance (σ) is small (<1ms) -- [ ] Compare against baseline (regression <2ms) - -### Memory Usage -- [ ] Profile with `/usr/bin/time -l rtk ` -- [ ] Verify <5MB resident set size -- [ ] Compare against baseline (regression <1MB) - -### Token Savings -- [ ] Run `cargo test test_token_savings` -- [ ] Verify all filters achieve ≥60% savings -- [ ] Check real fixtures used (not synthetic) - -### Binary Size -- [ ] Check `ls -lh target/release/rtk` -- [ ] Verify <5MB stripped binary -- [ ] Run `cargo bloat --release --crates` if >5MB - -## Continuous Performance Monitoring - -### Pre-Commit Hook - -Add to `.claude/hooks/bash/pre-commit-performance.sh`: - -```bash -#!/bin/bash -# Performance regression check before commit - -echo "🚀 Running performance checks..." - -# Benchmark startup time -CURRENT_TIME=$(hyperfine 'rtk git status' --warmup 3 --export-json /tmp/perf.json 2>&1 | grep "Time (mean" | awk '{print $4}') - -# Extract numeric value (remove "ms") -CURRENT_MS=$(echo $CURRENT_TIME | sed 's/ms//') - -# Check if > 10ms -if (( $(echo "$CURRENT_MS > 10" | bc -l) )); then - echo "❌ Startup time regression: ${CURRENT_MS}ms (target: <10ms)" - exit 1 -fi - -# Check binary size -BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}') -MAX_SIZE=$((5 * 1024 * 1024)) # 5MB - -if [ $BINARY_SIZE -gt $MAX_SIZE ]; then - echo "❌ Binary size regression: $(($BINARY_SIZE / 1024 / 1024))MB (target: <5MB)" - exit 1 -fi - -echo "✅ Performance checks passed" -``` - -### CI/CD Integration - -Add to `.github/workflows/ci.yml`: - -```yaml -- name: Performance Regression Check - run: | - cargo build --release - cargo install hyperfine - - # Benchmark startup time - hyperfine 'target/release/rtk git status' --warmup 3 --max-runs 10 - - # Check binary size - BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}') - MAX_SIZE=$((5 * 1024 * 1024)) - if [ $BINARY_SIZE -gt $MAX_SIZE ]; then - echo "Binary too large: $(($BINARY_SIZE / 1024 / 1024))MB" - exit 1 - fi -``` - -## Performance Optimization Priorities - -**Priority order** (highest to lowest impact): - -1. **🔴 Lazy static regex** (5-10ms per pattern if compiled at runtime) -2. **🔴 Remove startup I/O** (10-50ms for config file reads) -3. **🟡 Zero-copy processing** (1-2MB memory, 1-2ms startup) -4. **🟡 Minimal dependencies** (1-2MB binary, 2-5ms startup) -5. **🟢 Algorithm optimization** (varies, measure first) - -**When in doubt**: Profile first with `flamegraph`, then optimize the hottest path. - -## Tools Reference - -| Tool | Purpose | Command | -|------|---------|---------| -| **hyperfine** | Benchmark startup time | `hyperfine 'rtk ' --warmup 3` | -| **time** | Memory usage (macOS) | `/usr/bin/time -l rtk ` | -| **time** | Memory usage (Linux) | `/usr/bin/time -v rtk ` | -| **flamegraph** | CPU profiling | `cargo flamegraph -- rtk ` | -| **cargo bloat** | Binary size analysis | `cargo bloat --release --crates` | -| **cargo tree** | Dependency tree | `cargo tree` | -| **DHAT** | Heap profiling | `cargo +nightly build && valgrind --tool=dhat` | -| **strace** | System call tracing (Linux) | `strace -c target/release/rtk ` | -| **dtrace** | System call tracing (macOS) | `sudo dtrace -n 'syscall::open*:entry'` | - -**Install tools**: -```bash -# macOS -brew install hyperfine - -# Linux / cross-platform via cargo -cargo install hyperfine -cargo install flamegraph -cargo install cargo-bloat -``` diff --git a/.claude/skills/performance/SKILL.md b/.claude/skills/performance/SKILL.md index 373b06f5e..30c90b0eb 100644 --- a/.claude/skills/performance/SKILL.md +++ b/.claude/skills/performance/SKILL.md @@ -1,215 +1,435 @@ --- -name: performance -description: RTK CLI performance analysis and optimization. Startup time (<10ms), binary size (<5MB), regex compilation, memory usage. Use when adding dependencies, changing initialization, or suspecting regressions. -triggers: - - "startup time" - - "performance regression" - - "too slow" - - "benchmark" - - "binary size" - - "memory usage" -allowed-tools: - - Bash - - Read - - Grep -effort: medium -tags: [performance, benchmark, startup, binary-size, memory, rtk] +description: CLI performance optimization - startup time, memory usage, token savings benchmarking --- -# RTK Performance Analysis +# Performance Optimization Skill -## Hard Targets (Non-Negotiable) +Systematic performance analysis and optimization for RTK CLI tool, focusing on **startup time (<10ms)**, **memory usage (<5MB)**, and **token savings (60-90%)**. -| Metric | Target | Blocker | -|--------|--------|---------| -| Startup time | <10ms | Release blocker | -| Binary size (stripped) | <5MB | Release blocker | -| Memory (resident) | <5MB | Release blocker | -| Token savings per filter | ≥60% | Release blocker | +## When to Use -## Benchmark Startup Time +- **Automatically triggered**: After filter changes, regex modifications, or dependency additions +- **Manual invocation**: When performance degradation suspected or before release +- **Proactive**: After any code change that could impact startup time or memory + +## RTK Performance Targets + +| Metric | Target | Verification Method | Failure Threshold | +|--------|--------|---------------------|-------------------| +| **Startup time** | <10ms | `hyperfine 'rtk '` | >15ms = blocker | +| **Memory usage** | <5MB resident | `/usr/bin/time -l rtk ` (macOS) | >7MB = blocker | +| **Token savings** | 60-90% | Tests with `count_tokens()` | <60% = blocker | +| **Binary size** | <5MB stripped | `ls -lh target/release/rtk` | >8MB = investigate | + +## Performance Analysis Workflow + +### 1. Establish Baseline + +Before making any changes, capture current performance: ```bash -# Install hyperfine (once) -brew install hyperfine +# Startup time baseline +hyperfine 'rtk git status' --warmup 3 --export-json /tmp/baseline_startup.json + +# Memory usage baseline (macOS) +/usr/bin/time -l rtk git status 2>&1 | grep "maximum resident set size" > /tmp/baseline_memory.txt + +# Memory usage baseline (Linux) +/usr/bin/time -v rtk git status 2>&1 | grep "Maximum resident set size" > /tmp/baseline_memory.txt + +# Binary size baseline +ls -lh target/release/rtk | tee /tmp/baseline_binary_size.txt +``` + +### 2. Make Changes + +Implement optimization or feature changes. -# Baseline (before changes) -hyperfine 'rtk git status' --warmup 3 --export-json /tmp/before.json +### 3. Rebuild and Measure -# After changes — rebuild first +```bash +# Rebuild with optimizations cargo build --release -# Compare against installed -hyperfine 'target/release/rtk git status' 'rtk git status' --warmup 3 +# Measure startup time +hyperfine 'target/release/rtk git status' --warmup 3 --export-json /tmp/after_startup.json + +# Measure memory usage +/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident set size" > /tmp/after_memory.txt -# Target: <10ms mean time +# Check binary size +ls -lh target/release/rtk | tee /tmp/after_binary_size.txt ``` -## Check Binary Size +### 4. Compare Results ```bash -# Release build with strip=true (already in Cargo.toml) -cargo build --release -ls -lh target/release/rtk -# Should be <5MB +# Startup time comparison +hyperfine 'rtk git status' 'target/release/rtk git status' --warmup 3 + +# Example output: +# Benchmark 1: rtk git status +# Time (mean ± σ): 6.2 ms ± 0.3 ms [User: 4.1 ms, System: 1.8 ms] +# Benchmark 2: target/release/rtk git status +# Time (mean ± σ): 7.8 ms ± 0.4 ms [User: 5.2 ms, System: 2.1 ms] +# +# Summary +# 'rtk git status' ran 1.26 times faster than 'target/release/rtk git status' + +# Memory comparison +diff /tmp/baseline_memory.txt /tmp/after_memory.txt + +# Binary size comparison +diff /tmp/baseline_binary_size.txt /tmp/after_binary_size.txt +``` -# If too large — check what's contributing -cargo bloat --release --crates -cargo bloat --release -n 20 -# Install: cargo install cargo-bloat +### 5. Identify Regressions + +**Startup time regression** (>15% increase or >2ms absolute): +```bash +# Profile with flamegraph +cargo install flamegraph +cargo flamegraph -- target/release/rtk git status + +# Open flamegraph.svg +open flamegraph.svg +# Look for: +# - Regex compilation (should be in lazy_static init) +# - Excessive allocations +# - File I/O on startup (should be zero) ``` -## Memory Usage +**Memory regression** (>20% increase or >1MB absolute): +```bash +# Profile allocations (requires nightly) +cargo +nightly build --release -Z build-std +RUSTFLAGS="-C link-arg=-fuse-ld=lld" cargo +nightly build --release + +# Use DHAT for heap profiling +cargo install dhat +# Add to main.rs: +# #[global_allocator] +# static ALLOC: dhat::Alloc = dhat::Alloc; +``` +**Token savings regression** (<60% savings): ```bash -# macOS -/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident" -# Target: <5,000,000 bytes (5MB) +# Run token accuracy tests +cargo test test_token_savings + +# Example failure output: +# Git log filter: expected ≥60% savings, got 52.3% -# Linux -/usr/bin/time -v target/release/rtk git status 2>&1 | grep "Maximum resident" -# Target: <5,000 kbytes +# Fix: Improve filter condensation logic ``` -## Regex Compilation Audit +## Common Performance Issues -Regex compilation on every function call is a common perf killer: +### Issue 1: Regex Recompilation -```bash -# Find all Regex::new calls -grep -n "Regex::new" src/*.rs +**Symptom**: Startup time >20ms, flamegraph shows regex compilation in hot path -# Verify ALL are inside lazy_static! blocks -# Any Regex::new outside lazy_static! = performance bug +**Detection**: +```bash +# Flamegraph shows Regex::new() calls during execution +cargo flamegraph -- target/release/rtk git log -10 +# Look for "regex::Regex::new" in non-lazy_static sections ``` +**Fix**: ```rust -// ❌ Recompiles on every filter_line() call -fn filter_line(line: &str) -> bool { - let re = Regex::new(r"^error").unwrap(); // BAD - re.is_match(line) +// ❌ WRONG: Recompiled on every call +fn filter_line(line: &str) -> Option<&str> { + let re = Regex::new(r"pattern").unwrap(); // RECOMPILED! + re.find(line).map(|m| m.as_str()) } -// ✅ Compiled once at first use +// ✅ RIGHT: Compiled once with lazy_static +use lazy_static::lazy_static; + lazy_static! { - static ref ERROR_RE: Regex = Regex::new(r"^error").unwrap(); + static ref LINE_PATTERN: Regex = Regex::new(r"pattern").unwrap(); } -fn filter_line(line: &str) -> bool { - ERROR_RE.is_match(line) // GOOD + +fn filter_line(line: &str) -> Option<&str> { + LINE_PATTERN.find(line).map(|m| m.as_str()) } ``` -## Dependency Impact Assessment +### Issue 2: Excessive Allocations -Before adding any new crate: +**Symptom**: Memory usage >5MB, many small allocations in flamegraph +**Detection**: ```bash -# Check startup impact (measure before adding) -hyperfine 'rtk git status' --warmup 3 - -# Add dependency to Cargo.toml -# Rebuild -cargo build --release +# DHAT heap profiling +cargo +nightly build --release +valgrind --tool=dhat target/release/rtk git status +``` -# Measure after -hyperfine 'target/release/rtk git status' --warmup 3 +**Fix**: +```rust +// ❌ WRONG: Allocates Vec for every line +fn filter_lines(input: &str) -> String { + input.lines() + .map(|line| line.to_string()) // Allocates String + .collect::>() + .join("\n") +} -# If startup increased >1ms — investigate -# If startup increased >3ms — reject the dependency +// ✅ RIGHT: Borrow slices, single allocation +fn filter_lines(input: &str) -> String { + input.lines() + .collect::>() // Vec of &str (no String allocation) + .join("\n") +} ``` -### Forbidden dependencies +### Issue 3: Startup I/O -| Crate | Reason | Alternative | -|-------|--------|-------------| -| `tokio` | +5-10ms startup | Blocking `std::process::Command` | -| `async-std` | +5-10ms startup | Blocking I/O | -| `rayon` | Thread pool init overhead | Sequential iteration | -| `reqwest` | Pulls tokio | `ureq` (blocking) if HTTP needed | - -### Dependency weight check +**Symptom**: Startup time varies wildly (5ms to 50ms), flamegraph shows file reads +**Detection**: ```bash -# After cargo build --release -cargo build --release --timings -# Open target/cargo-timings/cargo-timing.html -# Look for crates with long compile times (correlates with complexity) +# strace on Linux +strace -c target/release/rtk git status 2>&1 | grep -E "open|read" + +# dtrace on macOS (requires SIP disabled) +sudo dtrace -n 'syscall::open*:entry { @[execname] = count(); }' & +target/release/rtk git status +sudo pkill dtrace ``` -## Allocation Profiling +**Fix**: +```rust +// ❌ WRONG: File I/O on startup +fn main() { + let config = load_config().unwrap(); // Reads ~/.config/rtk/config.toml + // ... +} + +// ✅ RIGHT: Lazy config loading (only if needed) +fn main() { + // No I/O on startup + // Config loaded on-demand when first accessed +} +``` + +### Issue 4: Dependency Bloat + +**Symptom**: Binary size >5MB, many unused dependencies in `Cargo.toml` +**Detection**: ```bash -# macOS — use Instruments -instruments -t Allocations target/release/rtk git log -10 +# Analyze dependency tree +cargo tree -# Or use cargo-instruments -cargo install cargo-instruments -cargo instruments --release -t Allocations -- git log -10 +# Find heavy dependencies +cargo install cargo-bloat +cargo bloat --release --crates + +# Example output: +# File .text Size Crate +# 0.5% 2.1% 42.3KB regex +# 0.4% 1.8% 36.1KB clap +# ... ``` -Common RTK allocation hotspots: +**Fix**: +```toml +# ❌ WRONG: Full feature set (bloat) +[dependencies] +clap = { version = "4", features = ["derive", "color", "suggestions"] } -```rust -// ❌ Allocates new String on every line -let lines: Vec = input.lines().map(|l| l.to_string()).collect(); +# ✅ RIGHT: Minimal features +[dependencies] +clap = { version = "4", features = ["derive"], default-features = false } +``` + +## Optimization Techniques -// ✅ Borrow slices -let lines: Vec<&str> = input.lines().collect(); +### Technique 1: Lazy Static Initialization -// ❌ Clone large output unnecessarily -let raw_copy = output.stdout.clone(); +**Use case**: Regex patterns, static configuration, one-time allocations -// ✅ Use reference until you actually need to own -let display = &output.stdout; +**Implementation**: +```rust +use lazy_static::lazy_static; +use regex::Regex; + +lazy_static! { + static ref COMMIT_HASH: Regex = Regex::new(r"[0-9a-f]{7,40}").unwrap(); + static ref AUTHOR_LINE: Regex = Regex::new(r"^Author: (.+)$").unwrap(); + static ref DATE_LINE: Regex = Regex::new(r"^Date: (.+)$").unwrap(); +} + +// All regex compiled once at startup, reused forever ``` -## Token Savings Measurement +**Impact**: ~5-10ms saved per regex pattern (if compiled at runtime) + +### Technique 2: Zero-Copy String Processing +**Use case**: Filter output without allocating intermediate Strings + +**Implementation**: ```rust -// In tests — always verify claims -fn count_tokens(text: &str) -> usize { - text.split_whitespace().count() +// ❌ WRONG: Allocates String for every line +fn filter(input: &str) -> String { + input.lines() + .filter(|line| !line.is_empty()) + .map(|line| line.to_string()) // Allocates! + .collect::>() + .join("\n") +} + +// ✅ RIGHT: Borrow slices, single final allocation +fn filter(input: &str) -> String { + input.lines() + .filter(|line| !line.is_empty()) + .collect::>() // Vec<&str> (no String alloc) + .join("\n") // Single allocation for joined result } +``` -#[test] -fn test_savings_claim() { - let input = include_str!("../tests/fixtures/mycmd_raw.txt"); - let output = filter_output(input).unwrap(); +**Impact**: ~1-2MB memory saved, ~1-2ms startup saved - let input_tokens = count_tokens(input); - let output_tokens = count_tokens(&output); - let savings = 100.0 * (1.0 - output_tokens as f64 / input_tokens as f64); +### Technique 3: Minimal Dependencies - assert!( - savings >= 60.0, - "Expected ≥60% savings, got {:.1}% ({} → {} tokens)", - savings, input_tokens, output_tokens - ); -} +**Use case**: Reduce binary size and compile time + +**Implementation**: +```toml +# Only include features you actually use +[dependencies] +clap = { version = "4", features = ["derive"], default-features = false } +serde = { version = "1", features = ["derive"], default-features = false } + +# Avoid heavy dependencies +# ❌ Avoid: tokio (adds 5-10ms startup overhead) +# ❌ Avoid: full regex (use regex-lite if possible) +# ✅ Use: anyhow (lightweight error handling) +# ✅ Use: lazy_static (zero runtime overhead) ``` -## Before/After Regression Check +**Impact**: ~1-2MB binary size reduction, ~2-5ms startup saved + +## Performance Testing Checklist + +Before committing filter changes: + +### Startup Time +- [ ] Benchmark with `hyperfine 'rtk ' --warmup 3` +- [ ] Verify <10ms mean time +- [ ] Check variance (σ) is small (<1ms) +- [ ] Compare against baseline (regression <2ms) + +### Memory Usage +- [ ] Profile with `/usr/bin/time -l rtk ` +- [ ] Verify <5MB resident set size +- [ ] Compare against baseline (regression <1MB) + +### Token Savings +- [ ] Run `cargo test test_token_savings` +- [ ] Verify all filters achieve ≥60% savings +- [ ] Check real fixtures used (not synthetic) -Template for any performance-sensitive change: +### Binary Size +- [ ] Check `ls -lh target/release/rtk` +- [ ] Verify <5MB stripped binary +- [ ] Run `cargo bloat --release --crates` if >5MB + +## Continuous Performance Monitoring + +### Pre-Commit Hook + +Add to `.claude/hooks/bash/pre-commit-performance.sh`: ```bash -# 1. Baseline -cargo build --release -hyperfine 'target/release/rtk git status' --warmup 5 --export-json /tmp/before.json -/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident" -ls -lh target/release/rtk +#!/bin/bash +# Performance regression check before commit -# 2. Make changes -# ... edit code ... +echo "🚀 Running performance checks..." -# 3. Rebuild and compare -cargo build --release -hyperfine 'target/release/rtk git status' --warmup 5 --export-json /tmp/after.json -/usr/bin/time -l target/release/rtk git status 2>&1 | grep "maximum resident" -ls -lh target/release/rtk - -# 4. Compare -# Startup: jq '.results[0].mean' /tmp/before.json /tmp/after.json -# If after > before + 1ms: investigate -# If after > 10ms: regression, do not merge +# Benchmark startup time +CURRENT_TIME=$(hyperfine 'rtk git status' --warmup 3 --export-json /tmp/perf.json 2>&1 | grep "Time (mean" | awk '{print $4}') + +# Extract numeric value (remove "ms") +CURRENT_MS=$(echo $CURRENT_TIME | sed 's/ms//') + +# Check if > 10ms +if (( $(echo "$CURRENT_MS > 10" | bc -l) )); then + echo "❌ Startup time regression: ${CURRENT_MS}ms (target: <10ms)" + exit 1 +fi + +# Check binary size +BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}') +MAX_SIZE=$((5 * 1024 * 1024)) # 5MB + +if [ $BINARY_SIZE -gt $MAX_SIZE ]; then + echo "❌ Binary size regression: $(($BINARY_SIZE / 1024 / 1024))MB (target: <5MB)" + exit 1 +fi + +echo "✅ Performance checks passed" +``` + +### CI/CD Integration + +Add to `.github/workflows/ci.yml`: + +```yaml +- name: Performance Regression Check + run: | + cargo build --release + cargo install hyperfine + + # Benchmark startup time + hyperfine 'target/release/rtk git status' --warmup 3 --max-runs 10 + + # Check binary size + BINARY_SIZE=$(ls -l target/release/rtk | awk '{print $5}') + MAX_SIZE=$((5 * 1024 * 1024)) + if [ $BINARY_SIZE -gt $MAX_SIZE ]; then + echo "Binary too large: $(($BINARY_SIZE / 1024 / 1024))MB" + exit 1 + fi +``` + +## Performance Optimization Priorities + +**Priority order** (highest to lowest impact): + +1. **🔴 Lazy static regex** (5-10ms per pattern if compiled at runtime) +2. **🔴 Remove startup I/O** (10-50ms for config file reads) +3. **🟡 Zero-copy processing** (1-2MB memory, 1-2ms startup) +4. **🟡 Minimal dependencies** (1-2MB binary, 2-5ms startup) +5. **🟢 Algorithm optimization** (varies, measure first) + +**When in doubt**: Profile first with `flamegraph`, then optimize the hottest path. + +## Tools Reference + +| Tool | Purpose | Command | +|------|---------|---------| +| **hyperfine** | Benchmark startup time | `hyperfine 'rtk ' --warmup 3` | +| **time** | Memory usage (macOS) | `/usr/bin/time -l rtk ` | +| **time** | Memory usage (Linux) | `/usr/bin/time -v rtk ` | +| **flamegraph** | CPU profiling | `cargo flamegraph -- rtk ` | +| **cargo bloat** | Binary size analysis | `cargo bloat --release --crates` | +| **cargo tree** | Dependency tree | `cargo tree` | +| **DHAT** | Heap profiling | `cargo +nightly build && valgrind --tool=dhat` | +| **strace** | System call tracing (Linux) | `strace -c target/release/rtk ` | +| **dtrace** | System call tracing (macOS) | `sudo dtrace -n 'syscall::open*:entry'` | + +**Install tools**: +```bash +# macOS +brew install hyperfine + +# Linux / cross-platform via cargo +cargo install hyperfine +cargo install flamegraph +cargo install cargo-bloat ``` diff --git a/.claude/skills/pr-triage/SKILL.md b/.claude/skills/pr-triage/SKILL.md index 8b398c1c1..830ed22e0 100644 --- a/.claude/skills/pr-triage/SKILL.md +++ b/.claude/skills/pr-triage/SKILL.md @@ -153,7 +153,16 @@ _Externes — Problématiques_ : un des critères suivants : Après affichage du tableau de triage, copier dans le presse-papier : ```bash -pbcopy <<'EOF' +# Cross-platform clipboard +clip() { + if command -v pbcopy &>/dev/null; then pbcopy + elif command -v xclip &>/dev/null; then xclip -selection clipboard + elif command -v wl-copy &>/dev/null; then wl-copy + else cat + fi +} + +clip <<'EOF' {tableau de triage complet} EOF ``` diff --git a/.claude/skills/repo-recap.md b/.claude/skills/repo-recap/SKILL.md similarity index 96% rename from .claude/skills/repo-recap.md rename to .claude/skills/repo-recap/SKILL.md index 7dfa186fd..24df1a8cf 100644 --- a/.claude/skills/repo-recap.md +++ b/.claude/skills/repo-recap/SKILL.md @@ -1,5 +1,6 @@ --- description: Generate a comprehensive repo recap (PRs, issues, releases) for sharing with team. Pass "en" or "fr" as argument for language (default fr). +allowed-tools: Bash Read Grep --- # Repo Recap @@ -139,7 +140,16 @@ Structure the full recap as Markdown with: After displaying the recap, automatically copy it to clipboard: ```bash -cat << 'EOF' | pbcopy +# Cross-platform clipboard +clip() { + if command -v pbcopy &>/dev/null; then pbcopy + elif command -v xclip &>/dev/null; then xclip -selection clipboard + elif command -v wl-copy &>/dev/null; then wl-copy + else cat + fi +} + +cat << 'EOF' | clip {formatted recap content} EOF ``` diff --git a/.claude/skills/security-guardian.md b/.claude/skills/security-guardian/SKILL.md similarity index 99% rename from .claude/skills/security-guardian.md rename to .claude/skills/security-guardian/SKILL.md index 6a74d4a01..b584353df 100644 --- a/.claude/skills/security-guardian.md +++ b/.claude/skills/security-guardian/SKILL.md @@ -1,5 +1,6 @@ --- description: CLI security expert for RTK - command injection, shell escaping, hook security +allowed-tools: Read Grep Glob Bash --- # Security Guardian diff --git a/.claude/skills/ship.md b/.claude/skills/ship/SKILL.md similarity index 99% rename from .claude/skills/ship.md rename to .claude/skills/ship/SKILL.md index 380a8ba2b..0aba1bf6a 100644 --- a/.claude/skills/ship.md +++ b/.claude/skills/ship/SKILL.md @@ -1,5 +1,6 @@ --- description: Build, commit, push & version bump workflow - automates the complete release cycle +allowed-tools: Read Write Edit Bash Grep Glob --- # Ship Release diff --git a/.rtk/filters.toml b/.rtk/filters.toml new file mode 100644 index 000000000..d9bd43fee --- /dev/null +++ b/.rtk/filters.toml @@ -0,0 +1,13 @@ +# Project-local RTK filters — commit this file with your repo. +# Filters here override user-global and built-in filters. +# Docs: https://github.com/rtk-ai/rtk#custom-filters +schema_version = 1 + +# Example: suppress build noise from a custom tool +# [filters.my-tool] +# description = "Compact my-tool output" +# match_command = "^my-tool\\s+build" +# strip_ansi = true +# strip_lines_matching = ["^\\s*$", "^Downloading", "^Installing"] +# max_lines = 30 +# on_empty = "my-tool: ok"