From b19c7dbc15dc5e5582944a772cc7d40f4af0009a Mon Sep 17 00:00:00 2001 From: heAdz0r Date: Sat, 14 Feb 2026 13:52:08 +0300 Subject: [PATCH 1/2] feat(init,docs,hooks): enforce rgai-first search policy --- .claude/hooks/rtk-rewrite.sh | 15 ++- INSTALL.md | 13 ++- README.md | 38 +++++++- docs/TROUBLESHOOTING.md | 9 ++ hooks/rtk-awareness.md | 17 ++++ hooks/rtk-rewrite.sh | 15 ++- hooks/test-rtk-rewrite.sh | 46 +++++++-- src/init.rs | 181 +++++++++++++++++++++++++++++++++-- 8 files changed, 311 insertions(+), 23 deletions(-) diff --git a/.claude/hooks/rtk-rewrite.sh b/.claude/hooks/rtk-rewrite.sh index 6574b01..3ef6854 100755 --- a/.claude/hooks/rtk-rewrite.sh +++ b/.claude/hooks/rtk-rewrite.sh @@ -2,6 +2,7 @@ # RTK auto-rewrite hook for Claude Code PreToolUse:Bash # Transparently rewrites raw commands to their rtk equivalents. # Outputs JSON with updatedInput to modify the command before execution. +# Source of truth: hooks/rtk-rewrite.sh (keep .claude/hooks copy in sync) # Guards: skip silently if dependencies missing if ! command -v rtk &>/dev/null || ! command -v jq &>/dev/null; then @@ -88,10 +89,20 @@ elif echo "$MATCH_CMD" | grep -qE '^cargo[[:space:]]+fmt([[:space:]]|$)'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^cargo fmt/rtk cargo fmt/')" # --- File operations --- +# Search priority (mandatory): rgai > rg > grep +# Tier 1: semantic intent search (grepai/rgai) -> rtk rgai +# Tier 2: exact search via ripgrep -> rtk grep (rtk grep runs rg -> grep fallback internally) +# Tier 3: exact search via grep -> rtk grep elif echo "$MATCH_CMD" | grep -qE '^cat[[:space:]]+'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^cat /rtk read /')" -elif echo "$MATCH_CMD" | grep -qE '^(rg|grep)[[:space:]]+'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^(rg|grep) /rtk grep /')" +elif echo "$MATCH_CMD" | grep -qE '^(grepai|rgai)[[:space:]]+search([[:space:]]|$)'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^(grepai|rgai)[[:space:]]+search[[:space:]]+/rtk rgai /')" +elif echo "$MATCH_CMD" | grep -qE '^rgai[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^rgai[[:space:]]+/rtk rgai /')" +elif echo "$MATCH_CMD" | grep -qE '^rg[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^rg /rtk grep /')" +elif echo "$MATCH_CMD" | grep -qE '^grep[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^grep /rtk grep /')" elif echo "$MATCH_CMD" | grep -qE '^ls([[:space:]]|$)'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^ls/rtk ls/')" elif echo "$MATCH_CMD" | grep -qE '^tree([[:space:]]|$)'; then diff --git a/INSTALL.md b/INSTALL.md index 55b32fd..e074ff7 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -69,6 +69,15 @@ rtk gain # MUST show token savings, not "command not found" ## Project Initialization +### Search Priority Policy + +**Search priority (mandatory): rgai > rg > grep.** + +- Use `rtk rgai` first for semantic/intention-based discovery. +- Use `rtk grep` for exact/regex matching. +- `rtk grep` internally follows `rg -> grep` backend fallback. +- For fully raw output, use `rtk proxy `. + ### Recommended: Global Hook-First Setup **Best for: All projects, automatic RTK usage** @@ -229,7 +238,8 @@ cp ~/.claude/settings.json.bak ~/.claude/settings.json ```bash rtk ls . # Compact tree view rtk read file.rs # Optimized reading -rtk grep "pattern" . # Grouped search results +rtk rgai "query" # Semantic code search (grepai-style) +rtk grep "pattern" . # Exact/regex search (internal rg -> grep fallback) ``` ### Git @@ -332,6 +342,7 @@ Before each session: - [ ] Verify RTK is installed: `rtk --version` - [ ] If not installed → follow "Install from fork" - [ ] If project not initialized → `rtk init` +- [ ] Search policy: `rgai > rg > grep` (use `rtk rgai` first) - [ ] Use `rtk` for ALL git/pnpm/test/vitest commands - [ ] Check savings: `rtk gain` diff --git a/README.md b/README.md index 910e6b5..711b510 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,30 @@ With rtk: **~45,000 tokens** → **70% reduction** > Estimates based on medium-sized TypeScript/Rust projects. Actual savings vary by project size. +### Migration Search Benchmark Snapshot (February 14, 2026) + +Source: `easy_math/tasks/benchmark_migration_search_compare_20260214-000212/compare.tsv` + +| Scenario | Agent | Est. tokens | Unique files | Files / 1K tokens | +|---|---|---:|---:|---:| +| token_opt | grep | 14106 | 94 | 6.66 | +| token_opt | rg | 14008 | 102 | 7.28 | +| token_opt | rgai | 3428 | 58 | 16.92 | +| quality_norm | grep | 14106 | 94 | 6.66 | +| quality_norm | rg | 14065 | 103 | 7.32 | +| quality_norm | rgai | 7349 | 103 | 14.02 | + +Methodology and raw artifacts: `easy_math/tasks/benchmark_migration_search_*` + +## Search Priority Policy + +**Search priority (mandatory): rgai > rg > grep.** + +- Use `rtk rgai` first for semantic/intention-based discovery. +- Use `rtk grep` for exact/regex matching. +- `rtk grep` internally follows `rg -> grep` backend fallback. +- For fully raw output, use `rtk proxy `. + ## Installation ### ⚠️ Pre-Installation Check (REQUIRED) @@ -130,7 +154,9 @@ rtk read file.rs # Smart file reading rtk read file.rs -l aggressive # Signatures only (strips bodies) rtk smart file.rs # 2-line heuristic code summary rtk find "*.rs" . # Compact find results -rtk grep "pattern" . # Grouped search results +rtk rgai "auth token refresh" # Semantic code search (grepai-style) +rtk grep "pattern" . # Exact/regex search (internal rg -> grep fallback) +rtk rgai auth token --path src # Unquoted multi-word query + explicit path ``` ### Git @@ -400,6 +426,11 @@ The most effective way to use rtk is with the **auto-rewrite hook** for Claude C **Result**: 100% rtk adoption across all conversations and subagents, zero token overhead in Claude's context. +**Predictable search ladder**: +- `rtk rgai` for semantic discovery +- `rtk grep` for exact/regex follow-up (`rg -> grep` fallback) +- `rtk proxy ...` when you need fully raw behavior + ### What Are Hooks? **For Beginners**: @@ -491,12 +522,16 @@ The hook is included in this repository at `.claude/hooks/rtk-rewrite.sh`. To us ### Commands Rewritten +Search rewrite order is strict and deterministic: `rgai > rg > grep`. + | Raw Command | Rewritten To | |-------------|-------------| | `git status/diff/log/add/commit/push/pull/branch/fetch/stash` | `rtk git ...` | | `gh pr/issue/run` | `rtk gh ...` | | `cargo test/build/clippy` | `rtk cargo ...` | | `cat ` | `rtk read ` | +| `grepai/rgai search ` | `rtk rgai ` | +| `rgai ` | `rtk rgai ` | | `rg/grep ` | `rtk grep ` | | `ls` | `rtk ls` | | `vitest/pnpm test` | `rtk vitest run` | @@ -531,6 +566,7 @@ If you prefer Claude Code to **suggest** rtk usage rather than automatically rew - You're learning rtk patterns and want visibility into the rewrite logic - You prefer Claude Code to make explicit decisions rather than transparent rewrites - You want to preserve exact command execution for debugging +- You suspect `strangeness tax` in a specific workflow and want explicit command choice #### Suggest Hook Setup diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 64d4576..d5bbcc5 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -165,6 +165,15 @@ Then add to `~/.claude/settings.json` (replace `~` with full path): **Note**: Use absolute path in `settings.json`, not `~/.claude/...` +### Search Priority Policy + +**Search priority (mandatory): rgai > rg > grep.** + +- Use `rtk rgai` first for semantic/intention-based discovery. +- Use `rtk grep` for exact/regex matching. +- `rtk grep` internally follows `rg -> grep` backend fallback. +- If you need fully raw output for debugging, use `rtk proxy `. + --- ## Problem: "command not found: rtk" after installation diff --git a/hooks/rtk-awareness.md b/hooks/rtk-awareness.md index 0eaf3d5..b95c118 100644 --- a/hooks/rtk-awareness.md +++ b/hooks/rtk-awareness.md @@ -25,5 +25,22 @@ which rtk # Verify correct binary All other commands are automatically rewritten by the Claude Code hook. Example: `git status` → `rtk git status` (transparent, 0 tokens overhead) +Example: `grepai search "auth token refresh"` → `rtk rgai "auth token refresh"` + +## Search Priority Policy + +**Search priority (mandatory): rgai > rg > grep.** + +- Use `rtk rgai` first for semantic/intention-based discovery. +- Use `rtk grep` for exact/regex matching. +- `rtk grep` internally uses `rg -> grep` backend fallback. + +## Semantic Search + +```bash +rtk rgai "auth token refresh" # Intent-aware code search +rtk rgai auth token refresh --compact # Unquoted multi-word query +rtk rgai "auth token refresh" --json # Machine-readable output +``` Refer to CLAUDE.md for full command reference. diff --git a/hooks/rtk-rewrite.sh b/hooks/rtk-rewrite.sh index 6574b01..3ef6854 100644 --- a/hooks/rtk-rewrite.sh +++ b/hooks/rtk-rewrite.sh @@ -2,6 +2,7 @@ # RTK auto-rewrite hook for Claude Code PreToolUse:Bash # Transparently rewrites raw commands to their rtk equivalents. # Outputs JSON with updatedInput to modify the command before execution. +# Source of truth: hooks/rtk-rewrite.sh (keep .claude/hooks copy in sync) # Guards: skip silently if dependencies missing if ! command -v rtk &>/dev/null || ! command -v jq &>/dev/null; then @@ -88,10 +89,20 @@ elif echo "$MATCH_CMD" | grep -qE '^cargo[[:space:]]+fmt([[:space:]]|$)'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^cargo fmt/rtk cargo fmt/')" # --- File operations --- +# Search priority (mandatory): rgai > rg > grep +# Tier 1: semantic intent search (grepai/rgai) -> rtk rgai +# Tier 2: exact search via ripgrep -> rtk grep (rtk grep runs rg -> grep fallback internally) +# Tier 3: exact search via grep -> rtk grep elif echo "$MATCH_CMD" | grep -qE '^cat[[:space:]]+'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^cat /rtk read /')" -elif echo "$MATCH_CMD" | grep -qE '^(rg|grep)[[:space:]]+'; then - REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^(rg|grep) /rtk grep /')" +elif echo "$MATCH_CMD" | grep -qE '^(grepai|rgai)[[:space:]]+search([[:space:]]|$)'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^(grepai|rgai)[[:space:]]+search[[:space:]]+/rtk rgai /')" +elif echo "$MATCH_CMD" | grep -qE '^rgai[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^rgai[[:space:]]+/rtk rgai /')" +elif echo "$MATCH_CMD" | grep -qE '^rg[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^rg /rtk grep /')" +elif echo "$MATCH_CMD" | grep -qE '^grep[[:space:]]+'; then + REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed -E 's/^grep /rtk grep /')" elif echo "$MATCH_CMD" | grep -qE '^ls([[:space:]]|$)'; then REWRITTEN="${ENV_PREFIX}$(echo "$CMD_BODY" | sed 's/^ls/rtk ls/')" elif echo "$MATCH_CMD" | grep -qE '^tree([[:space:]]|$)'; then diff --git a/hooks/test-rtk-rewrite.sh b/hooks/test-rtk-rewrite.sh index 2a68ff8..61b5b86 100755 --- a/hooks/test-rtk-rewrite.sh +++ b/hooks/test-rtk-rewrite.sh @@ -2,9 +2,11 @@ # Test suite for rtk-rewrite.sh # Feeds mock JSON through the hook and verifies the rewritten commands. # -# Usage: bash ~/.claude/hooks/test-rtk-rewrite.sh +# Usage: bash hooks/test-rtk-rewrite.sh +# Override hook path: HOOK=/path/to/rtk-rewrite.sh bash hooks/test-rtk-rewrite.sh -HOOK="$HOME/.claude/hooks/rtk-rewrite.sh" +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HOOK="${HOOK:-$SCRIPT_DIR/rtk-rewrite.sh}" PASS=0 FAIL=0 TOTAL=0 @@ -109,6 +111,22 @@ test_rewrite "rg pattern src/" \ "rg pattern src/" \ "rtk grep pattern src/" +test_rewrite "grepai search query" \ + "grepai search auth middleware" \ + "rtk rgai auth middleware" + +test_rewrite "grepai search with flags" \ + "grepai search \"error handler\" --json --compact" \ + "rtk rgai \"error handler\" --json --compact" + +test_rewrite "rgai search query (priority over rg/grep)" \ + "rgai search auth middleware --compact" \ + "rtk rgai auth middleware --compact" + +test_rewrite "plain rgai" \ + "rgai auth middleware --json" \ + "rtk rgai auth middleware --json" + test_rewrite "cargo test" \ "cargo test" \ "rtk cargo test" @@ -149,6 +167,18 @@ test_rewrite "env + docker compose" \ "COMPOSE_PROJECT_NAME=test docker compose up -d" \ "COMPOSE_PROJECT_NAME=test rtk docker compose up -d" +test_rewrite "env + grepai search" \ + "NODE_ENV=test grepai search token refresh --json" \ + "NODE_ENV=test rtk rgai token refresh --json" + +test_rewrite "env + rg exact search" \ + "RG_IGNORE_DOT=1 rg token src/" \ + "RG_IGNORE_DOT=1 rtk grep token src/" + +test_rewrite "env + grep exact search" \ + "LC_ALL=C grep -rn token src/" \ + "LC_ALL=C rtk grep -rn token src/" + echo "" # ---- SECTION 3: New patterns ---- @@ -193,17 +223,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 with native args" \ "find . -name '*.ts'" \ - "" + "rtk find . -name '*.ts'" -test_rewrite "tree (NOT rewritten — different arg format)" \ +test_rewrite "tree with path arg" \ "tree src/" \ - "" + "rtk tree src/" -test_rewrite "wget (NOT rewritten — different arg format)" \ +test_rewrite "wget URL" \ "wget https://example.com/file" \ - "" + "rtk wget https://example.com/file" test_rewrite "gh api repos/owner/repo" \ "gh api repos/owner/repo" \ diff --git a/src/init.rs b/src/init.rs index 482f9f8..263f016 100644 --- a/src/init.rs +++ b/src/init.rs @@ -44,6 +44,14 @@ git add . && git commit -m "msg" && git push rtk git add . && rtk git commit -m "msg" && rtk git push ``` +## Search Priority Policy + +**Search priority (mandatory): rgai > rg > grep.** + +- Use `rtk rgai` first for semantic/intention-based discovery. +- Use `rtk grep` for exact/regex matching. +- `rtk grep` internally follows `rg -> grep` backend fallback automatically. + ## RTK Commands by Workflow ### Build & Compile (80-90% savings) @@ -106,7 +114,8 @@ rtk prisma # Prisma without ASCII art (88%) ```bash rtk ls # Tree format, compact (65%) rtk read # Code reading with filtering (60%) -rtk grep # Search grouped by file (75%) +rtk rgai # Semantic search ranked by relevance (85%) +rtk grep # Exact/regex search (internal rg -> grep fallback) rtk find # Find grouped by directory (70%) ``` @@ -155,7 +164,7 @@ rtk init --global # Add RTK to ~/.claude/CLAUDE.md | Git | status, log, diff, add, commit | 59-80% | | GitHub | gh pr, gh run, gh issue | 26-87% | | Package Managers | pnpm, npm, npx | 70-90% | -| Files | ls, read, grep, find | 60-75% | +| Files | ls, read, grep, rgai, find | 60-85% | | Infrastructure | docker, kubectl | 85% | | Network | curl, wget | 65-70% | @@ -767,15 +776,44 @@ fn run_claude_md_mode(global: bool, verbose: u8) -> Result<()> { if path.exists() { let existing = fs::read_to_string(&path)?; + let (new_content, action) = upsert_rtk_block(&existing, RTK_INSTRUCTIONS); - if existing.contains(""; + + if let Some(start) = content.find(start_marker) { + if let Some(relative_end) = content[start..].find(end_marker) { + let end = start + relative_end; + let end_pos = end + end_marker.len(); + let current_block = content[start..end_pos].trim(); + let desired_block = block.trim(); + + if current_block == desired_block { + return (content.to_string(), RtkBlockUpsert::Unchanged); + } + + let before = content[..start].trim_end(); + let after = content[end_pos..].trim_start(); + + let result = match (before.is_empty(), after.is_empty()) { + (true, true) => desired_block.to_string(), + (true, false) => format!("{desired_block}\n\n{after}"), + (false, true) => format!("{before}\n\n{desired_block}"), + (false, false) => format!("{before}\n\n{desired_block}\n\n{after}"), + }; + + return (result, RtkBlockUpsert::Updated); + } + + return (content.to_string(), RtkBlockUpsert::Malformed); + } + + let trimmed = content.trim(); + if trimmed.is_empty() { + (block.to_string(), RtkBlockUpsert::Added) + } else { + ( + format!("{trimmed}\n\n{}", block.trim()), + RtkBlockUpsert::Added, + ) + } +} + /// Patch CLAUDE.md: add @RTK.md, migrate if old block exists fn patch_claude_md(path: &Path, verbose: u8) -> Result { let mut content = if path.exists() { @@ -986,6 +1074,8 @@ pub fn show_config() -> Result<()> { } println!("\nUsage:"); + println!(" Search priority (mandatory): rgai > rg > grep."); + println!(" Use rtk rgai first; use rtk grep for exact/regex (internal rg -> grep fallback)."); println!(" rtk init # Full injection into local CLAUDE.md"); println!(" rtk init -g # Hook + RTK.md + @RTK.md + settings.json (recommended)"); println!(" rtk init -g --auto-patch # Same as above but no prompt"); @@ -1020,6 +1110,7 @@ mod tests { "rtk git", "rtk docker", "rtk kubectl", + "rtk rgai", ] { assert!( RTK_INSTRUCTIONS.contains(cmd), @@ -1099,10 +1190,82 @@ More content"#; // Just verify RTK_INSTRUCTIONS constant has the right content assert!(RTK_INSTRUCTIONS.contains("")); assert!(RTK_INSTRUCTIONS.len() > 4000); } + #[test] + fn test_search_priority_policy_in_init_and_slim_templates() { + let policy = "Search priority (mandatory): rgai > rg > grep."; + assert!( + RTK_INSTRUCTIONS.contains(policy), + "RTK_INSTRUCTIONS must include strict search priority policy" + ); + assert!( + RTK_SLIM.contains(policy), + "RTK_SLIM must include strict search priority policy" + ); + } + + #[test] + fn test_files_search_examples_prioritize_rgai_before_grep() { + let rgai_pos = RTK_INSTRUCTIONS + .find("rtk rgai ") + .expect("rtk rgai example missing"); + let grep_pos = RTK_INSTRUCTIONS + .find("rtk grep ") + .expect("rtk grep example missing"); + assert!( + rgai_pos < grep_pos, + "Files/Search examples must prioritize rtk rgai before rtk grep" + ); + } + + #[test] + fn test_upsert_rtk_block_appends_when_missing() { + let input = "# Team instructions"; + let (content, action) = upsert_rtk_block(input, RTK_INSTRUCTIONS); + assert_eq!(action, RtkBlockUpsert::Added); + assert!(content.contains("# Team instructions")); + assert!(content.contains(" +OLD RTK CONTENT + + +More notes +"#; + + let (content, action) = upsert_rtk_block(input, RTK_INSTRUCTIONS); + assert_eq!(action, RtkBlockUpsert::Updated); + assert!(!content.contains("OLD RTK CONTENT")); + assert!(content.contains("Search priority (mandatory): rgai > rg > grep.")); + assert!(content.contains("# Team instructions")); + assert!(content.contains("More notes")); + } + + #[test] + fn test_upsert_rtk_block_noop_when_already_current() { + let input = format!("# Team instructions\n\n{}\n\nMore notes\n", RTK_INSTRUCTIONS); + let (content, action) = upsert_rtk_block(&input, RTK_INSTRUCTIONS); + assert_eq!(action, RtkBlockUpsert::Unchanged); + assert_eq!(content, input); + } + + #[test] + fn test_upsert_rtk_block_detects_malformed_block() { + let input = "\npartial"; + let (content, action) = upsert_rtk_block(input, RTK_INSTRUCTIONS); + assert_eq!(action, RtkBlockUpsert::Malformed); + assert_eq!(content, input); + } + #[test] fn test_init_is_idempotent() { let temp = TempDir::new().unwrap(); From 4b0a413562c775757d5bc09a6ff966b4e532508c Mon Sep 17 00:00:00 2001 From: heAdz0r Date: Sat, 14 Feb 2026 14:32:45 +0300 Subject: [PATCH 2/2] docs(readme): remove private benchmark source references --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 711b510..121ae47 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ With rtk: **~45,000 tokens** → **70% reduction** ### Migration Search Benchmark Snapshot (February 14, 2026) -Source: `easy_math/tasks/benchmark_migration_search_compare_20260214-000212/compare.tsv` +Source: internal migration benchmark artifacts (private repository) | Scenario | Agent | Est. tokens | Unique files | Files / 1K tokens | |---|---|---:|---:|---:| @@ -61,7 +61,7 @@ Source: `easy_math/tasks/benchmark_migration_search_compare_20260214-000212/comp | quality_norm | rg | 14065 | 103 | 7.32 | | quality_norm | rgai | 7349 | 103 | 14.02 | -Methodology and raw artifacts: `easy_math/tasks/benchmark_migration_search_*` +Methodology and raw artifacts are private and not publicly linked. ## Search Priority Policy