diff --git a/.claude/agents/rust-rtk.md b/.claude/agents/rust-rtk.md
index 8efe67f0e..5adca48b9 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
---
@@ -509,7 +509,7 @@ rtk newcmd args
- Update `CLAUDE.md` Module Responsibilities table
- Update `README.md` with command support
-- Update `CHANGELOG.md`
+- CHANGELOG.md is auto-generated by release-please — do not edit manually
## Performance Targets
diff --git a/.claude/agents/technical-writer.md b/.claude/agents/technical-writer.md
index f5341af46..9d81a6a60 100644
--- a/.claude/agents/technical-writer.md
+++ b/.claude/agents/technical-writer.md
@@ -115,7 +115,7 @@ rtk --version # Should show rtk X.Y.Z
**Option 2: From Source**
```bash
-git clone https://github.com/rtk-ai/rtk.git
+git clone https://github.com/algolia/rtk.git
cd rtk
cargo install --path .
rtk --version # Verify installation
@@ -130,7 +130,7 @@ rtk gain # Should show token savings analytics
**From Source** (Cargo required):
```bash
-git clone https://github.com/rtk-ai/rtk.git
+git clone https://github.com/algolia/rtk.git
cd rtk
cargo install --path .
@@ -141,7 +141,7 @@ rtk --version
**Binary Download** (faster):
```bash
-curl -sSL https://github.com/rtk-ai/rtk/releases/download/v0.16.0/rtk-linux-x86_64 -o rtk
+curl -sSL https://github.com/algolia/rtk/releases/download/v0.16.0/rtk-linux-x86_64 -o rtk
chmod +x rtk
sudo mv rtk /usr/local/bin/
rtk --version
@@ -172,7 +172,7 @@ rtk --version
- **Fix**: Uninstall and reinstall correct RTK
```bash
cargo uninstall rtk
- cargo install --path . # From rtk-ai/rtk repo
+ cargo install --path . # From algolia/rtk repo
rtk gain --help # Should work
```
```
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/hooks/rtk-suggest.sh b/.claude/hooks/rtk-suggest.sh
index 34fb50f3b..80c356582 100755
--- a/.claude/hooks/rtk-suggest.sh
+++ b/.claude/hooks/rtk-suggest.sh
@@ -97,10 +97,8 @@ elif echo "$FIRST_CMD" | grep -qE '^head\s+'; then
fi
# --- JS/TS tooling ---
-elif echo "$FIRST_CMD" | grep -qE '^(pnpm\s+)?vitest(\s|$)'; then
- SUGGESTION="rtk vitest run"
-elif echo "$FIRST_CMD" | grep -qE '^pnpm\s+test(\s|$)'; then
- SUGGESTION="rtk vitest run"
+elif echo "$FIRST_CMD" | grep -qE '^(pnpm\s+)?vitest(\s+run)?(\s|$)'; then
+ SUGGESTION="rtk vitest"
elif echo "$FIRST_CMD" | grep -qE '^pnpm\s+tsc(\s|$)'; then
SUGGESTION="rtk tsc"
elif echo "$FIRST_CMD" | grep -qE '^(npx\s+)?tsc(\s|$)'; then
diff --git a/.claude/rules/search-strategy.md b/.claude/rules/search-strategy.md
index 0b4504df7..09a212c91 100644
--- a/.claude/rules/search-strategy.md
+++ b/.claude/rules/search-strategy.md
@@ -23,8 +23,7 @@ src/
│ ├── utils.rs ← strip_ansi, truncate, execute_command
│ ├── filter.rs ← Language-aware code filtering engine
│ ├── toml_filter.rs ← TOML DSL filter engine
-│ ├── display_helpers.rs ← Terminal formatting helpers
-│ └── telemetry.rs ← Analytics ping
+│ └── display_helpers.rs ← Terminal formatting helpers
├── hooks/ ← Hook system
│ ├── init.rs ← rtk init command
│ ├── rewrite_cmd.rs ← rtk rewrite command
diff --git a/.claude/skills/code-simplifier/SKILL.md b/.claude/skills/code-simplifier/SKILL.md
index 15a3ae19d..4c3c22e06 100644
--- a/.claude/skills/code-simplifier/SKILL.md
+++ b/.claude/skills/code-simplifier/SKILL.md
@@ -7,6 +7,13 @@ triggers:
- "over-engineered"
- "refactor this"
- "make this idiomatic"
+allowed-tools:
+ - Read
+ - Grep
+ - Glob
+ - Edit
+effort: low
+tags: [rust, simplify, refactor, idioms, rtk]
---
# RTK Code Simplifier
diff --git a/.claude/skills/design-patterns/SKILL.md b/.claude/skills/design-patterns/SKILL.md
index 10045f48b..c44f79f93 100644
--- a/.claude/skills/design-patterns/SKILL.md
+++ b/.claude/skills/design-patterns/SKILL.md
@@ -6,6 +6,12 @@ triggers:
- "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
diff --git a/.claude/skills/issue-triage/SKILL.md b/.claude/skills/issue-triage/SKILL.md
index 8fb1aadaa..635167757 100644
--- a/.claude/skills/issue-triage/SKILL.md
+++ b/.claude/skills/issue-triage/SKILL.md
@@ -1,7 +1,14 @@
---
+name: issue-triage
description: >
Issue 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.
+allowed-tools:
+ - Bash
+ - Read
+ - Grep
+effort: medium
+tags: [triage, issues, github, categorize, duplicates, risk]
---
# Issue Triage
@@ -155,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/issue-triage/templates/issue-comment.md b/.claude/skills/issue-triage/templates/issue-comment.md
index 64e9146f4..93917a239 100644
--- a/.claude/skills/issue-triage/templates/issue-comment.md
+++ b/.claude/skills/issue-triage/templates/issue-comment.md
@@ -32,7 +32,7 @@ To move forward, we need the following:
{What happens once the info is provided — e.g., "Once confirmed, we'll prioritize this for the next release."}
---
-*Triaged via [rtk](https://github.com/rtk-ai/rtk) `/issue-triage`*
+*Triaged via [rtk](https://github.com/algolia/rtk) `/issue-triage`*
```
---
@@ -53,7 +53,7 @@ This issue covers the same problem as #{original_number}: **{original_title}**.
If your situation differs in an important way (different command, different OS, different error message), please reopen and add that context. Otherwise, follow the original issue for updates.
---
-*Triaged via [rtk](https://github.com/rtk-ai/rtk) `/issue-triage`*
+*Triaged via [rtk](https://github.com/algolia/rtk) `/issue-triage`*
```
---
@@ -74,7 +74,7 @@ If this is still relevant:
Thanks for taking the time to report it.
---
-*Triaged via [rtk](https://github.com/rtk-ai/rtk) `/issue-triage`*
+*Triaged via [rtk](https://github.com/algolia/rtk) `/issue-triage`*
```
---
@@ -99,7 +99,7 @@ After review, this request falls outside RTK's current design goals.
If the use case evolves or the scope changes in a future version, feel free to reopen with updated context.
---
-*Triaged via [rtk](https://github.com/rtk-ai/rtk) `/issue-triage`*
+*Triaged via [rtk](https://github.com/algolia/rtk) `/issue-triage`*
```
---
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 e4f45c0c9..30c90b0eb 100644
--- a/.claude/skills/performance/SKILL.md
+++ b/.claude/skills/performance/SKILL.md
@@ -1,209 +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"
+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-review/SKILL.md b/.claude/skills/pr-review/SKILL.md
new file mode 100644
index 000000000..936419554
--- /dev/null
+++ b/.claude/skills/pr-review/SKILL.md
@@ -0,0 +1,227 @@
+---
+description: >
+ Batch 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:" pour
+ reprendre à partir d'un numéro de PR spécifique.
+allowed-tools:
+ - Bash
+ - Read
+ - Grep
+ - Glob
+ - Write
+ - AskUserQuestion
+---
+
+# /pr-review
+
+Batch review des PRs RTK — du plus simple au plus complexe, une par une, avec validation utilisateur avant chaque merge.
+
+---
+
+## Quand utiliser
+
+- Après un `/rtk-triage` pour agir sur les résultats
+- Régulièrement pour dégraisser le backlog
+- Avant une release pour vider la file quick wins
+
+---
+
+## Workflow
+
+### Phase 0 — Préconditions
+
+```bash
+git rev-parse --is-inside-work-tree
+gh auth status
+date +%Y-%m-%d
+```
+
+Si l'argument `triage` est passé, exécuter `/rtk-triage` d'abord et utiliser sa liste de quick wins comme séquence. Sinon, construire la liste soi-même.
+
+---
+
+### Phase 1 — Construire la liste de PRs (si pas de triage)
+
+```bash
+gh pr list --state open --limit 200 \
+ --json number,title,author,additions,deletions,changedFiles,mergeable,mergeStateStatus,isDraft,statusCheckRollup,reviewDecision,body \
+ | jq 'sort_by(.additions + .deletions)'
+```
+
+**Classement par taille** :
+
+| Taille | Critère | Traitement |
+|--------|---------|------------|
+| XS | < 30 lignes, 1 fichier | En premier |
+| S | 30-100 lignes, 1-3 fichiers | Ensuite |
+| M | 100-200 lignes, logique non triviale | Après |
+| L | > 200 lignes | Dernier ou skip |
+| XL | > 500 lignes | Skip (session dédiée) |
+
+**Filtrer d'emblée** :
+- Exclure les PRs draft
+- Exclure les PRs de nous (les nôtres ont une review flow différente)
+- Si `from:` passé en argument : commencer à ce numéro
+
+---
+
+### Phase 2 — Pour chaque PR (une par une, dans l'ordre)
+
+#### Étape A — Vérification état (AVANT de lire le diff)
+
+```bash
+# 1. Etat mergeable + CLA
+gh pr view --json mergeable,mergeStateStatus,statusCheckRollup,reviewDecision
+
+# 2. Reviews existantes (CHANGES_REQUESTED ?)
+gh api repos/algolia/rtk/pulls//reviews \
+ --jq '.[] | {author: .user.login, state: .state, body: .body}'
+
+# 3. Commentaires inline (si CHANGES_REQUESTED)
+gh api repos/algolia/rtk/pulls//comments \
+ --jq '.[] | {author: .user.login, body: .body, path: .path, line: .line}'
+```
+
+**Décision rapide selon état** :
+
+| État | Action |
+|------|--------|
+| MERGEABLE + CLA ok + pas de CHANGES_REQUESTED | → lire le diff |
+| CONFLICTING | → préparer commentaire rebase, skip diff |
+| CLA non signé | → préparer commentaire CLA, skip diff |
+| CHANGES_REQUESTED par un maintainer | → skip (ne pas override), noter |
+| Draft | → skip silencieusement |
+
+#### Étape B — Lire le diff complet
+
+```bash
+gh pr diff
+```
+
+Si le diff touche une logique complexe (filter functions, regex, routing) → lire le fichier source en contexte avec `Read` pour comprendre l'impact réel.
+
+#### Étape C — Présenter à l'utilisateur
+
+Format de présentation **obligatoire** pour chaque PR :
+
+```
+**PR #** — https://github.com/algolia/rtk/pull/
+
+**Author**: | **Size**: (+ -, fichiers) | **CLA**: | **Mergeable**:
+
+**Ce que ça fait** — [description en 2-4 phrases : le problème résolu, les fichiers touchés, la logique modifiée, les tests ajoutés]
+
+**Qualité du diff** : [analyse honnête : propre/à vérifier/problème détecté]
+
+Merge # ?
+```
+
+**Règles de présentation** :
+- Toujours inclure le lien GitHub cliquable
+- Toujours mentionner si des tests couvrent le changement
+- Si une fonction complexe est touchée, expliquer l'impact
+- Ne pas embellir — si le diff est moyen, le dire
+- Langue : français pour l'analyse (comme ici)
+
+#### Étape D — Attendre la validation
+
+**NE JAMAIS MERGER SANS RÉPONSE EXPLICITE.** Les réponses attendues :
+
+| Réponse | Action |
+|---------|--------|
+| "ok" / "go" / "merge" | Merger avec `gh pr merge --merge` |
+| "skip" / "next" | Passer à la PR suivante sans merger |
+| "comment" | Poster un commentaire (demander le texte si pas fourni) |
+| "close" | Fermer la PR |
+| Retour avec instructions | Appliquer puis redemander confirmation |
+
+#### Étape E — Merger (si validé)
+
+```bash
+gh pr merge --merge --squash
+```
+
+Confirmer immédiatement : `Merged #. ✓`
+
+Puis **vérifier que la PR suivante n'est pas passée en CONFLICTING** à cause du merge (surtout si les deux touchent `rules.rs`, `registry.rs`, `main.rs`, ou `CHANGELOG.md`).
+
+---
+
+### Phase 3 — PRs bloquées : commentaire boldguy-adapt
+
+Pour les PRs avec conflit, CLA manquant, ou besoin de rebase, poster un commentaire en anglais, ton boldguy-adapt.
+
+**Règles du commentaire** :
+- **Anglais uniquement** (GitHub)
+- Remercier la contribution en ouverture (sincèrement, pas de manière générique)
+- Dire clairement ce qui bloque (1-2 points max)
+- Donner les étapes exactes pour débloquer
+- Pas d'em dash (`—`), pas de staccato, longueurs de phrases variées
+- Ne pas sonner comme un bot
+
+**Template conflit + CLA** :
+```
+Hey @, thanks for the contribution! [mention spécifique de ce que la PR apporte]
+
+Two things before we can merge:
+
+1. The branch needs a rebase on `develop` — there's a conflict on [fichier]. A `git rebase origin/develop` should do it.
+
+2. The CLA hasn't been signed yet. The CLAassistant bot left instructions in the PR — just follow the link, takes about a minute.
+
+Once both are sorted, this will move quickly.
+```
+
+**Template conflit seul** :
+```
+Hey @, good fix on [description spécifique]. One thing to address before merge: the branch has a conflict on [fichier] after recent changes to develop. A `git rebase origin/develop` should resolve it cleanly.
+```
+
+**Template CLA seul** :
+```
+Hey @, thanks for [description spécifique]. The only thing blocking merge is the CLA signature — the CLAassistant bot left the link in the PR. Once that's done, we're good to go.
+```
+
+---
+
+### Phase 4 — Récap de session
+
+Après avoir traité toutes les PRs (ou à la demande) :
+
+```
+## Session recap — YYYY-MM-DD
+
+| PR | Titre | Action | Raison |
+|----|-------|--------|--------|
+| #N | titre | Mergé ✓ | — |
+| #N | titre | Skip | CHANGES_REQUESTED (KuSh) |
+| #N | titre | Commenté | Conflit + CLA |
+| #N | titre | Fermé | Doublon avec #M |
+
+Mergées : N | Skippées : N | Commentées : N
+```
+
+---
+
+## Règles
+
+- **Une PR à la fois** — ne jamais présenter plusieurs PRs en attente de validation
+- **Jamais merger sans "ok" explicite** — "ça a l'air bien" n'est pas un ok
+- **Ne pas overrider un CHANGES_REQUESTED** d'un maintainer sans instructions explicites de l'utilisateur
+- **Vérifier les conflits post-merge** sur la PR suivante si les deux touchent les mêmes fichiers
+- **Langue** : analyse en français, commentaires GitHub en anglais
+- **Ton boldguy** : factuel, direct, bienveillant, pas de marqueurs AI (em dash, staccato, punchline finale parfaite)
+
+---
+
+## Fichiers fréquemment en conflit (surveiller)
+
+- `CHANGELOG.md` — toutes les PRs y touchent
+- `src/discover/rules.rs` — ajouts fréquents de règles
+- `src/discover/registry.rs` — tests de classify/rewrite
+- `src/main.rs` — routing des commandes
+- `src/hooks/rewrite_cmd.rs` — rewrites hooks
diff --git a/.claude/skills/pr-triage/SKILL.md b/.claude/skills/pr-triage/SKILL.md
index f55a5dd19..830ed22e0 100644
--- a/.claude/skills/pr-triage/SKILL.md
+++ b/.claude/skills/pr-triage/SKILL.md
@@ -1,7 +1,15 @@
---
+name: pr-triage
description: >
PR 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.
+allowed-tools:
+ - Bash
+ - Read
+ - Grep
+ - Glob
+effort: medium
+tags: [triage, pr, github, review, code-review, rtk]
---
# PR Triage
@@ -145,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/pr-triage/templates/review-comment.md b/.claude/skills/pr-triage/templates/review-comment.md
index fbf582de4..5f7df791a 100644
--- a/.claude/skills/pr-triage/templates/review-comment.md
+++ b/.claude/skills/pr-triage/templates/review-comment.md
@@ -42,7 +42,7 @@ Use this template to generate GitHub PR review comments. Fill in each section ba
{- Description of what's done right.}
---
-*Automated review via [rtk](https://github.com/rtk-ai/rtk) `/pr-triage`*
+*Automated review via [rtk](https://github.com/algolia/rtk) `/pr-triage`*
```
---
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/rtk-tdd/SKILL.md b/.claude/skills/rtk-tdd/SKILL.md
index 79caf4972..e13ec58a5 100644
--- a/.claude/skills/rtk-tdd/SKILL.md
+++ b/.claude/skills/rtk-tdd/SKILL.md
@@ -5,6 +5,13 @@ description: >
implementation, testing, refactoring, and bug fixing tasks. Provides
Rust-idiomatic testing patterns with anyhow/thiserror, cfg(test), and
Arrange-Act-Assert workflow.
+allowed-tools:
+ - Read
+ - Write
+ - Edit
+ - Bash
+effort: medium
+tags: [tdd, testing, rust, red-green-refactor, rtk]
---
# Rust TDD Workflow
diff --git a/.claude/skills/rtk-triage/SKILL.md b/.claude/skills/rtk-triage/SKILL.md
index 9aed21a26..705d18210 100644
--- a/.claude/skills/rtk-triage/SKILL.md
+++ b/.claude/skills/rtk-triage/SKILL.md
@@ -1,4 +1,5 @@
---
+name: rtk-triage
description: >
Triage complet RTK : exécute issue-triage + pr-triage en parallèle,
puis croise les données pour détecter doubles couvertures, trous sécurité,
@@ -9,6 +10,8 @@ allowed-tools:
- Write
- Read
- AskUserQuestion
+effort: high
+tags: [triage, orchestration, issues, pr, security, cross-analysis, rtk]
---
# /rtk-triage
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 94%
rename from .claude/skills/ship.md
rename to .claude/skills/ship/SKILL.md
index 380a8ba2b..f34ab53a4 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
@@ -61,8 +62,9 @@ git status # Should show "nothing to commit, working tree clean"
**Files to update**:
1. `Cargo.toml` (line 3): `version = "X.Y.Z"`
-2. `CHANGELOG.md` (add new section)
-3. `README.md` (if version mentioned)
+2. `README.md` (if version mentioned)
+
+> **Note**: `CHANGELOG.md` is auto-generated by release-please from conventional commit messages — do not edit manually.
**Example**:
```toml
@@ -119,13 +121,12 @@ hyperfine 'target/release/rtk git status' --warmup 3
```bash
# Stage version files
-git add Cargo.toml Cargo.lock CHANGELOG.md README.md
+git add Cargo.toml Cargo.lock README.md
# Commit with version tag
git commit -m "chore(release): bump version to v0.17.0
- Updated Cargo.toml version
-- Updated CHANGELOG.md with release notes
- Verified all quality checks pass
- Benchmarked performance (<10ms startup)
@@ -187,7 +188,7 @@ gh release view v0.17.0
### 3. Installation Verification
```bash
# Test installation from release
-curl -sSL https://github.com/rtk-ai/rtk/releases/download/v0.17.0/rtk-macos-latest -o rtk
+curl -sSL https://github.com/algolia/rtk/releases/download/v0.17.0/rtk-macos-latest -o rtk
chmod +x rtk
./rtk --version
# Should show v0.17.0
@@ -361,14 +362,7 @@ target/release/rtk --version
**Symptom**: CHANGELOG.md has conflicts after rebase
-**Solution**:
-```bash
-# Always add new entries at top
-# Manual merge:
-# 1. Keep all entries from both branches
-# 2. Sort by version (newest first)
-# 3. Ensure date format consistency
-```
+**Solution**: Do not edit CHANGELOG.md manually. It is auto-generated by release-please from conventional commit messages when merging to master.
## Security Considerations
diff --git a/.claude/skills/tdd-rust/SKILL.md b/.claude/skills/tdd-rust/SKILL.md
index 4e8c3bb56..87d556941 100644
--- a/.claude/skills/tdd-rust/SKILL.md
+++ b/.claude/skills/tdd-rust/SKILL.md
@@ -8,6 +8,13 @@ triggers:
- "write tests for"
- "test coverage"
- "fix failing test"
+allowed-tools:
+ - Read
+ - Write
+ - Edit
+ - Bash
+effort: medium
+tags: [tdd, testing, rust, filters, snapshots, token-savings, rtk]
---
# RTK TDD Workflow
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 413e94f04..cf6901c54 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -52,7 +52,7 @@ PRs target the **`develop`** branch, not `main`. All commits require a DCO sign-
rtk routes CLI commands via a Clap `Commands` enum in `main.rs` to specialized filter modules in `src/cmds/*/`, each executing the underlying command and compressing output. Token savings are tracked in SQLite via `src/core/tracking.rs`.
-For full details see [ARCHITECTURE.md](../ARCHITECTURE.md) and [docs/TECHNICAL.md](../docs/TECHNICAL.md). Module responsibilities are documented in each folder's `README.md` and each file's `//!` doc header.
+For full details see [ARCHITECTURE.md](../docs/contributing/ARCHITECTURE.md) and [docs/contributing/TECHNICAL.md](../docs/contributing/TECHNICAL.md). Module responsibilities are documented in each folder's `README.md` and each file's `//!` doc header.
## Key Conventions
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 000000000..57dba0f42
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,17 @@
+version: 2
+updates:
+ - package-ecosystem: "cargo"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
+ open-pull-requests-limit: 5
+
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ labels:
+ - "dependencies"
+ - "ci"
diff --git a/.github/workflows/CICD.md b/.github/workflows/CICD.md
index 53776a00d..b20e9cff6 100644
--- a/.github/workflows/CICD.md
+++ b/.github/workflows/CICD.md
@@ -14,27 +14,29 @@ Trigger: pull_request to develop or master
└────────┬─────────┘
│
┌────────▼─────────┐
- │ clippy │
- └──┬───┬───┬───┬───┘
- │ │ │ │
- ┌──────────────┘ │ │ └──────────────┐
- │ ┌───────┘ └───────┐ │
- ▼ ▼ ▼ ▼
- ┌──────────────┐ ┌──────────────┐ ┌───────────┐ ┌──────────┐
- │ test │ │Security Scan │ │ benchmark │ │ validate │
- │ ubuntu │ │ cargo audit │ │ >=80% │ │ ai agent │
- │ windows │ │ (advisory) │ │ savings │ │ doc │
- │ macos │ │ │ │ │ │ │
- └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ └────┬─────┘
- │ │ │ │
- └────────────────┴───────┬───────┴─────────────┘
- │
- ┌──────────▼─────────┐
- │ All must pass │
- │ to merge │
- └────────────────────┘
+ │ clippy │
+ │ -D unsafe_code │
+ └┬───┬───┬───┬───┬─┘
+ │ │ │ │ │
+ ┌───────────────┘ │ │ │ └───────────────┐
+ │ ┌───────────┘ │ └──────────┐ │
+ ▼ ▼ ▼ ▼ ▼
+ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌─────────┐ ┌──────────┐
+ │ test │ │ security │ │ semgrep │ │benchmark│ │ doc │
+ │ ubuntu │ │ cargo │ │ AST-aware │ │ >=80% │ │ review │
+ │ windows │ │ audit │ │ diff-only │ │ savings │ │ ai agent │
+ │ macos │ │ patterns │ │ │ │ │ │ │
+ └────┬─────┘ └────┬─────┘ └─────┬─────┘ └────┬────┘ └────┬─────┘
+ │ │ │ │ │
+ └────────────┴─────────┬───┴─────────────┴────────────┘
+ │
+ ┌──────────▼─────────┐
+ │ All must pass │
+ │ to merge │
+ └────────────────────┘
+ DCO check (independent, develop PRs only)
+ + Dependabot (weekly: Cargo deps + GitHub Actions)
```
## Merge to develop — pre-release (cd.yml)
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 393f9e500..0ed2bc0fc 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -98,17 +98,25 @@ jobs:
# ═══════════════════════════════════════════════
release-please:
- if: github.ref == 'refs/heads/master' && github.event_name == 'push'
+ if: github.ref == 'refs/heads/master' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
runs-on: ubuntu-latest
outputs:
release_created: ${{ steps.release.outputs.release_created }}
tag_name: ${{ steps.release.outputs.tag_name }}
steps:
+ - uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ client-id: ${{ secrets.APP_CLIENT_ID }}
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
+ permission-contents: write
+ permission-pull-requests: write
- uses: googleapis/release-please-action@v4
id: release
with:
release-type: rust
package-name: rtk
+ token: ${{ steps.app-token.outputs.token }}
build-release:
name: Build and upload release assets
@@ -127,9 +135,17 @@ jobs:
if: ${{ needs.release-please.outputs.release_created == 'true' }}
runs-on: ubuntu-latest
steps:
+ - uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ client-id: ${{ secrets.APP_CLIENT_ID }}
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
+ permission-contents: write
+
- uses: actions/checkout@v4
with:
fetch-depth: 0
+ token: ${{ steps.app-token.outputs.token }}
- name: Update latest tag
run: |
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bad4b5d62..b56acffad 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,6 +14,18 @@ env:
jobs:
# ─── Fast gates (fail early, save CI minutes) ───
+ check-test-presence:
+ name: test presence
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 50
+ - name: Check filter modules have tests
+ run: |
+ git fetch origin "${{ github.base_ref }}" --depth=1 || true
+ bash scripts/check-test-presence.sh "origin/${{ github.base_ref }}"
+
fmt:
name: fmt
runs-on: ubuntu-latest
@@ -34,7 +46,7 @@ jobs:
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- - run: cargo clippy --all-targets
+ - run: cargo clippy --all-targets -- -D unsafe_code
# ─── Parallel gates (all need code to compile) ───
@@ -173,6 +185,18 @@ jobs:
echo "- Require approval from 2 maintainers" >> $GITHUB_STEP_SUMMARY
echo "- Test in isolated environment before merge" >> $GITHUB_STEP_SUMMARY
+ semgrep:
+ name: semgrep security scan
+ needs: clippy
+ runs-on: ubuntu-latest
+ container:
+ image: semgrep/semgrep
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ - run: semgrep scan --config .semgrep.yml --baseline-commit ${{ github.event.pull_request.base.sha }} --error
+
benchmark:
name: benchmark
needs: clippy
@@ -187,8 +211,11 @@ jobs:
- name: Build rtk
run: cargo build --release
+ - name: Install system tools
+ run: sudo apt-get install -y tree
+
- name: Install Python tools
- run: pip install ruff pytest
+ run: pip install ruff pytest mypy
- name: Install Go
uses: actions/setup-go@v5
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 79b667c02..691a9c554 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -81,9 +81,6 @@ jobs:
- name: Build
run: cargo build --release --target ${{ matrix.target }}
- env:
- RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
- RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Package (Unix)
if: matrix.os != 'windows-latest'
@@ -120,9 +117,6 @@ jobs:
- name: Build DEB
run: cargo deb
- env:
- RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
- RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Upload DEB
uses: actions/upload-artifact@v4
@@ -147,9 +141,6 @@ jobs:
- name: Build release
run: cargo build --release
- env:
- RTK_TELEMETRY_URL: ${{ vars.RTK_TELEMETRY_URL }}
- RTK_TELEMETRY_TOKEN: ${{ secrets.RTK_TELEMETRY_TOKEN }}
- name: Generate RPM
run: cargo generate-rpm
@@ -165,6 +156,13 @@ jobs:
needs: [build, build-deb, build-rpm]
runs-on: ubuntu-latest
steps:
+ - uses: actions/create-github-app-token@v3
+ id: app-token
+ with:
+ client-id: ${{ secrets.APP_CLIENT_ID }}
+ private-key: ${{ secrets.APP_PRIVATE_KEY }}
+ permission-contents: write
+
- name: Checkout
uses: actions/checkout@v4
@@ -208,8 +206,7 @@ jobs:
tag_name: ${{ steps.version.outputs.version }}
files: release/*
prerelease: ${{ inputs.prerelease }}
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ token: ${{ steps.app-token.outputs.token }}
notify-discord:
name: Notify Discord
@@ -232,10 +229,10 @@ jobs:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="${{ steps.version.outputs.tag }}"
- RELEASE_URL="https://github.com/rtk-ai/rtk/releases/tag/${TAG}"
+ RELEASE_URL="https://github.com/algolia/rtk/releases/tag/${TAG}"
# Fetch release notes from GitHub API
- NOTES=$(gh api "repos/rtk-ai/rtk/releases/tags/${TAG}" --jq '.body' 2>/dev/null | head -c 1800 || echo "")
+ NOTES=$(gh api "repos/algolia/rtk/releases/tags/${TAG}" --jq '.body' 2>/dev/null | head -c 1800 || echo "")
DESC=$(echo "${NOTES:-No release notes}" | jq -Rs .)
jq -n \
@@ -265,7 +262,7 @@ jobs:
- name: Download checksums
run: |
gh release download "${{ steps.version.outputs.tag }}" \
- --repo rtk-ai/rtk \
+ --repo algolia/rtk \
--pattern checksums.txt
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -283,21 +280,21 @@ jobs:
cat > rtk.rb << 'FORMULA'
class Rtk < Formula
desc "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
- homepage "https://www.rtk-ai.app"
+ homepage "https://github.com/algolia/rtk"
version "VERSION_PLACEHOLDER"
license "MIT"
if OS.mac? && Hardware::CPU.arm?
- url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-apple-darwin.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-apple-darwin.tar.gz"
sha256 "SHA_MAC_ARM_PLACEHOLDER"
elsif OS.mac? && Hardware::CPU.intel?
- url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-apple-darwin.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-apple-darwin.tar.gz"
sha256 "SHA_MAC_INTEL_PLACEHOLDER"
elsif OS.linux? && Hardware::CPU.arm?
- url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-unknown-linux-gnu.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/TAG_PLACEHOLDER/rtk-aarch64-unknown-linux-gnu.tar.gz"
sha256 "SHA_LINUX_ARM_PLACEHOLDER"
elsif OS.linux? && Hardware::CPU.intel?
- url "https://github.com/rtk-ai/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-unknown-linux-musl.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/TAG_PLACEHOLDER/rtk-x86_64-unknown-linux-musl.tar.gz"
sha256 "SHA_LINUX_INTEL_PLACEHOLDER"
end
@@ -319,7 +316,7 @@ jobs:
# Measure your token savings
rtk gain
- Full documentation: https://www.rtk-ai.app
+ Full documentation: https://github.com/algolia/rtk
EOS
end
@@ -340,14 +337,14 @@ jobs:
- name: Push to homebrew-tap
run: |
CONTENT=$(base64 -w 0 rtk.rb)
- SHA=$(gh api repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb --jq '.sha' 2>/dev/null || echo "")
+ SHA=$(gh api repos/algolia/homebrew-tap/contents/Formula/rtk.rb --jq '.sha' 2>/dev/null || echo "")
if [ -n "$SHA" ]; then
- gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
+ gh api -X PUT repos/algolia/homebrew-tap/contents/Formula/rtk.rb \
-f message="rtk ${{ steps.version.outputs.version }}" \
-f content="$CONTENT" \
-f sha="$SHA"
else
- gh api -X PUT repos/rtk-ai/homebrew-tap/contents/Formula/rtk.rb \
+ gh api -X PUT repos/algolia/homebrew-tap/contents/Formula/rtk.rb \
-f message="rtk ${{ steps.version.outputs.version }}" \
-f content="$CONTENT"
fi
diff --git a/.gitignore b/.gitignore
index fdba09783..947ca4fe7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -28,8 +28,10 @@ Thumbs.db
# Test artifacts
*.cast.bak
-# Benchmark results
-scripts/benchmark/
+# Benchmark results (fixture data, not infra)
+scripts/benchmark/diff/
+scripts/benchmark/rtk/
+scripts/benchmark/unix/
benchmark-report.md
# SQLite databases
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
index 57647ad29..521104886 100644
--- a/.release-please-manifest.json
+++ b/.release-please-manifest.json
@@ -1,3 +1,3 @@
{
- ".": "0.34.2"
+ ".": "0.38.0-algolia.1"
}
diff --git a/.rtk/filters.toml b/.rtk/filters.toml
new file mode 100644
index 000000000..9a137dcd5
--- /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/algolia/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"
diff --git a/.semgrep.yml b/.semgrep.yml
new file mode 100644
index 000000000..314fe90ca
--- /dev/null
+++ b/.semgrep.yml
@@ -0,0 +1,107 @@
+rules:
+ - id: dynamic-command-execution
+ patterns:
+ - pattern: Command::new($ARG)
+ - pattern-not: Command::new("...")
+ message: >
+ Dynamic shell invocation via Command::new($ARG).
+ RTK only executes known CLI tools — use string literals, not variables.
+ languages: [rust]
+ severity: ERROR
+
+ - id: unsafe-block
+ pattern: unsafe { ... }
+ message: >
+ Unsafe block detected. RTK has no legitimate need for unsafe code.
+ languages: [rust]
+ severity: ERROR
+
+ - id: ld-preload-manipulation
+ pattern-either:
+ - pattern: $CMD.env("LD_PRELOAD", ...)
+ - pattern: $CMD.env("LD_LIBRARY_PATH", ...)
+ message: >
+ LD_PRELOAD/LD_LIBRARY_PATH manipulation detected.
+ This can hijack shared library loading — forbidden in RTK.
+ languages: [rust]
+ severity: ERROR
+
+ - id: raw-socket-usage
+ pattern-either:
+ - pattern: TcpStream::$METHOD(...)
+ - pattern: UdpSocket::$METHOD(...)
+ - pattern: TcpListener::$METHOD(...)
+ message: >
+ Raw socket usage detected. RTK is a CLI proxy — it should not
+ open network connections directly. Network access is banned in
+ this fork (no telemetry, no remote calls).
+ languages: [rust]
+ severity: ERROR
+
+ - id: reqwest-forbidden
+ pattern: reqwest::$METHOD(...)
+ message: >
+ reqwest is forbidden in RTK. Network access is banned in this fork
+ (no telemetry, no remote calls). Adding reqwest also increases
+ binary size and attack surface.
+ languages: [rust]
+ severity: ERROR
+
+ - id: interpreter-execution
+ pattern-either:
+ - pattern: Command::new("curl")
+ - pattern: Command::new("wget")
+ - pattern: Command::new("python")
+ - pattern: Command::new("python3")
+ - pattern: Command::new("node")
+ - pattern: Command::new("bash")
+ - pattern: Command::new("sh")
+ - pattern: Command::new("perl")
+ - pattern: Command::new("ruby")
+ message: >
+ Direct interpreter/downloader execution detected.
+ RTK proxies user commands — it should never spawn interpreters
+ or download tools on its own.
+ languages: [rust]
+ severity: ERROR
+
+ - id: ureq-forbidden
+ pattern: ureq::$METHOD(...)
+ message: >
+ ureq usage detected. Network access is banned in this fork
+ (no telemetry, no remote calls).
+ languages: [rust]
+ severity: ERROR
+
+ # ── WARNING rules (non-blocking, flag for review) ──
+
+ - id: path-env-manipulation
+ pattern-either:
+ - pattern: $CMD.env("PATH", ...)
+ - pattern: std::env::set_var("PATH", ...)
+ - pattern: env::set_var("PATH", ...)
+ message: >
+ PATH environment variable manipulation detected.
+ Hijacking PATH can redirect command resolution to attacker-controlled binaries.
+ languages: [rust]
+ severity: WARNING
+
+ - id: sensitive-path-reference
+ pattern-regex: \.(ssh|bashrc|zshrc|bash_profile|profile)|authorized_keys|/etc/passwd|/etc/shadow
+ message: >
+ Reference to sensitive system path detected.
+ RTK filters should not access dotfiles, SSH keys, or system credential files.
+ languages: [rust]
+ severity: WARNING
+
+ - id: filesystem-deletion
+ pattern-either:
+ - pattern: fs::remove_file(...)
+ - pattern: fs::remove_dir_all(...)
+ - pattern: std::fs::remove_file(...)
+ - pattern: std::fs::remove_dir_all(...)
+ message: >
+ File/directory deletion detected. Expected in hooks/init cleanup,
+ surprising in a filter module. Verify intent.
+ languages: [rust]
+ severity: WARNING
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ed7ca6ab..6ef8bb35c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,247 @@ All notable changes to rtk (Rust Token Killer) will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.38.0](https://github.com/rtk-ai/rtk/compare/v0.37.2...v0.38.0) (2026-04-29)
+
+
+### Features
+
+* **cicd:** enforce cicd sast & package check ([3bbbb49](https://github.com/rtk-ai/rtk/commit/3bbbb492f33f0e619ab0d1dbce4389ad49e763ae))
+* **gains:** add --reset flag ([e3149cb](https://github.com/rtk-ai/rtk/commit/e3149cb7fbed18eae95f753664ddd8eaaaf6cc39))
+* **glab:** add GitLab CLI (glab) command support ([048f2f9](https://github.com/rtk-ai/rtk/commit/048f2f980bd95c5918f309d1d7ebc096d196f00d))
+* **glab:** add GitLab CLI (glab) command support ([bc31f3f](https://github.com/rtk-ai/rtk/commit/bc31f3f0f39077884e8d52c3508e840b355f682e)), closes [#851](https://github.com/rtk-ai/rtk/issues/851)
+
+
+### Bug Fixes
+
+* **benchmark:** benchmark capture all fd only stream ([c590bd6](https://github.com/rtk-ai/rtk/commit/c590bd69329bb82608666958c7e06bf169a7d577))
+* **benchmark:** capture all fd for stream cmd benchmark ([e6c2523](https://github.com/rtk-ai/rtk/commit/e6c2523be1180772e40c175e2f9a523d349fb13d))
+* **benchmark:** extract format_diff_changes + remove wrong diff test ([e7ae6bf](https://github.com/rtk-ai/rtk/commit/e7ae6bf018882dba248f151ba4ec4929300b3e36))
+* **cicd:** : no semgrep alert on sh call cicd ([7681daf](https://github.com/rtk-ai/rtk/commit/7681dafc76f164cfad588fe37d9a165dcb476e10))
+* **discover:** also encode '_', '\', and non-ASCII chars in project path slug ([73a05c3](https://github.com/rtk-ai/rtk/commit/73a05c3262b6410cb24370d939c428d1dc0c7a77)), closes [#1457](https://github.com/rtk-ai/rtk/issues/1457)
+* **discover:** encode '.' as '-' in project path slug ([2d031f3](https://github.com/rtk-ai/rtk/commit/2d031f32e9ad4452c2cc229c030ea6c0936c8bec)), closes [#1457](https://github.com/rtk-ai/rtk/issues/1457)
+* **filters:** benchmark ci update + fix stream + filter quality ([137af04](https://github.com/rtk-ai/rtk/commit/137af0493189a86020da1feaa1de74df92466137))
+* **filters:** benchmark ci update + fix stream filter quality ([88d9f6a](https://github.com/rtk-ai/rtk/commit/88d9f6a0d94fd2b5b3d40c956e966756670a2704))
+* **git:** fix empty output when branch name contains '/' in git diff ([e070226](https://github.com/rtk-ai/rtk/commit/e0702260a94377b6bbec5cb79d91d81cba17b0ec))
+* **git:** fix empty output when branch name contains '/' in git diff ([13188a8](https://github.com/rtk-ai/rtk/commit/13188a88b22f692157b89874f4c76287a0b3ecae)), closes [#1431](https://github.com/rtk-ai/rtk/issues/1431)
+* grep false negatives, output mangling, and truncation annotations ([de41533](https://github.com/rtk-ai/rtk/commit/de415335ea069c06370855366945a3704579ee18))
+* **install:** resolve version via redirect to avoid GitHub API rate limits ([5e1a641](https://github.com/rtk-ai/rtk/commit/5e1a64180f094ae456780a78b675f243312089c6))
+* **npm:** regex match end line ([5e84e94](https://github.com/rtk-ai/rtk/commit/5e84e9471736fe58e89094854f4123ecb07c2d3b))
+* **npx:** dispatch unknown tools to npx instead of npm ([2c4569c](https://github.com/rtk-ai/rtk/commit/2c4569caa64d013ad4ada0b7580f9f16d8334c19)), closes [#815](https://github.com/rtk-ai/rtk/issues/815)
+* remove wrong cicd benchmark + npm test regex ([7e3690a](https://github.com/rtk-ai/rtk/commit/7e3690a23ab158ca8e1e890650554e20e3a0c17b))
+* **stream:** add semgrep flag for sh tests ([7cfcdbe](https://github.com/rtk-ai/rtk/commit/7cfcdbec8681b15b794b6aef982ccb38feb79fd7))
+* **stream:** add semgrep flag for sh tests ([d327724](https://github.com/rtk-ai/rtk/commit/d327724f814b6875903366db0b0616780b454ad1))
+* **stream:** route to respective fd ([605e335](https://github.com/rtk-ai/rtk/commit/605e335f0546d2ed8554a95e7749a0b494c510e3))
+* **stream:** route to respective fd ([81a1be6](https://github.com/rtk-ai/rtk/commit/81a1be6a744942515347dd296ddcf7d9f126200d))
+* **tracking:** test env path ([70b36b4](https://github.com/rtk-ai/rtk/commit/70b36b4dbc3e147219ad87cf539d073523b86a85))
+
+## [0.37.2](https://github.com/rtk-ai/rtk/compare/v0.37.1...v0.37.2) (2026-04-20)
+
+
+### Bug Fixes
+
+* **discover:** exclude_commands bypass for env-prefix, sub cmd + regex ([ca4c59c](https://github.com/rtk-ai/rtk/commit/ca4c59c230306d310069bed3c0ba930068dc4dc4))
+* **discover:** exclude_commands bypass for env-prefix, sub cmd + regex ([42d3161](https://github.com/rtk-ai/rtk/commit/42d3161872713bc0b20ef49b0714add40c40d5e3))
+* **discover:** word boundary in exclude_commands ([0ea115b](https://github.com/rtk-ai/rtk/commit/0ea115bca5fa66daa69fda2f0eeaaf103346b3a4))
+* **docs:** add missing docs for exclude commands patterns ([2e401ac](https://github.com/rtk-ai/rtk/commit/2e401ac38feec88de8d5e46f0301c8a532b95614))
+* **hooks:** add regression test for windows native ([115e448](https://github.com/rtk-ai/rtk/commit/115e44853b8cdd2d7af3af2b52c9c31e924a45d3))
+* **hooks:** windows use 'rtk hook claude' no fallback ([da3c432](https://github.com/rtk-ai/rtk/commit/da3c432201240f0da9627d8cc6bc70e5b7f8bdfe))
+* **hooks:** windows use 'rtk hook claude' no fallback ([0e29650](https://github.com/rtk-ai/rtk/commit/0e29650e11959730f4c4a2e6d6c0519e14dc8595))
+* **tests:** windows regression test fix path ([13a73dd](https://github.com/rtk-ai/rtk/commit/13a73ddfd78460560a1f5fde94b54b1f848b41b5))
+
+## [0.37.1](https://github.com/rtk-ai/rtk/compare/v0.37.0...v0.37.1) (2026-04-18)
+
+
+### Bug Fixes
+
+* **docs:** user facing docs ([c8d6878](https://github.com/rtk-ai/rtk/commit/c8d68787fb8b31c52125e9fc7ea62e0aa590485f))
+
+## [0.37.0](https://github.com/rtk-ai/rtk/compare/v0.36.0...v0.37.0) (2026-04-17)
+
+
+### Features
+
+* **discover:** handle more npm/npx/pnpm/pnpx patterns ([9e96caa](https://github.com/rtk-ai/rtk/commit/9e96caa0a18a95c84da82ba57716a9d3ef86d0c8))
+* **refacto-core:** binary hook w/ native cmd exec + streaming ([e7b7f9a](https://github.com/rtk-ai/rtk/commit/e7b7f9ab665a0f7303d41d23ad156d24e5e8964e))
+
+
+### Bug Fixes
+
+* **docs:** use release please changelog no manual ([7591a14](https://github.com/rtk-ai/rtk/commit/7591a14e4ceb732ab7ca160ac01a852926abe77a))
+* isolate cursor hook tests from local settings (determinist) ([d8ddefe](https://github.com/rtk-ai/rtk/commit/d8ddefe78efe25c35bb2a2f9083f2eacb9dd7274))
+* P0+P1 fixes from pre-merge review of hook engine ([df8e035](https://github.com/rtk-ai/rtk/commit/df8e03558d4d6cc2f5cbac91c63ab1b3b51d3bcd))
+* P0+P1 fixes from pre-merge review of hook engine ([d34389c](https://github.com/rtk-ai/rtk/commit/d34389c3d0936c2b0790e14f450bb50a28a7edf7))
+* rename ship.md to ship/SKILL.md to match develop ([5916ecd](https://github.com/rtk-ai/rtk/commit/5916ecd86fb319c2519a0b4fb2891309833a3bb4))
+* **runner:** preserve fd separation on command failure ([e92d099](https://github.com/rtk-ai/rtk/commit/e92d0993c93f0b732316dfa932d265aeca7488d6))
+* **stream:** missing stderr fields ([a1d46f3](https://github.com/rtk-ai/rtk/commit/a1d46f39c291e3356b9c26a062bde05ba1de591a))
+
+## [0.36.0](https://github.com/rtk-ai/rtk/compare/v0.35.0...v0.36.0) (2026-04-13)
+
+
+### Features
+
+* **benchmark:** add multipass VM integration test suite ([6e7863b](https://github.com/rtk-ai/rtk/commit/6e7863bf313b0d18a47cf0ca2cdaea03cc2ed900))
+* **benchmark:** add multipass VM integration test suite ([d22759b](https://github.com/rtk-ai/rtk/commit/d22759b8c5254ad9c4a455f10cb7de75e92df581))
+* **benchmark:** add Swift ecosystem tests (6 commands + savings) ([1fbb6d9](https://github.com/rtk-ai/rtk/commit/1fbb6d935b4a0d031a7862cba312eebe1303ba9b))
+* **init:** add native support for Kilo Code and Google Antigravity ([d0a3797](https://github.com/rtk-ai/rtk/commit/d0a3797ec580f96948489d1e7c3329ac22a6c4eb))
+* **init:** add support for kilocode and antigravity agents ([66b90f1](https://github.com/rtk-ai/rtk/commit/66b90f1ed3de81acdce61164c068c24ed7ef29db))
+* **pnpm:** Add filter argument support ([2ba8d37](https://github.com/rtk-ai/rtk/commit/2ba8d372df186b4056a3b8906fc25cde8586dd42))
+* **skills:** add /pr-review skill for batch PR review ([21e67a1](https://github.com/rtk-ai/rtk/commit/21e67a1113041b74542d0285e5f74587dfb30b65))
+* **telemetry:** enrich daily ping with gap detection and quality metrics ([644c50f](https://github.com/rtk-ai/rtk/commit/644c50f786e5c567617e7ea907c5f312797b1265))
+
+
+### Bug Fixes
+
+* **benchmark:** address PR review feedback ([87ee81f](https://github.com/rtk-ai/rtk/commit/87ee81f08be5e7b1ca79513b1a91925d455f4f5c))
+* **benchmark:** address review feedback from @FlorianBruniaux ([d13c185](https://github.com/rtk-ai/rtk/commit/d13c185aac64d14288b574df44623723a69e7b95))
+* **ccusage:** add --yes flag and warn when falling back to npx ([f68fa00](https://github.com/rtk-ai/rtk/commit/f68fa0087c03d6882993b7b3eaee98e1dbab41b4))
+* **clippy:** show full error blocks instead of truncated headline ([95d9d13](https://github.com/rtk-ai/rtk/commit/95d9d134b0b76d83b6162614b0a79269b2135f40))
+* **clippy:** show full error blocks instead of truncated headline ([f4074f8](https://github.com/rtk-ai/rtk/commit/f4074f898a9b73b72bbcd8b18afab4831dcda406)), closes [#602](https://github.com/rtk-ai/rtk/issues/602)
+* **curl:** skip JSON schema conversion for internal/localhost URLs ([577c311](https://github.com/rtk-ai/rtk/commit/577c311ecaaa8ae94f22dbe252152424d4333d04))
+* **discover:** preserve golangci-lint flags in rewrite ([d85303e](https://github.com/rtk-ai/rtk/commit/d85303ec4893deb904260f5dc11b7df906a50c07))
+* **docs:** update TELEMETRY.md to match code after review fixes ([be5c057](https://github.com/rtk-ai/rtk/commit/be5c0576d95566f37f266fd9f92e2a1b263697bd))
+* **find:** include hidden files when pattern targets dotfiles ([#1101](https://github.com/rtk-ai/rtk/issues/1101)) ([dbeeaed](https://github.com/rtk-ai/rtk/commit/dbeeaed16aee79674ec2fd3778b7b11b10b847c6))
+* **git:** re-insert -- separator when clap consumes it from git diff args ([#1215](https://github.com/rtk-ai/rtk/issues/1215)) ([9979c69](https://github.com/rtk-ai/rtk/commit/9979c699307a4adad2c2df0f2bc3b663df653311))
+* **git:** remove -u short alias from --ultra-compact to fix git push -u ([6b76fdb](https://github.com/rtk-ai/rtk/commit/6b76fdb87d7c54cfc2a1b0e6117dd78b8430910b))
+* **golangci-lint:** restore run wrapper and align guidance ([4f4e4d2](https://github.com/rtk-ai/rtk/commit/4f4e4d2b5a3529030fe4089f60d2f4b8740b5d53))
+* **golangci-lint:** support inline global flags before run ([24f2ada](https://github.com/rtk-ai/rtk/commit/24f2adaf8fb541c2564fa7dfb423947932e68fb4))
+* **go:** prevent double-counted failures when test-level fail also triggers package-level fail ([#958](https://github.com/rtk-ai/rtk/issues/958)) ([4fc15ef](https://github.com/rtk-ai/rtk/commit/4fc15ef2c1c80336ffaafa4179db4cee6f39236a))
+* **go:** prevent double-counting failures when package-level fail cascades from test failures ([#958](https://github.com/rtk-ai/rtk/issues/958)) ([9722d5e](https://github.com/rtk-ai/rtk/commit/9722d5ebd8916f9b398bdc01b1102d42ab2b8795))
+* **hooks:** ensure default permission verdict prompts user for confirmation ([40462c0](https://github.com/rtk-ai/rtk/commit/40462c05e66f116928de365a0d271bdfd61cec72))
+* **hooks:** require all segments to match allow rules ([#1213](https://github.com/rtk-ai/rtk/issues/1213)) ([40c9dbc](https://github.com/rtk-ai/rtk/commit/40c9dbc7dbbf9332d6859060765c582a880f0fde))
+* **init:** honor CODEX_HOME for Codex global paths ([d442799](https://github.com/rtk-ai/rtk/commit/d442799e34d522c87a6eb60c2ff373385d201315))
+* **init:** install Codex global instructions in CODEX_HOME ([a257688](https://github.com/rtk-ai/rtk/commit/a2576883a27c5f915ba0ae7883a51006411b3ae5))
+* **json:** rename --schema to --keys-only, closes [#621](https://github.com/rtk-ai/rtk/issues/621) ([c16713a](https://github.com/rtk-ai/rtk/commit/c16713a973b563a6cba283c830b67c8c470e419f))
+* **ls:** filter quality wrong truncation ([aa6317f](https://github.com/rtk-ai/rtk/commit/aa6317fb83a5d9883623a4d3bee7a25bc99dcb4c))
+* **permissions:** glob_matches middle-wildcard matches commands without trailing args ([#1105](https://github.com/rtk-ai/rtk/issues/1105)) ([3db8070](https://github.com/rtk-ai/rtk/commit/3db8070b51b9a312fcca20a8460d3d6259cc38b7))
+* **pnpm:** list command not working ([ba235d8](https://github.com/rtk-ai/rtk/commit/ba235d85974c0a85b25e290a8bb83648800438a6))
+* **pytest:** -q mode summary line not detected ([57502a5](https://github.com/rtk-ai/rtk/commit/57502a5bef1fb56109a57cf2ea7377fd271253a7))
+* report package-level failures (timeouts, signals) in go test summary ([0b1c32b](https://github.com/rtk-ai/rtk/commit/0b1c32b3cc9a3e73418d401d1d481c1611c7ec0b))
+* report package-level failures (timeouts, signals) in go test summary ([c85a387](https://github.com/rtk-ai/rtk/commit/c85a387363e2079234b6141aad26418172c0e61a)), closes [#958](https://github.com/rtk-ai/rtk/issues/958)
+* **security:** correct email domain from .dev to .app ([47383e8](https://github.com/rtk-ai/rtk/commit/47383e80197fc56e38f880f33a6b54261b82523c))
+* **tee:** prevent panic on UTF-8 multi-byte truncation boundary ([da486bf](https://github.com/rtk-ai/rtk/commit/da486bf394330c804cd1cd12e4b6835f18de5205))
+* **telemetry:** 7 bugs in enrichment — privacy leak, broken meta_usage, pricing ([15f666d](https://github.com/rtk-ai/rtk/commit/15f666dd8dbd18648cb7bd14a6f9f3cac2f7d10b))
+* **telemetry:** clean code ([8156081](https://github.com/rtk-ai/rtk/commit/81560812610686fa5ca3633c2bf0b79c05eaa7d9))
+* **telemetry:** consent, erasure, auth, docs ([2e4cc4b](https://github.com/rtk-ai/rtk/commit/2e4cc4bb5226444c8c0bfc827baf0c101c3759e8))
+* **telemetry:** non-terminal consent, single config load ([7821e98](https://github.com/rtk-ai/rtk/commit/7821e9872fd1f1ae9b40eb8a4458049869acc36b))
+* **telemetry:** RGPD-compliant, consent gate, erasure, privacy controls ([6a5bc84](https://github.com/rtk-ai/rtk/commit/6a5bc847e06cf6066e6f4aeed5a3ad0803a3649b))
+
+## [0.35.0](https://github.com/rtk-ai/rtk/compare/v0.34.3...v0.35.0) (2026-04-06)
+
+
+### Features
+
+* **aws:** expand CLI filters from 8 to 25 subcommands ([402c48e](https://github.com/rtk-ai/rtk/commit/402c48e66988e638a5b4f4dd193238fc1d0fe18f))
+
+
+### Bug Fixes
+
+* **cmd:** read/cat multiple file and consistent behavior ([3f58018](https://github.com/rtk-ai/rtk/commit/3f58018f4af1d7206457929cf80bb4534203c3ee))
+* **docs:** clean some docs + disclaimer ([deda44f](https://github.com/rtk-ai/rtk/commit/deda44f73607981f3d27ecc6341ce927aab34d37))
+* **gh:** pass through gh pr merge instead of canned response ([#938](https://github.com/rtk-ai/rtk/issues/938)) ([8465ca9](https://github.com/rtk-ai/rtk/commit/8465ca953fa9d70dcc971a941c19465d456eb7d4))
+* **gh:** pass through gh pr merge instead of canned response ([#938](https://github.com/rtk-ai/rtk/issues/938)) ([e1f2845](https://github.com/rtk-ai/rtk/commit/e1f2845df06a8d8b8325945dc4940ec5f530e4cc))
+* **git:** inherit stdin for commit and push to preserve SSH signing ([#733](https://github.com/rtk-ai/rtk/issues/733)) ([eefeae4](https://github.com/rtk-ai/rtk/commit/eefeae45656ff2607c3f519c8eae235e3f0fe411))
+* **git:** inherit stdin for commit and push to preserve SSH signing ([#733](https://github.com/rtk-ai/rtk/issues/733)) ([6cee6c6](https://github.com/rtk-ai/rtk/commit/6cee6c60b80f914ed9505e3925d85cadec43ab97))
+* **git:** preserve full diff hunk headers ([62f4452](https://github.com/rtk-ai/rtk/commit/62f445227679f3df293fe35e9b18cc5ab39d7963))
+* **git:** preserve full diff hunk headers ([09b3ff9](https://github.com/rtk-ai/rtk/commit/09b3ff9424e055f5fe25e535e5b60e077f8344f9))
+* **go:** avoid false build errors from download logs ([9c1cf2f](https://github.com/rtk-ai/rtk/commit/9c1cf2f403534fa7874638b1b983c2d7f918a185))
+* **go:** avoid false build errors from download logs ([d44fd3e](https://github.com/rtk-ai/rtk/commit/d44fd3e034208e3bcd59c2c46f7720eec4f10c98))
+* **go:** cover more build failure shapes ([2425ad6](https://github.com/rtk-ai/rtk/commit/2425ad68e5386d19e5ec9ff1ca151a6d2c9a56d3))
+* **go:** preserve failing test location context ([1481bc5](https://github.com/rtk-ai/rtk/commit/1481bc590924031456a6022510275c29c09e330e))
+* **go:** preserve failing test location context ([374fe64](https://github.com/rtk-ai/rtk/commit/374fe64cfbedcd676733973e81a63a6dfecbb1b7))
+* **go:** restore build error coverage ([1177c9c](https://github.com/rtk-ai/rtk/commit/1177c9c873ac63b6c0bcc9e1b664a705baa0ad7a))
+* **grep:** close subprocess stdin to prevent memory leak ([#897](https://github.com/rtk-ai/rtk/issues/897)) ([7217562](https://github.com/rtk-ai/rtk/commit/72175623551f40b581b4a7f6ed966c1e4a9c7358))
+* **grep:** close subprocess stdin to prevent memory leak ([#897](https://github.com/rtk-ai/rtk/issues/897)) ([09979cf](https://github.com/rtk-ai/rtk/commit/09979cf29701a1b775bcac761d24ec0e055d1bec))
+* **hook_check:** detect missing integrations ([9cf9ccc](https://github.com/rtk-ai/rtk/commit/9cf9ccc1ac39f8bba37e932c7d318a3aa7a34ae9))
+* **init:** remove opt-out instruction from telemetry message ([7571c8e](https://github.com/rtk-ai/rtk/commit/7571c8e101c41ee64c51e2bd64697f85f9142423))
+* **init:** remove telemetry info lines from init output ([7dbef2c](https://github.com/rtk-ai/rtk/commit/7dbef2ce00824d26f2057e4c3c76e429e2e23088))
+* **main:** kill zombie processes + path for rtk md ([d16fc6d](https://github.com/rtk-ai/rtk/commit/d16fc6dacbfec912c21522939b15b7bbd9719487))
+* **main:** kill zombie processes + path for rtk md + missing intergrations ([a919335](https://github.com/rtk-ai/rtk/commit/a919335519ed4a5259a212e56407cb312aa99bac))
+* **merge:** changelog conflicts ([d92c5d2](https://github.com/rtk-ai/rtk/commit/d92c5d264a49483c8d6079e04d946a79bc990a74))
+* **proxy:** kill child process on SIGINT/SIGTERM to prevent orphans ([d813919](https://github.com/rtk-ai/rtk/commit/d813919a24546e044e7844fc7ed05fef4ec24033))
+* **proxy:** kill child process on SIGINT/SIGTERM to prevent orphans ([3318510](https://github.com/rtk-ai/rtk/commit/33185101fc122d0c11a25a4e02ac9f3a7dc7e3bb))
+* **review:** address ChildGuard disarm, stdin dedup, hook masking ([d85fe33](https://github.com/rtk-ai/rtk/commit/d85fe3384b87c16fafd25ec7bcadbff6e69f3f1f))
+* **security:** default to ask when no permission rule matches ([#886](https://github.com/rtk-ai/rtk/issues/886)) ([158c745](https://github.com/rtk-ai/rtk/commit/158c74527f6591d372e40a78cd604d73a20649a9))
+* **security:** default to ask when no permission rule matches ([#886](https://github.com/rtk-ai/rtk/issues/886)) ([41a6c6b](https://github.com/rtk-ai/rtk/commit/41a6c6bf6da78a4754794fdc6a1469df2e327920))
+* **tracking:** use std::env::temp_dir() for compatibility (instead of unix tmp) ([e918661](https://github.com/rtk-ai/rtk/commit/e918661440d7b50321f0535032f52c5e87aaf3cb))
+
+## [Unreleased]
+
+### Bug Fixes
+
+* **git:** remove `-u` short alias from `--ultra-compact` to fix `git push -u` upstream tracking ([#1086](https://github.com/rtk-ai/rtk/issues/1086))
+
+## [0.35.0](https://github.com/rtk-ai/rtk/compare/v0.34.3...v0.35.0) (2026-04-06)
+
+
+### Features
+
+* **aws:** expand CLI filters from 8 to 25 subcommands ([402c48e](https://github.com/rtk-ai/rtk/commit/402c48e66988e638a5b4f4dd193238fc1d0fe18f))
+
+
+### Bug Fixes
+
+* **cmd:** read/cat multiple file and consistent behavior ([3f58018](https://github.com/rtk-ai/rtk/commit/3f58018f4af1d7206457929cf80bb4534203c3ee))
+* **docs:** clean some docs + disclaimer ([deda44f](https://github.com/rtk-ai/rtk/commit/deda44f73607981f3d27ecc6341ce927aab34d37))
+* **gh:** pass through gh pr merge instead of canned response ([#938](https://github.com/rtk-ai/rtk/issues/938)) ([8465ca9](https://github.com/rtk-ai/rtk/commit/8465ca953fa9d70dcc971a941c19465d456eb7d4))
+* **gh:** pass through gh pr merge instead of canned response ([#938](https://github.com/rtk-ai/rtk/issues/938)) ([e1f2845](https://github.com/rtk-ai/rtk/commit/e1f2845df06a8d8b8325945dc4940ec5f530e4cc))
+* **git:** inherit stdin for commit and push to preserve SSH signing ([#733](https://github.com/rtk-ai/rtk/issues/733)) ([eefeae4](https://github.com/rtk-ai/rtk/commit/eefeae45656ff2607c3f519c8eae235e3f0fe411))
+* **git:** inherit stdin for commit and push to preserve SSH signing ([#733](https://github.com/rtk-ai/rtk/issues/733)) ([6cee6c6](https://github.com/rtk-ai/rtk/commit/6cee6c60b80f914ed9505e3925d85cadec43ab97))
+* **git:** preserve full diff hunk headers ([62f4452](https://github.com/rtk-ai/rtk/commit/62f445227679f3df293fe35e9b18cc5ab39d7963))
+* **git:** preserve full diff hunk headers ([09b3ff9](https://github.com/rtk-ai/rtk/commit/09b3ff9424e055f5fe25e535e5b60e077f8344f9))
+* **go:** avoid false build errors from download logs ([9c1cf2f](https://github.com/rtk-ai/rtk/commit/9c1cf2f403534fa7874638b1b983c2d7f918a185))
+* **go:** avoid false build errors from download logs ([d44fd3e](https://github.com/rtk-ai/rtk/commit/d44fd3e034208e3bcd59c2c46f7720eec4f10c98))
+* **go:** cover more build failure shapes ([2425ad6](https://github.com/rtk-ai/rtk/commit/2425ad68e5386d19e5ec9ff1ca151a6d2c9a56d3))
+* **go:** preserve failing test location context ([1481bc5](https://github.com/rtk-ai/rtk/commit/1481bc590924031456a6022510275c29c09e330e))
+* **go:** preserve failing test location context ([374fe64](https://github.com/rtk-ai/rtk/commit/374fe64cfbedcd676733973e81a63a6dfecbb1b7))
+* **go:** restore build error coverage ([1177c9c](https://github.com/rtk-ai/rtk/commit/1177c9c873ac63b6c0bcc9e1b664a705baa0ad7a))
+* **grep:** close subprocess stdin to prevent memory leak ([#897](https://github.com/rtk-ai/rtk/issues/897)) ([7217562](https://github.com/rtk-ai/rtk/commit/72175623551f40b581b4a7f6ed966c1e4a9c7358))
+* **grep:** close subprocess stdin to prevent memory leak ([#897](https://github.com/rtk-ai/rtk/issues/897)) ([09979cf](https://github.com/rtk-ai/rtk/commit/09979cf29701a1b775bcac761d24ec0e055d1bec))
+* **hook_check:** detect missing integrations ([9cf9ccc](https://github.com/rtk-ai/rtk/commit/9cf9ccc1ac39f8bba37e932c7d318a3aa7a34ae9))
+* **init:** remove opt-out instruction from telemetry message ([7571c8e](https://github.com/rtk-ai/rtk/commit/7571c8e101c41ee64c51e2bd64697f85f9142423))
+* **init:** remove telemetry info lines from init output ([7dbef2c](https://github.com/rtk-ai/rtk/commit/7dbef2ce00824d26f2057e4c3c76e429e2e23088))
+* **main:** kill zombie processes + path for rtk md ([d16fc6d](https://github.com/rtk-ai/rtk/commit/d16fc6dacbfec912c21522939b15b7bbd9719487))
+* **main:** kill zombie processes + path for rtk md + missing intergrations ([a919335](https://github.com/rtk-ai/rtk/commit/a919335519ed4a5259a212e56407cb312aa99bac))
+* **merge:** changelog conflicts ([d92c5d2](https://github.com/rtk-ai/rtk/commit/d92c5d264a49483c8d6079e04d946a79bc990a74))
+* **proxy:** kill child process on SIGINT/SIGTERM to prevent orphans ([d813919](https://github.com/rtk-ai/rtk/commit/d813919a24546e044e7844fc7ed05fef4ec24033))
+* **proxy:** kill child process on SIGINT/SIGTERM to prevent orphans ([3318510](https://github.com/rtk-ai/rtk/commit/33185101fc122d0c11a25a4e02ac9f3a7dc7e3bb))
+* **review:** address ChildGuard disarm, stdin dedup, hook masking ([d85fe33](https://github.com/rtk-ai/rtk/commit/d85fe3384b87c16fafd25ec7bcadbff6e69f3f1f))
+* **security:** default to ask when no permission rule matches ([#886](https://github.com/rtk-ai/rtk/issues/886)) ([158c745](https://github.com/rtk-ai/rtk/commit/158c74527f6591d372e40a78cd604d73a20649a9))
+* **security:** default to ask when no permission rule matches ([#886](https://github.com/rtk-ai/rtk/issues/886)) ([41a6c6b](https://github.com/rtk-ai/rtk/commit/41a6c6bf6da78a4754794fdc6a1469df2e327920))
+* **tracking:** use std::env::temp_dir() for compatibility (instead of unix tmp) ([e918661](https://github.com/rtk-ai/rtk/commit/e918661440d7b50321f0535032f52c5e87aaf3cb))
+
+## [Unreleased]
+
+### Features
+
+* **aws:** expand CLI filters from 8 to 25 subcommands — CloudWatch Logs, CloudFormation events, Lambda, IAM, DynamoDB (with type unwrapping), ECS tasks, EC2 security groups, S3API objects, S3 sync/cp, EKS, SQS, Secrets Manager ([#885](https://github.com/rtk-ai/rtk/pull/885))
+* **aws:** add shared runner `run_aws_filtered()` eliminating per-handler boilerplate
+* **tee:** add `force_tee_hint()` — truncated output saves full data to file with recovery hint
+
+## [0.34.3](https://github.com/rtk-ai/rtk/compare/v0.34.2...v0.34.3) (2026-04-02)
+
+
+### Bug Fixes
+
+* **automod:** add auto discovery for cmds ([234909d](https://github.com/rtk-ai/rtk/commit/234909d2c754ade2fdc939b0a1435a8e34ffc305))
+* **ci:** fix validate-docs.sh broken module count check ([bbe3da6](https://github.com/rtk-ai/rtk/commit/bbe3da642b5fc4b065b13a65647ea0ebf5264e65))
+* **cleaning:** constant extract ([aabc016](https://github.com/rtk-ai/rtk/commit/aabc0167bc013fd2d0c61a687580f6e69305500a))
+* **cmds:** migrate remaining exit_code to exit_code_from_output ([ba9fa34](https://github.com/rtk-ai/rtk/commit/ba9fa345f3d1d14bd0af236ec9aa8a9a0e5581d6))
+* **cmds:** more covering for run_filtered ([e48485a](https://github.com/rtk-ai/rtk/commit/e48485adc6a33d12b70664598020595cf7dfcd7e))
+* **docs:** add documentation ([2f7278a](https://github.com/rtk-ai/rtk/commit/2f7278ac5992bf2e84b763fb05642d89900ba495))
+* **docs:** add maintainers docs ([14265b4](https://github.com/rtk-ai/rtk/commit/14265b48c3a15e459a31da11250a51ab5830a508))
+* **refacto-p1:** unified cmds execution flow (+ rm dead code) ([75bd607](https://github.com/rtk-ai/rtk/commit/75bd607d55235f313855f5fe8c9eceafd73700a7))
+* **refacto-p2:** more standardize ([47a76ea](https://github.com/rtk-ai/rtk/commit/47a76ea35ed2fe02a3600792163f727fa3a94ff2))
+* **refacto-p2:** more standardize ([92c671a](https://github.com/rtk-ai/rtk/commit/92c671a175a5e2bf09720fd1a8591140bcb473a0))
+* **refacto:** wrappers for standardization, exit codes lexer tokenizer, constants, code clean ([bff0258](https://github.com/rtk-ai/rtk/commit/bff02584243f1b73418418b0c05365acf56fbb36))
+* **registry:** quoted env prefix + inline regex cleanup + routing docs ([f3217a4](https://github.com/rtk-ai/rtk/commit/f3217a467b543a3181605b257162f2b3ab5d5df0))
+* **review:** address PR [#910](https://github.com/rtk-ai/rtk/issues/910) review feedback ([0a8b8fd](https://github.com/rtk-ai/rtk/commit/0a8b8fd0693fa504f376146cbbcafe9ddf4632c8))
+* **review:** PR [#934](https://github.com/rtk-ai/rtk/issues/934) ([5bd35a3](https://github.com/rtk-ai/rtk/commit/5bd35a33ad6abe5278749726bed19912664531c2))
+* **review:** PR [#934](https://github.com/rtk-ai/rtk/issues/934) ([bae7930](https://github.com/rtk-ai/rtk/commit/bae79301194bbb48d1cbb39554096c3225f7cb73))
+* **rules:** add wc RtkRule with pattern field for develop compat ([d75e864](https://github.com/rtk-ai/rtk/commit/d75e864f20451a5e17918c75f2ea32672f65e1f4))
+* **standardize:** git+kube sub wrappers run_filtered ([7fd221f](https://github.com/rtk-ai/rtk/commit/7fd221f44660bcf411aa333d2c35a49ff89e7961))
+* **standardize:** merge pattern into rues ([08aabb9](https://github.com/rtk-ai/rtk/commit/08aabb95c3ae6e0b734f696264e1e1a8c0f0b22e))
+
## [0.34.2](https://github.com/rtk-ai/rtk/compare/v0.34.1...v0.34.2) (2026-03-30)
diff --git a/CLAUDE.md b/CLAUDE.md
index 0dddf14e5..b80dd1307 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -11,12 +11,12 @@ This is a fork with critical fixes for git argument parsing and modern JavaScrip
### Name Collision Warning
**Two different "rtk" projects exist:**
-- This project: Rust Token Killer (rtk-ai/rtk)
+- This project: Rust Token Killer (algolia/rtk, fork of rtk-ai/rtk)
- reachingforthejack/rtk: Rust Type Kit (DIFFERENT - generates Rust types)
**Verify correct installation:**
```bash
-rtk --version # Should show "rtk 0.28.2" (or newer)
+rtk --version # Should show the version from Cargo.toml (rtk 0.38.0-algolia.1 or newer)
rtk gain # Should show token savings stats (NOT "command not found")
```
@@ -70,11 +70,13 @@ cargo generate-rpm # RPM package (needs cargo-generate-rpm, after rel
rtk uses a **command proxy architecture**: `main.rs` routes CLI commands via a Clap `Commands` enum to specialized filter modules in `src/cmds/*/`, each of which executes the underlying command and compresses its output. Token savings are tracked in SQLite via `src/core/tracking.rs`.
For the full architecture, component details, and module development patterns, see:
-- [ARCHITECTURE.md](ARCHITECTURE.md) — System design, module organization, filtering strategies, error handling
-- [docs/TECHNICAL.md](docs/TECHNICAL.md) — End-to-end flow, folder map, hook system, filter pipeline
+- [ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md) — System design, module organization, filtering strategies, error handling
+- [docs/contributing/TECHNICAL.md](docs/contributing/TECHNICAL.md) — End-to-end flow, folder map, hook system, filter pipeline
Module responsibilities are documented in each folder's `README.md` and each file's `//!` doc header. Browse `src/cmds/*/` to discover available filters.
+Supported ecosystems: git/gh/gt, cargo, go/golangci-lint, npm/pnpm/npx, ruff/pytest/pip/mypy, rspec/rubocop/rake, dotnet, playwright/vitest/jest, docker/kubectl/aws.
+
### Proxy Mode
**Purpose**: Execute commands without filtering but track usage for metrics.
@@ -158,6 +160,124 @@ git branch # Verify correct branch (main, feature/*, etc.)
- "Should I test Y edge case?" → ASK if not mentioned in requirements
- "Should I verify Z across N platforms?" → ASK if N > 2
+## Fork Policy (algolia/rtk)
+
+This repository is a **fork** of [rtk-ai/rtk](https://github.com/rtk-ai/rtk).
+The fork stays as thin as possible: upstream is authoritative, we add the
+minimum needed for Algolia.
+
+### What we change from upstream
+- **Telemetry stripped**: no phone-home, no `ureq` / `hostname` deps,
+ no opt-out config, no `RTK_TELEMETRY_*` env vars
+- **No Homebrew tap**: install via `cargo install --git` or `install.sh`
+- **Branding**: all install URLs and repo references point to `algolia/rtk`
+- **Targeted bug fixes**: only when upstream hasn't addressed the issue.
+ Each lives under `bug-reports/` with a reproducer.
+
+### What we DON'T change
+- All upstream features, filters, commands, TOML DSL, rewrite engine
+- Test suite (must pass)
+- Architecture and module structure
+
+### Upstream Catchup Procedure (re-fork strategy)
+
+When upstream has new releases to absorb, **do not merge** — re-fork.
+Merging accumulates conflict-resolution noise and can silently re-apply
+patches that upstream has fixed. The re-fork procedure forces patch
+re-validation at every catchup:
+
+```bash
+# 1. Fetch upstream
+git remote add upstream https://github.com/rtk-ai/rtk.git # one-time
+git fetch upstream
+
+# 2. Branch off upstream/master directly
+git checkout -b fork/upstream-realign-vX.Y.Z upstream/master
+
+# 3. Strip telemetry (always required)
+# - Delete src/core/telemetry.rs, src/core/telemetry_cmd.rs
+# - Remove `pub mod telemetry;` and `pub mod telemetry_cmd;` from
+# src/core/mod.rs
+# - Remove `core::telemetry::maybe_ping();` from main.rs
+# - Remove the `Telemetry` Commands enum variant + match arm in main.rs
+# - Remove TelemetryConfig + tests from src/core/config.rs
+# - Remove prompt_telemetry_consent / save_telemetry_consent from
+# src/hooks/init.rs and the call site
+# - Remove `ureq` (and `hostname` if present) from Cargo.toml
+# - Remove RTK_TELEMETRY_URL/TOKEN env vars from .github/workflows/release.yml
+# - Delete docs/TELEMETRY.md, docs/guide/resources/telemetry.md
+# - Strip Privacy & Telemetry sections from README*.md and other docs
+
+# 4. Fork hygiene rebrand (see below)
+# sed-based rewrite of rtk-ai/rtk → algolia/rtk in install instructions,
+# rtk-ai.app links, master → main where appropriate
+
+# 5. Re-test bug-reports/*.md against the new base.
+# Upstream fixes most issues over time — re-apply ONLY patches still
+# needed. Each preserved patch should reference its bug-report file
+# in the commit message.
+
+# 6. Bump version in Cargo.toml: 0.X.Y → 0.X.Y-algolia.1
+# Also update .release-please-manifest.json
+
+# 7. Build + test
+cargo fmt --all && cargo clippy --all-targets && cargo test --all
+
+# 8. Open a PR from fork/upstream-realign-vX.Y.Z against main.
+# The PR diff against main is large (300+ commits absorbed), but the
+# diff against upstream/master is small (telemetry strip + 2-3
+# targeted patches + rebrand). Reviewers should review it the second
+# way: `git diff upstream/master..fork/upstream-realign-vX.Y.Z`.
+
+# 9. Once approved, the merge replaces main. Force-push if your team's
+# convention requires linear history; merge commit if not.
+```
+
+**Key principle**: every catchup re-validates every patch. Patches that
+upstream silently fixed get dropped automatically because the re-fork
+starts from a clean upstream tree.
+
+## Fork Hygiene (Mandatory)
+
+This is the **Algolia fork** (`algolia/rtk`), not the upstream (`rtk-ai/rtk`).
+Upstream references leak in during rebases and releases. **Every rebase
+and every release MUST include a fork hygiene check.**
+
+### Pre-commit Checklist (after rebase or before release)
+
+Run this grep to catch upstream leaks:
+
+```bash
+rg -i 'brew install rtk[^-]|rtk-ai\.app|contact@rtk|"rtk 0\.\d+\.\d+"' --glob '*.md' --glob '*.rb'
+```
+
+**Zero matches required.** Any hit must be fixed before commit. Historical
+references in `CHANGELOG.md` (auto-generated from upstream commits) are
+the one exception.
+
+### Banned Patterns in User-Facing Docs
+
+| Pattern | Why | Replace With |
+|---------|-----|--------------|
+| `brew install rtk` | No Homebrew tap for fork | `cargo install --git https://github.com/algolia/rtk` or `curl \| sh` |
+| `https://www.rtk-ai.app` | Upstream website | Remove or use `https://github.com/algolia/rtk` |
+| `contact@rtk-ai.app` | Upstream email | `#proj-internal-skills` on Slack |
+| `rtk-ai/rtk` in install instructions | Upstream repo | `algolia/rtk` |
+| `brew uninstall rtk` | No Homebrew install exists | `cargo uninstall rtk` |
+| Hardcoded version strings (`"rtk 0.28.2"`) | Goes stale on every release | Use current `Cargo.toml` version |
+
+### Where to Check
+
+All `README*.md`, `INSTALL.md`, `CLAUDE.md`, `openclaw/README.md`,
+`Formula/rtk.rb`, GitHub repo metadata (`gh repo edit --homepage`).
+
+### On Release
+
+1. Run the grep above — fix any matches
+2. Update version strings in docs to match `Cargo.toml`
+3. Verify `gh repo view --json homepageUrl` returns the algolia URL
+ (not upstream)
+
## Plan Execution Protocol
When user provides a numbered plan (QW1-QW4, Phase 1-5, sprint tasks, etc.):
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 08e5ec0ac..6cd87369b 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -7,7 +7,7 @@
- [Report an Issue](../../issues/new)
- [Open Pull Requests](../../pulls)
- [Start a Discussion](../../discussions)
-- [Technical Documentation](docs/TECHNICAL.md) — Architecture, end-to-end flow, folder map, how to write tests
+- [Technical Documentation](docs/contributing/TECHNICAL.md) — Architecture, end-to-end flow, folder map, how to write tests
---
@@ -107,15 +107,50 @@ For the step-by-step checklist (create filter, register rewrite pattern, registe
---
+## Commit Messages & Changelog
+
+RTK uses [Conventional Commits](https://www.conventionalcommits.org/) and [release-please](https://github.com/googleapis/release-please) to **auto-generate CHANGELOG.md, version bumps, and GitHub releases**. Never edit `CHANGELOG.md` manually — it is fully managed by release-please from your commit messages.
+
+### Commit format
+
+```
+():
+```
+
+| Type | Semver Impact | When to Use |
+|------|---------------|-------------|
+| `feat` | Minor | New features, new filters, new command support |
+| `fix` | Patch | Bug fixes, corrections |
+| `perf` | Patch | Performance improvements |
+| `refactor` | — | Code restructuring (no changelog entry) |
+| `docs` | — | Documentation only |
+| `chore` | — | Maintenance, CI, deps |
+| `feat!` / `fix!` | Major | Breaking changes (add `!` after type) |
+
+**Scope** should match the module or area: `git`, `cargo`, `gh`, `hook`, `tracking`, `cicd`, etc.
+
+### Examples
+
+```
+feat(kubectl): add pod log filtering
+fix(git): preserve merge commit messages in log filter
+perf(cargo): lazy-compile clippy regex patterns
+feat!(hook): change rewrite config format
+```
+
+These commit messages directly become CHANGELOG entries when release-please creates a release PR. Write them as if they will be read by users.
+
+---
+
## Branch Naming Convention
Git branch names cannot include spaces or colons, so we use slash-prefixed names. Pick the prefix that matches your change type and follow it with an optional scope and a short, kebab-case description.
-| Prefix | Semver Impact | When to Use |
-|--------|---------------|-------------|
-| `fix/` | Patch | Bug fixes, corrections, minor adjustments |
-| `feat/` | Minor | New features, new filters, new command support |
-| `chore/` | Major | Breaking changes, API changes, removed functionality |
+| Prefix | When to Use |
+|--------|-------------|
+| `fix/` | Bug fixes, corrections, minor adjustments |
+| `feat/` | New features, new filters, new command support |
+| `chore/` | CI/CD, deps, maintenance, breaking changes |
Combine the prefix with a scope if it adds clarity (e.g. `git`, `kubectl`, `filter`, `tracking`, `config`) and finish with a descriptive slug: `fix/-` or `feat/`.
@@ -137,7 +172,7 @@ chore/release-pipeline-cleanup
**For large features or refactors**, prefer multi-part PRs over one enormous PR. Split the work into logical, reviewable chunks that can each be merged independently. Examples:
- feat(Part 1): Add data model and tests
- feat(Part 2): Add CLI command and integration
-- feat(Part 3): Update documentation and CHANGELOG
+- feat(Part 3): Update documentation
**Why**: Small, focused PRs are easier to review, safer to merge, and faster to ship. Large PRs slow down review, hide bugs, and increase merge conflict risk.
@@ -166,7 +201,7 @@ Every change **must** include tests. See [Testing](#testing) below.
### 4. Add Documentation
-Every change **must** include documentation updates. See [Documentation](#documentation) below.
+Documentation updates are required for new filters, new features, and changes that affect already-documented behavior. Bug fixes and refactors typically don't need doc updates. See [Documentation](#documentation) below.
### Contributor License Agreement (CLA)
@@ -203,7 +238,7 @@ your branch --> develop (review + CI + integration testing) --> version branch -
Every change **must** include tests. We follow **TDD (Red-Green-Refactor)**: write a failing test first, implement the minimum to pass, then refactor.
-For how to write tests (fixtures, snapshots, token savings verification), see [docs/TECHNICAL.md — Testing](docs/TECHNICAL.md#testing).
+For how to write tests (fixtures, snapshots, token savings verification), see [docs/contributing/TECHNICAL.md — Testing](docs/contributing/TECHNICAL.md#testing).
### Test Types
@@ -235,19 +270,20 @@ cargo fmt --all --check && cargo clippy --all-targets && cargo test
## Documentation
-Every change **must** include documentation updates. Use this table to find which docs to update:
+Documentation updates are required for new filters, new features, and changes that affect already-documented behavior. Use this table to find which docs to update:
| What you changed | Update these docs |
|------------------|-------------------|
-| New Rust filter (`src/cmds/`) | Ecosystem `README.md` (e.g., `src/cmds/git/README.md`), [README.md](README.md) command list, [CHANGELOG.md](CHANGELOG.md) |
-| New TOML filter (`src/filters/`) | [src/filters/README.md](src/filters/README.md) if naming conventions change, [README.md](README.md) command list, [CHANGELOG.md](CHANGELOG.md) |
+| New Rust filter (`src/cmds/`) | Ecosystem `README.md` (e.g., `src/cmds/git/README.md`), [README.md](README.md) command list |
+| New TOML filter (`src/filters/`) | [src/filters/README.md](src/filters/README.md) if naming conventions change, [README.md](README.md) command list |
| New rewrite pattern | `src/discover/rules.rs` — see [Adding a New Command Filter](src/cmds/README.md#adding-a-new-command-filter) |
-| Core infrastructure (`src/core/`) | [src/core/README.md](src/core/README.md), [docs/TECHNICAL.md](docs/TECHNICAL.md) if flow changes |
+| Core infrastructure (`src/core/`) | [src/core/README.md](src/core/README.md), [docs/contributing/TECHNICAL.md](docs/contributing/TECHNICAL.md) if flow changes |
| Hook system (`src/hooks/`) | [src/hooks/README.md](src/hooks/README.md), [hooks/README.md](hooks/README.md) for agent-facing docs |
-| Architecture or design change | [ARCHITECTURE.md](ARCHITECTURE.md), [docs/TECHNICAL.md](docs/TECHNICAL.md) |
-| Bug fix or breaking change | [CHANGELOG.md](CHANGELOG.md) |
+| Architecture or design change | [ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md), [docs/contributing/TECHNICAL.md](docs/contributing/TECHNICAL.md) |
+
+> **Note**: Do NOT edit `CHANGELOG.md` manually — it is auto-generated by [release-please](https://github.com/googleapis/release-please) from your commit messages. See [Commit Messages & Changelog](#commit-messages--changelog).
-**Navigation**: [CONTRIBUTING.md](CONTRIBUTING.md) (you are here) → [docs/TECHNICAL.md](docs/TECHNICAL.md) (architecture + flow) → each folder's `README.md` (implementation details).
+**Navigation**: [CONTRIBUTING.md](CONTRIBUTING.md) (you are here) → [docs/contributing/TECHNICAL.md](docs/contributing/TECHNICAL.md) (architecture + flow) → each folder's `README.md` (implementation details).
Keep documentation concise and practical -- examples over explanations.
diff --git a/Cargo.lock b/Cargo.lock
index 49b7c1aef..1a15fce9b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -101,10 +101,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
-name = "base64"
-version = "0.22.1"
+name = "automod"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+checksum = "e8b5778837666541195063243828c5b6139221b47dc4ec3ba81738e532469ab1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "bitflags"
@@ -312,17 +317,6 @@ dependencies = [
"windows-sys 0.48.0",
]
-[[package]]
-name = "displaydoc"
-version = "0.2.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "env_home"
version = "0.1.0"
@@ -385,15 +379,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
-[[package]]
-name = "form_urlencoded"
-version = "1.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
-dependencies = [
- "percent-encoding",
-]
-
[[package]]
name = "generic-array"
version = "0.14.7"
@@ -480,17 +465,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
-[[package]]
-name = "hostname"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
-dependencies = [
- "cfg-if",
- "libc",
- "windows-link",
-]
-
[[package]]
name = "iana-time-zone"
version = "0.1.65"
@@ -515,114 +489,12 @@ dependencies = [
"cc",
]
-[[package]]
-name = "icu_collections"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43"
-dependencies = [
- "displaydoc",
- "potential_utf",
- "yoke",
- "zerofrom",
- "zerovec",
-]
-
-[[package]]
-name = "icu_locale_core"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6"
-dependencies = [
- "displaydoc",
- "litemap",
- "tinystr",
- "writeable",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599"
-dependencies = [
- "icu_collections",
- "icu_normalizer_data",
- "icu_properties",
- "icu_provider",
- "smallvec",
- "zerovec",
-]
-
-[[package]]
-name = "icu_normalizer_data"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a"
-
-[[package]]
-name = "icu_properties"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec"
-dependencies = [
- "icu_collections",
- "icu_locale_core",
- "icu_properties_data",
- "icu_provider",
- "zerotrie",
- "zerovec",
-]
-
-[[package]]
-name = "icu_properties_data"
-version = "2.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af"
-
-[[package]]
-name = "icu_provider"
-version = "2.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614"
-dependencies = [
- "displaydoc",
- "icu_locale_core",
- "writeable",
- "yoke",
- "zerofrom",
- "zerotrie",
- "zerovec",
-]
-
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
-[[package]]
-name = "idna"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
-dependencies = [
- "idna_adapter",
- "smallvec",
- "utf8_iter",
-]
-
-[[package]]
-name = "idna_adapter"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344"
-dependencies = [
- "icu_normalizer",
- "icu_properties",
-]
-
[[package]]
name = "ignore"
version = "0.4.25"
@@ -717,12 +589,6 @@ version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
-[[package]]
-name = "litemap"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77"
-
[[package]]
name = "log"
version = "0.4.29"
@@ -772,27 +638,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
-[[package]]
-name = "percent-encoding"
-version = "2.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
-
[[package]]
name = "pkg-config"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
-[[package]]
-name = "potential_utf"
-version = "0.1.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77"
-dependencies = [
- "zerovec",
-]
-
[[package]]
name = "prettyplease"
version = "0.2.37"
@@ -876,34 +727,21 @@ version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
-[[package]]
-name = "ring"
-version = "0.17.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
-dependencies = [
- "cc",
- "cfg-if",
- "getrandom 0.2.17",
- "libc",
- "untrusted",
- "windows-sys 0.52.0",
-]
-
[[package]]
name = "rtk"
-version = "0.34.2"
+version = "0.38.0-algolia.1"
dependencies = [
"anyhow",
+ "automod",
"chrono",
"clap",
"colored",
"dirs",
"flate2",
"getrandom 0.4.2",
- "hostname",
"ignore",
"lazy_static",
+ "libc",
"quick-xml",
"regex",
"rusqlite",
@@ -911,9 +749,7 @@ dependencies = [
"serde_json",
"sha2",
"tempfile",
- "thiserror",
"toml",
- "ureq",
"walkdir",
"which",
]
@@ -945,41 +781,6 @@ dependencies = [
"windows-sys 0.61.2",
]
-[[package]]
-name = "rustls"
-version = "0.23.37"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4"
-dependencies = [
- "log",
- "once_cell",
- "ring",
- "rustls-pki-types",
- "rustls-webpki",
- "subtle",
- "zeroize",
-]
-
-[[package]]
-name = "rustls-pki-types"
-version = "1.14.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd"
-dependencies = [
- "zeroize",
-]
-
-[[package]]
-name = "rustls-webpki"
-version = "0.103.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
-dependencies = [
- "ring",
- "rustls-pki-types",
- "untrusted",
-]
-
[[package]]
name = "rustversion"
version = "1.0.22"
@@ -1083,24 +884,12 @@ version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
-[[package]]
-name = "stable_deref_trait"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
-
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
-[[package]]
-name = "subtle"
-version = "2.6.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
-
[[package]]
name = "syn"
version = "2.0.117"
@@ -1112,17 +901,6 @@ dependencies = [
"unicode-ident",
]
-[[package]]
-name = "synstructure"
-version = "0.13.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "tempfile"
version = "3.26.0"
@@ -1156,16 +934,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "tinystr"
-version = "0.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
-dependencies = [
- "displaydoc",
- "zerovec",
-]
-
[[package]]
name = "toml"
version = "0.8.23"
@@ -1225,46 +993,6 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
-[[package]]
-name = "untrusted"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
-
-[[package]]
-name = "ureq"
-version = "2.12.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d"
-dependencies = [
- "base64",
- "flate2",
- "log",
- "once_cell",
- "rustls",
- "rustls-pki-types",
- "url",
- "webpki-roots 0.26.11",
-]
-
-[[package]]
-name = "url"
-version = "2.5.8"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
-dependencies = [
- "form_urlencoded",
- "idna",
- "percent-encoding",
- "serde",
-]
-
-[[package]]
-name = "utf8_iter"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
-
[[package]]
name = "utf8parse"
version = "0.2.2"
@@ -1396,24 +1124,6 @@ dependencies = [
"semver",
]
-[[package]]
-name = "webpki-roots"
-version = "0.26.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9"
-dependencies = [
- "webpki-roots 1.0.6",
-]
-
-[[package]]
-name = "webpki-roots"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed"
-dependencies = [
- "rustls-pki-types",
-]
-
[[package]]
name = "which"
version = "8.0.1"
@@ -1502,15 +1212,6 @@ dependencies = [
"windows-targets 0.48.5",
]
-[[package]]
-name = "windows-sys"
-version = "0.52.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
-dependencies = [
- "windows-targets 0.52.6",
-]
-
[[package]]
name = "windows-sys"
version = "0.59.0"
@@ -1753,35 +1454,6 @@ dependencies = [
"wasmparser",
]
-[[package]]
-name = "writeable"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9"
-
-[[package]]
-name = "yoke"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954"
-dependencies = [
- "stable_deref_trait",
- "yoke-derive",
- "zerofrom",
-]
-
-[[package]]
-name = "yoke-derive"
-version = "0.8.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
[[package]]
name = "zerocopy"
version = "0.8.40"
@@ -1802,66 +1474,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "zerofrom"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
-dependencies = [
- "zerofrom-derive",
-]
-
-[[package]]
-name = "zerofrom-derive"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "synstructure",
-]
-
-[[package]]
-name = "zeroize"
-version = "1.8.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
-
-[[package]]
-name = "zerotrie"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851"
-dependencies = [
- "displaydoc",
- "yoke",
- "zerofrom",
-]
-
-[[package]]
-name = "zerovec"
-version = "0.11.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
-dependencies = [
- "yoke",
- "zerofrom",
- "zerovec-derive",
-]
-
-[[package]]
-name = "zerovec-derive"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "zmij"
version = "1.0.21"
diff --git a/Cargo.toml b/Cargo.toml
index 2c672aef3..b774553e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
[package]
name = "rtk"
-version = "0.34.2"
+version = "0.38.0-algolia.1"
edition = "2021"
authors = ["Patrick Szymkowiak"]
description = "Rust Token Killer - High-performance CLI proxy to minimize LLM token consumption"
license = "MIT"
-homepage = "https://www.rtk-ai.app"
-repository = "https://github.com/rtk-ai/rtk"
+homepage = "https://github.com/algolia/rtk"
+repository = "https://github.com/algolia/rtk"
readme = "README.md"
keywords = ["cli", "llm", "token", "filter", "productivity"]
categories = ["command-line-utilities", "development-tools"]
@@ -25,15 +25,16 @@ dirs = "5"
rusqlite = { version = "0.31", features = ["bundled"] }
toml = "0.8"
chrono = "0.4"
-thiserror = "1.0"
tempfile = "3"
sha2 = "0.10"
-ureq = "2"
-hostname = "0.4"
getrandom = "0.4"
flate2 = "1.0"
quick-xml = "0.37"
which = "8"
+automod = "1"
+
+[target.'cfg(unix)'.dependencies]
+libc = "0.2"
[build-dependencies]
toml = "0.8"
@@ -58,7 +59,6 @@ priority = "optional"
assets = [
["target/release/rtk", "usr/bin/", "755"],
]
-
# cargo-generate-rpm configuration
[package.metadata.generate-rpm]
assets = [
diff --git a/DISCLAIMER.md b/DISCLAIMER.md
new file mode 100644
index 000000000..af81e1432
--- /dev/null
+++ b/DISCLAIMER.md
@@ -0,0 +1,25 @@
+# Disclaimer
+
+## No Warranty
+
+This software is provided "AS IS", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose, and non-infringement. The entire risk as to the quality and performance of the software is with you.
+
+## Limitation of Liability
+
+In no event shall the authors or copyright holders be liable for any claim, damages, or other liability, whether in an action of contract, tort, or otherwise, arising from, out of, or in connection with the software or the use or other dealings in the software. This includes, without limitation, any direct, indirect, incidental, special, exemplary, or consequential damages (including but not limited to loss of data, loss of profits, or business interruption).
+
+## Precompiled Binaries
+
+Precompiled binaries are provided solely for convenience and are covered by the same license as the source code (Apache License 2.0). They are provided without warranties or conditions of any kind. You are responsible for verifying the integrity and suitability of any binary before use. Always verify checksums when available.
+
+## Third-Party Dependencies
+
+This software incorporates third-party open-source components, each governed by their respective licenses. The authors make no representations or warranties regarding these dependencies and accept no liability for any issues arising from their use.
+
+## Use at Your Own Risk
+
+This software interacts with your development environment, file system, and external commands. It is your responsibility to ensure that its use is appropriate for your environment and complies with any applicable policies, regulations, or agreements. The authors are not responsible for any unintended side effects resulting from its use.
+
+---
+
+See [LICENSE](LICENSE) for the full terms of the Apache License 2.0 under which this software is distributed.
diff --git a/Formula/rtk.rb b/Formula/rtk.rb
index 25a8b2e8a..d39a25b75 100644
--- a/Formula/rtk.rb
+++ b/Formula/rtk.rb
@@ -1,34 +1,35 @@
# typed: false
# frozen_string_literal: true
-# Homebrew formula for rtk - Rust Token Killer
-# To install: brew tap rtk-ai/tap && brew install rtk
+# Homebrew formula for rtk - Rust Token Killer (algolia fork)
+# This formula is shipped only for parity with upstream — the algolia
+# fork has no Homebrew tap. Install via `cargo install --git` instead.
class Rtk < Formula
desc "High-performance CLI proxy to minimize LLM token consumption"
- homepage "https://www.rtk-ai.app"
+ homepage "https://github.com/algolia/rtk"
version "0.1.0"
license "MIT"
on_macos do
on_intel do
- url "https://github.com/rtk-ai/rtk/releases/download/v#{version}/rtk-x86_64-apple-darwin.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/v#{version}/rtk-x86_64-apple-darwin.tar.gz"
sha256 "PLACEHOLDER_SHA256_INTEL"
end
on_arm do
- url "https://github.com/rtk-ai/rtk/releases/download/v#{version}/rtk-aarch64-apple-darwin.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/v#{version}/rtk-aarch64-apple-darwin.tar.gz"
sha256 "PLACEHOLDER_SHA256_ARM"
end
end
on_linux do
on_intel do
- url "https://github.com/rtk-ai/rtk/releases/download/v#{version}/rtk-x86_64-unknown-linux-gnu.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/v#{version}/rtk-x86_64-unknown-linux-gnu.tar.gz"
sha256 "PLACEHOLDER_SHA256_LINUX_INTEL"
end
on_arm do
- url "https://github.com/rtk-ai/rtk/releases/download/v#{version}/rtk-aarch64-unknown-linux-gnu.tar.gz"
+ url "https://github.com/algolia/rtk/releases/download/v#{version}/rtk-aarch64-unknown-linux-gnu.tar.gz"
sha256 "PLACEHOLDER_SHA256_LINUX_ARM"
end
end
diff --git a/INSTALL.md b/INSTALL.md
index 98457d09a..d35fa0da0 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -5,7 +5,7 @@
**There are TWO completely different projects named "rtk":**
1. ✅ **Rust Token Killer** (this project) - LLM token optimizer
- - Repos: `rtk-ai/rtk`
+ - Repos: `algolia/rtk`
- Has `rtk gain` command for token savings stats
2. ❌ **Rust Type Kit** (reachingforthejack/rtk) - DIFFERENT PROJECT
@@ -44,7 +44,7 @@ cargo uninstall rtk
### Quick Install (Linux/macOS)
```bash
-curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/master/install.sh | sh
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/main/install.sh | sh
```
After installation, **verify you have the correct rtk**:
@@ -55,8 +55,8 @@ rtk gain # Must show token savings stats (not "command not found")
### Alternative: Manual Installation
```bash
-# From rtk-ai repository (NOT reachingforthejack!)
-cargo install --git https://github.com/rtk-ai/rtk
+# From algolia repository (NOT reachingforthejack!)
+cargo install --git https://github.com/algolia/rtk
# OR (if published and correct on crates.io)
cargo install rtk
@@ -179,7 +179,7 @@ rtk init --show
### First-Time User (Recommended)
```bash
# 1. Install RTK
-cargo install --git https://github.com/rtk-ai/rtk
+cargo install --git https://github.com/algolia/rtk
rtk gain # Verify (must show token stats)
# 2. Setup with prompts
@@ -231,11 +231,11 @@ rtk ls .
# Test with git
rtk git status
-# Test with pnpm (fork only)
+# Test with pnpm
rtk pnpm list
-# Test with Vitest (feat/vitest-support branch only)
-rtk vitest run
+# Test with Vitest
+rtk vitest
```
## Uninstalling
@@ -303,8 +303,15 @@ rtk pnpm install pkg # Silent installation
### Tests
```bash
-rtk test cargo test # Failures only (-90%)
-rtk vitest run # Filtered Vitest output (-99.6%)
+rtk cargo test # Filtered Cargo test output (-90%)
+rtk go test # Filtered Go tests (NDJSON, -90%)
+rtk jest # Filtered Jest output (-99.6%)
+rtk vitest # Filtered Vitest output (-99.6%)
+rtk playwright test # Filtered Playwright output (-94%)
+rtk pytest # Filtered Python tests (-90%)
+rtk rake test # Filtered Ruby tests (-90%)
+rtk rspec # Filtered RSpec tests (-60%)
+rtk test # Generic test wrapper - failures only (-90%)
```
### Statistics
@@ -319,7 +326,7 @@ rtk gain --history # With command history
### Production T3 Stack Project
| Operation | Standard | RTK | Reduction |
|-----------|----------|-----|-----------|
-| `vitest run` | 102,199 chars | 377 chars | **-99.6%** |
+| `vitest` | 102,199 chars | 377 chars | **-99.6%** |
| `git status` | 529 chars | 217 chars | **-59%** |
| `pnpm list` | ~8,000 tokens | ~2,400 | **-70%** |
| `pnpm outdated` | ~12,000 tokens | ~1,200-2,400 | **-80-90%** |
@@ -369,11 +376,11 @@ cargo install --path . --force
## Support and Contributing
-- **Website**: https://www.rtk-ai.app
-- **Contact**: contact@rtk-ai.app
+- **Website**: https://github.com/algolia/rtk
+- **Contact**: #proj-internal-skills on Slack
- **Troubleshooting**: See [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) for common issues
-- **GitHub issues**: https://github.com/rtk-ai/rtk/issues
-- **Pull Requests**: https://github.com/rtk-ai/rtk/pulls
+- **GitHub issues**: https://github.com/algolia/rtk/issues
+- **Pull Requests**: https://github.com/algolia/rtk/pulls
⚠️ **If you installed the wrong rtk (Type Kit)**, see [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#problem-rtk-gain-command-not-found)
diff --git a/README.md b/README.md
index 05f77de1a..5c6cd087f 100644
--- a/README.md
+++ b/README.md
@@ -7,18 +7,16 @@
-
-
+
+
-
- Website •
Install •
- Troubleshooting •
- Architecture •
+ Troubleshooting •
+ Architecture •
Discord
@@ -57,16 +55,12 @@ rtk filters and compresses command outputs before they reach your LLM context. S
## Installation
-### Homebrew (recommended)
-
-```bash
-brew install rtk
-```
+> The Algolia fork has no Homebrew tap. Use one of the methods below.
### Quick Install (Linux/macOS)
```bash
-curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/refs/heads/main/install.sh | sh
```
> Installs to `~/.local/bin`. Add to PATH if needed:
@@ -77,20 +71,22 @@ curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/instal
### Cargo
```bash
-cargo install --git https://github.com/rtk-ai/rtk
+cargo install --git https://github.com/algolia/rtk
```
### Pre-built Binaries
-Download from [releases](https://github.com/rtk-ai/rtk/releases):
+Download from [releases](https://github.com/algolia/rtk/releases):
- macOS: `rtk-x86_64-apple-darwin.tar.gz` / `rtk-aarch64-apple-darwin.tar.gz`
- Linux: `rtk-x86_64-unknown-linux-musl.tar.gz` / `rtk-aarch64-unknown-linux-gnu.tar.gz`
- Windows: `rtk-x86_64-pc-windows-msvc.zip`
+> **Windows users**: Extract the zip and place `rtk.exe` somewhere in your PATH (e.g. `C:\Users\\.local\bin`). Run RTK from **Command Prompt**, **PowerShell**, or **Windows Terminal** — do not double-click the `.exe` (it will flash and close). For the best experience, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) where the full hook system works natively. See [Windows setup](#windows) below for details.
+
### Verify Installation
```bash
-rtk --version # Should show "rtk 0.28.2"
+rtk --version # Should show the version from Cargo.toml (rtk 0.38.0-algolia.1 or newer)
rtk gain # Should show token savings stats
```
@@ -106,6 +102,8 @@ rtk init -g --codex # Codex (OpenAI)
rtk init -g --agent cursor # Cursor
rtk init --agent windsurf # Windsurf
rtk init --agent cline # Cline / Roo Code
+rtk init --agent kilocode # Kilo Code
+rtk init --agent antigravity # Google Antigravity
# 2. Restart your AI tool, then test
git status # Automatically rewritten to rtk git status
@@ -167,15 +165,16 @@ rtk gh run list # Workflow run status
### Test Runners
```bash
-rtk test cargo test # Show failures only (-90%)
-rtk err npm run build # Errors/warnings only
-rtk vitest run # Vitest compact (failures only)
+rtk jest # Jest compact (failures only)
+rtk vitest # Vitest compact (failures only)
rtk playwright test # E2E results (failures only)
rtk pytest # Python tests (-90%)
rtk go test # Go tests (NDJSON, -90%)
rtk cargo test # Cargo tests (-90%)
rtk rake test # Ruby minitest (-90%)
rtk rspec # RSpec tests (JSON, -60%+)
+rtk err # Filter errors only from any command
+rtk test # Generic test wrapper - failures only (-90%)
```
### Build & Lint
@@ -201,6 +200,18 @@ rtk bundle install # Ruby gems (strip Using lines)
rtk prisma generate # Schema generation (no ASCII art)
```
+### AWS
+```bash
+rtk aws sts get-caller-identity # One-line identity
+rtk aws ec2 describe-instances # Compact instance list
+rtk aws lambda list-functions # Name/runtime/memory (strips secrets)
+rtk aws logs get-log-events # Timestamped messages only
+rtk aws cloudformation describe-stack-events # Failures first
+rtk aws dynamodb scan # Unwraps type annotations
+rtk aws iam list-roles # Strips policy documents
+rtk aws s3 ls # Truncated with tee recovery
+```
+
### Containers
```bash
rtk docker ps # Compact container list
@@ -218,7 +229,7 @@ rtk json config.json # Structure without values
rtk deps # Dependencies summary
rtk env -f AWS # Filtered env vars
rtk log app.log # Deduplicated logs
-rtk curl # Auto-detect JSON + schema
+rtk curl # Truncate + save full output
rtk wget # Download, strip progress bars
rtk summary # Heuristic summary
rtk proxy # Raw passthrough + tracking
@@ -294,159 +305,78 @@ rtk init --show # Verify installation
After install, **restart Claude Code**.
-## Supported AI Tools
-
-RTK supports 10 AI coding tools. Each integration transparently rewrites shell commands to `rtk` equivalents for 60-90% token savings.
-
-| Tool | Install | Method |
-|------|---------|--------|
-| **Claude Code** | `rtk init -g` | PreToolUse hook (bash) |
-| **GitHub Copilot (VS Code)** | `rtk init -g --copilot` | PreToolUse hook (`rtk hook copilot`) — transparent rewrite |
-| **GitHub Copilot CLI** | `rtk init -g --copilot` | PreToolUse deny-with-suggestion (CLI limitation) |
-| **Cursor** | `rtk init -g --agent cursor` | preToolUse hook (hooks.json) |
-| **Gemini CLI** | `rtk init -g --gemini` | BeforeTool hook (`rtk hook gemini`) |
-| **Codex** | `rtk init -g --codex` | AGENTS.md + RTK.md instructions |
-| **Windsurf** | `rtk init --agent windsurf` | .windsurfrules (project-scoped) |
-| **Cline / Roo Code** | `rtk init --agent cline` | .clinerules (project-scoped) |
-| **OpenCode** | `rtk init -g --opencode` | Plugin TS (tool.execute.before) |
-| **OpenClaw** | `openclaw plugins install ./openclaw` | Plugin TS (before_tool_call) |
-| **Mistral Vibe** | Planned (#800) | Blocked on upstream BeforeToolCallback |
-
-### Claude Code (default)
-
-```bash
-rtk init -g # Install hook + RTK.md
-rtk init -g --auto-patch # Non-interactive (CI/CD)
-rtk init --show # Verify installation
-rtk init -g --uninstall # Remove
-```
-
-### GitHub Copilot (VS Code + CLI)
-
-```bash
-rtk init -g --copilot # Install hook + instructions
-```
-
-Creates `.github/hooks/rtk-rewrite.json` (PreToolUse hook) and `.github/copilot-instructions.md` (prompt-level awareness).
-
-The hook (`rtk hook copilot`) auto-detects the format:
-- **VS Code Copilot Chat**: transparent rewrite via `updatedInput` (same as Claude Code)
-- **Copilot CLI**: deny-with-suggestion (CLI does not support `updatedInput` yet — see [copilot-cli#2013](https://github.com/github/copilot-cli/issues/2013))
-
-### Cursor
-
-```bash
-rtk init -g --agent cursor
-```
-
-Creates `~/.cursor/hooks/rtk-rewrite.sh` + patches `~/.cursor/hooks.json` with preToolUse matcher. Works with both Cursor editor and `cursor-agent` CLI.
+## Windows
-### Gemini CLI
+RTK works on Windows with some limitations. The auto-rewrite hook (`rtk-rewrite.sh`) requires a Unix shell, so on native Windows RTK falls back to **CLAUDE.md injection mode** — your AI assistant receives RTK instructions but commands are not rewritten automatically.
-```bash
-rtk init -g --gemini
-rtk init -g --gemini --uninstall
-```
-
-Creates `~/.gemini/hooks/rtk-hook-gemini.sh` + patches `~/.gemini/settings.json` with BeforeTool hook.
-
-### Codex (OpenAI)
-
-```bash
-rtk init -g --codex
-```
-
-Creates `~/.codex/RTK.md` + `~/.codex/AGENTS.md` with `@RTK.md` reference. Codex reads these as global instructions.
+### Recommended: WSL (full support)
-### Windsurf
+For the best experience, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) (Windows Subsystem for Linux). Inside WSL, RTK works exactly like Linux — full hook support, auto-rewrite, everything:
```bash
-rtk init --agent windsurf
+# Inside WSL
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/refs/heads/main/install.sh | sh
+rtk init -g
```
-Creates `.windsurfrules` in the current project. Cascade reads rules and prefixes commands with `rtk`.
+### Native Windows (limited support)
-### Cline / Roo Code
+On native Windows (cmd.exe / PowerShell), RTK filters work but the hook does not auto-rewrite commands:
-```bash
-rtk init --agent cline
+```powershell
+# 1. Download and extract rtk-x86_64-pc-windows-msvc.zip from releases
+# 2. Add rtk.exe to your PATH
+# 3. Initialize (falls back to CLAUDE.md injection)
+rtk init -g
+# 4. Use rtk explicitly
+rtk cargo test
+rtk git status
```
-Creates `.clinerules` in the current project. Cline reads rules and prefixes commands with `rtk`.
+**Important**: Do not double-click `rtk.exe` — it is a CLI tool that prints usage and exits immediately. Always run it from a terminal (Command Prompt, PowerShell, or Windows Terminal).
-### OpenCode
+| Feature | WSL | Native Windows |
+|---------|-----|----------------|
+| Filters (cargo, git, etc.) | Full | Full |
+| Auto-rewrite hook | Yes | No (CLAUDE.md fallback) |
+| `rtk init -g` | Hook mode | CLAUDE.md mode |
+| `rtk gain` / analytics | Full | Full |
-```bash
-rtk init -g --opencode
-```
-
-Creates `~/.config/opencode/plugins/rtk.ts`. Uses `tool.execute.before` hook.
+## Supported AI Tools
-### OpenClaw
+RTK supports 12 AI coding tools. Each integration transparently rewrites shell commands to `rtk` equivalents for 60-90% token savings.
-```bash
-openclaw plugins install ./openclaw
-```
+| Tool | Install | Method |
+|------|---------|--------|
+| **Claude Code** | `rtk init -g` | PreToolUse hook (bash) |
+| **GitHub Copilot (VS Code)** | `rtk init -g --copilot` | PreToolUse hook — transparent rewrite |
+| **GitHub Copilot CLI** | `rtk init -g --copilot` | PreToolUse deny-with-suggestion (CLI limitation) |
+| **Cursor** | `rtk init -g --agent cursor` | preToolUse hook (hooks.json) |
+| **Gemini CLI** | `rtk init -g --gemini` | BeforeTool hook |
+| **Codex** | `rtk init -g --codex` | AGENTS.md + RTK.md instructions |
+| **Windsurf** | `rtk init --agent windsurf` | .windsurfrules (project-scoped) |
+| **Cline / Roo Code** | `rtk init --agent cline` | .clinerules (project-scoped) |
+| **OpenCode** | `rtk init -g --opencode` | Plugin TS (tool.execute.before) |
+| **OpenClaw** | `openclaw plugins install ./openclaw` | Plugin TS (before_tool_call) |
+| **Mistral Vibe** | Planned (tracked upstream) | Blocked on upstream |
+| **Kilo Code** | `rtk init --agent kilocode` | .kilocode/rules/rtk-rules.md (project-scoped) |
+| **Google Antigravity** | `rtk init --agent antigravity` | .agents/rules/antigravity-rtk-rules.md (project-scoped) |
-Plugin in `openclaw/` directory. Uses `before_tool_call` hook, delegates to `rtk rewrite`.
-
-### Mistral Vibe (planned)
-
-Blocked on upstream BeforeToolCallback support ([mistral-vibe#531](https://github.com/mistralai/mistral-vibe/issues/531), [PR #533](https://github.com/mistralai/mistral-vibe/pull/533)). Tracked in [#800](https://github.com/rtk-ai/rtk/issues/800).
-
-### Commands Rewritten
-
-| Raw Command | Rewritten To |
-|-------------|-------------|
-| `git status/diff/log/add/commit/push/pull` | `rtk git ...` |
-| `gh pr/issue/run` | `rtk gh ...` |
-| `cargo test/build/clippy` | `rtk cargo ...` |
-| `cat/head/tail ` | `rtk read ` |
-| `rg/grep ` | `rtk grep ` |
-| `ls` | `rtk ls` |
-| `vitest/jest` | `rtk vitest run` |
-| `tsc` | `rtk tsc` |
-| `eslint/biome` | `rtk lint` |
-| `prettier` | `rtk prettier` |
-| `playwright` | `rtk playwright` |
-| `prisma` | `rtk prisma` |
-| `ruff check/format` | `rtk ruff ...` |
-| `pytest` | `rtk pytest` |
-| `pip list/install` | `rtk pip ...` |
-| `go test/build/vet` | `rtk go ...` |
-| `golangci-lint` | `rtk golangci-lint` |
-| `rake test` / `rails test` | `rtk rake test` |
-| `rspec` / `bundle exec rspec` | `rtk rspec` |
-| `rubocop` / `bundle exec rubocop` | `rtk rubocop` |
-| `bundle install/update` | `rtk bundle ...` |
-| `docker ps/images/logs` | `rtk docker ...` |
-| `kubectl get/logs` | `rtk kubectl ...` |
-| `curl` | `rtk curl` |
-| `pnpm list/outdated` | `rtk pnpm ...` |
-
-Commands already using `rtk`, heredocs (`<<`), and unrecognized commands pass through unchanged.
+For per-agent setup details, override controls, and graceful degradation, see [`docs/guide/getting-started/supported-agents.md`](docs/guide/getting-started/supported-agents.md).
## Configuration
-### Config File
-
`~/.config/rtk/config.toml` (macOS: `~/Library/Application Support/rtk/config.toml`):
```toml
-[tracking]
-database_path = "/path/to/custom.db" # default: ~/.local/share/rtk/history.db
-
[hooks]
exclude_commands = ["curl", "playwright"] # skip rewrite for these
[tee]
enabled = true # save raw output on failure (default: true)
mode = "failures" # "failures", "always", or "never"
-max_files = 20 # rotation limit
```
-### Tee: Full Output Recovery
-
When a command fails, RTK saves the full unfiltered output so the LLM can read it without re-executing:
```
@@ -454,50 +384,39 @@ FAILED: 2/15 tests
[full output: ~/.local/share/rtk/tee/1707753600_cargo_test.log]
```
+For the full config reference (all sections, env vars, per-project filters), see [`docs/guide/getting-started/configuration.md`](docs/guide/getting-started/configuration.md).
+
### Uninstall
```bash
rtk init -g --uninstall # Remove hook, RTK.md, settings.json entry
cargo uninstall rtk # Remove binary
-brew uninstall rtk # If installed via Homebrew
```
## Documentation
-- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - Fix common issues
-- **[INSTALL.md](INSTALL.md)** - Detailed installation guide
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Technical architecture
-- **[SECURITY.md](SECURITY.md)** - Security policy and PR review process
-- **[AUDIT_GUIDE.md](docs/AUDIT_GUIDE.md)** - Token savings analytics guide
-
-## Privacy & Telemetry
-
-RTK collects **anonymous, aggregate usage metrics** once per day, **enabled by default**. This helps prioritize development. See opt-out options below.
+- **[INSTALL.md](INSTALL.md)** — detailed installation reference
+- **[docs/contributing/ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** — system design and technical decisions
+- **[CONTRIBUTING.md](CONTRIBUTING.md)** — contribution guide
+- **[SECURITY.md](SECURITY.md)** — security policy
-**What is collected:**
-- Device hash (salted SHA-256 — per-user random salt stored locally, not reversible)
-- RTK version, OS, architecture
-- Command count (last 24h) and top command names (e.g. "git", "cargo" — no arguments, no file paths)
-- Token savings percentage
+## Privacy
-**What is NOT collected:** source code, file paths, command arguments, secrets, environment variables, or any personally identifiable information.
-
-**Opt-out** (any of these):
-```bash
-# Environment variable
-export RTK_TELEMETRY_DISABLED=1
-
-# Or in config file (~/.config/rtk/config.toml)
-[telemetry]
-enabled = false
-```
+This Algolia fork strips all telemetry from upstream. RTK does not phone
+home: no anonymous pings, no usage stats, no remote calls. All metrics
+(`rtk gain`, `rtk discover`) stay in `~/.local/share/rtk/tracking.db`
+on your machine.
## Contributing
-Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/rtk-ai/rtk).
+Contributions welcome! Please open an issue or PR on [GitHub](https://github.com/algolia/rtk).
Join the community on [Discord](https://discord.gg/RySmvNF5kF).
## License
MIT License - see [LICENSE](LICENSE) for details.
+
+## Disclaimer
+
+See [DISCLAIMER.md](DISCLAIMER.md).
diff --git a/README_es.md b/README_es.md
index c099d6649..dda34d5b0 100644
--- a/README_es.md
+++ b/README_es.md
@@ -15,10 +15,10 @@
- Sitio web •
+
Instalar •
Solucion de problemas •
- Arquitectura •
+ Arquitectura •
Discord
@@ -121,10 +121,12 @@ rtk git push # -> "ok main"
### Tests
```bash
-rtk test cargo test # Solo fallos (-90%)
-rtk vitest run # Vitest compacto
+rtk jest # Jest compacto
+rtk vitest # Vitest compacto
rtk pytest # Tests Python (-90%)
rtk go test # Tests Go (-90%)
+rtk cargo test # Tests Rust (-90%)
+rtk test # Solo fallos (-90%)
```
### Build & Lint
@@ -146,7 +148,7 @@ rtk discover # Descubrir ahorros perdidos
- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - Resolver problemas comunes
- **[INSTALL.md](INSTALL.md)** - Guia de instalacion detallada
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Arquitectura tecnica
+- **[ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** - Arquitectura tecnica
## Contribuir
@@ -157,3 +159,7 @@ Unete a la comunidad en [Discord](https://discord.gg/RySmvNF5kF).
## Licencia
Licencia MIT - ver [LICENSE](LICENSE) para detalles.
+
+## Descargo de responsabilidad
+
+Ver [DISCLAIMER.md](DISCLAIMER.md).
diff --git a/README_fr.md b/README_fr.md
index 4c5e749da..fcd83c688 100644
--- a/README_fr.md
+++ b/README_fr.md
@@ -15,10 +15,10 @@
- Site web •
+
Installer •
Depannage •
- Architecture •
+ Architecture •
Discord
@@ -135,11 +135,12 @@ rtk git push # -> "ok main"
### Tests
```bash
-rtk test cargo test # Echecs uniquement (-90%)
-rtk vitest run # Vitest compact
+rtk jest # Jest compact
+rtk vitest # Vitest compact
rtk pytest # Tests Python (-90%)
rtk go test # Tests Go (-90%)
rtk cargo test # Tests Cargo (-90%)
+rtk test # Echecs uniquement (-90%)
```
### Build & Lint
@@ -184,7 +185,7 @@ mode = "failures"
- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - Resoudre les problemes courants
- **[INSTALL.md](INSTALL.md)** - Guide d'installation detaille
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - Architecture technique
+- **[ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** - Architecture technique
## Contribuer
@@ -195,3 +196,7 @@ Rejoignez la communaute sur [Discord](https://discord.gg/RySmvNF5kF).
## Licence
Licence MIT - voir [LICENSE](LICENSE) pour les details.
+
+## Avertissement
+
+Voir [DISCLAIMER.md](DISCLAIMER.md).
diff --git a/README_ja.md b/README_ja.md
index 6c690affa..e8c6683ae 100644
--- a/README_ja.md
+++ b/README_ja.md
@@ -15,10 +15,10 @@
- ウェブサイト •
+
インストール •
トラブルシューティング •
- アーキテクチャ •
+ アーキテクチャ •
Discord
@@ -121,10 +121,11 @@ rtk git push # -> "ok main"
### テスト
```bash
-rtk test cargo test # 失敗のみ表示(-90%)
-rtk vitest run # Vitest コンパクト
+rtk jest # Jest コンパクト
+rtk vitest # Vitest コンパクト
rtk pytest # Python テスト(-90%)
rtk go test # Go テスト(-90%)
+rtk test # 失敗のみ表示(-90%)
```
### ビルド & リント
@@ -146,7 +147,7 @@ rtk discover # 見逃した節約機会を発見
- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - よくある問題の解決
- **[INSTALL.md](INSTALL.md)** - 詳細インストールガイド
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 技術アーキテクチャ
+- **[ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** - 技術アーキテクチャ
## コントリビュート
@@ -157,3 +158,7 @@ rtk discover # 見逃した節約機会を発見
## ライセンス
MIT ライセンス - 詳細は [LICENSE](LICENSE) を参照。
+
+## 免責事項
+
+詳細は [DISCLAIMER.md](DISCLAIMER.md) を参照。
diff --git a/README_ko.md b/README_ko.md
index 5d3b1a0b2..8d8450d09 100644
--- a/README_ko.md
+++ b/README_ko.md
@@ -15,10 +15,10 @@
- 웹사이트 •
+
설치 •
문제 해결 •
- 아키텍처 •
+ 아키텍처 •
Discord
@@ -121,10 +121,11 @@ rtk git push # -> "ok main"
### 테스트
```bash
-rtk test cargo test # 실패만 표시 (-90%)
-rtk vitest run # Vitest 컴팩트
+rtk jest # Jest 컴팩트
+rtk vitest # Vitest 컴팩트
rtk pytest # Python 테스트 (-90%)
rtk go test # Go 테스트 (-90%)
+rtk test # 실패만 표시 (-90%)
```
### 빌드 & 린트
@@ -146,7 +147,7 @@ rtk discover # 놓친 절약 기회 발견
- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - 일반적인 문제 해결
- **[INSTALL.md](INSTALL.md)** - 상세 설치 가이드
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 기술 아키텍처
+- **[ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** - 기술 아키텍처
## 기여
@@ -157,3 +158,7 @@ rtk discover # 놓친 절약 기회 발견
## 라이선스
MIT 라이선스 - 자세한 내용은 [LICENSE](LICENSE)를 참조하세요.
+
+## 면책 조항
+
+자세한 내용은 [DISCLAIMER.md](DISCLAIMER.md)를 참조하세요.
diff --git a/README_zh.md b/README_zh.md
index 00b9c001f..394ed8dab 100644
--- a/README_zh.md
+++ b/README_zh.md
@@ -15,10 +15,10 @@
- 官网 •
+
安装 •
故障排除 •
- 架构 •
+ 架构 •
Discord
@@ -122,10 +122,11 @@ rtk git push # -> "ok main"
### 测试
```bash
-rtk test cargo test # 仅显示失败(-90%)
-rtk vitest run # Vitest 紧凑输出
+rtk jest # Jest 紧凑输出
+rtk vitest # Vitest 紧凑输出
rtk pytest # Python 测试(-90%)
rtk go test # Go 测试(-90%)
+rtk test # 仅显示失败(-90%)
```
### 构建 & 检查
@@ -154,7 +155,7 @@ rtk discover # 发现遗漏的节省机会
- **[TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)** - 解决常见问题
- **[INSTALL.md](INSTALL.md)** - 详细安装指南
-- **[ARCHITECTURE.md](ARCHITECTURE.md)** - 技术架构
+- **[ARCHITECTURE.md](docs/contributing/ARCHITECTURE.md)** - 技术架构
## 贡献
@@ -165,3 +166,7 @@ rtk discover # 发现遗漏的节省机会
## 许可证
MIT 许可证 - 详见 [LICENSE](LICENSE)。
+
+## 免责声明
+
+详见 [DISCLAIMER.md](DISCLAIMER.md)。
diff --git a/ROADMAP.md b/ROADMAP.md
deleted file mode 100644
index 7f8e1d6eb..000000000
--- a/ROADMAP.md
+++ /dev/null
@@ -1,15 +0,0 @@
-# RTK Roadmap -
-
-Stability & Reliability
-
- Critical Fixes: Resolve bugs and stabilize Vitest/pnpm support.
-
- Fork Strategy: Establish the fork as the new standard if upstream remains inactive.
-
- Pro Tooling: Add a configuration file (TOML) and structured logging.
-
- Easy Install: Launch a Homebrew formula and pre-compiled binaries for one-click setup.
-
- Early Adoption: Prove token savings on real projects to onboard the first 5 teams.
-
----
diff --git a/SECURITY.md b/SECURITY.md
index 2d06b77c3..876da07fd 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -21,7 +21,7 @@ RTK is a CLI tool that executes shell commands and handles user input. PRs from
- **Shell injection** (command execution vulnerabilities)
- **Supply chain attacks** (malicious dependencies)
- **Backdoors** (logic bombs, exfiltration code)
-- **Data leaks** (tracking.db exposure, telemetry abuse)
+- **Data leaks** (tracking.db exposure)
---
@@ -50,7 +50,7 @@ The following files are considered **high-risk** and trigger mandatory 2-reviewe
### Tier 1: Shell Execution & System Interaction
- **`src/runner.rs`** - Shell command execution engine (primary injection vector)
- **`src/summary.rs`** - Command output aggregation (data exfiltration risk)
-- **`src/tracking.rs`** - SQLite database operations (privacy/telemetry concerns)
+- **`src/tracking.rs`** - SQLite database operations (local privacy concerns)
- **`src/discover/registry.rs`** - Rewrite logic for all commands (command injection risk via rewrite rules)
- **`hooks/rtk-rewrite.sh`** / **`.claude/hooks/rtk-rewrite.sh`** - Thin delegator hook (executes in Claude Code context, intercepts all commands)
@@ -114,7 +114,7 @@ bash scripts/detect-dangerous-patterns.sh /tmp/pr.diff
| `SystemTime::now() > ...` | Logic bombs | Delayed malicious behavior |
| Base64/hex strings | Obfuscation | Hides malicious URLs/commands |
-See [Dangerous Patterns Reference](https://github.com/rtk-ai/rtk/wiki/Dangerous-Patterns) for exploitation examples.
+See [Dangerous Patterns Reference](https://github.com/algolia/rtk/wiki/Dangerous-Patterns) for exploitation examples.
---
@@ -209,7 +209,7 @@ Critical vulnerabilities (remote code execution, data exfiltration) may be fast-
## Contact
- **Security issues**: security@rtk-ai.dev
-- **General questions**: https://github.com/rtk-ai/rtk/discussions
+- **General questions**: https://github.com/algolia/rtk/discussions
- **Maintainers**: @FlorianBruniaux (active fork maintainer)
---
diff --git a/bug-reports/2026-03-31-curl-python3-not-found-in-shell-functions.md b/bug-reports/2026-03-31-curl-python3-not-found-in-shell-functions.md
new file mode 100644
index 000000000..d44dbdb23
--- /dev/null
+++ b/bug-reports/2026-03-31-curl-python3-not-found-in-shell-functions.md
@@ -0,0 +1,58 @@
+# RTK Bug: `curl` and `python3` not found inside shell function bodies
+
+**Date**: 2026-03-31
+**Severity**: HIGH — completely breaks multi-step API scripts
+**Category**: Command rewrite breaks function definitions
+
+---
+
+## Symptom
+
+When a bash command defines a shell function that uses `curl` and pipes to `python3`, both commands fail with `command not found` despite being present at `/usr/bin/curl` and `/usr/bin/python3`.
+
+```
+create_link:2: command not found: curl
+create_link:11: command not found: python3
+```
+
+## Reproduction
+
+```bash
+create_link() {
+ local url="$1"
+ curl -s -X POST "https://api.short.io/links" \
+ -H "Content-Type: application/json" \
+ -H "Authorization: $API_KEY" \
+ -d '{"originalURL": "'$url'"}' | python3 -c "import sys,json; print(json.load(sys.stdin))"
+}
+create_link "https://example.com"
+```
+
+## Expected
+
+`curl` and `python3` execute normally inside the function body.
+
+## Actual
+
+RTK hook rewrites `curl` and/or `python3` inside the function definition, producing invalid command references that fail at invocation time.
+
+## Workaround
+
+Use `/usr/bin/curl` and `/usr/bin/python3` absolute paths, or use a dedicated CLI tool instead of curl.
+
+## Context
+
+Trying to create short.io links via API. Both `which curl` and `which python3` confirm they exist at `/usr/bin/`.
+
+## Resolution (v0.34.2-algolia.1)
+
+**Root cause**: Two issues compounding:
+1. `curl` piped to `python3`/`jq` was rewritten to `rtk curl`, which auto-compresses JSON output — breaking downstream pipe consumers that expect raw JSON.
+2. Shell function definitions containing rewritable commands could theoretically be corrupted (though current parser already skipped most function forms via the `$((` / compound detection).
+
+**Fix**:
+- Added `curl`/`wget` to the pipe-incompatible list in `rewrite_compound()` — they are not rewritten when piped.
+- Added explicit shell function definition detection (`() {`, `function `) in `rewrite_command()` — bail early on function bodies.
+- 8 new tests covering function definitions, curl pipe skipping, and compound edge cases.
+
+**Status**: FIXED
diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md
deleted file mode 100644
index cf52f026d..000000000
--- a/docs/TROUBLESHOOTING.md
+++ /dev/null
@@ -1,337 +0,0 @@
-# RTK Troubleshooting Guide
-
-## Problem: "rtk gain" command not found
-
-### Symptom
-```bash
-$ rtk --version
-rtk 1.0.0 # (or similar)
-
-$ rtk gain
-rtk: 'gain' is not a rtk command. See 'rtk --help'.
-```
-
-### Root Cause
-You installed the **wrong rtk package**. You have **Rust Type Kit** (reachingforthejack/rtk) instead of **Rust Token Killer** (rtk-ai/rtk).
-
-### Solution
-
-**1. Uninstall the wrong package:**
-```bash
-cargo uninstall rtk
-```
-
-**2. Install the correct one (Token Killer):**
-
-#### Quick Install (Linux/macOS)
-```bash
-curl -fsSL https://github.com/rtk-ai/rtk/blob/master/install.sh | sh
-```
-
-#### Alternative: Manual Installation
-```bash
-cargo install --git https://github.com/rtk-ai/rtk
-```
-
-**3. Verify installation:**
-```bash
-rtk --version
-rtk gain # MUST show token savings stats, not error
-```
-
-If `rtk gain` now works, installation is correct.
-
----
-
-## Problem: Confusion Between Two "rtk" Projects
-
-### The Two Projects
-
-| Project | Repository | Purpose | Key Command |
-|---------|-----------|---------|-------------|
-| **Rust Token Killer** ✅ | rtk-ai/rtk | LLM token optimizer for Claude Code | `rtk gain` |
-| **Rust Type Kit** ❌ | reachingforthejack/rtk | Rust codebase query and type generator | `rtk query` |
-
-### How to Identify Which One You Have
-
-```bash
-# Check if "gain" command exists
-rtk gain
-
-# Token Killer → Shows token savings stats
-# Type Kit → Error: "gain is not a rtk command"
-```
-
----
-
-## Problem: cargo install rtk installs wrong package
-
-### Why This Happens
-If **Rust Type Kit** is published to crates.io under the name `rtk`, running `cargo install rtk` will install the wrong package.
-
-### Solution
-**NEVER use** `cargo install rtk` without verifying.
-
-**Always use explicit repository URLs:**
-
-```bash
-# CORRECT - Token Killer
-cargo install --git https://github.com/rtk-ai/rtk
-
-# OR install from fork
-git clone https://github.com/rtk-ai/rtk.git
-cd rtk && git checkout feat/all-features
-cargo install --path . --force
-```
-
-**After any installation, ALWAYS verify:**
-```bash
-rtk gain # Must work if you want Token Killer
-```
-
----
-
-## Problem: RTK not working in Claude Code
-
-### Symptom
-Claude Code doesn't seem to be using rtk, outputs are verbose.
-
-### Checklist
-
-**1. Verify rtk is installed and correct:**
-```bash
-rtk --version
-rtk gain # Must show stats
-```
-
-**2. Initialize rtk for Claude Code:**
-```bash
-# Global (all projects)
-rtk init --global
-
-# Per-project
-cd /your/project
-rtk init
-```
-
-**3. Verify CLAUDE.md file exists:**
-```bash
-# Check global
-cat ~/.claude/CLAUDE.md | grep rtk
-
-# Check project
-cat ./CLAUDE.md | grep rtk
-```
-
-**4. Install auto-rewrite hook (recommended for automatic RTK usage):**
-
-**Option A: Automatic (recommended)**
-```bash
-rtk init -g
-# → Installs hook + RTK.md automatically
-# → Follow printed instructions to add hook to ~/.claude/settings.json
-# → Restart Claude Code
-
-# Verify installation
-rtk init --show # Should show "✅ Hook: executable, with guards"
-```
-
-**Option B: Manual (fallback)**
-```bash
-# Copy hook to Claude Code hooks directory
-mkdir -p ~/.claude/hooks
-cp .claude/hooks/rtk-rewrite.sh ~/.claude/hooks/
-chmod +x ~/.claude/hooks/rtk-rewrite.sh
-```
-
-Then add to `~/.claude/settings.json` (replace `~` with full path):
-```json
-{
- "hooks": {
- "PreToolUse": [
- {
- "matcher": "Bash",
- "hooks": [
- {
- "type": "command",
- "command": "/Users/yourname/.claude/hooks/rtk-rewrite.sh"
- }
- ]
- }
- ]
- }
-}
-```
-
-**Note**: Use absolute path in `settings.json`, not `~/.claude/...`
-
----
-
-## Problem: RTK not working in OpenCode
-
-### Symptom
-OpenCode runs commands without rtk, outputs are verbose.
-
-### Checklist
-
-**1. Verify rtk is installed and correct:**
-```bash
-rtk --version
-rtk gain # Must show stats
-```
-
-**2. Install the OpenCode plugin (global only):**
-```bash
-rtk init -g --opencode
-```
-
-**3. Verify plugin file exists:**
-```bash
-ls -la ~/.config/opencode/plugins/rtk.ts
-```
-
-**4. Restart OpenCode**
-OpenCode must be restarted to load the plugin.
-
-**5. Verify status:**
-```bash
-rtk init --show # Should show "OpenCode: plugin installed"
-```
-
----
-
-## Problem: RTK commands fail on Windows ("program not found" or "No such file")
-
-### Symptom
-```
-rtk vitest --run
-# Error: program not found
-# Or: The system cannot find the file specified
-
-rtk lint .
-# Error: No such file or directory
-```
-
-### Root Cause
-On Windows, Node.js tools (vitest, eslint, tsc, etc.) are installed as `.CMD` or `.BAT` wrapper scripts, not as native `.exe` binaries. Rust's `std::process::Command::new("vitest")` does not honor the Windows `PATHEXT` environment variable, so it cannot find `vitest.CMD` even when it's on PATH.
-
-### Solution
-Update to rtk v0.23.1+ which resolves this via the `which` crate for proper PATH+PATHEXT resolution. All 16+ command modules now use `resolved_command()` instead of `Command::new()`.
-
-```bash
-cargo install --git https://github.com/rtk-ai/rtk
-rtk --version # Should be 0.23.1+
-```
-
-### Affected Commands
-All commands that spawn external tools: `rtk vitest`, `rtk lint`, `rtk tsc`, `rtk pnpm`, `rtk playwright`, `rtk prisma`, `rtk next`, `rtk prettier`, `rtk ruff`, `rtk pytest`, `rtk pip`, `rtk mypy`, `rtk golangci-lint`, and others.
-
----
-
-## Problem: "command not found: rtk" after installation
-
-### Symptom
-```bash
-$ cargo install --path . --force
- Compiling rtk v0.7.1
- Finished release [optimized] target(s)
- Installing ~/.cargo/bin/rtk
-
-$ rtk --version
-zsh: command not found: rtk
-```
-
-### Root Cause
-`~/.cargo/bin` is not in your PATH.
-
-### Solution
-
-**1. Check if cargo bin is in PATH:**
-```bash
-echo $PATH | grep -o '[^:]*\.cargo[^:]*'
-```
-
-**2. If not found, add to PATH:**
-
-For **bash** (`~/.bashrc`):
-```bash
-export PATH="$HOME/.cargo/bin:$PATH"
-```
-
-For **zsh** (`~/.zshrc`):
-```bash
-export PATH="$HOME/.cargo/bin:$PATH"
-```
-
-For **fish** (`~/.config/fish/config.fish`):
-```fish
-set -gx PATH $HOME/.cargo/bin $PATH
-```
-
-**3. Reload shell config:**
-```bash
-source ~/.bashrc # or ~/.zshrc or restart terminal
-```
-
-**4. Verify:**
-```bash
-which rtk
-rtk --version
-rtk gain
-```
-
----
-
-## Problem: Compilation errors during installation
-
-### Symptom
-```bash
-$ cargo install --path .
-error: failed to compile rtk v0.7.1
-```
-
-### Solutions
-
-**1. Update Rust toolchain:**
-```bash
-rustup update stable
-rustup default stable
-```
-
-**2. Clean and rebuild:**
-```bash
-cargo clean
-cargo build --release
-cargo install --path . --force
-```
-
-**3. Check Rust version (minimum required):**
-```bash
-rustc --version # Should be 1.70+ for most features
-```
-
-**4. If still fails, report issue:**
-- GitHub: https://github.com/rtk-ai/rtk/issues
-
----
-
-## Need More Help?
-
-**Report issues:**
-- Fork-specific: https://github.com/rtk-ai/rtk/issues
-- Upstream: https://github.com/rtk-ai/rtk/issues
-
-**Run the diagnostic script:**
-```bash
-# From the rtk repository root
-bash scripts/check-installation.sh
-```
-
-This script will check:
-- ✅ RTK installed and in PATH
-- ✅ Correct version (Token Killer, not Type Kit)
-- ✅ Available features (pnpm, vitest, next, etc.)
-- ✅ Claude Code integration (CLAUDE.md files)
-- ✅ Auto-rewrite hook status
-
-The script provides specific fix commands for any issues found.
diff --git a/ARCHITECTURE.md b/docs/contributing/ARCHITECTURE.md
similarity index 98%
rename from ARCHITECTURE.md
rename to docs/contributing/ARCHITECTURE.md
index a803dcbdc..86f77b51d 100644
--- a/ARCHITECTURE.md
+++ b/docs/contributing/ARCHITECTURE.md
@@ -1,6 +1,6 @@
# rtk Architecture Documentation
-> **Deep reference** for RTK's system design, filtering taxonomy, performance characteristics, and architecture decisions. For a guided tour of the end-to-end flow, start with [docs/TECHNICAL.md](docs/TECHNICAL.md).
+> **Deep reference** for RTK's system design, filtering taxonomy, performance characteristics, and architecture decisions. For a guided tour of the end-to-end flow, start with [TECHNICAL.md](TECHNICAL.md).
**rtk (Rust Token Killer)** is a high-performance CLI proxy that minimizes LLM token consumption through intelligent output filtering and compression.
@@ -26,7 +26,7 @@
## System Overview
-> For the proxy pattern diagram and key components table, see [docs/TECHNICAL.md](docs/TECHNICAL.md#2-architecture-overview).
+> For the proxy pattern diagram and key components table, see [TECHNICAL.md](TECHNICAL.md#2-architecture-overview).
### Design Principles
@@ -38,7 +38,7 @@
### Hook Architecture (v0.9.5+)
-> For the hook interception diagram and agent-specific JSON formats, see [docs/TECHNICAL.md](docs/TECHNICAL.md#32-hook-interception-command-rewriting) and [hooks/README.md](hooks/README.md).
+> For the hook interception diagram and agent-specific JSON formats, see [TECHNICAL.md](TECHNICAL.md#32-hook-interception-command-rewriting) and [hooks/README.md](hooks/README.md).
Two hook strategies:
@@ -159,7 +159,7 @@ Database: ~/.local/share/rtk/history.db
### Module Map
-> For the full file-level module tree, see [docs/TECHNICAL.md](docs/TECHNICAL.md#4-folder-map) and each folder's README.
+> For the full file-level module tree, see [TECHNICAL.md](TECHNICAL.md#4-folder-map) and each folder's README.
**Token savings by ecosystem:**
@@ -181,7 +181,7 @@ Savings by ecosystem:
### Module Breakdown
- **Command Modules**: `src/cmds/` — organized by ecosystem (git, rust, js, python, go, dotnet, cloud, system, ruby). Each ecosystem README lists its files.
-- **Core Infrastructure**: `src/core/` — utils, filter, tracking, tee, config, toml_filter, display_helpers, telemetry
+- **Core Infrastructure**: `src/core/` — utils, filter, tracking, tee, config, toml_filter, display_helpers
- **Hook System**: `src/hooks/` — init, rewrite, permissions, hook_cmd, hook_check, hook_audit, verify, trust, integrity
- **Analytics**: `src/analytics/` — gain, cc_economics, ccusage, session_cmd
@@ -1034,7 +1034,7 @@ Overhead Sources:
## Resources
-- **[docs/TECHNICAL.md](docs/TECHNICAL.md)**: Guided tour of end-to-end flow
+- **[TECHNICAL.md](TECHNICAL.md)**: Guided tour of end-to-end flow
- **[CONTRIBUTING.md](CONTRIBUTING.md)**: Design philosophy, contribution workflow, checklist
- **CLAUDE.md**: Quick reference for AI agents (dev commands, build verification)
- **README.md**: User guide, installation, examples
diff --git a/docs/contributing/CODING_PRACTICES.md b/docs/contributing/CODING_PRACTICES.md
new file mode 100644
index 000000000..bc0975541
--- /dev/null
+++ b/docs/contributing/CODING_PRACTICES.md
@@ -0,0 +1,186 @@
+# RTK Coding Practices v1.0
+
+This document follows the [Design Philosophy](../../CONTRIBUTING.md#design-philosophy) in `CONTRIBUTING.md`. Once you understand the mental model there, this guide describes the coding practices we use day-to-day in RTK and what reviewers will look for on your PR.
+
+Our goal is to keep the codebase consistent and easy to extend. PRs that deviate from these practices may be asked for changes during review — this is guidance, not a gate. If a rule seems wrong for your specific case, flag it in the PR and we'll discuss.
+
+> **Heads up:** RTK has grown quickly and some code in the repository predates these practices. You may spot modules that don't fully follow them — this is expected, and core/ecosystem maintainers will refactor them over time. When in doubt, follow the practices below for new code rather than mirroring older patterns.
+
+---
+
+## Quick Start for Contributors
+
+New to RTK? The fastest path to a mergeable first PR:
+
+1. **Read the flow once.** Start at [`CONTRIBUTING.md`](../../CONTRIBUTING.md), then skim [`docs/contributing/TECHNICAL.md`](TECHNICAL.md) to see how a command flows from `main.rs` → a `*_cmd.rs` filter → tracking → stdout.
+2. **Look at a good example.** [`src/cmds/git/git.rs`](../../src/cmds/git/git.rs) is a representative filter — it shows the `run()` entry point, `lazy_static!` regex setup, filter helpers, and embedded tests all in one file.
+3. **Know the shared helpers before reimplementing.** Two files cover most of what you need:
+ - [`src/core/runner.rs`](../../src/core/runner.rs) — command execution wrappers: `run_filtered()` (run a command, then apply your filter function), `run_passthrough()` (run unfiltered but tracked), `run_streamed()` (streaming filter).
+ - [`src/core/utils.rs`](../../src/core/utils.rs) — shared utilities: `resolved_command()`, `strip_ansi()`, `truncate()`, `count_tokens()`, and more.
+4. **Follow the checklist.** [`src/cmds/README.md — Adding a New Command Filter`](../../src/cmds/README.md#adding-a-new-command-filter) walks you through creating a filter, registering it, and adding tests.
+5. **Write the test first.** We follow Red-Green-Refactor. A snapshot test plus a token-savings assertion (see [Testing](#testing) below) is enough for most filters.
+
+If you're unsure whether your approach fits, open a draft PR or a discussion early — we'd rather help shape the design than ask for a rewrite at review.
+
+---
+
+## Design Philosophy
+
+For the full framing (Correctness vs. Token Savings, Transparency, Never Block, Zero Overhead, Extensibility), see the [Design Philosophy](../../CONTRIBUTING.md#design-philosophy) section in `CONTRIBUTING.md`.
+
+Two practical reminders that come up often in review:
+
+**Portability.** RTK should behave the same across platforms. Use `#[cfg(target_os = "...")]` for platform-specific code; never assume a single OS.
+
+**Extensibility.** RTK should be modular. Before writing a new feature or filter, check whether an existing entry point fits — `runner::run_filtered()`, `runner::run_passthrough()`, helpers in `src/core/utils.rs`, etc. If your logic could be reused elsewhere, lift it into a shared component rather than burying it in one `*_cmd.rs` file.
+
+---
+
+## Files, Functions, and Documentation
+
+Each folder contains a root `README.md` that explains the main principles, flows, and specificities of the source files it owns. These READMEs should describe concepts and cases — not list individual source files or counts, to avoid stale lists as the code evolves. Because the root README reflects core features and logic, it should not change often; meaningful edits usually imply a core refactor.
+
+Tests live in the same file as the code they test (inside `#[cfg(test)] mod tests { ... }`), not in a separate test file. This keeps the filter, its fixtures, and its assertions close together.
+
+---
+
+## Edge Cases
+
+When you add an edge-case branch or a non-obvious exception, leave a short comment above it explaining *why* it exists. This prevents a future contributor from removing it because the reason isn't visible from the code alone.
+
+Referencing an issue is often the clearest form:
+
+```rust
+// ISSUE #463: some `git log` output contains NUL bytes when --format=%x00 is used;
+// skip the line rather than panicking on invalid UTF-8.
+if line.contains('\0') {
+ continue;
+}
+```
+
+---
+
+## Comments
+
+Prefer code that reads clearly over code that needs comments to explain it. In particular, avoid redundant comments that restate what the function signature already says.
+
+Comments are welcome when they add information the code cannot carry on its own. The common cases:
+
+- **File header (`//!`)** — purpose and scope of the current file.
+- **Edge case** — a non-obvious branch or exception, as described above.
+- **Issue reference** — e.g. `// ISSUE #463: the fix for this`.
+- **"Why, not what"** — when the intent or tradeoff behind a decision isn't obvious from the code.
+
+In short: avoid noise comments; keep the ones that would save a future reader a trip to `git blame`.
+
+---
+
+## Variables
+
+Use explicit, descriptive names for variables, just like for functions.
+
+Do not hardcode repetitive patterns or values that control behavior — extract them into named constants at the top of the file. For anything a user might want to tune (thresholds, limits, display cutoffs), use `config::limits()` so it flows through `~/.config/rtk/config.toml`.
+
+Example from `src/cmds/git/git.rs`:
+
+```rust
+let limits = config::limits();
+let max_files = limits.status_max_files;
+let max_untracked = limits.status_max_untracked;
+```
+
+---
+
+## Function and File Size
+
+**Prefer functions under ~60 lines.** Shorter functions are easier to read, test, and reuse. If a function grows beyond that, it's usually a sign the logic should be split into helpers — but this is a guideline, not a hard cap.
+
+Legitimate exceptions include:
+- Dispatcher / match functions that route to subcommands, where each arm delegates to a focused helper.
+- State-machine parsers where splitting would harm readability.
+
+When you keep a longer function, aim to make each block obviously cohesive — and consider leaving a short comment on *why* splitting it would hurt.
+
+**Files are expected to be large** in RTK because each module keeps its tests and fixtures alongside the implementation. When a file becomes hard to navigate, split responsibilities across multiple files where possible. If it isn't possible, a big file is acceptable for now.
+
+---
+
+## Imports and Dependencies
+
+RTK is a low-dependency project. Before adding a crate, check whether the functionality is already covered by `std`, an existing dependency, or `src/core/utils.rs`. If a few lines of straightforward code will do the job, prefer that over a new dependency.
+
+When a new dependency is genuinely needed, justify it in the PR description. For non-trivial additions, it's worth opening a discussion with maintainers first.
+
+---
+
+## Error Handling
+
+Use `anyhow::Result` everywhere, and always attach context with `.context("description")?` or `.with_context(|| format!(...))`.
+
+Never silently swallow errors (`Err(_) => {}`). Either log with `eprintln!` and fall back to raw output (the common case for filters), or propagate the error.
+
+Example of the standard fallback pattern for a filter:
+
+```rust
+let filtered = filter_output(&output.stdout)
+ .unwrap_or_else(|e| {
+ eprintln!("rtk: filter warning: {}", e);
+ output.stdout.clone() // passthrough on failure — never block the user
+ });
+```
+
+For the full error-handling architecture (propagation chain, exit code preservation), see [ARCHITECTURE.md — Error Handling](ARCHITECTURE.md#error-handling).
+
+---
+
+## Testing
+
+See [`CONTRIBUTING.md` — Testing](../../CONTRIBUTING.md#testing) for the full strategy. In short, for a new filter you typically want:
+
+- **Unit + snapshot tests** in the same file, using the `insta` crate.
+- **A token-savings assertion** verifying the filter hits the ≥60% target on a real fixture.
+
+Minimal example:
+
+```rust
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use insta::assert_snapshot;
+
+ fn count_tokens(s: &str) -> usize { s.split_whitespace().count() }
+
+ #[test]
+ fn filter_git_log_snapshot() {
+ let input = include_str!("../../../tests/fixtures/git_log_raw.txt");
+ let output = filter_git_log(input);
+ assert_snapshot!(output);
+ }
+
+ #[test]
+ fn filter_git_log_savings() {
+ let input = include_str!("../../../tests/fixtures/git_log_raw.txt");
+ let output = filter_git_log(input);
+ let savings = 100.0 - (count_tokens(&output) as f64 / count_tokens(input) as f64 * 100.0);
+ assert!(savings >= 60.0, "expected ≥60% savings, got {:.1}%", savings);
+ }
+}
+```
+
+Fixtures go in `tests/fixtures/` and should be captured from real command output rather than hand-written.
+
+---
+
+## Security
+
+RTK executes shell commands on behalf of the user, so security is a first-class concern.
+
+**Command execution.** All commands go through argument arrays via `Command::new().args()` — never through shell string concatenation. This prevents injection. Always use `resolved_command()` from `src/core/utils.rs` instead of a raw `Command::new()`.
+
+**Hook integrity.** RTK verifies hook files via SHA-256 hashes before operational commands. If a hook has been tampered with, RTK exits with code 1. See [`src/hooks/integrity.rs`](../../src/hooks/integrity.rs).
+
+**Project filter trust.** `.rtk/filters.toml` files are not loaded until the user explicitly trusts them, and content changes require re-trust. See [`src/hooks/trust.rs`](../../src/hooks/trust.rs).
+
+**Permission whitelist.** `is_operational_command()` in `main.rs` uses a whitelist pattern — new commands are *not* integrity-checked until explicitly added. This is an intentional security posture: fail-open with an audit trail is preferred over false confidence.
+
+**`unsafe` code.** Not allowed except for Unix signal handling in proxy mode, which is correctly scoped to `#[cfg(unix)]`.
diff --git a/docs/TECHNICAL.md b/docs/contributing/TECHNICAL.md
similarity index 71%
rename from docs/TECHNICAL.md
rename to docs/contributing/TECHNICAL.md
index 541f712d4..db2ac0af1 100644
--- a/docs/TECHNICAL.md
+++ b/docs/contributing/TECHNICAL.md
@@ -3,7 +3,7 @@
> **Start here** for a guided tour of how RTK works end-to-end.
>
> - [CONTRIBUTING.md](../CONTRIBUTING.md) — Design philosophy, PR process, branch naming, testing requirements
-> - [ARCHITECTURE.md](../ARCHITECTURE.md) — Deep reference: filtering taxonomy, performance benchmarks, architecture decisions
+> - [ARCHITECTURE.md](ARCHITECTURE.md) — Deep reference: filtering taxonomy, performance benchmarks, architecture decisions
> - Each folder has its own `README.md` with implementation details and file descriptions
---
@@ -94,15 +94,129 @@ All rewrite logic lives in Rust (`src/discover/registry.rs`). Hooks are thin del
> **Details**: [`hooks/README.md`](../hooks/README.md) covers each agent's JSON format, the rewrite registry, compound command handling, and the `RTK_DISABLED` override.
+#### Rewrite Pipeline
+
+The rewrite pipeline is how RTK intercepts and rewrites commands. The call chain is:
+
+```
+hook shell → rewrite_cmd.rs → rewrite_command() → rewrite_compound() → rewrite_segment() → classify_command()
+```
+
+Traced step by step for `cargo fmt --all && cargo test 2>&1 | tail -20`:
+
+```
+LLM Agent: "cargo fmt --all && cargo test 2>&1 | tail -20"
+ |
+ | Hook shell (hooks/claude/rtk-rewrite.sh)
+ | Reads JSON from agent, extracts command, calls `rtk rewrite "$CMD"`
+ | On failure (jq missing, rtk missing, old version): exit 0 (passthrough)
+ |
+ v
+rewrite_cmd::run(cmd) [src/hooks/rewrite_cmd.rs]
+ | 1. Load config → hooks.exclude_commands
+ | 2. check_command(cmd) → Deny → exit(2)
+ | 3. registry::rewrite_command(cmd, excluded)
+ | → None → exit(1) (no RTK equivalent, passthrough)
+ | → Some + Allow → print, exit(0)
+ | → Some + Ask → print, exit(3)
+ |
+ v
+rewrite_command(cmd, excluded) [src/discover/registry.rs]
+ | Early exits:
+ | - Empty → None
+ | - Contains "<<" or "$((" (heredoc/arithmetic) → None
+ | - Simple "rtk ..." (no operators) → return as-is
+ | - Otherwise → rewrite_compound(cmd, excluded)
+ |
+ v
+rewrite_compound(cmd, excluded) [src/discover/registry.rs]
+ |
+ | Step 1 — Tokenize (lexer.rs)
+ | tokenize() produces typed tokens with byte offsets:
+ | Arg("cargo") Arg("fmt") Arg("--all")
+ | Operator("&&")
+ | Arg("cargo") Arg("test") Redirect("2>&1")
+ | Pipe("|")
+ | Arg("tail") Arg("-20")
+ |
+ | Step 2 — Split on operators, rewrite each segment
+ | Operator (&&, ||, ;) → rewrite both sides
+ | Pipe (|) → rewrite left side only, keep right side raw
+ | exception: find/fd before pipe → skip rewrite
+ | Shellism (&) → rewrite both sides (background)
+ |
+ | Calls rewrite_segment() per segment:
+ | segment 1: "cargo fmt --all"
+ | segment 2: "cargo test 2>&1"
+ | after pipe: "tail -20" kept raw
+ |
+ v
+rewrite_segment(seg, excluded) [src/discover/registry.rs]
+ |
+ | Step 3 — Strip trailing redirects
+ | strip_trailing_redirects() re-tokenizes the segment:
+ | "cargo test 2>&1" → cmd_part="cargo test", redirect=" 2>&1"
+ | (simple commands like "cargo fmt --all" → no redirect, suffix is "")
+ |
+ | Step 4 — Already RTK → return as-is
+ |
+ | Step 5 — Special cases (short-circuit before classification)
+ | head -N / --lines=N → rewrite_line_range() → "rtk read file --max-lines N"
+ | tail -N / -n N / --lines N → rewrite_line_range() → "rtk read file --tail-lines N"
+ | head/tail with unsupported flag (-c, -f) → None (skip rewrite)
+ | cat with incompatible flag (-A, -v, -e) → None (skip rewrite)
+ |
+ | Step 6 — classify_command(cmd_part) [see below]
+ | → Supported → check excluded list → continue
+ | → Unsupported/Ignored → None (skip rewrite)
+ |
+ | Step 7 — Build rewritten command
+ | a. Find matching rule from rules.rs
+ | b. Extract env prefix (ENV_PREFIX regex, second pass — first was in classify)
+ | e.g. "GIT_SSH_COMMAND=\"ssh -o ...\" git push" → prefix="GIT_SSH_COMMAND=..."
+ | c. Guard: RTK_DISABLED=1 in prefix → None
+ | d. Guard: gh with --json/--jq/--template → None
+ | e. Apply rule's rewrite_prefixes: "cargo fmt" → "rtk cargo fmt"
+ | f. Reassemble: env_prefix + rtk_cmd + args + redirect_suffix
+ |
+ v
+classify_command(cmd) [src/discover/registry.rs]
+ | 1. Check IGNORED_EXACT (cd, echo, fi, done, ...)
+ | 2. Check IGNORED_PREFIXES (rtk, mkdir, mv, ...)
+ | 3. Strip env prefix with ENV_PREFIX regex (for pattern matching only)
+ | 4. Normalize absolute paths: /usr/bin/grep → grep
+ | 5. Strip git global opts: git -C /tmp status → git status
+ | 6. Guard: cat/head/tail with redirect (>, >>) → Unsupported (write, not read)
+ | 7. Match against REGEX_SET (60+ compiled patterns from rules.rs)
+ | 8. Extract subcommand → lookup custom savings/status overrides
+ | 9. Return Classification::Supported { rtk_equivalent, category, savings, status }
+ |
+ v
+Result: "rtk cargo fmt --all && rtk cargo test 2>&1 | tail -20"
+ |
+ | Hook response
+ | Hook wraps result in agent-specific JSON, returns to LLM agent
+ |
+ v
+LLM Agent executes rewritten command
+ (bash handles && and |, each rtk invocation is a separate process)
+```
+
+Key design decisions:
+- **Lexer-based tokenization**: A single-pass state machine (`lexer.rs`) handles all shell constructs (quotes, escapes, redirects, operators). Used for both compound splitting and redirect stripping.
+- **Segment-level rewriting**: Compound commands are split by operators, each segment rewritten independently. Bash recombines them at execution time.
+- **Pipe semantics**: Only the left side of `|` is rewritten. The pipe consumer (grep, head, wc) runs raw. `find`/`fd` before a pipe is never rewritten (output format incompatible with xargs).
+- **Double env prefix handling**: `classify_command()` strips env prefixes to match the underlying command against rules. `rewrite_segment()` extracts the same prefix separately to re-prepend it to the rewritten command.
+- **Fallback contract**: If any segment fails to match, it stays raw. `rewrite_command()` returns `None` only when zero segments were rewritten.
+
### 3.3 CLI Parsing and Routing
Once the rewritten command reaches RTK:
-1. **Telemetry**: `telemetry::maybe_ping()` fires a non-blocking daily usage ping
-2. **Clap parsing**: `Cli::try_parse()` matches against the `Commands` enum
-3. **Hook check**: `hook_check::maybe_warn()` warns if the installed hook is outdated (rate-limited to 1/day)
-4. **Integrity check**: `integrity::runtime_check()` verifies the hook's SHA-256 hash for operational commands
-5. **Routing**: A `match cli.command` dispatches to the specialized filter module
+1. **Clap parsing**: `Cli::try_parse()` matches against the `Commands` enum
+2. **Hook check**: `hook_check::maybe_warn()` warns if the installed hook is outdated (rate-limited to 1/day)
+3. **Integrity check**: `integrity::runtime_check()` verifies the hook's SHA-256 hash for operational commands
+4. **Routing**: A `match cli.command` dispatches to the specialized filter module
If Clap parsing fails (command not in the enum), the fallback path runs instead.
diff --git a/docs/filter-workflow.md b/docs/filter-workflow.md
deleted file mode 100644
index 0b0d32c1b..000000000
--- a/docs/filter-workflow.md
+++ /dev/null
@@ -1,102 +0,0 @@
-# How a TOML filter goes from file to execution
-
-This document explains what happens between "I created `src/filters/my-tool.toml`" and "RTK filters the output of `my-tool`".
-
-## Build pipeline
-
-```mermaid
-flowchart TD
- A[["📄 src/filters/my-tool.toml\n(new file)"]] --> B
-
- subgraph BUILD ["🔨 cargo build"]
- B["build.rs\n① ls src/filters/*.toml\n② sort alphabetically\n③ concat → schema_version = 1 + all files"] --> C
- C{"TOML valid?\nDuplicate names?"} -->|"❌ panic! (build fails)"| D[["🛑 Error message\npoints to bad file"]]
- C -->|"✅ ok"| E[["OUT_DIR/builtin_filters.toml\n(generated file)"]]
- E --> F["rustc\ninclude_str!(concat!(env!(OUT_DIR),\n'/builtin_filters.toml'))"]
- F --> G[["🦀 rtk binary\nBUILTIN_TOML embedded"]]
- end
-
- subgraph TESTS ["🧪 cargo test"]
- H["test_builtin_filter_count\nassert_eq!(filters.len(), N)"] -->|"❌ count wrong"| I[["FAIL\n'Expected N, got N+1'\nUpdate the count'"]]
- J["test_builtin_all_expected_\nfilters_present\nassert!(names.contains('my-tool'))"] -->|"❌ name missing"| K[["FAIL\n'my-tool is missing—\nwas its .toml deleted?'"]]
- L["test_builtin_all_filters_\nhave_inline_tests\nassert!(tested.contains(name))"] -->|"❌ no tests"| M[["FAIL\n'Add tests.my-tool\nentries'"]]
- end
-
- subgraph VERIFY ["✅ rtk verify"]
- N["runs [[tests.my-tool]]\ninput → filter → compare expected"]
- N -->|"❌ mismatch"| O[["FAIL\nshows actual vs expected"]]
- N -->|"✅ pass"| P[["60/60 tests passed"]]
- end
-
- G --> H
- G --> J
- G --> L
- G --> N
-
- subgraph RUNTIME ["⚡ rtk my-tool --verbose"]
- Q["Claude Code hook\nmy-tool ... → rtk my-tool ..."] --> R
- R["TomlFilterRegistry::load()\n① .rtk/filters.toml (project)\n② ~/.config/rtk/filters.toml (user)\n③ BUILTIN_TOML (binary)\n④ passthrough"] --> S
- S{"match_command\n'^my-tool\\b'\nmatches?"} -->|"No match"| T[["exec raw\n(passthrough)"]]
- S -->|"✅ match"| U["exec command\ncapture stdout"]
- U --> V
-
- subgraph PIPELINE ["8-stage filter pipeline"]
- V["strip_ansi"] --> W["replace"]
- W --> X{"match_output\nshort-circuit?"}
- X -->|"✅ pattern matched"| Y[["emit message\nstop pipeline"]]
- X -->|"no match"| Z["strip/keep_lines"]
- Z --> AA["truncate_lines_at"]
- AA --> AB["tail_lines"]
- AB --> AC["max_lines"]
- AC --> AD{"output\nempty?"}
- AD -->|"yes"| AE[["emit on_empty"]]
- AD -->|"no"| AF[["print filtered\noutput + exit code"]]
- end
- end
-
- G --> Q
-
- style BUILD fill:#1e3a5f,color:#fff
- style TESTS fill:#1a3a1a,color:#fff
- style VERIFY fill:#2d1b69,color:#fff
- style RUNTIME fill:#3a1a1a,color:#fff
- style PIPELINE fill:#4a2a00,color:#fff
- style D fill:#8b0000,color:#fff
- style I fill:#8b0000,color:#fff
- style K fill:#8b0000,color:#fff
- style M fill:#8b0000,color:#fff
- style O fill:#8b0000,color:#fff
-```
-
-## Step-by-step summary
-
-| Step | Who | What happens | Fails if |
-|------|-----|--------------|----------|
-| 1 | Contributor | Creates `src/filters/my-tool.toml` | — |
-| 2 | `build.rs` | Concatenates all `.toml` files alphabetically | TOML syntax error, duplicate filter name |
-| 3 | `rustc` | Embeds result in binary via `BUILTIN_TOML` const | — |
-| 4 | `cargo test` | 3 guards check count, names, inline test presence | Count not updated, name not in list, no `[[tests.*]]` |
-| 5 | `rtk verify` | Runs each `[[tests.my-tool]]` entry | Filter logic doesn't match expected output |
-| 6 | Runtime | Hook rewrites command, registry looks up filter, pipeline runs | No match → passthrough (not an error) |
-
-## Filter lookup priority at runtime
-
-```mermaid
-flowchart LR
- CMD["rtk my-tool args"] --> P1
- P1{"1. .rtk/filters.toml\n(project-local)"}
- P1 -->|"✅ match"| WIN["apply filter"]
- P1 -->|"no match"| P2
- P2{"2. ~/.config/rtk/filters.toml\n(user-global)\n(macOS alt: ~/Library/Application Support/rtk/filters.toml)"}
- P2 -->|"✅ match"| WIN
- P2 -->|"no match"| P3
- P3{"3. BUILTIN_TOML\n(binary)"}
- P3 -->|"✅ match"| WIN
- P3 -->|"no match"| P4[["exec raw\n(passthrough)"]]
-```
-
-First match wins. A project filter with the same name as a built-in shadows the built-in and triggers a warning:
-
-```
-[rtk] warning: filter 'make' is shadowing a built-in filter
-```
diff --git a/docs/guide/analytics/discover.md b/docs/guide/analytics/discover.md
new file mode 100644
index 000000000..575ca73b2
--- /dev/null
+++ b/docs/guide/analytics/discover.md
@@ -0,0 +1,58 @@
+---
+title: Discover and Session
+description: Find missed savings opportunities with rtk discover, and track RTK adoption with rtk session
+sidebar:
+ order: 2
+---
+
+# Discover and Session
+
+## rtk discover — find missed savings
+
+`rtk discover` analyzes your Claude Code command history to identify commands that ran without RTK filtering and calculates how many tokens you lost.
+
+```bash
+rtk discover # analyze current project history
+rtk discover --all # all projects
+rtk discover --all --since 7 # last 7 days, all projects
+```
+
+**Example output:**
+
+```
+Missed savings analysis (last 7 days)
+────────────────────────────────────
+Command Count Est. lost
+cargo test 12 ~48,000 tokens
+git log 8 ~12,000 tokens
+pnpm list 3 ~6,000 tokens
+────────────────────────────────────
+Total missed: 23 ~66,000 tokens
+
+Run `rtk init --global` to capture these automatically.
+```
+
+If commands appear in the missed list after installing RTK, it usually means the hook isn't active for that agent. See [Troubleshooting](../resources/troubleshooting.md) — "Agent not using RTK".
+
+## rtk session — adoption tracking
+
+`rtk session` shows RTK adoption across recent Claude Code sessions: how many shell commands ran through RTK vs. raw.
+
+```bash
+rtk session
+```
+
+**Example output:**
+
+```
+Recent sessions (last 10)
+─────────────────────────────────────────────────────
+Session Total RTK Coverage
+2026-04-06 14:32 (45 cmds) 45 43 95.6%
+2026-04-05 09:14 (38 cmds) 38 38 100.0%
+2026-04-04 16:50 (52 cmds) 52 49 94.2%
+─────────────────────────────────────────────────────
+Average coverage: 96.6%
+```
+
+Low coverage on a session usually means RTK was disabled (`RTK_DISABLED=1`) or the hook wasn't active for a specific subagent.
diff --git a/docs/guide/analytics/gain.md b/docs/guide/analytics/gain.md
new file mode 100644
index 000000000..706508fce
--- /dev/null
+++ b/docs/guide/analytics/gain.md
@@ -0,0 +1,215 @@
+---
+title: Token Savings Analytics
+description: Measure and analyze your RTK token savings with rtk gain
+sidebar:
+ order: 1
+---
+
+# Token Savings Analytics
+
+`rtk gain` shows how many tokens RTK has saved across all your commands, with daily, weekly, and monthly breakdowns.
+
+## Quick reference
+
+```bash
+# Default summary
+rtk gain
+
+# Temporal breakdowns
+rtk gain --daily # all days since tracking started
+rtk gain --weekly # aggregated by week
+rtk gain --monthly # aggregated by month
+rtk gain --all # all breakdowns at once
+
+# Classic flags
+rtk gain --graph # ASCII graph, last 30 days
+rtk gain --history # last 10 commands
+rtk gain --quota # monthly quota savings estimate (default tier: 20x)
+rtk gain --quota -t pro # use pro tier token budget for estimate
+
+# Export
+rtk gain --all --format json > savings.json
+rtk gain --all --format csv > savings.csv
+```
+
+## Daily breakdown
+
+```bash
+rtk gain --daily
+```
+
+```
+📅 Daily Breakdown (3 days)
+════════════════════════════════════════════════════════════════
+Date Cmds Input Output Saved Save%
+────────────────────────────────────────────────────────────────
+2026-01-28 89 380.9K 26.7K 355.8K 93.4%
+2026-01-29 102 894.5K 32.4K 863.7K 96.6%
+2026-01-30 5 749 55 694 92.7%
+────────────────────────────────────────────────────────────────
+TOTAL 196 1.3M 59.2K 1.2M 95.6%
+```
+
+- **Cmds**: RTK commands executed
+- **Input**: Estimated tokens from raw command output
+- **Output**: Actual tokens after filtering
+- **Saved**: Input - Output (tokens that never reached the LLM)
+- **Save%**: Saved / Input × 100
+
+## Weekly and monthly breakdowns
+
+```bash
+rtk gain --weekly
+rtk gain --monthly
+```
+
+Same columns as daily, aggregated by Sunday-Saturday week or calendar month.
+
+## Export formats
+
+| Format | Flag | Use case |
+|--------|------|----------|
+| `text` | default | Terminal display |
+| `json` | `--format json` | Programmatic analysis, dashboards |
+| `csv` | `--format csv` | Excel, Python/R, Google Sheets |
+
+**JSON structure:**
+```json
+{
+ "summary": {
+ "total_commands": 196,
+ "total_input": 1276098,
+ "total_output": 59244,
+ "total_saved": 1220217,
+ "avg_savings_pct": 95.62
+ },
+ "daily": [...],
+ "weekly": [...],
+ "monthly": [...]
+}
+```
+
+## Typical savings by command
+
+| Command | Typical savings | Mechanism |
+|---------|----------------|-----------|
+| `git status` | 77-93% | Compact stat format |
+| `eslint` | 84% | Group by rule |
+| `jest` | 94-99% | Show failures only |
+| `vitest` | 94-99% | Show failures only |
+| `find` | 75% | Tree format |
+| `pnpm list` | 70-90% | Compact dependencies |
+| `grep` | 70% | Truncate + group |
+
+## How token estimation works
+
+RTK estimates tokens using `text.len() / 4` (4 characters per token average). This is accurate to ±10% compared to actual LLM tokenization — sufficient for trend analysis.
+
+```
+Input Tokens = estimate_tokens(raw_command_output)
+Output Tokens = estimate_tokens(rtk_filtered_output)
+Saved Tokens = Input - Output
+Savings % = (Saved / Input) × 100
+```
+
+## Database
+
+Savings data is stored locally in SQLite:
+
+- **Location**: `~/.local/share/rtk/history.db` (Linux / macOS)
+- **Retention**: 90 days (automatic cleanup)
+- **Scope**: Global across all projects and Claude sessions
+
+```bash
+# Inspect raw data
+sqlite3 ~/.local/share/rtk/history.db \
+ "SELECT timestamp, rtk_cmd, saved_tokens FROM commands
+ ORDER BY timestamp DESC LIMIT 10"
+
+# Backup
+cp ~/.local/share/rtk/history.db ~/backups/rtk-history-$(date +%Y%m%d).db
+
+# Reset
+rm ~/.local/share/rtk/history.db # recreated on next command
+```
+
+## Analysis workflows
+
+```bash
+# Weekly progress: generate a CSV report every Monday
+rtk gain --weekly --format csv > reports/week-$(date +%Y-%W).csv
+
+# Monthly budget review
+rtk gain --monthly --format json | jq '.monthly[] |
+ {month, saved_tokens, quota_pct: (.saved_tokens / 6000000 * 100)}'
+
+# Cron: daily JSON snapshot for a dashboard
+0 0 * * * rtk gain --all --format json > /var/www/dashboard/rtk-stats.json
+```
+
+**Python/pandas:**
+```python
+import pandas as pd
+import subprocess
+
+result = subprocess.run(['rtk', 'gain', '--all', '--format', 'csv'],
+ capture_output=True, text=True)
+lines = result.stdout.split('\n')
+daily_start = lines.index('# Daily Data') + 2
+daily_end = lines.index('', daily_start)
+daily_df = pd.read_csv(pd.StringIO('\n'.join(lines[daily_start:daily_end])))
+daily_df['date'] = pd.to_datetime(daily_df['date'])
+daily_df.plot(x='date', y='savings_pct', kind='line')
+```
+
+**GitHub Actions (weekly stats):**
+```yaml
+on:
+ schedule:
+ - cron: '0 0 * * 1'
+jobs:
+ stats:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - run: cargo install rtk
+ - run: rtk gain --weekly --format json > stats/week-$(date +%Y-%W).json
+ - run: git add stats/ && git commit -m "Weekly rtk stats" && git push
+```
+
+## Quota estimate
+
+`--quota` estimates how many tokens RTK has saved relative to your monthly subscription budget, so you can see the cost impact of those savings.
+
+```bash
+rtk gain --quota # uses 20x tier by default
+rtk gain --quota -t pro # Claude Pro plan budget
+rtk gain --quota -t 5x # 5× usage plan budget
+rtk gain --quota -t 20x # 20× usage plan budget
+```
+
+The tiers (`pro`, `5x`, `20x`) correspond to Anthropic Claude API subscription levels, each with a different monthly token allocation. RTK uses those allocations as a denominator to express your savings as a percentage of your budget.
+
+:::tip[Find missed savings]
+`rtk gain` shows what RTK saved. To find commands that ran *without* RTK and calculate what you lost, see [rtk discover](./discover.md).
+:::
+
+## Troubleshooting
+
+**No data showing:**
+```bash
+ls -lh ~/.local/share/rtk/history.db
+sqlite3 ~/.local/share/rtk/history.db "SELECT COUNT(*) FROM commands"
+git status # run any tracked command to generate data
+```
+
+**Incorrect statistics:** Token estimation is a heuristic. For precise counts, use `tiktoken`:
+```bash
+pip install tiktoken
+git status > output.txt
+python -c "
+import tiktoken
+enc = tiktoken.get_encoding('cl100k_base')
+print(len(enc.encode(open('output.txt').read())), 'actual tokens')
+"
+```
diff --git a/docs/guide/getting-started/configuration.md b/docs/guide/getting-started/configuration.md
new file mode 100644
index 000000000..b242fa125
--- /dev/null
+++ b/docs/guide/getting-started/configuration.md
@@ -0,0 +1,109 @@
+---
+title: Configuration
+description: Customize RTK behavior via config.toml, environment variables, and per-project filters
+sidebar:
+ order: 4
+---
+
+# Configuration
+
+## Config file location
+
+| Platform | Path |
+|----------|------|
+| Linux | `~/.config/rtk/config.toml` |
+| macOS | `~/Library/Application Support/rtk/config.toml` |
+
+```bash
+rtk config # show current configuration
+rtk config --create # create config file with defaults
+```
+
+## Full config structure
+
+```toml
+[tracking]
+enabled = true # enable/disable token tracking
+history_days = 90 # retention in days (auto-cleanup)
+database_path = "/custom/path/history.db" # optional override
+
+[display]
+colors = true # colored output
+emoji = true # use emojis in output
+max_width = 120 # maximum output width
+
+[filters]
+# These apply to file-reading commands (ls, find, grep, cat/rtk read).
+# Paths matching these patterns are excluded from output, keeping noise low.
+ignore_dirs = [".git", "node_modules", "target", "__pycache__", ".venv", "vendor"]
+ignore_files = ["*.lock", "*.min.js", "*.min.css"]
+
+[tee]
+enabled = true # save raw output on failure
+mode = "failures" # "failures" (default), "always", "never"
+max_files = 20 # rotation: keep last N files
+# directory = "/custom/tee/path" # optional override
+
+[hooks]
+exclude_commands = [] # commands to never auto-rewrite
+```
+
+## Environment variables
+
+| Variable | Description |
+|----------|-------------|
+| `RTK_DISABLED=1` | Disable RTK for a single command (`RTK_DISABLED=1 git status`) |
+| `RTK_TEE_DIR` | Override the tee directory |
+| `RTK_HOOK_AUDIT=1` | Enable hook audit logging |
+| `SKIP_ENV_VALIDATION=1` | Skip env validation (useful with Next.js) |
+
+## Tee system
+
+When a command fails, RTK saves the full raw output to a local file and prints the path:
+
+```
+FAILED: 2/15 tests
+[full output: ~/.local/share/rtk/tee/1707753600_cargo_test.log]
+```
+
+Your AI assistant can then read the file if it needs more detail, without re-running the command.
+
+| Setting | Default | Description |
+|---------|---------|-------------|
+| `tee.enabled` | `true` | Enable/disable |
+| `tee.mode` | `"failures"` | `"failures"`, `"always"`, `"never"` |
+| `tee.max_files` | `20` | Rotation: keep last N files |
+| Min size | 500 bytes | Outputs shorter than this are not saved |
+| Max file size | 1 MB | Truncated above this |
+
+## Excluding commands from auto-rewrite
+
+Prevent specific commands from being rewritten by the hook:
+
+```toml
+[hooks]
+exclude_commands = ["git rebase", "git cherry-pick", "docker exec"]
+```
+
+Patterns match against the full command after stripping env prefixes (`sudo`, `VAR=val`), so `"psql"` excludes both `psql -h localhost` and `PGPASSWORD=x psql -h localhost`.
+
+Subcommand patterns work too: `"git push"` excludes `git push origin main` but not `git status`.
+
+Patterns starting with `^` are treated as regex:
+
+```toml
+[hooks]
+exclude_commands = ["^curl", "^wget", "git rebase"]
+```
+
+Invalid regex patterns fall back to prefix matching.
+
+Or for a single invocation:
+
+```bash
+RTK_DISABLED=1 git rebase main
+```
+
+## Per-project filters
+
+Create `.rtk/filters.toml` in your project root to add custom filters or override built-ins. See [`src/filters/README.md`](https://github.com/algolia/rtk/blob/main/src/filters/README.md) for the full TOML DSL reference.
diff --git a/docs/guide/getting-started/installation.md b/docs/guide/getting-started/installation.md
new file mode 100644
index 000000000..a7c7bb682
--- /dev/null
+++ b/docs/guide/getting-started/installation.md
@@ -0,0 +1,95 @@
+---
+title: Installation
+description: Install RTK via curl, Homebrew, Cargo, or from source, and verify the correct version
+sidebar:
+ order: 1
+---
+
+# Installation
+
+## Name collision warning
+
+Two unrelated projects share the name `rtk`. Make sure you install the right one:
+
+- **Rust Token Killer** (`algolia/rtk`) — this project, a token-saving CLI proxy
+- **Rust Type Kit** (`reachingforthejack/rtk`) — a different tool for generating Rust types
+
+The easiest way to verify you have the correct one: run `rtk gain`. It should display token savings stats. If it returns "command not found", you either have the wrong package or RTK is not installed.
+
+## Check before installing
+
+```bash
+rtk --version # should print: rtk x.y.z
+rtk gain # should show token savings stats
+```
+
+If both commands work, RTK is already installed. Skip to [Project initialization](#project-initialization).
+
+## Quick install (Linux and macOS)
+
+```bash
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/main/install.sh | sh
+```
+
+## Homebrew (macOS and Linux)
+
+```bash
+brew install rtk-ai/tap/rtk
+```
+
+## Cargo
+
+:::caution[Name collision risk]
+`cargo install rtk` may install **Rust Type Kit** instead of Rust Token Killer — two unrelated projects share the same crate name. Use the explicit Git URL to guarantee the correct package:
+:::
+
+```bash
+cargo install --git https://github.com/algolia/rtk rtk
+```
+
+## Pre-built binaries (Windows, Linux, macOS)
+
+Download from [GitHub releases](https://github.com/algolia/rtk/releases):
+
+- macOS: `rtk-x86_64-apple-darwin.tar.gz` / `rtk-aarch64-apple-darwin.tar.gz`
+- Linux: `rtk-x86_64-unknown-linux-musl.tar.gz` / `rtk-aarch64-unknown-linux-gnu.tar.gz`
+- Windows: `rtk-x86_64-pc-windows-msvc.zip`
+
+**Windows users**: Extract the zip and place `rtk.exe` in a directory on your PATH. Run RTK from Command Prompt, PowerShell, or Windows Terminal — do not double-click the `.exe` (it prints usage and exits immediately). For full hook support, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) instead.
+
+## Verify installation
+
+```bash
+rtk --version # rtk x.y.z
+rtk gain # token savings dashboard
+```
+
+If `rtk gain` fails but `rtk --version` succeeds, you installed Rust Type Kit by mistake. Uninstall it first:
+
+```bash
+cargo uninstall rtk
+```
+
+Then reinstall using one of the methods above.
+
+## Project initialization
+
+Run once per project to enable the Claude Code hook:
+
+```bash
+rtk init
+```
+
+For a global install that patches `settings.json` automatically:
+
+```bash
+rtk init --global
+```
+
+## Uninstall
+
+```bash
+rtk init -g --uninstall # remove hook, RTK.md, and settings.json entry
+cargo uninstall rtk # remove binary (if installed via Cargo)
+brew uninstall rtk # remove binary (if installed via Homebrew)
+```
diff --git a/docs/guide/getting-started/quick-start.md b/docs/guide/getting-started/quick-start.md
new file mode 100644
index 000000000..6e1b7b558
--- /dev/null
+++ b/docs/guide/getting-started/quick-start.md
@@ -0,0 +1,70 @@
+---
+title: Quick Start
+description: Get RTK running in 5 minutes and see your first token savings
+sidebar:
+ order: 2
+---
+
+# Quick Start
+
+This guide walks you through your first RTK commands after installation.
+
+## Prerequisites
+
+RTK is installed and verified:
+
+```bash
+rtk --version # rtk x.y.z
+rtk gain # shows token savings dashboard
+```
+
+If not, see [Installation](./installation.md).
+
+## Step 1: Initialize for your AI assistant
+
+```bash
+# For Claude Code (global — applies to all projects)
+rtk init --global
+
+# For a single project only
+cd /your/project && rtk init
+```
+
+This installs the hook that automatically rewrites commands. Restart your AI assistant after this step.
+
+## Step 2: Use your tools normally
+
+Once the hook is installed, nothing changes in how you work. Your AI assistant runs commands as usual — the hook intercepts them transparently and rewrites them before execution.
+
+For example, when Claude Code runs `cargo test`, the hook rewrites it to `rtk cargo test` before it executes. The LLM receives filtered output with only the failures — not 500 lines of passing tests. You never see or type `rtk`.
+
+RTK covers all major ecosystems — Git, Cargo/Rust, JavaScript, Python, Go, Ruby, .NET, Docker/Kubernetes, and more. See [What RTK Optimizes](../resources/what-rtk-covers.md) for the full list.
+
+## Step 3: Check your savings
+
+After a few commands, see how much was saved:
+
+```bash
+rtk gain
+```
+
+```
+Total commands : 12
+Input tokens : 45,230
+Output tokens : 4,890
+Saved : 40,340 (89.2%)
+```
+
+## Step 4: Unsupported commands
+
+Commands RTK doesn't recognize run through passthrough — output is unchanged, usage is tracked:
+
+```bash
+rtk proxy make install
+```
+
+## Next steps
+
+- [What RTK Optimizes](../resources/what-rtk-covers.md) — all supported commands and savings by ecosystem
+- [Supported agents](./supported-agents.md) — Claude Code, Cursor, Copilot, and more
+- [Configuration](./configuration.md) — customize RTK behavior
diff --git a/docs/guide/getting-started/supported-agents.md b/docs/guide/getting-started/supported-agents.md
new file mode 100644
index 000000000..0a1b50219
--- /dev/null
+++ b/docs/guide/getting-started/supported-agents.md
@@ -0,0 +1,175 @@
+---
+title: Supported Agents
+description: How to integrate RTK with Claude Code, Cursor, Copilot, Cline, Windsurf, Codex, OpenCode, Kilo Code, and Antigravity
+sidebar:
+ order: 3
+---
+
+# Supported Agents
+
+RTK supports all major AI coding agents across 3 integration tiers. Mistral Vibe support is planned.
+
+## How it works
+
+Each agent integration intercepts CLI commands before execution and rewrites them to their RTK equivalent. The agent runs `rtk cargo test` instead of `cargo test`, sees filtered output, and uses up to 90% fewer tokens — without any change to your workflow.
+
+All rewrite logic lives in the RTK binary (`rtk rewrite`). Agent hooks are thin delegates that parse the agent-specific JSON format and call `rtk rewrite` for the actual decision.
+
+```
+Agent runs "cargo test"
+ -> Hook intercepts (PreToolUse / plugin event)
+ -> Calls rtk rewrite "cargo test"
+ -> Returns "rtk cargo test"
+ -> Agent executes filtered command
+ -> LLM sees 90% fewer tokens
+```
+
+## Supported agents
+
+| Agent | Integration tier | Can rewrite transparently? |
+|-------|-----------------|---------------------------|
+| Claude Code | Shell hook (`PreToolUse`) | Yes |
+| VS Code Copilot Chat | Shell hook (`PreToolUse`) | Yes |
+| GitHub Copilot CLI | Shell hook (deny-with-suggestion) | No (agent retries) |
+| Cursor | Shell hook (`preToolUse`) | Yes |
+| Gemini CLI | Rust binary (`BeforeTool`) | Yes |
+| OpenCode | TypeScript plugin (`tool.execute.before`) | Yes |
+| OpenClaw | TypeScript plugin (`before_tool_call`) | Yes |
+| Cline / Roo Code | Rules file (prompt-level) | N/A |
+| Windsurf | Rules file (prompt-level) | N/A |
+| Codex CLI | AGENTS.md instructions | N/A |
+| Kilo Code | Rules file (prompt-level) | N/A |
+| Google Antigravity | Rules file (prompt-level) | N/A |
+| Mistral Vibe | Planned ([#800](https://github.com/algolia/rtk/issues)) | Pending upstream |
+
+## Installation by agent
+
+### Claude Code
+
+```bash
+rtk init --global # installs hook + patches settings.json
+```
+
+Restart Claude Code. Verify:
+
+```bash
+rtk init --show # shows hook status
+```
+
+### Cursor
+
+```bash
+rtk init --global --cursor
+```
+
+Restart Cursor. The hook uses `preToolUse` with Cursor's `updated_input` format.
+
+### VS Code Copilot Chat
+
+```bash
+rtk init --global --copilot
+```
+
+### Gemini CLI
+
+```bash
+rtk init --global --gemini
+```
+
+### OpenCode
+
+```bash
+rtk init --global --opencode
+```
+
+Creates `~/.config/opencode/plugins/rtk.ts`. Uses the `tool.execute.before` hook.
+
+### OpenClaw
+
+```bash
+openclaw plugins install ./openclaw
+```
+
+Plugin in the `openclaw/` directory. Uses the `before_tool_call` hook, delegates to `rtk rewrite`.
+
+### Cline / Roo Code
+
+```bash
+rtk init --cline # creates .clinerules in current project
+```
+
+Cline reads `.clinerules` as custom instructions. RTK adds guidance telling Cline to prefer `rtk ` over raw commands.
+
+### Windsurf
+
+```bash
+rtk init --windsurf # creates .windsurfrules in current project
+```
+
+### Codex CLI
+
+```bash
+rtk init --codex # creates AGENTS.md or patches existing one
+```
+
+### Kilo Code
+
+```bash
+rtk init --agent kilocode # creates .kilocode/rules/rtk-rules.md in current project
+```
+
+Kilo Code reads `.kilocode/rules/` as custom instructions. RTK adds guidance telling Kilo Code to prefer `rtk ` over raw commands.
+
+### Google Antigravity
+
+```bash
+rtk init --agent antigravity # creates .agents/rules/antigravity-rtk-rules.md in current project
+```
+
+Antigravity reads `.agents/rules/` as custom instructions. RTK adds guidance telling Antigravity to prefer `rtk ` over raw commands.
+
+### Mistral Vibe (planned)
+
+Support is blocked on upstream `BeforeToolCallback` ([mistral-vibe#531](https://github.com/mistralai/mistral-vibe/issues/531)). Tracked in [#800](https://github.com/algolia/rtk/issues).
+
+## Integration tiers explained
+
+| Tier | Mechanism | How rewrites work |
+|------|-----------|------------------|
+| **Full hook** | Shell script or Rust binary, intercepts via agent API | Transparent — agent never sees the raw command |
+| **Plugin** | TypeScript/JS in agent's plugin system | Transparent — in-place mutation |
+| **Rules file** | Prompt-level instructions | Guidance only — agent is told to prefer `rtk ` |
+
+Rules file integrations (Cline, Windsurf, Codex, Kilo Code, Antigravity) rely on the model following instructions. Full hook integrations (Claude Code, Cursor, Gemini) are guaranteed — the command is rewritten before the agent sees it.
+
+## Windows support
+
+The shell hook (`rtk-rewrite.sh`) requires a Unix shell. On native Windows:
+
+- `rtk init -g` automatically falls back to **CLAUDE.md injection mode** (prompt-level instructions)
+- Filters work normally (`rtk cargo test`, `rtk git status`)
+- Auto-rewrite does not work — the AI assistant is instructed to use RTK but commands are not intercepted
+
+For full hook support on Windows, use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install). Inside WSL, all agents with shell hook integration (Claude Code, Cursor, Gemini) work identically to Linux.
+
+## Graceful degradation
+
+Hooks never block command execution. If RTK is missing, the hook exits cleanly and the raw command runs unchanged:
+
+- RTK binary not found: warning to stderr, exit 0
+- Invalid JSON input: pass through unchanged
+- RTK version too old: warning to stderr, exit 0
+- Filter logic error: fallback to raw command output
+
+## Override: disable RTK for one command
+
+```bash
+RTK_DISABLED=1 git status # runs raw git status, no rewrite
+```
+
+Or exclude commands permanently in `~/.config/rtk/config.toml`:
+
+```toml
+[hooks]
+exclude_commands = ["git rebase", "git cherry-pick"]
+```
diff --git a/docs/guide/index.md b/docs/guide/index.md
new file mode 100644
index 000000000..4ef74a532
--- /dev/null
+++ b/docs/guide/index.md
@@ -0,0 +1,64 @@
+---
+title: RTK Documentation
+description: RTK (Rust Token Killer) — reduce LLM token consumption by 60-90% on common dev commands, with zero workflow changes
+sidebar:
+ order: 1
+---
+
+# RTK — Rust Token Killer
+
+RTK is a CLI proxy that sits between your AI assistant and your development tools. It filters command output before it reaches the LLM, keeping only what matters and discarding boilerplate, progress bars, and noise.
+
+**Result:** 60-90% fewer tokens consumed per command, without changing how you work. You run `git status` as usual — RTK's hook intercepts it, filters the output, and the LLM sees a compact 3-line summary instead of 40 lines.
+
+## How it works
+
+```
+Your AI assistant runs: git status
+ ↓
+ Hook intercepts (PreToolUse)
+ ↓
+ rtk git status (transparent rewrite)
+ ↓
+ Raw output: 40 lines → Filtered: 3 lines
+ ~800 tokens → ~60 tokens (92% saved)
+ ↓
+ LLM sees the compact output
+```
+
+Zero config changes to your workflow. The hook handles everything automatically.
+
+## What RTK optimizes
+
+Dozens of commands across all major ecosystems — Git, Cargo/Rust, JavaScript, Python, Go, Ruby, .NET, Docker/Kubernetes, and more. See [What RTK Optimizes](./resources/what-rtk-covers.md) for the full list with savings percentages.
+
+## Get started
+
+1. **[Installation](./getting-started/installation.md)** — Install RTK and verify you have the right package
+2. **[Quick Start](./getting-started/quick-start.md)** — Connect to your AI assistant in 5 minutes
+3. **[Supported Agents](./getting-started/supported-agents.md)** — Claude Code, Cursor, Copilot, Gemini, and more
+
+## Measure your savings
+
+```bash
+rtk gain # total savings across all sessions
+rtk gain --daily # day-by-day breakdown
+rtk gain --weekly # weekly aggregation
+```
+
+See [Token Savings Analytics](./analytics/gain.md) for export formats and analysis workflows.
+
+## Analyze your usage
+
+```bash
+rtk discover # find commands that ran without RTK (missed savings)
+rtk session # RTK adoption rate per Claude Code session
+```
+
+See [Discover and Session](./analytics/discover.md) for details.
+
+## Further reading
+
+- [Configuration](./getting-started/configuration.md) — config.toml, global flags, env vars, tee recovery
+- [Troubleshooting](./resources/troubleshooting.md) — common issues and fixes
+- [ARCHITECTURE.md](../contributing/ARCHITECTURE.md) — system design for contributors
diff --git a/docs/guide/resources/troubleshooting.md b/docs/guide/resources/troubleshooting.md
new file mode 100644
index 000000000..09bb608fb
--- /dev/null
+++ b/docs/guide/resources/troubleshooting.md
@@ -0,0 +1,184 @@
+---
+title: Troubleshooting
+description: Common RTK issues and how to fix them
+sidebar:
+ order: 2
+---
+
+# Troubleshooting
+
+## `rtk gain` says "not a rtk command"
+
+**Symptom:**
+```bash
+$ rtk gain
+rtk: 'gain' is not a rtk command. See 'rtk --help'.
+```
+
+**Cause:** You installed **Rust Type Kit** (`reachingforthejack/rtk`) instead of **Rust Token Killer** (`algolia/rtk`). They share the same binary name.
+
+**Fix:**
+```bash
+cargo uninstall rtk
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/main/install.sh | sh
+rtk gain # should now show token savings stats
+```
+
+## How to tell which rtk you have
+
+| If `rtk gain`... | You have |
+|------------------|----------|
+| Shows token savings dashboard | Rust Token Killer ✅ |
+| Returns "not a rtk command" | Rust Type Kit ❌ |
+
+## AI assistant not using RTK
+
+**Symptom:** Claude Code (or another agent) runs `cargo test` instead of `rtk cargo test`.
+
+**Checklist:**
+
+1. Verify RTK is installed:
+ ```bash
+ rtk --version
+ rtk gain
+ ```
+
+2. Initialize the hook:
+ ```bash
+ rtk init --global # Claude Code
+ rtk init --global --cursor # Cursor
+ rtk init --global --opencode # OpenCode
+ ```
+
+3. Restart your AI assistant.
+
+4. Verify hook status:
+ ```bash
+ rtk init --show
+ ```
+
+5. Check `settings.json` has the hook registered (Claude Code):
+ ```bash
+ cat ~/.claude/settings.json | grep rtk
+ ```
+
+## RTK not found after `cargo install`
+
+**Symptom:**
+```bash
+$ rtk --version
+zsh: command not found: rtk
+```
+
+**Cause:** `~/.cargo/bin` is not in your PATH.
+
+**Fix:**
+
+For bash (`~/.bashrc`) or zsh (`~/.zshrc`):
+```bash
+export PATH="$HOME/.cargo/bin:$PATH"
+```
+
+For fish (`~/.config/fish/config.fish`):
+```fish
+set -gx PATH $HOME/.cargo/bin $PATH
+```
+
+Then reload:
+```bash
+source ~/.zshrc # or ~/.bashrc
+rtk --version
+```
+
+## RTK on Windows
+
+### Double-clicking rtk.exe does nothing
+
+**Symptom:** You double-click `rtk.exe`, a terminal flashes and closes instantly.
+
+**Cause:** RTK is a command-line tool. With no arguments, it prints usage and exits. The console window opens and closes before you can read anything.
+
+**Fix:** Open a terminal first, then run RTK from there:
+- Press `Win+R`, type `cmd`, press Enter
+- Or open PowerShell or Windows Terminal
+- Then run: `rtk --version`
+
+### Hook not working (no auto-rewrite)
+
+**Symptom:** `rtk init -g` shows "Falling back to --claude-md mode" on Windows.
+
+**Cause:** The auto-rewrite hook (`rtk-rewrite.sh`) requires a Unix shell. Native Windows doesn't have one.
+
+**Fix:** Use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) for full hook support:
+```bash
+# Inside WSL
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/refs/heads/main/install.sh | sh
+rtk init -g # full hook mode works in WSL
+```
+
+On native Windows, RTK falls back to CLAUDE.md injection. Your AI assistant gets RTK instructions but won't auto-rewrite commands. It can still use RTK manually: `rtk cargo test`, `rtk git status`, etc.
+
+### Node.js tools not found
+
+**Symptom:**
+```
+rtk vitest --run
+Error: program not found
+```
+
+**Cause:** On Windows, Node.js tools are installed as `.CMD`/`.BAT` wrappers. Older RTK versions couldn't find them.
+
+**Fix:** Update to RTK v0.23.1+:
+```bash
+cargo install --git https://github.com/algolia/rtk
+rtk --version # should be 0.23.1+
+```
+
+## Compilation error during installation
+
+```bash
+rustup update stable
+rustup default stable
+cargo clean
+cargo build --release
+cargo install --path . --force
+```
+
+Minimum required Rust version: 1.70+.
+
+## OpenCode not using RTK
+
+```bash
+rtk init --global --opencode
+# restart OpenCode
+rtk init --show # should show "OpenCode: plugin installed"
+```
+
+## `cargo install rtk` installs the wrong package
+
+If Rust Type Kit is published to crates.io under the name `rtk`, `cargo install rtk` may install the wrong one.
+
+Always use the explicit URL:
+
+```bash
+cargo install --git https://github.com/algolia/rtk
+```
+
+## Run the diagnostic script
+
+From the RTK repository root:
+
+```bash
+bash scripts/check-installation.sh
+```
+
+Checks:
+- RTK installed and in PATH
+- Correct version (Token Killer, not Type Kit)
+- Available features
+- Claude Code integration
+- Hook status
+
+## Still stuck?
+
+Open an issue: https://github.com/algolia/rtk/issues
diff --git a/docs/guide/resources/what-rtk-covers.md b/docs/guide/resources/what-rtk-covers.md
new file mode 100644
index 000000000..dd5c39e89
--- /dev/null
+++ b/docs/guide/resources/what-rtk-covers.md
@@ -0,0 +1,157 @@
+---
+title: What RTK Optimizes
+description: Commands and ecosystems automatically optimized by RTK with typical token savings
+sidebar:
+ order: 1
+---
+
+# What RTK Optimizes
+
+Once RTK is installed with a hook, these commands are automatically intercepted and filtered. You run them normally — the hook rewrites them transparently before execution.
+
+Typical savings: 60-99%.
+
+## Git
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `git status` | 75-93% | Compact stat format, grouped by state |
+| `git log` | 80-92% | Hash + author + subject only |
+| `git diff` | 70% | Context reduced, headers stripped |
+| `git show` | 70% | Same as diff |
+| `git stash list` | 75% | Compact one-line per entry |
+
+## GitHub CLI
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `gh pr view` | 87% | Removes ASCII art and verbose metadata |
+| `gh pr checks` | 79% | Status + name only, failures highlighted |
+| `gh run list` | 82% | Compact workflow run summary |
+| `gh issue view` | 80% | Body only, no decoration |
+
+## Graphite (Stacked PRs)
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `gt log` | 75% | Stack summary only |
+| `gt status` | 70% | Current branch context |
+
+## Cargo / Rust
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `cargo test` | 90% | Failures only, passed tests suppressed |
+| `cargo nextest` | 90% | Same as test |
+| `cargo build` | 80% | Errors and warnings only |
+| `cargo check` | 80% | Errors and warnings only |
+| `cargo clippy` | 80% | Lint warnings grouped by file |
+
+## JavaScript / TypeScript
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `jest` | 94-99% | Failures only |
+| `vitest` | 94-99% | Failures only |
+| `tsc` | 75% | Type errors grouped by file |
+| `eslint` | 84% | Violations grouped by rule |
+| `pnpm list` | 70-90% | Compact dependency tree |
+| `pnpm outdated` | 70% | Package + current + latest only |
+| `next build` | 80% | Route summary + errors only |
+| `prisma migrate` | 75% | Migration status only |
+| `playwright test` | 90% | Failures + trace links only |
+
+## Python
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `pytest` | 80-90% | Failures only |
+| `ruff check` | 75% | Violations grouped by file |
+| `mypy` | 75% | Type errors grouped by file |
+| `pip install` | 70% | Installed packages only, progress stripped |
+
+## Go
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `go test` | 80-90% | Failures only |
+| `golangci-lint run` | 75% | Violations grouped by file |
+| `go build` | 75% | Errors only |
+
+## Ruby
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `rspec` | 80-90% | Failures only |
+| `rubocop` | 75% | Offenses grouped by file |
+| `rake` | 70% | Task output, build errors highlighted |
+
+## .NET
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `dotnet build` | 80% | Errors and warnings only |
+| `dotnet test` | 85-90% | Failures only |
+| `dotnet format` | 75% | Changed files only |
+
+## Docker / Kubernetes
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `docker ps` | 65% | Essential columns (name, image, status, port) |
+| `docker images` | 60% | Name + tag + size only |
+| `docker logs` | 70% | Deduplicated, last N lines |
+| `docker compose up` | 75% | Service status, errors highlighted |
+| `kubectl get pods` | 65% | Name + status + restarts only |
+| `kubectl logs` | 70% | Deduplicated entries |
+
+## Files and Search
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `ls` | 80% | Tree format with file counts |
+| `find` | 75% | Tree format |
+| `grep` | 70% | Truncated lines, grouped by file |
+| `diff` | 65% | Context reduced |
+| `wc` | 60% | Compact counts |
+| `cat` / `head` / `tail ` | 60-80% | Smart file reading via `rtk read` |
+| `rtk smart ` | 85% | 2-line heuristic code summary (signatures only) |
+
+## Cloud and Data
+
+| Command | Savings | What changes |
+|---------|---------|--------------|
+| `aws` | 70% | JSON condensed, relevant fields only |
+| `psql` | 65% | Query results without decoration |
+| `curl` | 60% | Response body only, headers stripped |
+
+## Global flags
+
+These flags apply to all RTK commands and can push savings even higher:
+
+| Flag | Description |
+|------|-------------|
+| `--ultra-compact` | ASCII icons, inline format — extra token reduction on top of normal filtering |
+| `-v` / `--verbose` | Show filtering details on stderr (`-v`, `-vv`, `-vvv` for increasing detail) |
+
+```bash
+# Ultra-compact: even smaller output
+rtk git log --ultra-compact
+
+# Debug: see what RTK is doing
+rtk git status -vvv
+```
+
+:::note
+Use `--ultra-compact` (long form) rather than `-u` when working with Git commands. Git's own `-u` flag means `--set-upstream` and the short form can cause confusion.
+:::
+
+## Commands that are not rewritten
+
+If a command isn't in the list above, RTK runs it through passthrough — the output reaches the LLM unchanged. You can explicitly track unsupported commands:
+
+```bash
+rtk proxy make install # runs make install, tracks usage, no filtering
+```
+
+To check which commands were missed opportunities: `rtk discover`.
diff --git a/docs/images/gain-dashboard.jpg b/docs/images/gain-dashboard.jpg
deleted file mode 100644
index 0d57d7b0b..000000000
Binary files a/docs/images/gain-dashboard.jpg and /dev/null differ
diff --git a/docs/maintainers/MAINTAINERS_APPLY.md b/docs/maintainers/MAINTAINERS_APPLY.md
new file mode 100644
index 000000000..3503f0bb3
--- /dev/null
+++ b/docs/maintainers/MAINTAINERS_APPLY.md
@@ -0,0 +1,72 @@
+# RTK Maintainers Application
+
+RTK is growing fast, with more contributors, PRs, and ideas than ever. To keep things moving smoothly, we're looking for new maintainers.
+
+We've introduced two types of maintainers to progressively build a clean process and strong collaboration between contributors.
+For now, we're starting by recruiting **Ecosystem Maintainers** only. As the project evolves, we'll soon begin accepting **Core Maintainers** as well.
+
+> Maintainers are expected to be active and involved over time, not just occasional contributors.
+
+---
+
+## How to apply guide
+
+#### ✅ Requirements
+
+To apply, you should have:
+
+- 3+ merged PRs to RTK (filters, fixes, docs — all contributions count)
+- 3+ PR reviews with helpful, constructive feedback
+
+---
+
+### ✍️ How to Apply
+
+1. Open a discussion in [algolia/rtk Maintainers Applications · Discussions · GitHub](https://github.com/algolia/rtk/discussions/categories/maintainers-applications) titled **Maintainer Application: [Your GitHub Handle]**
+2. In your application, include:
+ - The ecosystem(s) you're interested in
+ - Your experience with those ecosystems
+ - Links to your merged PRs and reviews
+ - Your Discord username (and make sure you've joined the server)
+ - Your PRs that have been accepted in RTK
+3. For **Core Maintainer** applications, also include:
+ - Your experience with Rust
+ - Your experience with Open Source
+4. A Core Maintainer will get back to you as soon as possible
+5. If it's a good fit, we'll continue the conversation on Discord and guide you through the next steps
+
+---
+
+### 👀 What to Expect
+
+- A review of your ecosystem experience and understanding of RTK concepts
+- A discussion with current maintainers
+- Introduction to the team
+
+---
+
+## What Maintainers Do
+
+### 🌱 Ecosystem Maintainers
+
+Ecosystem Maintainers are responsible for specific environments inside the `cmds/` folder (e.g. `git`, `system`, etc.). They own and manage their ecosystem end-to-end:
+
+- Responsible for the quality of filters
+- Review and ensure quality of contributions
+- Maintain consistency with the rest of the RTK ecosystem
+- Help shape and grow their specific domain
+- Handle issues and PRs related to their environment *(security and quality review from core maintainers still required for release)*
+
+### 🔧 Core Maintainers (once we've fully integrated some Ecosystem Maintainers)
+
+Core Maintainers are responsible for the core of RTK. They have a broader scope and higher responsibilities and permissions, including:
+
+- Maintaining core functionalities and architecture
+- Reviewing and merging PRs for release with the core team
+- Defining project direction and standards with the core team
+- Ensuring consistency across the entire project
+- Refactoring for optimization, standardization & conformity
+
+---
+
+If you enjoy contributing and want to help RTK scale in a healthy way, we'd be excited to have you onboard 🚀
diff --git a/docs/AUDIT_GUIDE.md b/docs/usage/AUDIT_GUIDE.md
similarity index 94%
rename from docs/AUDIT_GUIDE.md
rename to docs/usage/AUDIT_GUIDE.md
index 8bcebdffe..b641f2450 100644
--- a/docs/AUDIT_GUIDE.md
+++ b/docs/usage/AUDIT_GUIDE.md
@@ -29,6 +29,10 @@ rtk gain --all --format csv > savings.csv
# Combined flags
rtk gain --graph --history --quota # Classic view with extras
rtk gain --daily --weekly --monthly # Multiple breakdowns
+
+# Reset all tracking data
+rtk gain --reset # prompts [y/N] before deleting
+rtk gain --reset --yes # skip prompt (CI/scripts)
```
## Command Options
@@ -51,6 +55,15 @@ rtk gain --daily --weekly --monthly # Multiple breakdowns
| `--quota` | Monthly quota analysis (Pro/5x/20x tiers) |
| `--tier ` | Quota tier: pro, 5x, 20x (default: 20x) |
+### Reset Flag
+
+| Flag | Description |
+|------|-------------|
+| `--reset` | Permanently delete all tracking data (commands + parse failures) |
+| `--yes` | Skip the confirmation prompt (for CI/scripts) |
+
+> **Warning**: `--reset` is irreversible. It clears both the `commands` and `parse_failures` tables atomically. A `[y/N]` confirmation prompt is shown by default. In non-interactive environments (piped stdin), it defaults to `N` unless `--yes` is passed.
+
### Export Formats
| Format | Flag | Use Case |
@@ -267,7 +280,8 @@ Savings % = (Saved / Input) × 100
|---------|----------------|-----------|
| `rtk git status` | 77-93% | Compact stat format |
| `rtk eslint` | 84% | Group by rule |
-| `rtk vitest run` | 94-99% | Show failures only |
+| `rtk jest` | 94-99% | Show failures only |
+| `rtk vitest` | 94-99% | Show failures only |
| `rtk find` | 75% | Tree format |
| `rtk pnpm list` | 70-90% | Compact dependencies |
| `rtk grep` | 70% | Truncate + group |
@@ -429,4 +443,4 @@ print(f'rtk estimate: {len(text) // 4}')
- [README.md](../README.md) - Full rtk documentation
- [CLAUDE.md](../CLAUDE.md) - Claude Code integration guide
-- [ARCHITECTURE.md](../ARCHITECTURE.md) - Technical architecture
+- [ARCHITECTURE.md](../contributing/ARCHITECTURE.md) - Technical architecture
diff --git a/docs/FEATURES.md b/docs/usage/FEATURES.md
similarity index 97%
rename from docs/FEATURES.md
rename to docs/usage/FEATURES.md
index 061a604a9..901288565 100644
--- a/docs/FEATURES.md
+++ b/docs/usage/FEATURES.md
@@ -25,7 +25,6 @@ Binaire Rust unique, zero dependances externes, overhead < 10ms par commande.
15. [Systeme de hooks](#systeme-de-hooks)
16. [Configuration](#configuration)
17. [Systeme Tee (recuperation de sortie)](#systeme-tee)
-18. [Telemetrie](#telemetrie)
---
@@ -576,12 +575,13 @@ Filtre la sortie de `cargo nextest` pour n'afficher que les echecs.
---
-### `rtk vitest run` -- Tests Vitest
+### `rtk jest` / `rtk vitest` -- Tests Jest/Vitest
**Economies :** ~99.5%
```bash
-rtk vitest run [args...]
+rtk jest [args...]
+rtk vitest [args...]
```
---
@@ -963,13 +963,13 @@ Les lignes repetees sont fusionnees : `[ERROR] Connection refused (x42)`.
---
-### `rtk curl` -- HTTP avec detection JSON
+### `rtk curl` -- HTTP avec troncature
```bash
rtk curl [args...]
```
-Auto-detecte les reponses JSON et affiche le schema au lieu du contenu complet.
+Tronque les reponses longues et sauvegarde la sortie complete dans un fichier pour recuperation.
---
@@ -1258,7 +1258,8 @@ rtk verify
| `ls` | `rtk ls` |
| `tree` | `rtk tree` |
| `wc` | `rtk wc` |
-| `vitest/jest` | `rtk vitest run` |
+| `jest` | `rtk jest` |
+| `vitest` | `rtk vitest` |
| `tsc` | `rtk tsc` |
| `eslint/biome` | `rtk lint` |
| `prettier` | `rtk prettier` |
@@ -1321,9 +1322,6 @@ mode = "failures" # "failures" (defaut), "always", ou "never"
max_files = 20 # Rotation : garder les N derniers fichiers
# directory = "/custom/tee/path" # Chemin personnalise (optionnel)
-[telemetry]
-enabled = true # Telemetrie anonyme (1 ping/jour, opt-out possible)
-
[hooks]
exclude_commands = [] # Commandes a exclure de la recriture automatique
```
@@ -1333,7 +1331,6 @@ exclude_commands = [] # Commandes a exclure de la recriture automatique
| Variable | Description |
|----------|-------------|
| `RTK_TEE_DIR` | Surcharge le repertoire tee |
-| `RTK_TELEMETRY_DISABLED=1` | Desactiver la telemetrie |
| `RTK_HOOK_AUDIT=1` | Activer l'audit du hook |
| `SKIP_ENV_VALIDATION=1` | Desactiver la validation d'env (Next.js, etc.) |
@@ -1369,26 +1366,6 @@ FAILED: 2/15 tests
---
-## Telemetrie
-
-RTK envoie un ping anonyme une fois par jour (23h d'intervalle) pour des statistiques d'utilisation.
-
-**Donnees envoyees :** hash de device, version, OS, architecture, nombre de commandes/24h, top commandes, pourcentage d'economies.
-
-**Desactiver :**
-```bash
-# Via variable d'environnement
-export RTK_TELEMETRY_DISABLED=1
-
-# Via config.toml
-[telemetry]
-enabled = false
-```
-
-Aucune donnee personnelle, aucun contenu de commande, aucun chemin de fichier n'est transmis.
-
----
-
## Resume des economies par categorie
| Categorie | Commandes | Economies typiques |
diff --git a/docs/tracking.md b/docs/usage/TRACKING.md
similarity index 97%
rename from docs/tracking.md
rename to docs/usage/TRACKING.md
index 82c12883d..6091893c9 100644
--- a/docs/tracking.md
+++ b/docs/usage/TRACKING.md
@@ -369,7 +369,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Install RTK
- run: cargo install --git https://github.com/rtk-ai/rtk
+ run: cargo install --git https://github.com/algolia/rtk
- name: Export weekly stats
run: |
@@ -444,7 +444,7 @@ if __name__ == "__main__":
```rust
// In your Cargo.toml
// [dependencies]
-// rtk = { git = "https://github.com/rtk-ai/rtk" }
+// rtk = { git = "https://github.com/algolia/rtk" }
use rtk::tracking::{Tracker, TimedExecution};
use anyhow::Result;
@@ -538,8 +538,8 @@ let _ = conn.execute(
## Security & Privacy
-- **Local storage only**: Tracking database never leaves the machine
-- **Telemetry enabled by default**: RTK sends a daily anonymous usage ping (version, OS, command counts, token savings). Device identity is a salted SHA-256 hash. Opt out with `RTK_TELEMETRY_DISABLED=1` or `[telemetry] enabled = false` in `~/.config/rtk/config.toml`
+- **Local storage only**: Tracking database never leaves the machine. The
+ Algolia fork ships with telemetry stripped — there is no remote ping.
- **User control**: Users can delete `~/.local/share/rtk/tracking.db` anytime
- **90-day retention**: Old data automatically purged
diff --git a/hooks/README.md b/hooks/README.md
index 9d5e63809..6a6744281 100644
--- a/hooks/README.md
+++ b/hooks/README.md
@@ -38,7 +38,7 @@ Each agent subdirectory has its own README with hook-specific details:
- **[`cursor/`](cursor/README.md)** — Shell hook, Cursor JSON format, empty `{}` response requirement
- **[`cline/`](cline/README.md)** — Rules file (prompt-level), `.clinerules` project-local installation
- **[`windsurf/`](windsurf/README.md)** — Rules file (prompt-level), `.windsurfrules` workspace-scoped
-- **[`codex/`](codex/README.md)** — Awareness document, `AGENTS.md` integration, `~/.codex/` location
+- **[`codex/`](codex/README.md)** — Awareness document, `AGENTS.md` integration, `$CODEX_HOME` or `~/.codex/` location
- **[`opencode/`](opencode/README.md)** — TypeScript plugin, `zx` library, `tool.execute.before` event, in-place mutation
## Supported Agents
@@ -184,7 +184,7 @@ Example: `cargo fmt --all && cargo test` becomes `rtk cargo fmt --all && rtk car
### Override Controls
- **`RTK_DISABLED=1`**: Per-command override (`RTK_DISABLED=1 git status` runs raw)
-- **`exclude_commands`**: In `~/.config/rtk/config.toml`, list commands to never rewrite
+- **`exclude_commands`**: In `~/.config/rtk/config.toml`, list commands to never rewrite. Matches against the full command after stripping env prefixes. Subcommand patterns work (`"git push"` excludes `git push origin main`). Patterns starting with `^` are treated as regex.
- **Already-RTK**: `rtk git status` passes through unchanged (no `rtk rtk git`)
## Exit Code Contract
diff --git a/hooks/antigravity/README.md b/hooks/antigravity/README.md
new file mode 100644
index 000000000..0b279254b
--- /dev/null
+++ b/hooks/antigravity/README.md
@@ -0,0 +1,9 @@
+# Google Antigravity Hooks
+
+> Part of [`hooks/`](../README.md) — see also [`src/hooks/`](../../src/hooks/README.md) for installation code
+
+## Specifics
+
+- Prompt-level guidance only (no programmatic hook) -- relies on Antigravity reading custom instructions
+- `rules.md` contains the instruction to prefix all shell commands with `rtk`, usage examples, and meta commands
+- Installed to `.agents/rules/antigravity-rtk-rules.md` (project-local) by `rtk init --agent antigravity`
diff --git a/hooks/antigravity/rules.md b/hooks/antigravity/rules.md
new file mode 100644
index 000000000..8ee44bcd4
--- /dev/null
+++ b/hooks/antigravity/rules.md
@@ -0,0 +1,32 @@
+# RTK - Rust Token Killer (Google Antigravity)
+
+**Usage**: Token-optimized CLI proxy for shell commands.
+
+## Rule
+
+Always prefix shell commands with `rtk` to minimize token consumption.
+
+Examples:
+
+```bash
+rtk git status
+rtk cargo test
+rtk ls src/
+rtk grep "pattern" src/
+rtk find "*.rs" .
+rtk docker ps
+rtk gh pr list
+```
+
+## Meta Commands
+
+```bash
+rtk gain # Show token savings
+rtk gain --history # Command history with savings
+rtk discover # Find missed RTK opportunities
+rtk proxy # Run raw (no filtering, for debugging)
+```
+
+## Why
+
+RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk ` instead of raw commands.
diff --git a/hooks/claude/rtk-rewrite.sh b/hooks/claude/rtk-rewrite.sh
index f7a42b5d4..e2d806b14 100644
--- a/hooks/claude/rtk-rewrite.sh
+++ b/hooks/claude/rtk-rewrite.sh
@@ -19,25 +19,33 @@ if ! command -v jq &>/dev/null; then
fi
if ! command -v rtk &>/dev/null; then
- echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/rtk-ai/rtk#installation" >&2
+ echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/algolia/rtk#installation" >&2
exit 0
fi
# Version guard: rtk rewrite was added in 0.23.0.
# Older binaries: warn once and exit cleanly (no silent failure).
-RTK_VERSION=$(rtk --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1)
-if [ -n "$RTK_VERSION" ]; then
- MAJOR=$(echo "$RTK_VERSION" | cut -d. -f1)
- MINOR=$(echo "$RTK_VERSION" | cut -d. -f2)
- # Require >= 0.23.0
- if [ "$MAJOR" -eq 0 ] && [ "$MINOR" -lt 23 ]; then
- echo "[rtk] WARNING: rtk $RTK_VERSION is too old (need >= 0.23.0). Upgrade: cargo install rtk" >&2
- exit 0
+# Cache the version check to avoid spawning multiple processes on every hook call.
+CACHE_DIR=${XDG_CACHE_HOME:-$HOME/.cache}
+CACHE_FILE="$CACHE_DIR/rtk-hook-version-ok"
+if [ ! -f "$CACHE_FILE" ]; then
+ RTK_VERSION_RAW=$(rtk --version 2>/dev/null)
+ RTK_VERSION=${RTK_VERSION_RAW#rtk }
+ RTK_VERSION=${RTK_VERSION%% *}
+ if [ -n "$RTK_VERSION" ]; then
+ IFS=. read -r MAJOR MINOR PATCH <<<"$RTK_VERSION"
+ # Require >= 0.23.0
+ if [ "$MAJOR" -eq 0 ] && [ "$MINOR" -lt 23 ]; then
+ echo "[rtk] WARNING: rtk $RTK_VERSION is too old (need >= 0.23.0). Upgrade: cargo install rtk" >&2
+ exit 0
+ fi
fi
+ mkdir -p "$CACHE_DIR" 2>/dev/null
+ touch "$CACHE_FILE" 2>/dev/null
fi
INPUT=$(cat)
-CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty')
+CMD=$(jq -r '.tool_input.command // empty' <<<"$INPUT")
if [ -z "$CMD" ]; then
exit 0
@@ -70,29 +78,24 @@ case $EXIT_CODE in
;;
esac
-ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input')
-UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd')
-
if [ "$EXIT_CODE" -eq 3 ]; then
# Ask: rewrite the command, omit permissionDecision so Claude Code prompts.
- jq -n \
- --argjson updated "$UPDATED_INPUT" \
- '{
+ jq -c --arg cmd "$REWRITTEN" \
+ '.tool_input.command = $cmd | {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
- "updatedInput": $updated
+ "updatedInput": .tool_input
}
- }'
+ }' <<<"$INPUT"
else
# Allow: rewrite the command and auto-allow.
- jq -n \
- --argjson updated "$UPDATED_INPUT" \
- '{
+ jq -c --arg cmd "$REWRITTEN" \
+ '.tool_input.command = $cmd | {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "RTK auto-rewrite",
- "updatedInput": $updated
+ "updatedInput": .tool_input
}
- }'
+ }' <<<"$INPUT"
fi
diff --git a/hooks/claude/test-rtk-rewrite.sh b/hooks/claude/test-rtk-rewrite.sh
index 85103163b..702fe9299 100644
--- a/hooks/claude/test-rtk-rewrite.sh
+++ b/hooks/claude/test-rtk-rewrite.sh
@@ -117,6 +117,10 @@ test_rewrite "npx prisma migrate" \
"npx prisma migrate" \
"rtk prisma migrate"
+test_rewrite "rtk git status" \
+ "rtk git status" \
+ "rtk git status"
+
echo ""
# ---- SECTION 2: Env var prefix handling (THE BIG FIX) ----
@@ -134,8 +138,8 @@ test_rewrite "env + git log" \
"GIT_PAGER=cat rtk git log --oneline -10"
test_rewrite "multi env + vitest" \
- "NODE_ENV=test CI=1 npx vitest run" \
- "NODE_ENV=test CI=1 rtk vitest run"
+ "NODE_ENV=test CI=1 npx vitest" \
+ "NODE_ENV=test CI=1 rtk vitest"
test_rewrite "env + ls" \
"LANG=C ls -la" \
@@ -143,7 +147,7 @@ test_rewrite "env + ls" \
test_rewrite "env + npm run" \
"NODE_ENV=test npm run test:e2e" \
- "NODE_ENV=test rtk npm test:e2e"
+ "NODE_ENV=test rtk npm run test:e2e"
test_rewrite "env + docker compose (unsupported subcommand, NOT rewritten)" \
"COMPOSE_PROJECT_NAME=test docker compose up -d" \
@@ -159,23 +163,15 @@ echo ""
echo "--- New patterns ---"
test_rewrite "npm run test:e2e" \
"npm run test:e2e" \
- "rtk npm test:e2e"
+ "rtk npm run test:e2e"
test_rewrite "npm run build" \
"npm run build" \
- "rtk npm build"
+ "rtk npm run build"
-test_rewrite "npm test" \
- "npm test" \
- "rtk npm test"
-
-test_rewrite "vue-tsc -b" \
- "vue-tsc -b" \
- "rtk tsc -b"
-
-test_rewrite "npx vue-tsc --noEmit" \
- "npx vue-tsc --noEmit" \
- "rtk tsc --noEmit"
+test_rewrite "npm jest run" \
+ "npm jest run" \
+ "rtk jest"
test_rewrite "docker compose up -d (NOT rewritten — unsupported by rtk)" \
"docker compose up -d" \
@@ -209,17 +205,17 @@ test_rewrite "docker exec -it db psql" \
"docker exec -it db psql" \
"rtk docker exec -it db psql"
-test_rewrite "find (NOT rewritten — different arg format)" \
+test_rewrite "find . -name '*.ts'" \
"find . -name '*.ts'" \
- ""
+ "rtk find . -name '*.ts'"
-test_rewrite "tree (NOT rewritten — different arg format)" \
+test_rewrite "tree src/" \
"tree src/" \
- ""
+ "rtk tree src/"
-test_rewrite "wget (NOT rewritten — different arg format)" \
+test_rewrite "wget https://example.com/file" \
"wget https://example.com/file" \
- ""
+ "rtk wget https://example.com/file"
test_rewrite "gh api repos/owner/repo" \
"gh api repos/owner/repo" \
@@ -281,32 +277,28 @@ echo ""
echo "--- Vitest run dedup ---"
test_rewrite "vitest (no args)" \
"vitest" \
- "rtk vitest run"
+ "rtk vitest"
-test_rewrite "vitest run (no double run)" \
+test_rewrite "vitest run (no run)" \
"vitest run" \
- "rtk vitest run"
+ "rtk vitest"
-test_rewrite "vitest run --reporter" \
- "vitest run --reporter=verbose" \
- "rtk vitest run --reporter=verbose"
+test_rewrite "vitest --reporter" \
+ "vitest --reporter=verbose" \
+ "rtk vitest --reporter=verbose"
-test_rewrite "npx vitest run" \
- "npx vitest run" \
- "rtk vitest run"
+test_rewrite "npx vitest" \
+ "npx vitest" \
+ "rtk vitest"
-test_rewrite "pnpm vitest run --coverage" \
- "pnpm vitest run --coverage" \
- "rtk vitest run --coverage"
+test_rewrite "pnpm vitest --coverage" \
+ "pnpm vitest --coverage" \
+ "rtk vitest --coverage"
echo ""
# ---- SECTION 5: Should NOT rewrite ----
echo "--- Should NOT rewrite ---"
-test_rewrite "already rtk" \
- "rtk git status" \
- ""
-
test_rewrite "heredoc" \
"cat <<'EOF'
hello
diff --git a/hooks/codex/README.md b/hooks/codex/README.md
index e922e6365..50030e958 100644
--- a/hooks/codex/README.md
+++ b/hooks/codex/README.md
@@ -6,4 +6,4 @@
- Prompt-level guidance via awareness document -- no programmatic hook
- `rtk-awareness.md` is injected into `AGENTS.md` with an `@RTK.md` reference
-- Installed to `~/.codex/` by `rtk init --codex`
+- Installed to `$CODEX_HOME` when set, otherwise `~/.codex/`, by `rtk init --codex`
diff --git a/hooks/cursor/rtk-rewrite.sh b/hooks/cursor/rtk-rewrite.sh
index 4b80b260c..658697554 100644
--- a/hooks/cursor/rtk-rewrite.sh
+++ b/hooks/cursor/rtk-rewrite.sh
@@ -15,7 +15,7 @@ if ! command -v jq &>/dev/null; then
fi
if ! command -v rtk &>/dev/null; then
- echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/rtk-ai/rtk#installation" >&2
+ echo "[rtk] WARNING: rtk is not installed or not in PATH. Hook cannot rewrite commands. Install: https://github.com/algolia/rtk#installation" >&2
exit 0
fi
diff --git a/hooks/kilocode/README.md b/hooks/kilocode/README.md
new file mode 100644
index 000000000..2b38d9427
--- /dev/null
+++ b/hooks/kilocode/README.md
@@ -0,0 +1,9 @@
+# Kilo Code Hooks
+
+> Part of [`hooks/`](../README.md) — see also [`src/hooks/`](../../src/hooks/README.md) for installation code
+
+## Specifics
+
+- Prompt-level guidance only (no programmatic hook) -- relies on Kilo Code reading custom instructions
+- `rules.md` contains the instruction to prefix all shell commands with `rtk`, usage examples, and meta commands
+- Installed to `.kilocode/rules/rtk-rules.md` (project-local) by `rtk init --agent kilocode`
diff --git a/hooks/kilocode/rules.md b/hooks/kilocode/rules.md
new file mode 100644
index 000000000..53d764596
--- /dev/null
+++ b/hooks/kilocode/rules.md
@@ -0,0 +1,32 @@
+# RTK - Rust Token Killer (Kilo Code)
+
+**Usage**: Token-optimized CLI proxy for shell commands.
+
+## Rule
+
+Always prefix shell commands with `rtk` to minimize token consumption.
+
+Examples:
+
+```bash
+rtk git status
+rtk cargo test
+rtk ls src/
+rtk grep "pattern" src/
+rtk find "*.rs" .
+rtk docker ps
+rtk gh pr list
+```
+
+## Meta Commands
+
+```bash
+rtk gain # Show token savings
+rtk gain --history # Command history with savings
+rtk discover # Find missed RTK opportunities
+rtk proxy # Run raw (no filtering, for debugging)
+```
+
+## Why
+
+RTK filters and compresses command output before it reaches the LLM context, saving 60-90% tokens on common operations. Always use `rtk ` instead of raw commands.
diff --git a/install.sh b/install.sh
index 1654245d9..ceddc987e 100644
--- a/install.sh
+++ b/install.sh
@@ -1,10 +1,10 @@
#!/usr/bin/env sh
-# rtk installer - https://github.com/rtk-ai/rtk
-# Usage: curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
+# rtk installer - https://github.com/algolia/rtk
+# Usage: curl -fsSL https://raw.githubusercontent.com/algolia/rtk/refs/heads/main/install.sh | sh
set -e
-REPO="rtk-ai/rtk"
+REPO="algolia/rtk"
BINARY_NAME="rtk"
INSTALL_DIR="${RTK_INSTALL_DIR:-$HOME/.local/bin}"
@@ -46,10 +46,25 @@ detect_arch() {
}
# Get latest release version
+# Primary: parse the 302 redirect on /releases/latest (no API call, no rate limit).
+# Fallback: the GitHub REST API (subject to 60 req/hour anonymous limit).
get_latest_version() {
- VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/')
+ # Try the web redirect first — does not count against the API rate limit.
+ VERSION=$(curl -sI "https://github.com/${REPO}/releases/latest" \
+ | grep -i '^location:' \
+ | sed -E 's|.*/tag/([^[:space:]]+).*|\1|' \
+ | tr -d '\r')
+
+ # Fallback to the REST API if the redirect didn't yield a tag.
+ if [ -z "$VERSION" ]; then
+ warn "Redirect lookup failed, falling back to GitHub API..."
+ VERSION=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
+ | grep '"tag_name":' \
+ | sed -E 's/.*"([^"]+)".*/\1/')
+ fi
+
if [ -z "$VERSION" ]; then
- error "Failed to get latest version"
+ error "Failed to get latest version (GitHub API may be rate-limited; set RTK_VERSION=vX.Y.Z to pin)"
fi
}
@@ -113,7 +128,12 @@ main() {
detect_os
detect_arch
get_target
- get_latest_version
+ if [ -n "$RTK_VERSION" ]; then
+ VERSION="$RTK_VERSION"
+ info "Using pinned version from RTK_VERSION: $VERSION"
+ else
+ get_latest_version
+ fi
install
verify
diff --git a/openclaw/README.md b/openclaw/README.md
index 301d7c0fa..4440db027 100644
--- a/openclaw/README.md
+++ b/openclaw/README.md
@@ -19,7 +19,7 @@ RTK must be installed and available in `$PATH`:
```bash
brew install rtk
# or
-curl -fsSL https://raw.githubusercontent.com/rtk-ai/rtk/refs/heads/master/install.sh | sh
+curl -fsSL https://raw.githubusercontent.com/algolia/rtk/refs/heads/main/install.sh | sh
```
### Install the plugin
@@ -61,7 +61,7 @@ In `openclaw.json`:
## What gets rewritten
-Everything that `rtk rewrite` supports (30+ commands). See the [full command list](https://github.com/rtk-ai/rtk#commands).
+Everything that `rtk rewrite` supports (30+ commands). See the [full command list](https://github.com/algolia/rtk#commands).
## What's NOT rewritten
diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json
index 3fce418d7..dcc2f1f9a 100644
--- a/openclaw/openclaw.plugin.json
+++ b/openclaw/openclaw.plugin.json
@@ -3,7 +3,7 @@
"name": "RTK Token Optimizer",
"version": "1.0.0",
"description": "Transparently rewrites shell commands to their RTK equivalents for 60-90% LLM token savings",
- "homepage": "https://github.com/rtk-ai/rtk",
+ "homepage": "https://github.com/algolia/rtk",
"license": "MIT",
"configSchema": {
"type": "object",
diff --git a/openclaw/package.json b/openclaw/package.json
index 18d359ff4..7195151b9 100644
--- a/openclaw/package.json
+++ b/openclaw/package.json
@@ -1,15 +1,15 @@
{
- "name": "@rtk-ai/rtk-rewrite",
+ "name": "@algolia/rtk-rewrite",
"version": "1.0.0",
"description": "RTK plugin for OpenClaw — rewrites shell commands for 60-90% LLM token savings",
"main": "index.ts",
"license": "MIT",
"repository": {
"type": "git",
- "url": "https://github.com/rtk-ai/rtk",
+ "url": "https://github.com/algolia/rtk",
"directory": "openclaw"
},
- "homepage": "https://github.com/rtk-ai/rtk",
+ "homepage": "https://github.com/algolia/rtk",
"keywords": [
"rtk",
"openclaw",
diff --git a/scripts/benchmark-sessions/lib/runner.py b/scripts/benchmark-sessions/lib/runner.py
new file mode 100644
index 000000000..192fbcd41
--- /dev/null
+++ b/scripts/benchmark-sessions/lib/runner.py
@@ -0,0 +1,155 @@
+from __future__ import annotations
+
+import asyncio
+import subprocess
+import tempfile
+from pathlib import Path
+
+from .config import TaskConfig
+from .manifest import (
+ RunManifest,
+ SessionEntry,
+ TbEntry,
+ TbTaskEntry,
+ write_manifest,
+)
+from .session import run_all_sessions, setup_codebase, setup_rtk
+from .terminal_bench import run_terminal_bench
+from .vm import create_vm_pool, destroy_vm_pool
+
+ROOT_DIR = Path(__file__).resolve().parent.parent
+
+
+def _create_tarball(source_dir: Path) -> str:
+ tarball = tempfile.mktemp(suffix=".tar.gz")
+ subprocess.run(
+ ["tar", "czf", tarball, "-C", str(source_dir), "."],
+ check=True,
+ )
+ return tarball
+
+
+def _print_step(step: int, total: int, msg: str):
+ print(f"\n[{step}/{total}] {msg}")
+
+
+def _session_to_entry(r) -> SessionEntry:
+ return SessionEntry(
+ vm_name=r.vm_name,
+ group=r.group,
+ stdout_json=f"{r.vm_name}-stdout.json",
+ otel_log=f"{r.vm_name}-otel.log",
+ rtk_db=f"{r.vm_name}-tracking.db" if r.rtk_db_path else None,
+ exit_code=r.exit_code,
+ error=r.error or None,
+ )
+
+
+def _tb_to_entry(r) -> TbEntry:
+ return TbEntry(
+ vm_name=r.vm_name,
+ group=r.group,
+ total=r.total,
+ passed=r.passed,
+ failed=r.failed,
+ tasks=[TbTaskEntry(name=t.name, passed=t.passed, duration_s=t.duration_s) for t in r.tasks],
+ error=r.error,
+ )
+
+
+async def run_benchmark(
+ task: TaskConfig,
+ vms: int,
+ api_key: str,
+ output_dir: Path,
+ cloud_init: Path | None = None,
+ terminal_bench: bool = False,
+ keep_vms: bool = False,
+) -> RunManifest:
+ if cloud_init is None:
+ cloud_init = ROOT_DIR / "cloud-init-base.yaml"
+
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ total_steps = 5 if terminal_bench else 4
+ vm_names: list[str] = []
+
+ manifest = RunManifest(
+ task_name=task.name,
+ model=task.model,
+ vm_count=vms,
+ )
+
+ try:
+ _print_step(1, total_steps, f"Creating {vms * 2} VMs ({vms} RTK ON + {vms} RTK OFF)")
+ vm_names = await create_vm_pool(vms, cloud_init)
+ print(f" VMs ready: {', '.join(vm_names)}")
+
+ _print_step(2, total_steps, "Setting up codebases")
+ local_tarball = None
+ if not task.codebase.is_github:
+ local_tarball = _create_tarball(task.codebase.local_path())
+
+ await asyncio.gather(*(
+ setup_codebase(name, task.codebase, local_tarball)
+ for name in vm_names
+ ))
+ print(" Codebases deployed")
+
+ _print_step(3, total_steps, "Configuring RTK on ON VMs")
+ setup_script = ROOT_DIR / "setup-rtk.sh"
+ on_vms = [n for n in vm_names if "-on-" in n]
+ off_vms = [n for n in vm_names if "-off-" in n]
+ await asyncio.gather(*(setup_rtk(vm, setup_script) for vm in on_vms))
+ print(f" RTK configured on {len(on_vms)} VMs")
+
+ _print_step(4, total_steps, f"Running Claude sessions (timeout: {task.timeout_minutes}min)")
+ results = await run_all_sessions(vm_names, task, api_key, output_dir)
+
+ on_ok = [r for r in results if r.group == "on" and not r.error]
+ off_ok = [r for r in results if r.group == "off" and not r.error]
+ errors = [r for r in results if r.error]
+ print(f" Completed: {len(on_ok)} ON, {len(off_ok)} OFF, {len(errors)} errors")
+ for r in errors:
+ print(f" {r.vm_name}: {r.error}")
+
+ manifest.sessions = [_session_to_entry(r) for r in results]
+
+ if terminal_bench:
+ _print_step(5, total_steps, "Running terminal-bench precision tests")
+ tb_on = await asyncio.gather(*(
+ run_terminal_bench(vm, "on", task.model, api_key)
+ for vm in on_vms
+ ))
+ tb_off = await asyncio.gather(*(
+ run_terminal_bench(vm, "off", task.model, api_key)
+ for vm in off_vms
+ ))
+
+ manifest.terminal_bench = [_tb_to_entry(r) for r in list(tb_on) + list(tb_off)]
+
+ ok_on = [r for r in tb_on if not r.error]
+ ok_off = [r for r in tb_off if not r.error]
+ if ok_on and ok_off:
+ on_total = sum(r.total for r in ok_on)
+ on_passed = sum(r.passed for r in ok_on)
+ off_total = sum(r.total for r in ok_off)
+ off_passed = sum(r.passed for r in ok_off)
+ on_rate = on_passed / on_total if on_total else 0
+ off_rate = off_passed / off_total if off_total else 0
+ print(f" terminal-bench: ON pass rate={on_rate:.0%}, OFF pass rate={off_rate:.0%}, delta={on_rate - off_rate:+.0%}")
+
+ tb_errors = [r for r in list(tb_on) + list(tb_off) if r.error]
+ for r in tb_errors:
+ print(f" {r.vm_name}: {r.error}")
+
+ write_manifest(manifest, output_dir)
+ print(f"\n Manifest written to {output_dir / 'manifest.json'}")
+
+ finally:
+ if not keep_vms and vm_names:
+ print("\nCleaning up VMs...")
+ await destroy_vm_pool(vm_names)
+ print(" VMs destroyed")
+
+ return manifest
diff --git a/scripts/benchmark.sh b/scripts/benchmark.sh
index a1e616bcc..0af7e4417 100755
--- a/scripts/benchmark.sh
+++ b/scripts/benchmark.sh
@@ -11,34 +11,31 @@ else
exit 1
fi
BENCH_DIR="$(pwd)/scripts/benchmark"
+RTK_ROOT="$(pwd)"
-# Mode local : générer les fichiers debug
if [ -z "$CI" ]; then
rm -rf "$BENCH_DIR"
mkdir -p "$BENCH_DIR/unix" "$BENCH_DIR/rtk" "$BENCH_DIR/diff"
fi
-# Nom de fichier safe
safe_name() {
echo "$1" | tr ' /' '_-' | tr -cd 'a-zA-Z0-9_-'
}
-# Fonction pour compter les tokens (~4 chars = 1 token)
count_tokens() {
local input="$1"
local len=${#input}
echo $(( (len + 3) / 4 ))
}
-# Compteurs globaux
TOTAL_UNIX=0
TOTAL_RTK=0
TOTAL_TESTS=0
GOOD_TESTS=0
FAIL_TESTS=0
-SKIP_TESTS=0
+WARN_TESTS=0
+NEGATIVE_TESTS=0
-# Fonction de benchmark — une ligne par test
bench() {
local name="$1"
local unix_cmd="$2"
@@ -55,24 +52,41 @@ bench() {
local icon=""
local tag=""
- if [ -z "$rtk_out" ]; then
+ if [ -z "$rtk_out" ] && [ -n "$unix_out" ]; then
icon="❌"
tag="FAIL"
FAIL_TESTS=$((FAIL_TESTS + 1))
TOTAL_UNIX=$((TOTAL_UNIX + unix_tokens))
TOTAL_RTK=$((TOTAL_RTK + unix_tokens))
- elif [ "$rtk_tokens" -ge "$unix_tokens" ] && [ "$unix_tokens" -gt 0 ]; then
+ elif [ "$rtk_tokens" -gt "$unix_tokens" ] && [ "$unix_tokens" -gt 0 ]; then
+ icon="🔴"
+ tag="NEG"
+ NEGATIVE_TESTS=$((NEGATIVE_TESTS + 1))
+ TOTAL_UNIX=$((TOTAL_UNIX + unix_tokens))
+ TOTAL_RTK=$((TOTAL_RTK + rtk_tokens))
+ elif [ "$unix_tokens" -gt 0 ] && [ "$rtk_tokens" -eq "$unix_tokens" ]; then
icon="⚠️"
- tag="SKIP"
- SKIP_TESTS=$((SKIP_TESTS + 1))
+ tag="WARN"
+ WARN_TESTS=$((WARN_TESTS + 1))
TOTAL_UNIX=$((TOTAL_UNIX + unix_tokens))
- TOTAL_RTK=$((TOTAL_RTK + unix_tokens))
- else
- icon="✅"
- tag="GOOD"
- GOOD_TESTS=$((GOOD_TESTS + 1))
+ TOTAL_RTK=$((TOTAL_RTK + rtk_tokens))
+ elif [ "$unix_tokens" -gt 0 ]; then
+ local savings=$(( (unix_tokens - rtk_tokens) * 100 / unix_tokens ))
+ if [ "$savings" -lt 60 ]; then
+ icon="⚠️"
+ tag="WARN"
+ WARN_TESTS=$((WARN_TESTS + 1))
+ else
+ icon="✅"
+ tag="GOOD"
+ GOOD_TESTS=$((GOOD_TESTS + 1))
+ fi
TOTAL_UNIX=$((TOTAL_UNIX + unix_tokens))
TOTAL_RTK=$((TOTAL_RTK + rtk_tokens))
+ else
+ icon="⏭️"
+ tag="SKIP"
+ WARN_TESTS=$((WARN_TESTS + 1))
fi
if [ "$tag" = "FAIL" ]; then
@@ -88,12 +102,13 @@ bench() {
"$icon" "$name" "$unix_cmd" "$rtk_cmd" "$unix_tokens" "$rtk_tokens" "$pct"
fi
- # Fichiers debug en local uniquement
if [ -z "$CI" ]; then
local filename=$(safe_name "$name")
local prefix="GOOD"
[ "$tag" = "FAIL" ] && prefix="FAIL"
- [ "$tag" = "SKIP" ] && prefix="BAD"
+ [ "$tag" = "NEG" ] && prefix="NEG"
+ [ "$tag" = "WARN" ] && prefix="WARN"
+ [ "$tag" = "SKIP" ] && prefix="SKIP"
local ts=$(date "+%d/%m/%Y %H:%M:%S")
@@ -124,7 +139,6 @@ bench() {
fi
}
-# Section header
section() {
echo ""
echo "── $1 ──"
@@ -149,6 +163,18 @@ bench "ls src/ -l" "ls -l src/" "$RTK ls src/ -l"
bench "ls -a" "ls -la" "$RTK ls -a"
bench "ls multi" "ls -la src/ scripts/" "$RTK ls src/ scripts/"
+# ===================
+# tree
+# ===================
+if command -v tree &>/dev/null; then
+ section "tree"
+ bench "tree" "tree -L 2" "$RTK tree -L 2"
+ bench "tree src/" "tree src/ -L 2" "$RTK tree src/ -L 2"
+else
+ echo ""
+ echo "⏭️ tree (not installed, skipped)"
+fi
+
# ===================
# read
# ===================
@@ -175,6 +201,7 @@ bench "git status" "git status" "$RTK git status"
bench "git log -n 10" "git log -10" "$RTK git log -n 10"
bench "git log -n 5" "git log -5" "$RTK git log -n 5"
bench "git diff" "git diff HEAD~1 2>/dev/null || echo ''" "$RTK git diff HEAD~1"
+bench "git show" "git show HEAD --stat 2>/dev/null || true" "$RTK git show HEAD --stat"
# ===================
# grep
@@ -183,7 +210,6 @@ section "grep"
bench "grep fn" "grep -rn 'fn ' src/ || true" "$RTK grep 'fn ' src/"
bench "grep struct" "grep -rn 'struct ' src/ || true" "$RTK grep 'struct ' src/"
bench "grep -l 40" "grep -rn 'fn ' src/ || true" "$RTK grep 'fn ' src/ -l 40"
-bench "grep --max 20" "grep -rn 'fn ' src/ | head -20 || true" "$RTK grep 'fn ' src/ --max 20"
bench "grep -c" "grep -ron 'fn ' src/ || true" "$RTK grep 'fn ' src/ -c"
# ===================
@@ -229,7 +255,7 @@ bench "env --show-all" "env" "$RTK env --show-all"
# ===================
section "err"
if command -v cargo &>/dev/null; then
- bench "err cargo build" "cargo build 2>&1 || true" "$RTK err cargo build"
+ bench "err cargo build" "cargo build 2>&1 || true" "$RTK err cargo build 2>&1"
else
echo "⏭️ err cargo build (cargo not in PATH, skipped)"
fi
@@ -239,7 +265,7 @@ fi
# ===================
section "test"
if command -v cargo &>/dev/null; then
- bench "test cargo test" "cargo test 2>&1 || true" "$RTK test cargo test"
+ bench "test cargo test" "cargo test 2>&1 || true" "$RTK test cargo test 2>&1"
else
echo "⏭️ test cargo test (cargo not in PATH, skipped)"
fi
@@ -287,20 +313,14 @@ fi
# ===================
section "cargo"
if command -v cargo &>/dev/null; then
- bench "cargo build" "cargo build 2>&1 || true" "$RTK cargo build"
- bench "cargo test" "cargo test 2>&1 || true" "$RTK cargo test"
- bench "cargo clippy" "cargo clippy 2>&1 || true" "$RTK cargo clippy"
- bench "cargo check" "cargo check 2>&1 || true" "$RTK cargo check"
+ bench "cargo build" "cargo build 2>&1 || true" "$RTK cargo build 2>&1"
+ bench "cargo test" "cargo test 2>&1 || true" "$RTK cargo test 2>&1"
+ bench "cargo clippy" "cargo clippy 2>&1 || true" "$RTK cargo clippy 2>&1"
+ bench "cargo check" "cargo check 2>&1 || true" "$RTK cargo check 2>&1"
else
echo "⏭️ cargo build/test/clippy/check (cargo not in PATH, skipped)"
fi
-# ===================
-# diff
-# ===================
-section "diff"
-bench "diff" "diff Cargo.toml LICENSE 2>&1 || true" "$RTK diff Cargo.toml LICENSE"
-
# ===================
# smart
# ===================
@@ -327,7 +347,16 @@ fi
# ===================
if command -v wget &> /dev/null; then
section "wget"
- bench "wget" "wget -qO- https://httpbin.org/robots.txt" "$RTK wget https://httpbin.org/robots.txt -O"
+ bench "wget" "wget -qO- https://httpbin.org/json" "$RTK wget https://httpbin.org/json"
+ rm -f json 2>/dev/null
+fi
+
+# ===================
+# npm (standalone — does not require package.json)
+# ===================
+if command -v npm &> /dev/null; then
+ section "npm"
+ bench "npm list" "npm list -g --depth 0 2>&1 || true" "$RTK npm list -g --depth 0"
fi
# ===================
@@ -337,7 +366,7 @@ if [ -f "package.json" ]; then
section "modern JS stack"
if command -v tsc &> /dev/null || [ -f "node_modules/.bin/tsc" ]; then
- bench "tsc" "tsc --noEmit 2>&1 || true" "$RTK tsc --noEmit"
+ bench "tsc" "tsc --noEmit 2>&1 || true" "$RTK tsc --noEmit 2>&1"
fi
if command -v prettier &> /dev/null || [ -f "node_modules/.bin/prettier" ]; then
@@ -367,7 +396,7 @@ if [ -f "package.json" ]; then
fi
if command -v vitest &> /dev/null || [ -f "node_modules/.bin/vitest" ]; then
- bench "vitest run" "vitest run --reporter=json 2>&1 || true" "$RTK vitest run"
+ bench "vitest" "vitest run --reporter=json 2>&1 || true" "$RTK vitest"
fi
if command -v pnpm &> /dev/null; then
@@ -379,14 +408,31 @@ fi
# ===================
# gh (skip si pas dispo ou pas dans un repo)
# ===================
-if command -v gh &> /dev/null && git rev-parse --git-dir &> /dev/null; then
+if command -v gh &> /dev/null && git rev-parse --git-dir &> /dev/null && gh auth status &> /dev/null; then
section "gh"
bench "gh pr list" "gh pr list 2>&1 || true" "$RTK gh pr list"
bench "gh run list" "gh run list 2>&1 || true" "$RTK gh run list"
fi
# ===================
-# docker (skip si pas dispo)
+# glab
+# ===================
+if command -v glab &> /dev/null; then
+ section "glab"
+ bench "glab mr list" "glab mr list 2>&1 || true" "$RTK glab mr list"
+ bench "glab issue list" "glab issue list 2>&1 || true" "$RTK glab issue list"
+fi
+
+# ===================
+# gt (Graphite)
+# ===================
+if command -v gt &> /dev/null; then
+ section "gt"
+ bench "gt log" "gt log 2>&1 || true" "$RTK gt log"
+fi
+
+# ===================
+# docker
# ===================
if command -v docker &> /dev/null; then
section "docker"
@@ -395,7 +441,7 @@ if command -v docker &> /dev/null; then
fi
# ===================
-# kubectl (skip si pas dispo)
+# kubectl
# ===================
if command -v kubectl &> /dev/null; then
section "kubectl"
@@ -412,7 +458,6 @@ if command -v python3 &> /dev/null && command -v ruff &> /dev/null && command -v
PYTHON_FIXTURE=$(mktemp -d)
cd "$PYTHON_FIXTURE"
- # pyproject.toml
cat > pyproject.toml << 'PYEOF'
[project]
name = "rtk-bench"
@@ -422,7 +467,6 @@ version = "0.1.0"
line-length = 88
PYEOF
- # sample.py avec quelques issues ruff
cat > sample.py << 'PYEOF'
import os
import sys
@@ -442,7 +486,6 @@ def unused_function(): # F841: local variable assigned but never used
return None
PYEOF
- # test_sample.py
cat > test_sample.py << 'PYEOF'
from sample import process_data
@@ -456,7 +499,15 @@ PYEOF
bench "ruff check" "ruff check . 2>&1 || true" "$RTK ruff check ."
bench "pytest" "pytest -v 2>&1 || true" "$RTK pytest -v"
- cd - > /dev/null
+ if command -v pip &>/dev/null; then
+ bench "pip list" "pip list 2>&1 || true" "$RTK pip list"
+ fi
+
+ if command -v mypy &>/dev/null; then
+ bench "mypy" "mypy sample.py 2>&1 || true" "$RTK mypy sample.py"
+ fi
+
+ cd "$RTK_ROOT"
rm -rf "$PYTHON_FIXTURE"
fi
@@ -469,14 +520,12 @@ if command -v go &> /dev/null && command -v golangci-lint &> /dev/null; then
GO_FIXTURE=$(mktemp -d)
cd "$GO_FIXTURE"
- # go.mod
cat > go.mod << 'GOEOF'
module bench
go 1.21
GOEOF
- # main.go
cat > main.go << 'GOEOF'
package main
@@ -496,7 +545,6 @@ func main() {
}
GOEOF
- # main_test.go
cat > main_test.go << 'GOEOF'
package main
@@ -522,16 +570,55 @@ GOEOF
bench "go build" "go build ./... 2>&1 || true" "$RTK go build ./..."
bench "go vet" "go vet ./... 2>&1 || true" "$RTK go vet ./..."
- cd - > /dev/null
+ cd "$RTK_ROOT"
rm -rf "$GO_FIXTURE"
fi
+# ===================
+# Ruby
+# ===================
+if command -v ruby &> /dev/null; then
+ section "ruby"
+ if command -v rake &>/dev/null; then
+ bench "rake -T" "rake -T 2>&1 || true" "$RTK rake -T"
+ fi
+ if command -v rubocop &>/dev/null; then
+ bench "rubocop" "rubocop --format simple 2>&1 || true" "$RTK rubocop --format simple"
+ fi
+ if command -v rspec &>/dev/null; then
+ bench "rspec --dry-run" "rspec --dry-run 2>&1 || true" "$RTK rspec --dry-run"
+ fi
+fi
+
+# ===================
+# dotnet
+# ===================
+if command -v dotnet &> /dev/null; then
+ section "dotnet"
+ bench "dotnet --info" "dotnet --info 2>&1 || true" "$RTK dotnet --info"
+fi
+
+# ===================
+# aws
+# ===================
+if command -v aws &> /dev/null; then
+ section "aws"
+ bench "aws --version" "aws --version 2>&1 || true" "$RTK aws --version"
+fi
+
+# ===================
+# psql
+# ===================
+if command -v psql &> /dev/null; then
+ section "psql"
+ bench "psql --version" "psql --version 2>&1 || true" "$RTK psql --version"
+fi
+
# ===================
# rewrite (verify rewrite works with and without quotes)
# ===================
section "rewrite"
-# bench_rewrite: verifies rewrite produces expected output (not token comparison)
bench_rewrite() {
local name="$1"
local cmd="$2"
@@ -558,7 +645,7 @@ bench_rewrite "rewrite cargo test" "$RTK rewrite cargo test" "rtk cargo
bench_rewrite "rewrite compound" "$RTK rewrite 'cargo test && git push'" "rtk cargo test && rtk git push"
# ===================
-# Résumé global
+# Summary
# ===================
echo ""
echo "═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════"
@@ -574,19 +661,30 @@ if [ "$TOTAL_TESTS" -gt 0 ]; then
fi
echo ""
- echo " ✅ $GOOD_TESTS good ⚠️ $SKIP_TESTS skip ❌ $FAIL_TESTS fail $GOOD_TESTS/$TOTAL_TESTS ($GOOD_PCT%)"
+ echo " ✅ $GOOD_TESTS good ⚠️ $WARN_TESTS warn 🔴 $NEGATIVE_TESTS negative ❌ $FAIL_TESTS fail $GOOD_TESTS/$TOTAL_TESTS ($GOOD_PCT%)"
echo " Tokens: $TOTAL_UNIX → $TOTAL_RTK (-$TOTAL_SAVE_PCT%)"
echo ""
- # Fichiers debug en local
if [ -z "$CI" ]; then
echo " Debug: $BENCH_DIR/{unix,rtk,diff}/"
fi
echo ""
- # Exit code non-zero si moins de 80% good
- if [ "$GOOD_PCT" -lt 80 ]; then
- echo " BENCHMARK FAILED: $GOOD_PCT% good (minimum 80%)"
- exit 1
+ EXIT_CODE=0
+
+ if [ "$NEGATIVE_TESTS" -gt 0 ]; then
+ echo " BENCHMARK FAILED: $NEGATIVE_TESTS filter(s) produced more tokens than raw output"
+ EXIT_CODE=1
+ fi
+
+ if [ "$FAIL_TESTS" -gt 0 ]; then
+ echo " BENCHMARK FAILED: $FAIL_TESTS filter(s) returned empty output"
+ EXIT_CODE=1
fi
+
+ if [ "$GOOD_PCT" -lt 60 ] && [ "$EXIT_CODE" -eq 0 ]; then
+ echo " WARNING: $GOOD_PCT% good (target 60%)"
+ fi
+
+ exit $EXIT_CODE
fi
diff --git a/scripts/benchmark/cleanup.ts b/scripts/benchmark/cleanup.ts
new file mode 100644
index 000000000..7cc38edba
--- /dev/null
+++ b/scripts/benchmark/cleanup.ts
@@ -0,0 +1,11 @@
+#!/usr/bin/env bun
+/**
+ * Delete the RTK test VM.
+ * Usage: bun run scripts/benchmark/cleanup.ts
+ */
+
+import { vmDelete } from "./lib/vm";
+
+console.log("Deleting rtk-test VM...");
+await vmDelete();
+console.log("Done.");
diff --git a/scripts/benchmark/cloud-init.yaml b/scripts/benchmark/cloud-init.yaml
new file mode 100644
index 000000000..a528c5aa0
--- /dev/null
+++ b/scripts/benchmark/cloud-init.yaml
@@ -0,0 +1,315 @@
+#cloud-config
+# RTK Integration Test VM — Ubuntu 24.04
+# Installs all tools needed for comprehensive RTK testing (~200 commands)
+# Usage: multipass launch --name rtk-test --cloud-init scripts/benchmark/cloud-init.yaml --cpus 2 --memory 4G --disk 20G 24.04
+
+package_update: true
+package_upgrade: false
+
+packages:
+ # System tools
+ - curl
+ - wget
+ - jq
+ - git
+ - make
+ - cmake
+ - rsync
+ - sqlite3
+ - shellcheck
+ - yamllint
+ - postgresql-client
+ - docker.io
+ - containerd
+ - python3
+ - python3-pip
+ - python3-venv
+ - pipx
+ # Build essentials (for Rust compilation)
+ - build-essential
+ - pkg-config
+ - libssl-dev
+ - libsqlite3-dev
+ # Misc
+ - hyperfine
+ - unzip
+ - tree
+
+runcmd:
+ # ── Rust toolchain ──
+ - su - ubuntu -c 'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y'
+
+ # ── Node.js 22 + package managers ──
+ - curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
+ - apt-get install -y nodejs
+ - npm install -g pnpm yarn
+ - npm install -g eslint prettier typescript
+ - npm install -g markdownlint-cli
+
+ # ── Go 1.22 ──
+ - curl -fsSL https://go.dev/dl/go1.22.5.linux-amd64.tar.gz | tar -C /usr/local -xz
+ - echo 'export PATH=$PATH:/usr/local/go/bin:/home/ubuntu/go/bin' >> /home/ubuntu/.bashrc
+ - su - ubuntu -c 'export PATH=$PATH:/usr/local/go/bin && go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest'
+
+ # ── Python tools ──
+ - pipx install ruff
+ - pipx install mypy
+ - pipx install poetry
+ - pip3 install --break-system-packages pytest uv pre-commit
+
+ # ── .NET 8 SDK ──
+ - |
+ wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh
+ chmod +x /tmp/dotnet-install.sh
+ /tmp/dotnet-install.sh --channel 8.0 --install-dir /usr/local/share/dotnet
+ ln -sf /usr/local/share/dotnet/dotnet /usr/local/bin/dotnet
+ echo 'export DOTNET_ROOT=/usr/local/share/dotnet' >> /home/ubuntu/.bashrc
+
+ # ── Terraform ──
+ - |
+ wget -qO- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg
+ echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/hashicorp.list
+ apt-get update && apt-get install -y terraform
+
+ # ── Helm ──
+ - curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
+
+ # ── Hadolint ──
+ - |
+ wget -qO /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/latest/download/hadolint-Linux-x86_64
+ chmod +x /usr/local/bin/hadolint
+
+ # ── Docker setup ──
+ - usermod -aG docker ubuntu
+ - systemctl enable docker
+ - systemctl start docker
+
+ # ── kubectl (standalone binary) ──
+ - |
+ curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
+ install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
+ rm kubectl
+
+ # ── ansible ──
+ - pip3 install --break-system-packages ansible-core
+
+ # ── Mock tools (too heavy to install) ──
+ - |
+ cat > /usr/local/bin/gcloud << 'MOCK'
+ #!/bin/bash
+ if [ "$1" = "version" ] || [ "$1" = "--version" ]; then
+ echo "Google Cloud SDK 400.0.0"
+ echo "bq 2.0.80"
+ echo "core 2023.01.01"
+ echo "gsutil 5.17"
+ else
+ echo "gcloud mock: $*"
+ fi
+ MOCK
+ chmod +x /usr/local/bin/gcloud
+
+ - |
+ cat > /usr/local/bin/shopify << 'MOCK'
+ #!/bin/bash
+ echo "Shopify CLI 3.0.0 (mock)"
+ if [ "$1" = "theme" ] && [ "$2" = "check" ]; then
+ echo "Running theme check..."
+ echo " 1 issue found"
+ echo " [warn] Missing alt text on image"
+ fi
+ MOCK
+ chmod +x /usr/local/bin/shopify
+
+ - |
+ cat > /usr/local/bin/pio << 'MOCK'
+ #!/bin/bash
+ if [ "$1" = "--version" ]; then echo "PlatformIO Core, version 6.1.0"
+ elif [ "$1" = "run" ]; then
+ echo "Processing esp32dev (platform: espressif32; board: esp32dev)"
+ echo "Linking .pio/build/esp32dev/firmware.elf"
+ echo "========================= [SUCCESS] ========================="
+ fi
+ MOCK
+ chmod +x /usr/local/bin/pio
+
+ - |
+ cat > /usr/local/bin/quarto << 'MOCK'
+ #!/bin/bash
+ if [ "$1" = "--version" ]; then echo "1.3.450"
+ elif [ "$1" = "render" ]; then echo "Rendering document..."; echo "Output created: document.html"
+ fi
+ MOCK
+ chmod +x /usr/local/bin/quarto
+
+ - |
+ cat > /usr/local/bin/sops << 'MOCK'
+ #!/bin/bash
+ if [ "$1" = "--version" ]; then echo "sops 3.7.3"; fi
+ MOCK
+ chmod +x /usr/local/bin/sops
+
+ - |
+ cat > /usr/local/bin/swift << 'MOCK'
+ #!/bin/bash
+ if [ "$1" = "--version" ]; then echo "Swift version 5.9.2 (swift-5.9.2-RELEASE)"
+ elif [ "$1" = "build" ]; then echo "Compiling Swift module..."; echo "Build complete! (0.42s)"
+ fi
+ MOCK
+ chmod +x /usr/local/bin/swift
+
+ # ── Fake test projects ──
+
+ # Node.js project with errors
+ - |
+ su - ubuntu -c '
+ mkdir -p /tmp/test-node/src && cd /tmp/test-node
+ npm init -y >/dev/null 2>&1
+ echo "{\"compilerOptions\":{\"strict\":true,\"noEmit\":true,\"target\":\"ES2020\",\"module\":\"ESNext\",\"moduleResolution\":\"node\"},\"include\":[\"src\"]}" > tsconfig.json
+ echo "const x: number = \"not a number\";\nconst unused = 42;\nfunction greet(name: string): string { return name }\ngreet(123);" > src/index.ts
+ echo "{\"rules\":{\"no-unused-vars\":\"error\",\"semi\":[\"error\",\"always\"]}}" > .eslintrc.json
+ echo "const x = 1;const y=2; const z =3" > src/ugly.ts
+ '
+
+ # Python project with errors
+ - |
+ su - ubuntu -c '
+ mkdir -p /tmp/test-python && cd /tmp/test-python
+ cat > main.py << "PYEOF"
+ import os
+ import sys
+ unused_import = 1
+ def add(a: int, b: int) -> str:
+ return a + b
+ x: int = "hello"
+ PYEOF
+ cat > test_main.py << "PYEOF"
+ def test_pass():
+ assert 1 + 1 == 2
+ def test_fail():
+ assert 1 + 1 == 3, "math is broken"
+ PYEOF
+ cat > pyproject.toml << "PYEOF"
+ [tool.ruff]
+ line-length = 80
+ select = ["E", "F", "W"]
+ [tool.mypy]
+ strict = true
+ [tool.pytest.ini_options]
+ testpaths = ["."]
+ PYEOF
+ '
+
+ # Go project with errors
+ - |
+ su - ubuntu -c '
+ export PATH=$PATH:/usr/local/go/bin
+ mkdir -p /tmp/test-go && cd /tmp/test-go
+ go mod init test-go 2>/dev/null
+ cat > main.go << "GOEOF"
+ package main
+ import "fmt"
+ func main() { fmt.Println("hello") }
+ func unused() { var x int; _ = x }
+ GOEOF
+ cat > main_test.go << "GOEOF"
+ package main
+ import "testing"
+ func TestPass(t *testing.T) { if 1+1 != 2 { t.Fatal("math") } }
+ func TestFail(t *testing.T) { t.Fatal("expected failure") }
+ GOEOF
+ '
+
+ # Rust project with errors
+ - |
+ su - ubuntu -c '
+ export PATH=$HOME/.cargo/bin:$PATH
+ mkdir -p /tmp/test-rust && cd /tmp/test-rust
+ cargo init --name test-rust 2>/dev/null
+ cat > src/main.rs << "RSEOF"
+ fn main() {
+ let x = vec![1, 2, 3];
+ let _y = x.iter().map(|i| i.clone()).collect::>();
+ println!("hello");
+ }
+ #[cfg(test)]
+ mod tests {
+ #[test] fn test_pass() { assert_eq!(1 + 1, 2); }
+ #[test] fn test_fail() { assert_eq!(1 + 1, 3); }
+ }
+ RSEOF
+ '
+
+ # Dockerfiles for hadolint
+ - |
+ su - ubuntu -c '
+ cat > /tmp/Dockerfile.bad << "DEOF"
+ FROM ubuntu:latest
+ RUN apt-get update && apt-get install -y curl wget git
+ RUN cd /tmp && wget http://example.com/script.sh && bash script.sh
+ EXPOSE 80 443 8080
+ DEOF
+ '
+
+ # Shell/YAML/Markdown test files
+ - |
+ su - ubuntu -c '
+ printf "#!/bin/bash\necho \$foo\nls *.txt\ncd \$(pwd)\n[ -f file ] && rm file\n" > /tmp/test.sh
+ printf "foo: bar\nbaz: qux\nlist:\n - item1\n - item2\ntruthy: yes\n" > /tmp/test.yaml
+ printf "#Header without space\nSome text\n\n* List item\n+ Mixed markers\n" > /tmp/test.md
+ '
+
+ # Git repo for testing
+ - |
+ su - ubuntu -c '
+ mkdir -p /tmp/test-git && cd /tmp/test-git
+ git init && git config user.email "test@rtk.dev" && git config user.name "RTK Test"
+ for i in $(seq 1 20); do echo "line $i" >> file.txt && git add file.txt && git commit -m "feat: commit number $i"; done
+ echo "modified" >> file.txt && echo "new file" > new.txt
+ '
+
+ # Large log file for dedup testing
+ - |
+ su - ubuntu -c '
+ for i in $(seq 1 500); do
+ printf "[2026-03-25 10:00:00] INFO Starting service...\n[2026-03-25 10:00:01] WARN Connection timeout\n[2026-03-25 10:00:01] ERROR Failed to connect: refused\n"
+ done > /tmp/large.log
+ for i in $(seq 1 50); do echo "[2026-03-25 10:05:00] FATAL Out of memory"; done >> /tmp/large.log
+ '
+
+ # .env file
+ - |
+ su - ubuntu -c '
+ printf "DATABASE_URL=postgres://user:pass@localhost:5432/db\nAPI_KEY=sk-1234567890abcdef\nSECRET_TOKEN=ghp_xxxx\nNODE_ENV=production\nPORT=3000\n" > /tmp/.env
+ '
+
+ # Makefile
+ - |
+ su - ubuntu -c '
+ printf ".PHONY: all test\nall:\n\t@echo Building...\n\t@echo Build complete\ntest:\n\t@echo Running tests...\n\t@echo 2 tests passed\n" > /tmp/Makefile
+ '
+
+ # Terraform project
+ - |
+ su - ubuntu -c '
+ mkdir -p /tmp/test-terraform && cd /tmp/test-terraform
+ printf "terraform {\n required_version = \">= 1.0\"\n}\nresource \"null_resource\" \"test\" {\n triggers = { always = timestamp() }\n}\noutput \"test\" { value = \"hello\" }\n" > main.tf
+ '
+
+ # Helm chart
+ - su - ubuntu -c 'mkdir -p /tmp/test-helm && cd /tmp/test-helm && helm create test-chart 2>/dev/null || true'
+
+ # .NET project
+ - |
+ export DOTNET_ROOT=/usr/local/share/dotnet
+ su - ubuntu -c '
+ export DOTNET_ROOT=/usr/local/share/dotnet && export PATH=$PATH:$DOTNET_ROOT
+ mkdir -p /tmp/test-dotnet && cd /tmp/test-dotnet
+ dotnet new console -n TestApp --force 2>/dev/null || true
+ '
+
+ # Signal completion
+ - touch /home/ubuntu/.cloud-init-complete
+ - chown ubuntu:ubuntu /home/ubuntu/.cloud-init-complete
+ - echo "RTK cloud-init setup complete" | tee /var/log/rtk-setup.log
+
+final_message: "RTK test VM ready in $UPTIME seconds"
diff --git a/scripts/benchmark/lib/report.ts b/scripts/benchmark/lib/report.ts
new file mode 100644
index 000000000..1fb751c39
--- /dev/null
+++ b/scripts/benchmark/lib/report.ts
@@ -0,0 +1,113 @@
+/**
+ * Report generation for RTK integration test results.
+ */
+
+import type { TestResult } from "./test";
+import { getCounts, getResults } from "./test";
+
+interface BuildInfo {
+ buildTime: number;
+ binarySize: number;
+ version: string;
+ branch: string;
+ commit: string;
+}
+
+export function generateReport(buildInfo: BuildInfo): string {
+ const { total, passed, failed, skipped } = getCounts();
+ const results = getResults();
+ const passRate = total > 0 ? Math.round((passed * 100) / total) : 0;
+
+ const lines: string[] = [];
+
+ lines.push("======================================================");
+ lines.push(" RTK INTEGRATION TEST REPORT");
+ lines.push("======================================================");
+ lines.push("");
+ lines.push(`Date: ${new Date().toISOString()}`);
+ lines.push(`Branch: ${buildInfo.branch}`);
+ lines.push(`Commit: ${buildInfo.commit}`);
+ lines.push(`Version: ${buildInfo.version}`);
+ lines.push(`Binary: ${buildInfo.binarySize} bytes`);
+ lines.push(`Build: ${buildInfo.buildTime}s`);
+ lines.push("");
+
+ // Summary
+ lines.push("--- Summary ---");
+ lines.push(`Total: ${total}`);
+ lines.push(`Passed: ${passed} (${passRate}%)`);
+ lines.push(`Failed: ${failed}`);
+ lines.push(`Skipped: ${skipped}`);
+ lines.push("");
+
+ // Group results by phase (name prefix before ":")
+ const phases = new Map();
+ for (const r of results) {
+ const colonIdx = r.name.indexOf(":");
+ const phase = colonIdx > 0 ? r.name.slice(0, colonIdx) : "misc";
+ if (!phases.has(phase)) phases.set(phase, []);
+ phases.get(phase)!.push(r);
+ }
+
+ for (const [phase, phaseResults] of phases) {
+ const pPassed = phaseResults.filter((r) => r.status === "PASS").length;
+ const pTotal = phaseResults.length;
+ lines.push(`--- ${phase} (${pPassed}/${pTotal}) ---`);
+
+ for (const r of phaseResults) {
+ const shortName = r.name.includes(":") ? r.name.split(":")[1] : r.name;
+ lines.push(` ${r.status.padEnd(4)} | ${shortName} | ${r.detail}`);
+ }
+ lines.push("");
+ }
+
+ // Failures detail
+ const failures = results.filter((r) => r.status === "FAIL");
+ if (failures.length > 0) {
+ lines.push("--- Failures ---");
+ for (const f of failures) {
+ lines.push(` ${f.name}: ${f.detail}`);
+ }
+ lines.push("");
+ }
+
+ // Token savings summary
+ const savingsResults = results.filter((r) => r.savings !== undefined);
+ if (savingsResults.length > 0) {
+ const avgSavings = Math.round(
+ savingsResults.reduce((sum, r) => sum + (r.savings ?? 0), 0) /
+ savingsResults.length
+ );
+ const minSavings = Math.min(
+ ...savingsResults.map((r) => r.savings ?? 100)
+ );
+ const maxSavings = Math.max(...savingsResults.map((r) => r.savings ?? 0));
+ lines.push("--- Token Savings ---");
+ lines.push(`Average: ${avgSavings}%`);
+ lines.push(`Min: ${minSavings}%`);
+ lines.push(`Max: ${maxSavings}%`);
+ lines.push("");
+ }
+
+ // Verdict
+ lines.push("======================================================");
+ if (failed === 0) {
+ lines.push(" Verdict: READY FOR RELEASE");
+ } else {
+ lines.push(` Verdict: NOT READY (${failed} failures)`);
+ }
+ lines.push("======================================================");
+
+ return lines.join("\n");
+}
+
+/** Save report to file */
+export async function saveReport(
+ buildInfo: BuildInfo,
+ outPath: string
+): Promise {
+ const report = generateReport(buildInfo);
+ await Bun.write(outPath, report);
+ console.log(`\nReport saved to: ${outPath}`);
+ return report;
+}
diff --git a/scripts/benchmark/lib/test.ts b/scripts/benchmark/lib/test.ts
new file mode 100644
index 000000000..cffe6148b
--- /dev/null
+++ b/scripts/benchmark/lib/test.ts
@@ -0,0 +1,167 @@
+/**
+ * Test helpers for RTK integration testing.
+ */
+
+import { vmExec, RTK_BIN } from "./vm";
+
+export type TestStatus = "PASS" | "FAIL" | "SKIP";
+
+export interface TestResult {
+ name: string;
+ status: TestStatus;
+ detail: string;
+ exitCode?: number;
+ outputSize?: number;
+ savings?: number;
+ duration?: number;
+}
+
+const results: TestResult[] = [];
+
+export function getResults(): TestResult[] {
+ return results;
+}
+
+export function getCounts() {
+ const total = results.length;
+ const passed = results.filter((r) => r.status === "PASS").length;
+ const failed = results.filter((r) => r.status === "FAIL").length;
+ const skipped = results.filter((r) => r.status === "SKIP").length;
+ return { total, passed, failed, skipped };
+}
+
+function record(result: TestResult) {
+ results.push(result);
+ const icon =
+ result.status === "PASS"
+ ? "\x1b[32mPASS\x1b[0m"
+ : result.status === "FAIL"
+ ? "\x1b[31mFAIL\x1b[0m"
+ : "\x1b[33mSKIP\x1b[0m";
+ console.log(` ${icon} | ${result.name} | ${result.detail}`);
+}
+
+/**
+ * Test a command exits with expected code and doesn't crash.
+ * expectedExit: number or "any" (just checks no signal death)
+ */
+export async function testCmd(
+ name: string,
+ cmd: string,
+ expectedExit: number | "any" = 0
+): Promise {
+ const start = Date.now();
+ const { stdout, stderr, exitCode } = await vmExec(cmd);
+ const duration = Date.now() - start;
+ const outputSize = stdout.length + stderr.length;
+
+ let status: TestStatus;
+ let detail: string;
+
+ if (expectedExit === "any") {
+ // Just check it didn't die from signal (exit >= 128)
+ if (exitCode < 128) {
+ status = "PASS";
+ detail = `exit=${exitCode} | ${outputSize}b | ${duration}ms`;
+ } else {
+ status = "FAIL";
+ detail = `SIGNAL exit=${exitCode} | ${outputSize}b`;
+ }
+ } else if (exitCode === expectedExit) {
+ status = "PASS";
+ detail = `exit=${exitCode} | ${outputSize}b | ${duration}ms`;
+ } else {
+ status = "FAIL";
+ detail = `expected exit=${expectedExit}, got ${exitCode} | ${outputSize}b`;
+ }
+
+ const result: TestResult = {
+ name,
+ status,
+ detail,
+ exitCode,
+ outputSize,
+ duration,
+ };
+ record(result);
+ return result;
+}
+
+/**
+ * Test token savings: compare raw command output vs RTK filtered output.
+ */
+export async function testSavings(
+ name: string,
+ rawCmd: string,
+ rtkCmd: string,
+ targetPct: number
+): Promise {
+ const raw = await vmExec(rawCmd);
+ const rtk = await vmExec(rtkCmd);
+
+ const rawSize = raw.stdout.length;
+ const rtkSize = rtk.stdout.length;
+
+ if (rawSize === 0) {
+ const result: TestResult = {
+ name,
+ status: "SKIP",
+ detail: "raw output empty",
+ };
+ record(result);
+ return result;
+ }
+
+ const savings = Math.round(100 - (rtkSize * 100) / rawSize);
+
+ let status: TestStatus;
+ let detail: string;
+
+ if (savings >= targetPct) {
+ status = "PASS";
+ detail = `raw=${rawSize}b filtered=${rtkSize}b savings=${savings}% (target: >=${targetPct}%)`;
+ } else {
+ status = "FAIL";
+ detail = `savings=${savings}% < target ${targetPct}% (raw=${rawSize}b filtered=${rtkSize}b)`;
+ }
+
+ const result: TestResult = { name, status, detail, savings };
+ record(result);
+ return result;
+}
+
+/**
+ * Test rewrite engine: input -> expected output.
+ */
+export async function testRewrite(
+ input: string,
+ expected: string
+): Promise {
+ const escaped = input.replace(/'/g, "'\\''");
+ const { stdout } = await vmExec(`${RTK_BIN} rewrite '${escaped}'`);
+ const actual = stdout.trim();
+
+ let status: TestStatus;
+ let detail: string;
+
+ if (actual === expected) {
+ status = "PASS";
+ detail = `'${input}' -> '${actual}'`;
+ } else {
+ status = "FAIL";
+ detail = `'${input}' -> expected '${expected}', got '${actual}'`;
+ }
+
+ const result: TestResult = { name: `rewrite: ${input}`, status, detail };
+ record(result);
+ return result;
+}
+
+/**
+ * Skip a test with a reason.
+ */
+export function skipTest(name: string, reason: string): TestResult {
+ const result: TestResult = { name, status: "SKIP", detail: reason };
+ record(result);
+ return result;
+}
diff --git a/scripts/benchmark/lib/vm.ts b/scripts/benchmark/lib/vm.ts
new file mode 100644
index 000000000..fcbf3a815
--- /dev/null
+++ b/scripts/benchmark/lib/vm.ts
@@ -0,0 +1,181 @@
+/**
+ * Multipass VM management for RTK integration testing.
+ */
+
+import { $ } from "bun";
+
+const VM_NAME = "rtk-test";
+const CLOUD_INIT = "scripts/benchmark/cloud-init.yaml";
+
+export interface VmInfo {
+ name: string;
+ state: string;
+ ipv4: string;
+}
+
+/** Check if VM exists and is running */
+export async function vmExists(): Promise {
+ const result = await $`multipass list --format json`.quiet();
+ const data = JSON.parse(result.stdout.toString());
+ return data.list?.some((vm: VmInfo) => vm.name === VM_NAME) ?? false;
+}
+
+/** Check if VM is running */
+export async function vmRunning(): Promise {
+ const result = await $`multipass list --format json`.quiet();
+ const data = JSON.parse(result.stdout.toString());
+ const vm = data.list?.find((v: VmInfo) => v.name === VM_NAME);
+ return vm?.state === "Running";
+}
+
+/** Create a new VM with cloud-init (20 min timeout for full provisioning) */
+export async function vmCreate(): Promise {
+ console.log(`[vm] Creating ${VM_NAME} with cloud-init (this takes ~10-15 min)...`);
+ // --timeout 1200 = 20 min for cloud-init to finish installing Rust, Go, Node, .NET, etc.
+ await $`multipass launch --name ${VM_NAME} --cpus 2 --memory 4G --disk 20G --timeout 1200 --cloud-init ${CLOUD_INIT} 24.04`;
+}
+
+/** Start existing VM */
+export async function vmStart(): Promise {
+ console.log(`[vm] Starting ${VM_NAME}...`);
+ await $`multipass start ${VM_NAME}`;
+}
+
+/** Execute a command in the VM, returns stdout (60s timeout per test by default) */
+export async function vmExec(
+ cmd: string,
+ timeoutMs = 60_000
+): Promise<{
+ stdout: string;
+ stderr: string;
+ exitCode: number;
+}> {
+ const exec = $`multipass exec ${VM_NAME} -- bash -c ${cmd}`
+ .quiet()
+ .nothrow()
+ .then((r) => ({
+ stdout: r.stdout.toString(),
+ stderr: r.stderr.toString(),
+ exitCode: r.exitCode,
+ }));
+
+ const timeout = new Promise<{ stdout: string; stderr: string; exitCode: number }>((_, reject) =>
+ setTimeout(() => reject(new Error(`vmExec timed out after ${timeoutMs}ms: ${cmd}`)), timeoutMs)
+ );
+
+ return Promise.race([exec, timeout]);
+}
+
+/** Transfer a file to the VM */
+export async function vmTransfer(
+ localPath: string,
+ remotePath: string
+): Promise {
+ await $`multipass transfer ${localPath} ${VM_NAME}:${remotePath}`;
+}
+
+/** Wait for cloud-init to complete (max 40 min — installs Rust, Go, Node, .NET, etc.) */
+export async function vmWaitReady(maxWaitSec = 2400): Promise {
+ console.log("[vm] Waiting for cloud-init...");
+ const start = Date.now();
+ while ((Date.now() - start) / 1000 < maxWaitSec) {
+ const { exitCode } = await vmExec(
+ "test -f /home/ubuntu/.cloud-init-complete"
+ );
+ if (exitCode === 0) {
+ const elapsed = Math.round((Date.now() - start) / 1000);
+ console.log(`[vm] Cloud-init complete after ${elapsed}s`);
+ return true;
+ }
+ await Bun.sleep(10_000);
+ }
+ console.error("[vm] Cloud-init timed out!");
+ return false;
+}
+
+/** Transfer RTK source and build in release mode */
+export async function vmBuildRtk(projectRoot: string): Promise<{
+ buildTime: number;
+ binarySize: number;
+ version: string;
+}> {
+ console.log("[vm] Transferring RTK source...");
+
+ // Create tarball excluding heavy dirs and macOS resource forks (._*)
+ await $`COPYFILE_DISABLE=1 tar czf /tmp/rtk-src.tar.gz --exclude target --exclude .git --exclude node_modules --exclude "index.html*" --exclude "._*" -C ${projectRoot} .`;
+ await vmTransfer("/tmp/rtk-src.tar.gz", "/tmp/rtk-src.tar.gz");
+ await vmExec(
+ "mkdir -p /home/ubuntu/rtk && cd /home/ubuntu/rtk && tar xzf /tmp/rtk-src.tar.gz"
+ );
+
+ console.log("[vm] Building RTK (release)...");
+ const start = Date.now();
+ const { stdout, exitCode } = await vmExec(
+ "export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo build --release 2>&1 | tail -5"
+ );
+ const buildTime = Math.round((Date.now() - start) / 1000);
+
+ if (exitCode !== 0) {
+ throw new Error(`Build failed:\n${stdout}`);
+ }
+
+ const { stdout: sizeStr } = await vmExec(
+ "stat -c%s /home/ubuntu/rtk/target/release/rtk"
+ );
+ const binarySize = parseInt(sizeStr.trim(), 10);
+
+ const { stdout: version } = await vmExec(
+ "/home/ubuntu/rtk/target/release/rtk --version"
+ );
+
+ console.log(
+ `[vm] Build OK in ${buildTime}s — ${binarySize} bytes — ${version.trim()}`
+ );
+
+ return { buildTime, binarySize, version: version.trim() };
+}
+
+/** Delete the VM */
+export async function vmDelete(): Promise {
+ console.log(`[vm] Deleting ${VM_NAME}...`);
+ await $`multipass delete ${VM_NAME} --purge`.nothrow();
+}
+
+/** Ensure VM is ready (create or reuse) */
+export async function vmEnsureReady(): Promise {
+ if (await vmExists()) {
+ if (!(await vmRunning())) {
+ await vmStart();
+ }
+ console.log(`[vm] Reusing existing VM ${VM_NAME}`);
+ // Check if cloud-init is still running
+ const { exitCode } = await vmExec(
+ "test -f /home/ubuntu/.cloud-init-complete"
+ );
+ if (exitCode !== 0) {
+ console.log("[vm] Cloud-init still running, waiting...");
+ const ready = await vmWaitReady();
+ if (!ready) {
+ throw new Error(
+ "Cloud-init timed out. Check: multipass exec rtk-test -- cat /var/log/cloud-init-output.log"
+ );
+ }
+ }
+ } else {
+ await vmCreate();
+ // multipass launch --timeout should wait, but double-check
+ const { exitCode } = await vmExec(
+ "test -f /home/ubuntu/.cloud-init-complete"
+ );
+ if (exitCode !== 0) {
+ const ready = await vmWaitReady();
+ if (!ready) {
+ throw new Error(
+ "Cloud-init timed out. Check: multipass exec rtk-test -- cat /var/log/cloud-init-output.log"
+ );
+ }
+ }
+ }
+}
+
+export const RTK_BIN = "/home/ubuntu/rtk/target/release/rtk";
diff --git a/scripts/benchmark/rebuild.ts b/scripts/benchmark/rebuild.ts
new file mode 100644
index 000000000..1d06277ff
--- /dev/null
+++ b/scripts/benchmark/rebuild.ts
@@ -0,0 +1,17 @@
+#!/usr/bin/env bun
+/**
+ * Fast rebuild: reuse existing VM, just transfer source and recompile.
+ * Usage: bun run scripts/benchmark/rebuild.ts
+ */
+
+import { vmEnsureReady, vmBuildRtk } from "./lib/vm";
+
+const PROJECT_ROOT = new URL("../../", import.meta.url).pathname.replace(/\/$/, "");
+
+await vmEnsureReady();
+const info = await vmBuildRtk(PROJECT_ROOT);
+
+console.log(`\nRebuild complete:`);
+console.log(` Version: ${info.version}`);
+console.log(` Binary: ${info.binarySize} bytes`);
+console.log(` Time: ${info.buildTime}s`);
diff --git a/scripts/benchmark/run.ts b/scripts/benchmark/run.ts
new file mode 100644
index 000000000..3d964963c
--- /dev/null
+++ b/scripts/benchmark/run.ts
@@ -0,0 +1,425 @@
+#!/usr/bin/env bun
+/**
+ * RTK Full Integration Test Suite — Multipass VM
+ *
+ * Usage:
+ * bun run scripts/benchmark/run.ts # Full suite
+ * bun run scripts/benchmark/run.ts --quick # Skip slow phases (perf, concurrency)
+ * bun run scripts/benchmark/run.ts --phase 3 # Run specific phase only
+ *
+ * Prerequisites:
+ * brew install multipass
+ */
+
+import { $ } from "bun";
+import { vmEnsureReady, vmBuildRtk, vmExec, RTK_BIN } from "./lib/vm";
+import { testCmd, testSavings, testRewrite, skipTest, getCounts } from "./lib/test";
+import { saveReport } from "./lib/report";
+
+const args = process.argv.slice(2);
+const quick = args.includes("--quick");
+const phaseArg = args.includes("--phase")
+ ? parseInt(args[args.indexOf("--phase") + 1], 10)
+ : null;
+const phaseOnly = phaseArg !== null && !Number.isNaN(phaseArg) ? phaseArg : null;
+if (args.includes("--phase") && phaseOnly === null) {
+ console.error("Error: --phase requires a number (e.g. --phase 3)");
+ process.exit(1);
+}
+const reportPath = args.includes("--report")
+ ? args[args.indexOf("--report") + 1]
+ : `${new URL("../../", import.meta.url).pathname.replace(/\/$/, "")}/benchmark-report.txt`;
+
+const PROJECT_ROOT = new URL("../../", import.meta.url).pathname.replace(/\/$/, "");
+const RTK = RTK_BIN;
+
+function shouldRun(phase: number): boolean {
+ return phaseOnly === null || phaseOnly === phase;
+}
+
+function heading(phase: number, title: string) {
+ console.log(`\n\x1b[34m[Phase ${phase}] ${title}\x1b[0m`);
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 0: VM Setup
+// ══════════════════════════════════════════════════════════════
+
+console.log("\x1b[34m[rtk-test] RTK Full Integration Test Suite\x1b[0m");
+console.log(`Project: ${PROJECT_ROOT}`);
+
+await vmEnsureReady();
+
+// ══════════════════════════════════════════════════════════════
+// Phase 1: Transfer & Build
+// ══════════════════════════════════════════════════════════════
+
+heading(1, "Transfer & Build");
+const branch = (await $`git -C ${PROJECT_ROOT} branch --show-current`.text()).trim();
+const commit = (await $`git -C ${PROJECT_ROOT} log --oneline -1`.text()).trim();
+const buildInfo = await vmBuildRtk(PROJECT_ROOT);
+
+// Binary size check
+// ARM Linux release binaries are ~6.5MB (vs ~4MB x86 stripped).
+// CLAUDE.md target is <5MB for stripped x86 release builds.
+// VM builds are ARM + not fully stripped, so we use a relaxed 8MB limit here.
+const sizeLimit = 8_388_608; // 8MB (relaxed for ARM Linux VM)
+if (buildInfo.binarySize < sizeLimit) {
+ console.log(` \x1b[32mPASS\x1b[0m | binary size | ${buildInfo.binarySize} bytes < 8MB`);
+} else {
+ console.log(` \x1b[31mFAIL\x1b[0m | binary size | ${buildInfo.binarySize} bytes >= 8MB`);
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 2: Cargo Quality (fmt, clippy, test)
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(2)) {
+ heading(2, "Cargo Quality");
+
+ await testCmd(
+ "quality:cargo fmt",
+ "export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo fmt --all --check 2>&1"
+ );
+
+ await testCmd(
+ "quality:cargo clippy",
+ "export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo clippy --all-targets -- -D warnings 2>&1"
+ );
+
+ await testCmd(
+ "quality:cargo test",
+ "export PATH=$HOME/.cargo/bin:$PATH && cd /home/ubuntu/rtk && cargo test --all 2>&1"
+ );
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 3: Rust Built-in Commands
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(3)) {
+ heading(3, "Rust Built-in Commands");
+
+ // Git
+ await testCmd("git:status", `cd /tmp/test-git && ${RTK} git status`);
+ await testCmd("git:log", `cd /tmp/test-git && ${RTK} git log -5`);
+ await testCmd("git:log --oneline", `cd /tmp/test-git && ${RTK} git log --oneline -10`);
+ await testCmd("git:diff", `cd /tmp/test-git && ${RTK} git diff`, "any");
+ await testCmd("git:branch", `cd /tmp/test-git && ${RTK} git branch`);
+ await testCmd("git:add --dry-run", `cd /tmp/test-git && ${RTK} git add --dry-run .`, "any");
+
+ // Files
+ await testCmd("files:ls", `${RTK} ls /home/ubuntu/rtk`);
+ await testCmd("files:ls src/", `${RTK} ls /home/ubuntu/rtk/src/`);
+ await testCmd("files:ls -R", `${RTK} ls -R /home/ubuntu/rtk/src/`);
+ await testCmd("files:read", `${RTK} read /home/ubuntu/rtk/src/main.rs`);
+ await testCmd("files:read aggressive", `${RTK} read /home/ubuntu/rtk/src/main.rs -l aggressive`);
+ await testCmd("files:smart", `${RTK} smart /home/ubuntu/rtk/src/main.rs`);
+ await testCmd("files:find *.rs", `${RTK} find '*.rs' /home/ubuntu/rtk/src/`);
+ await testCmd("files:wc", `${RTK} wc /home/ubuntu/rtk/src/main.rs`);
+ await testCmd("files:diff", `${RTK} diff /home/ubuntu/rtk/src/main.rs /home/ubuntu/rtk/src/utils.rs`);
+
+ // Search
+ await testCmd("search:grep", `${RTK} grep 'fn main' /home/ubuntu/rtk/src/`);
+
+ // Data
+ await testCmd("data:json", `${RTK} json /tmp/test-node/package.json`);
+ await testCmd("data:deps", `cd /home/ubuntu/rtk && ${RTK} deps`);
+ await testCmd("data:env", `${RTK} env`);
+
+ // Runners
+ await testCmd("runner:summary", `${RTK} summary 'echo hello world'`);
+ // BUG: rtk err swallows exit code — tracked in #846
+ await testCmd("runner:err", `${RTK} err false`, "any");
+ await testCmd("runner:test", `${RTK} test 'echo ok'`, "any");
+
+ // Logs
+ await testCmd("log:large", `${RTK} log /tmp/large.log`);
+
+ // Network
+ await testCmd("net:curl", `${RTK} curl https://httpbin.org/get`, "any");
+
+ // GitHub
+ await testCmd("gh:pr list", `cd /home/ubuntu/rtk && ${RTK} gh pr list`, "any");
+
+ // Cargo (test project has intentional test failure → exit 101)
+ await testCmd("cargo:build", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo build`);
+ await testCmd("cargo:test", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo test`, 101);
+ await testCmd("cargo:clippy", `export PATH=$HOME/.cargo/bin:$PATH && cd /tmp/test-rust && ${RTK} cargo clippy`);
+
+ // Python (test project has intentional failures)
+ await testCmd("python:pytest", `cd /tmp/test-python && ${RTK} pytest`, 1);
+ await testCmd("python:ruff check", `cd /tmp/test-python && ${RTK} ruff check .`, 1);
+ await testCmd("python:mypy", `cd /tmp/test-python && ${RTK} mypy .`, 1);
+ await testCmd("python:pip list", `${RTK} pip list`);
+
+ // Go (test project has intentional test failure)
+ await testCmd("go:test", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go test ./...`, 1);
+ await testCmd("go:build", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go build .`, 1);
+ await testCmd("go:vet", `export PATH=$PATH:/usr/local/go/bin && cd /tmp/test-go && ${RTK} go vet ./...`, 1);
+ await testCmd("go:golangci-lint", `export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin && cd /tmp/test-go && ${RTK} golangci-lint run`, 1);
+
+ // TypeScript
+ await testCmd("ts:tsc", `cd /tmp/test-node && ${RTK} tsc --noEmit`, "any");
+
+ // Linters
+ await testCmd("lint:eslint", `cd /tmp/test-node && ${RTK} lint 'eslint src/'`, "any");
+ await testCmd("lint:prettier", `cd /tmp/test-node && ${RTK} prettier --check src/`, "any");
+
+ // Docker
+ await testCmd("docker:ps", `${RTK} docker ps`, "any");
+ await testCmd("docker:images", `${RTK} docker images`, "any");
+
+ // Kubernetes
+ await testCmd("k8s:pods", `${RTK} kubectl pods`, "any");
+
+ // .NET
+ await testCmd("dotnet:build", `export DOTNET_ROOT=/usr/local/share/dotnet && export PATH=$PATH:$DOTNET_ROOT && cd /tmp/test-dotnet/TestApp 2>/dev/null && ${RTK} dotnet build || echo 'dotnet skip'`, "any");
+
+ // Meta
+ await testCmd("meta:gain", `${RTK} gain`);
+ await testCmd("meta:gain --history", `${RTK} gain --history`);
+ await testCmd("meta:proxy", `${RTK} proxy echo 'proxy test'`);
+ await testCmd("meta:verify", `${RTK} verify`, "any");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 4: TOML Filter Commands
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(4)) {
+ heading(4, "TOML Filter Commands");
+
+ // System
+ await testCmd("toml:df", `${RTK} df -h`);
+ await testCmd("toml:du", `${RTK} du -sh /tmp`, "any");
+ await testCmd("toml:ps", `${RTK} ps aux`);
+ await testCmd("toml:ping", `${RTK} ping -c 2 127.0.0.1`);
+
+ // Build tools
+ await testCmd("toml:make", `cd /tmp && ${RTK} make -f Makefile`, "any");
+ await testCmd("toml:rsync", `${RTK} rsync --version`);
+
+ // Linters
+ await testCmd("toml:shellcheck", `${RTK} shellcheck /tmp/test.sh`, "any");
+ await testCmd("toml:hadolint", `${RTK} hadolint /tmp/Dockerfile.bad`, "any");
+ await testCmd("toml:yamllint", `${RTK} yamllint /tmp/test.yaml`, "any");
+ await testCmd("toml:markdownlint", `${RTK} markdownlint /tmp/test.md`, "any");
+
+ // Cloud/Infra
+ await testCmd("toml:terraform", `${RTK} terraform --version`, "any");
+ await testCmd("toml:helm", `${RTK} helm version`, "any");
+ await testCmd("toml:ansible", `${RTK} ansible-playbook --version`, "any");
+
+ // Mocked tools
+ await testCmd("toml:gcloud", `${RTK} gcloud version`);
+ await testCmd("toml:shopify", `${RTK} shopify theme check`, "any");
+ await testCmd("toml:pio", `${RTK} pio run`, "any");
+ await testCmd("toml:quarto", `${RTK} quarto render`, "any");
+ await testCmd("toml:sops", `${RTK} sops --version`);
+ // Swift ecosystem
+ await testCmd("toml:swift build", `${RTK} swift build`, "any");
+ await testCmd("toml:swift test", `${RTK} swift test`, "any");
+ await testCmd("toml:swift run", `${RTK} swift run`, "any");
+ await testCmd("toml:swift package", `${RTK} swift package resolve`, "any");
+ await testCmd("toml:swiftlint", `${RTK} swiftlint`, "any");
+ await testCmd("toml:swiftformat", `${RTK} swiftformat`, "any");
+ await testCmd("toml:kubectl", `${RTK} kubectl version --client`, "any");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 5: Hook Rewrite Engine
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(5)) {
+ heading(5, "Hook Rewrite Engine");
+
+ // Basic rewrites
+ await testRewrite("git status", "rtk git status");
+ await testRewrite("git log --oneline -10", "rtk git log --oneline -10");
+ await testRewrite("cargo test", "rtk cargo test");
+ await testRewrite("cargo build --release", "rtk cargo build --release");
+ await testRewrite("docker ps", "rtk docker ps");
+ // NOTE: rtk rewrites "kubectl get pods" to "rtk kubectl get pods" (preserves get)
+ await testRewrite("kubectl get pods", "rtk kubectl get pods");
+ await testRewrite("ruff check", "rtk ruff check");
+ await testRewrite("pytest", "rtk pytest");
+ await testRewrite("go test", "rtk go test");
+ await testRewrite("pnpm list", "rtk pnpm list");
+ await testRewrite("gh pr list", "rtk gh pr list");
+ await testRewrite("df -h", "rtk df -h");
+ await testRewrite("ps aux", "rtk ps aux");
+
+ // Compound
+ await testRewrite("cargo test && git status", "rtk cargo test && rtk git status");
+ // NOTE: shell strips single quotes in vmExec, so 'msg' becomes msg
+ await testRewrite("git add . && git commit -m msg", "rtk git add . && rtk git commit -m msg");
+
+ // No rewrite (shell builtins) — rtk rewrite returns empty string + exit 1
+ // We test via testCmd since testRewrite expects non-empty output
+ await testCmd("rewrite:cd (no rewrite)", `${RTK} rewrite 'cd /tmp'`, 1);
+ await testCmd("rewrite:export (no rewrite)", `${RTK} rewrite 'export FOO=bar'`, 1);
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 6: Exit Code Preservation
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(6)) {
+ heading(6, "Exit Code Preservation");
+
+ // Success
+ await testCmd("exit:git status=0", `cd /tmp/test-git && ${RTK} git status`, 0);
+ await testCmd("exit:ls=0", `${RTK} ls /tmp`, 0);
+ await testCmd("exit:gain=0", `${RTK} gain`, 0);
+
+ // Failures
+ // rg returns exit 1 (no match) or 2 (error) — accept both
+ await testCmd("exit:grep NOTFOUND", `${RTK} grep NOTFOUND_XYZ_123 /tmp`, "any");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 7: Token Savings
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(7)) {
+ heading(7, "Token Savings");
+
+ await testSavings(
+ "savings:git log",
+ "cd /tmp/test-git && git log -20",
+ `cd /tmp/test-git && ${RTK} git log -20`,
+ 60
+ );
+ await testSavings(
+ "savings:ls",
+ "ls -la /home/ubuntu/rtk/src/",
+ `${RTK} ls /home/ubuntu/rtk/src/`,
+ 60
+ );
+ await testSavings(
+ "savings:log dedup",
+ "cat /tmp/large.log",
+ `${RTK} log /tmp/large.log`,
+ 80
+ );
+ await testSavings(
+ "savings:read aggressive",
+ "cat /home/ubuntu/rtk/src/main.rs",
+ `${RTK} read /home/ubuntu/rtk/src/main.rs -l aggressive`,
+ 50
+ );
+ await testSavings(
+ "savings:swift test",
+ "swift test",
+ `${RTK} swift test`,
+ 60
+ );
+ await testSavings(
+ "savings:swiftlint",
+ "swiftlint",
+ `${RTK} swiftlint`,
+ 20
+ );
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 8: Pipe Compatibility
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(8)) {
+ heading(8, "Pipe Compatibility");
+
+ await testCmd("pipe:git status|wc", `cd /tmp/test-git && ${RTK} git status | wc -l`);
+ await testCmd("pipe:ls|wc", `${RTK} ls /home/ubuntu/rtk/src/ | wc -l`);
+ await testCmd("pipe:grep|head", `${RTK} grep 'fn' /home/ubuntu/rtk/src/ | head -5`);
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 9: Edge Cases
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(9)) {
+ heading(9, "Edge Cases");
+
+ await testCmd("edge:summary true", `${RTK} summary 'true'`, "any");
+ await testCmd("edge:grep NOTFOUND", `${RTK} grep NOTFOUND_XYZ /home/ubuntu/rtk/src/`, 1);
+ await testCmd("edge:unicode", `echo 'hello world' > /tmp/uni.txt && ${RTK} grep 'hello' /tmp`, "any");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 10: Performance (skip with --quick)
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(10) && !quick) {
+ heading(10, "Performance");
+
+ // hyperfine
+ const { exitCode: hfExist } = await vmExec("command -v hyperfine");
+ if (hfExist === 0) {
+ const { stdout: hfOut } = await vmExec(
+ `cd /tmp/test-git && hyperfine --warmup 3 --min-runs 5 '${RTK} git status' 'git status' --export-json /dev/stdout 2>/dev/null`
+ );
+ try {
+ const hf = JSON.parse(hfOut);
+ const rtkMean = (hf.results?.[0]?.mean * 1000).toFixed(1);
+ const rawMean = (hf.results?.[1]?.mean * 1000).toFixed(1);
+ console.log(` Startup: rtk=${rtkMean}ms raw=${rawMean}ms`);
+ } catch {
+ console.log(" hyperfine output parse failed");
+ }
+ } else {
+ skipTest("perf:hyperfine", "not installed");
+ }
+
+ // Memory
+ const { stdout: memOut } = await vmExec(
+ `cd /tmp/test-git && /usr/bin/time -v ${RTK} git status 2>&1 | grep 'Maximum resident'`
+ );
+ const memKb = parseInt(memOut.match(/(\d+)/)?.[1] ?? "0", 10);
+ if (memKb > 0 && memKb < 20000) {
+ await testCmd("perf:memory", `echo '${memKb} KB < 20MB'`);
+ } else if (memKb > 0) {
+ await testCmd("perf:memory", `echo '${memKb} KB >= 20MB' && exit 1`, 0);
+ }
+} else if (quick && shouldRun(10)) {
+ skipTest("perf:hyperfine", "--quick mode");
+ skipTest("perf:memory", "--quick mode");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Phase 11: Concurrency (skip with --quick)
+// ══════════════════════════════════════════════════════════════
+
+if (shouldRun(11) && !quick) {
+ heading(11, "Concurrency");
+
+ await testCmd(
+ "concurrency:10x git status",
+ `cd /tmp/test-git && for i in $(seq 1 10); do ${RTK} git status >/dev/null & done; wait`
+ );
+} else if (quick && shouldRun(11)) {
+ skipTest("concurrency:10x", "--quick mode");
+}
+
+// ══════════════════════════════════════════════════════════════
+// Report
+// ══════════════════════════════════════════════════════════════
+
+const report = await saveReport(
+ { ...buildInfo, branch, commit },
+ reportPath
+);
+
+console.log("\n" + report);
+
+const { total, passed, failed, skipped } = getCounts();
+const passRate = total > 0 ? Math.round((passed * 100) / total) : 0;
+
+if (failed === 0) {
+ console.log(`\n\x1b[32m READY FOR RELEASE — ${passed}/${total} (${passRate}%)\x1b[0m\n`);
+ process.exit(0);
+} else {
+ console.log(`\n\x1b[31m NOT READY — ${failed} failures — ${passed}/${total} (${passRate}%)\x1b[0m\n`);
+ process.exit(1);
+}
diff --git a/scripts/check-installation.sh b/scripts/check-installation.sh
index e7a56fb7a..ce0d2eb73 100755
--- a/scripts/check-installation.sh
+++ b/scripts/check-installation.sh
@@ -24,7 +24,7 @@ else
echo -e " ${RED}❌ RTK is NOT installed${NC}"
echo ""
echo " Install with:"
- echo " curl -fsSL https://github.com/rtk-ai/rtk/blob/master/install.sh| sh"
+ echo " curl -fsSL https://github.com/algolia/rtk/blob/main/install.sh| sh"
exit 1
fi
echo ""
@@ -45,7 +45,7 @@ else
echo ""
echo " You installed the wrong package. Fix it with:"
echo " cargo uninstall rtk"
- echo " curl -fsSL https://github.com/rtk-ai/rtk/blob/master/install.sh | sh"
+ echo " curl -fsSL https://github.com/algolia/rtk/blob/main/install.sh | sh"
CORRECT_RTK=false
fi
echo ""
@@ -142,7 +142,7 @@ if [ ${#MISSING_FEATURES[@]} -gt 0 ]; then
echo ""
echo "To get all features, install the fork:"
echo " cargo uninstall rtk"
- echo " curl -fsSL https://github.com/rtk-ai/rtk/blob/master/install.sh | sh"
+ echo " curl -fsSL https://github.com/algolia/rtk/blob/main/install.sh | sh"
echo " cd rtk && git checkout feat/all-features"
echo " cargo install --path . --force"
else
diff --git a/scripts/check-test-presence.sh b/scripts/check-test-presence.sh
new file mode 100755
index 000000000..08147c4dc
--- /dev/null
+++ b/scripts/check-test-presence.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+# check-test-presence.sh — CI guard: new/modified *_cmd.rs files must have #[cfg(test)]
+#
+# Usage:
+# bash scripts/check-test-presence.sh [BASE_BRANCH]
+# bash scripts/check-test-presence.sh --self-test
+#
+# BASE_BRANCH defaults to origin/develop
+
+if [ "${1:-}" = "--self-test" ]; then
+ # Self-test: create a tempfile without tests and verify the check catches it
+ TMPFILE="src/cmds/system/_rtk_check_self_test_cmd.rs"
+ echo "pub fn run() {}" > "$TMPFILE"
+ trap 'rm -f "$TMPFILE"' EXIT
+
+ if grep -q '#\[cfg(test)\]' "$TMPFILE"; then
+ echo "FAIL: self-test broken (false negative)"
+ exit 1
+ fi
+ rm "$TMPFILE"
+ trap - EXIT
+ echo "PASS: --self-test detection works correctly"
+ exit 0
+fi
+
+BASE_BRANCH="${1:-origin/develop}"
+EXIT_CODE=0
+
+# Find *_cmd.rs files that were added or modified in this PR
+CHANGED_FILES=$(git diff --name-only --diff-filter=AM --no-renames "$BASE_BRANCH"...HEAD \
+ 2>/dev/null | grep -E 'src/cmds/.+_cmd\.rs$' || true)
+
+if [ -z "$CHANGED_FILES" ]; then
+ echo "check-test-presence: no *_cmd.rs changes detected — OK"
+ exit 0
+fi
+
+echo "check-test-presence: checking $(echo "$CHANGED_FILES" | wc -l | tr -d ' ') filter module(s)..."
+echo ""
+
+while IFS= read -r file; do
+ if [ ! -f "$file" ]; then
+ continue
+ fi
+
+ if grep -q '#\[cfg(test)\]' "$file"; then
+ echo " PASS $file"
+ else
+ echo " FAIL $file"
+ echo " Missing #[cfg(test)] module."
+ echo " Every *_cmd.rs filter must include inline unit tests."
+ echo " Reference: src/cmds/cloud/aws_cmd.rs"
+ echo ""
+ EXIT_CODE=1
+ fi
+done <<< "$CHANGED_FILES"
+
+echo ""
+
+if [ "$EXIT_CODE" -ne 0 ]; then
+ echo "check-test-presence: FAILED — add tests before merging."
+ echo "See .claude/rules/cli-testing.md for the testing guide."
+else
+ echo "check-test-presence: all filter modules have tests — OK"
+fi
+
+exit "$EXIT_CODE"
diff --git a/scripts/validate-docs.sh b/scripts/validate-docs.sh
index 508f40a71..e7c3a1592 100755
--- a/scripts/validate-docs.sh
+++ b/scripts/validate-docs.sh
@@ -3,41 +3,25 @@ set -e
echo "🔍 Validating RTK documentation consistency..."
-# 1. Nombre de modules cohérent
-MAIN_MODULES=$(grep -c '^mod ' src/main.rs)
-echo "📊 Module count in main.rs: $MAIN_MODULES"
-
-# Extract module count from ARCHITECTURE.md
-if [ -f "ARCHITECTURE.md" ]; then
- ARCH_MODULES=$(grep 'Total:.*modules' ARCHITECTURE.md | grep -o '[0-9]\+' | head -1)
- if [ -z "$ARCH_MODULES" ]; then
- echo "⚠️ Could not extract module count from ARCHITECTURE.md"
- else
- echo "📊 Module count in ARCHITECTURE.md: $ARCH_MODULES"
- if [ "$MAIN_MODULES" != "$ARCH_MODULES" ]; then
- echo "❌ Module count mismatch: main.rs=$MAIN_MODULES, ARCHITECTURE.md=$ARCH_MODULES"
- exit 1
- fi
- fi
-fi
+# 1. Source file count sanity check
+SRC_FILES=$(find src -name "*.rs" ! -name "mod.rs" ! -name "main.rs" | wc -l | tr -d ' ')
+echo "📊 Rust source files in src/: $SRC_FILES"
# 3. Commandes Python/Go présentes partout
PYTHON_GO_CMDS=("ruff" "pytest" "pip" "go" "golangci")
echo "🐍 Checking Python/Go commands documentation..."
for cmd in "${PYTHON_GO_CMDS[@]}"; do
- for file in README.md CLAUDE.md; do
- if [ ! -f "$file" ]; then
- echo "⚠️ $file not found, skipping"
- continue
- fi
- if ! grep -q "$cmd" "$file"; then
- echo "❌ $file ne mentionne pas commande $cmd"
- exit 1
- fi
- done
+ if [ ! -f "README.md" ]; then
+ echo "⚠️ README.md not found, skipping"
+ break
+ fi
+ if ! grep -q "$cmd" "README.md"; then
+ echo "❌ README.md ne mentionne pas commande $cmd"
+ exit 1
+ fi
done
-echo "✅ Python/Go commands: documented in README.md and CLAUDE.md"
+echo "✅ Python/Go commands: documented in README.md"
# 4. Hooks cohérents avec doc
HOOK_FILE=".claude/hooks/rtk-rewrite.sh"
diff --git a/src/analytics/README.md b/src/analytics/README.md
index 584b52d40..5cea9ce77 100644
--- a/src/analytics/README.md
+++ b/src/analytics/README.md
@@ -1,10 +1,10 @@
# Analytics
-> See also [docs/TECHNICAL.md](../../docs/TECHNICAL.md) for the full architecture overview
+> See also [docs/contributing/TECHNICAL.md](../../docs/contributing/TECHNICAL.md) for the full architecture overview
## Scope
-**Read-only dashboards** over the tracking database. Analytics presents the value that `cmds/` creates — it queries token savings, correlates with external spending data, and surfaces adoption opportunities. It never modifies the tracking DB.
+**Read-only dashboards** over the tracking database. Queries token savings, correlates with external spending data, and surfaces adoption metrics. Never modifies the tracking DB.
Owns: `rtk gain` (savings dashboard), `rtk cc-economics` (cost reduction), `rtk session` (adoption analysis), and Claude Code usage data parsing.
@@ -15,7 +15,7 @@ Boundary rule: if a new module writes to the DB, it belongs in `core/` or `cmds/
## Purpose
Token savings analytics, economic modeling, and adoption metrics.
-These modules read from the SQLite tracking database to produce dashboards, spending estimates, and session-level adoption reports that help users understand the value RTK provides.
+These modules read from the SQLite tracking database to produce dashboards, spending estimates, and session-level adoption reports.
## Adding New Functionality
To add a new analytics view: (1) create a new `*_cmd.rs` file in this directory, (2) query `core/tracking` for the metrics you need using the existing `TrackingDb` API, (3) register the command in `main.rs` under the `Commands` enum, and (4) add `#[cfg(test)]` unit tests with sample tracking data. Analytics modules should be read-only against the tracking database and never modify it.
diff --git a/src/analytics/cc_economics.rs b/src/analytics/cc_economics.rs
index 693dc61e2..037593102 100644
--- a/src/analytics/cc_economics.rs
+++ b/src/analytics/cc_economics.rs
@@ -14,9 +14,6 @@ use crate::core::utils::{format_cpt, format_tokens, format_usd};
// ── Constants ──
-#[allow(dead_code)]
-const BILLION: f64 = 1e9;
-
// API pricing ratios (verified Feb 2026, consistent across Claude models <=200K context)
// Source: https://docs.anthropic.com/en/docs/about-claude/models
const WEIGHT_OUTPUT: f64 = 5.0; // Output = 5x input
diff --git a/src/analytics/ccusage.rs b/src/analytics/ccusage.rs
index 49bd5bc8d..c291615b7 100644
--- a/src/analytics/ccusage.rs
+++ b/src/analytics/ccusage.rs
@@ -4,6 +4,7 @@
//! Claude Code API usage metrics. Handles subprocess execution, JSON parsing,
//! and graceful degradation when ccusage is unavailable.
+use crate::core::stream::exec_capture;
use crate::core::utils::{resolved_command, tool_exists};
use anyhow::{Context, Result};
use serde::Deserialize;
@@ -95,7 +96,9 @@ fn build_command() -> Option {
}
// Fallback: try npx
+ eprintln!("[info] ccusage not installed globally, fetching via npx...");
let npx_check = resolved_command("npx")
+ .arg("--yes")
.arg("ccusage")
.arg("--help")
.stdout(std::process::Stdio::null())
@@ -104,6 +107,7 @@ fn build_command() -> Option {
if npx_check.map(|s| s.success()).unwrap_or(false) {
let mut cmd = resolved_command("npx");
+ cmd.arg("--yes");
cmd.arg("ccusage");
return Some(cmd);
}
@@ -111,12 +115,6 @@ fn build_command() -> Option {
None
}
-/// Check if ccusage CLI is available (binary or via npx)
-#[allow(dead_code)]
-pub fn is_available() -> bool {
- build_command().is_some()
-}
-
/// Fetch usage data from ccusage for the last 90 days
///
/// Returns `Ok(None)` if ccusage is unavailable (graceful degradation)
@@ -137,34 +135,30 @@ pub fn fetch(granularity: Granularity) -> Result