feat: binary hook engine with lexer, streaming, and rewrite_compound integration#536
Open
ahundt wants to merge 294 commits intortk-ai:developfrom
Open
feat: binary hook engine with lexer, streaming, and rewrite_compound integration#536ahundt wants to merge 294 commits intortk-ai:developfrom
ahundt wants to merge 294 commits intortk-ai:developfrom
Conversation
…s--master--components--rtk chore(master): release 0.5.0
1. CRITICAL: Fix 'latest' tag creation after releases - Move update-latest-tag job from release.yml to release-please.yml - release-please creates tags via API (no push event) → must run in same workflow - Job now conditional on release_created output 2. IMPORTANT: Add npx fallback for ccusage + improve message - Check binary in PATH first, fallback to 'npx ccusage' - Updated message: "npm i -g ccusage (or use npx ccusage)" - Consistent with other JS tooling (next_cmd, tsc_cmd, prettier_cmd) 3. PROCESS: Slow down version bumps with release-please config - Add release-please-config.json with bump-patch-for-minor-pre-major - In 0.x versions: feat: → patch bump instead of minor - Prevents rapid version inflation (0.3.1 → 0.5.0 in 21h) Fixes issues raised by Patrick after PR rtk-ai#21 merge. Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
fix: 3 issues (latest tag, ccusage fallback, versioning)
…s--master--components--rtk chore(master): release 0.5.1
Patrick reported 2 critical issues post-merge PR rtk-ai#23: 1. **release.yml never triggers**: release-please creates tags via GitHub API → no push event generated → build workflow never runs → v0.5.1 released without binaries 2. **README install URLs 404**: DEB/RPM URLs hardcode version 0.3.1 → /releases/latest/download/ serves different filenames → all releases > 0.3.1 break installation instructions Root cause analysis: - release-please creates GitHub Releases (triggers `release.published` event) - release.yml only listens to `on: push: tags` (doesn't fire for API-created tags) - Standard pattern: release-please + binary builds = `on: release.published` Fixes: 1. release.yml trigger: - Add `on: release: types: [published]` (standard release-please pattern) - Remove `on: push: tags: ['v*']` (dead code with release-please) - Update version extraction to handle `release` event - Split release job: upload assets (release event) vs create release (workflow_dispatch) 2. Version-agnostic package naming: - Create copies: rtk_0.5.0-1_amd64.deb → rtk_amd64.deb - Create copies: rtk-0.5.0-1.x86_64.rpm → rtk.x86_64.rpm - Update README URLs to use version-agnostic names - /releases/latest/download/ now serves stable filenames Impact: - release-please releases now auto-trigger binary builds - Installation URLs work for all future releases - No manual workflow_dispatch needed for new versions Manual action required: Patrick needs to re-run build for v0.5.1 via workflow_dispatch (or create v0.5.2 with a trivial fix commit) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
fix: release pipeline trigger and version-agnostic package URLs
…s--master--components--rtk chore(master): release 0.5.2
- Make compact_diff pub(crate) in git.rs for cross-module use - Extract filter_json_string() from json_cmd.rs for reuse - Add ok_confirmation() to utils.rs for write operation confirmations - Add detect_package_manager() and package_manager_exec() to utils.rs Co-Authored-By: Claude Opus 4.5 <[email protected]>
- cargo build: strip Compiling/Downloading lines, show errors + summary - cargo test: show failures only + summary line - cargo clippy: group warnings by lint rule with locations New module: src/cargo_cmd.rs with 6 unit tests Co-Authored-By: Claude Opus 4.5 <[email protected]>
- git branch: compact listing (current/local/remote-only) - git fetch: "ok fetched (N new refs)" confirmation - git stash: list/show/pop/apply/drop with compact output - git worktree: compact listing with home dir abbreviation 4 new tests in git.rs Co-Authored-By: Claude Opus 4.5 <[email protected]>
- gh pr create: capture URL + number, "ok created #N url"
- gh pr merge: "ok merged #N" confirmation
- gh pr diff: reuse compact_diff() for condensed output
- gh pr comment/edit: generic "ok {action} #N" confirmations
- gh api: auto-detect JSON, pipe through filter_json_string()
5 new tests in gh_cmd.rs
Co-Authored-By: Claude Opus 4.5 <[email protected]>
- npm run: filter boilerplate (> script, npm WARN, progress) - npx: intelligent routing to specialized filters (tsc, eslint, prisma, next, prettier, playwright) - pnpm build: delegates to next_cmd filter - pnpm typecheck: delegates to tsc_cmd filter - --skip-env global flag: propagates SKIP_ENV_VALIDATION=1 New module: src/npm_cmd.rs with 2 unit tests Co-Authored-By: Claude Opus 4.5 <[email protected]>
- Execute curl -s, auto-detect JSON responses - JSON output piped through filter_json_string() for schema view - Non-JSON output truncated to 30 lines with byte count New module: src/curl_cmd.rs with 4 unit tests Co-Authored-By: Claude Opus 4.5 <[email protected]>
feat: shared infrastructure for new commands
- Fix test_analyze_logs: use normalizable paths instead of distinct IPs - Fix test_filter_outdated: match real pnpm outdated output format - Fix test_filter_ansi_colors: add missing Tests line for stats parsing - Add .claude/skills/rtk-tdd/ with Red-Green-Refactor workflow - Add testing-patterns.md reference with untested modules backlog - Update CLAUDE.md Testing Strategy with TDD mandate and pre-commit gate - Add 17 tests to diff_cmd.rs (similarity, truncate, compute_diff, condense) - Test suite: 102/105 → 122/122 Co-Authored-By: Claude Opus 4.5 <[email protected]>
…s--master--components--rtk
- New `rtk discover` command scans Claude Code JSONL session history to identify missed RTK savings and suggest unhandled commands for issues. Modular architecture: registry (RegexSet classification), provider (SessionProvider trait + ClaudeProvider), report (text/json formatting). - New PreToolUse auto-rewrite hook transparently intercepts Bash commands and rewrites them to rtk equivalents before execution, ensuring 100% adoption across all conversations including subagents. - New `rtk git show` command with compact output: one-line commit summary + stat + compacted diff (reuses compact_diff from git diff). Co-Authored-By: Claude Opus 4.5 <[email protected]>
rtk git push and rtk git pull now accept trailing arguments and forward them to git. Fixes the auto-rewrite hook failing on commands like `git push -u origin feat/branch`. Co-Authored-By: Claude Opus 4.5 <[email protected]>
feat: discover command, auto-rewrite hook, git show
…s--master--components--rtk chore(master): release 0.7.0
# Conflicts: # src/git.rs # src/grep_cmd.rs
ahundt
added a commit
to ahundt/rtk
that referenced
this pull request
Mar 13, 2026
Address PR rtk-ai#536 review comments from KuSh: - get_summary() delegates to get_summary_filtered(None), so the match on project_scope was unnecessary — call get_summary_filtered() directly - Same simplification for get_recent_filtered() src/gain.rs: 2 match blocks replaced with direct .as_deref() calls
Author
|
@KuSh @pszymkowiak everything that is outstanding should be addressed including the comments above! There are also no conflicts with the base branch and it is ready to be merged w.r.t. git conflicts at the time of writing. |
Previously split_safe_suffix only stripped one suffix per call, so compound patterns like "cargo test 2>&1 | tail -50" would only strip "| tail -50" leaving "2>&1" in the core, causing needs_shell to force passthrough. Now split_safe_suffix loops, stripping suffixes right-to-left until no more match. Uses Vec::truncate for zero-allocation per iteration and Vec<String> with reverse+join to preserve left-to-right suffix order. Adds 6 TDD tests for compound suffix patterns: - 2>&1 | tail -50 (the rtk-ai#1 missed compound pattern) - > /dev/null 2>&1 - 2>&1 | tee /tmp/log - >> /tmp/log 2>&1 | tail -5 (triple compound) - | grep FAILED 2>&1 (unsafe pipe correctly NOT stripped) - > /dev/null & (redirect + background) All 67 analysis tests pass. All 1348 project tests pass.
Clap rejected grep flags like -A 5 before reaching trailing_var_arg. Add after_context, before_context, grep_context, ignore_case, and word_regexp as named Clap args. Convert to extra_args at dispatch site — no changes to grep_cmd.rs function signature. Includes 3 TDD tests verifying Clap accepts the new flags.
Both subcommands have dedicated handlers in git.rs with write-detection, compact output, and exit code propagation. Adding to hook_lookup routes them through RTK filters instead of raw passthrough. Includes 4 TDD tests verifying lookup and end-to-end routing.
Remove after_context, before_context, grep_context Clap args and their dispatch logic. These flags correctly reached rg but RTK's output parser at grep_cmd.rs:77-88 uses splitn(3, ':') which misparses rg context lines (dash-separated format) as filenames, producing garbled output. Keep working -i (ignore-case) and -w (word-regexp) flags which only affect matching, not output format. Discovered via post-implementation audit: rtk grep -A 2 "fn run_branch" src/git.rs showed context lines misidentified as separate files.
filter_branch_output() hardcoded "remotes/origin/" prefix check at
git.rs:1111, causing branches from other remotes (upstream, fork, etc.)
to be dumped verbatim into the local section. On multi-remote repos
this yielded only 2.7% savings instead of expected 40-70%.
Fix: match any "remotes/*/" prefix using strip_prefix + find('/').
Deduplicate across remotes using HashSet before filtering against
local branches.
Add TDD tests: multi-remote correctness (3 remotes, dedup, HEAD skip)
and multi-remote savings assertion (>=40% on 3x9 remote branches).
* fix: P1 exit codes, grep regex perf, SQLite concurrency Exit code propagation (same pattern as existing modules): - wget_cmd: run() and run_stdout() now exit on failure - container: docker_logs, kubectl_pods/services/logs now check status before parsing JSON (was showing "No pods found" on error) - pnpm_cmd: replace bail!() with eprint + process::exit in run_list and run_install Performance: - grep_cmd: compile context regex once before loop instead of per-line in clean_line() (was N compilations per grep call) Data integrity: - tracking: add PRAGMA journal_mode=WAL and busy_timeout=5000 to prevent SQLite corruption with concurrent Claude Code instances Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]> * fix: address review findings on P1 fixes - tracking: WAL pragma non-fatal (NFS/read-only compat) - wget: forward raw stderr on failure, track raw==raw (no fake savings) - container: remove stderr shadow in docker_logs, add empty-stderr guard on all 4 new exit code paths for consistency with prisma pattern Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]> --------- Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]>
… (rtk-ai#630) * fix: raise output caps for grep, git status, and parser fallback (rtk-ai#617, rtk-ai#618, rtk-ai#620) - grep: per-file match cap 10 → 25, global max 50 → 200 - git status: file list caps 5/5/3 → 15/15/10 - parser fallback: truncate 500 → 2000 chars across all modules These P0 bugs caused LLM retry loops when RTK returned less signal than the raw command, making RTK worse than not using it. Fixes rtk-ai#617, rtk-ai#618, rtk-ai#620 Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]> * fix: update README example and add truncation tests for modified/untracked - parser/README.md: update example from 500 → 2000 to match code - git.rs: add test_format_status_modified_truncation (cap 15) - git.rs: add test_format_status_untracked_truncation (cap 10) Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]> * refactor: extract output caps into [limits] config section Move hardcoded caps into config.toml so users can tune them: [limits] grep_max_results = 200 # global grep match limit grep_max_per_file = 25 # per-file match limit status_max_files = 15 # staged/modified file list cap status_max_untracked = 10 # untracked file list cap passthrough_max_chars = 2000 # parser fallback truncation All 8 modules now read from config::limits() instead of hardcoded values. Defaults unchanged from previous commit. Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]> --------- Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]>
…es (rtk-ai#662) * feat(.claude): add /rtk-triage skill — orchestrated PR+issue cross-analysis New skill that runs issue-triage + pr-triage in parallel then produces a cross-analysis layer that neither skill can do individually: - Double coverage detection: identifies when 2+ PRs target the same issue (via body scan + file overlap), recommends which to keep/close - Security gap detection: for security review issues, maps each finding to a PR (or flags it as uncovered) - P0/P1 bugs without PR: groups by pattern to suggest sprint batching - Our dirty PRs: identifies probable cause (conflict with sibling PR, needs rebase, missing linked issue) Output is saved automatically to claudedocs/RTK-YYYY-MM-DD.md. Usage: /rtk-triage (French, auto-save) /rtk-triage en (English output) Signed-off-by: Florian Bruniaux <[email protected]> Signed-off-by: Florian BRUNIAUX <[email protected]> * docs(architecture): update module count to 66 Sync ARCHITECTURE.md with current main.rs state. Previous count (60) was stale since several modules were added (dotnet_cmd, dotnet_format_report, dotnet_trx, npm_cmd, gt_cmd, etc.). Signed-off-by: Florian Bruniaux <[email protected]> Signed-off-by: Florian BRUNIAUX <[email protected]> --------- Signed-off-by: Florian Bruniaux <[email protected]> Signed-off-by: Florian BRUNIAUX <[email protected]>
…tk-ai#601) - git stash: pass unknown subcommands (save, branch, clear) through instead of silently falling back to git stash push - git branch: add --show-current, --set-upstream-to, --format, --sort to flag detection so they don't get overridden by -a injection - pip: replace bail!() with passthrough for unknown subcommands (freeze, download, wheel, etc.) Fixes rtk-ai#600 Signed-off-by: Patrick szymkowiak <[email protected]>
cargo fmt diffs in config.rs, git.rs, playwright_cmd.rs were failing the fmt CI check, which cascaded to block clippy/test/security on PRs rtk-ai#632, rtk-ai#635, rtk-ai#638. Also fixes all clippy warnings: dead code annotations, iterator simplifications, assert patterns, and unnecessary allocations. Signed-off-by: Patrick Szymkowiak <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]>
…#163) (rtk-ai#518) * fix: discover classifies absolute paths like /usr/bin/grep (rtk-ai#485) Normalize absolute binary paths before classification: /usr/bin/grep → grep, /bin/ls → ls, /usr/local/bin/git → git Adds strip_absolute_path() helper + 5 tests. Signed-off-by: Patrick szymkowiak <[email protected]> * fix: discover and rewrite support git global options -C, --no-pager, etc. (rtk-ai#163) Strip git global options (-C <path>, -c <key=val>, --git-dir, --work-tree, --no-pager, --no-optional-locks, --bare, --literal-pathspecs) before classification so git -C /tmp status is recognized as rtk git. Rewrite preserves global options: git -C /tmp status → rtk git -C /tmp status Adds GIT_GLOBAL_OPT lazy_static regex + strip_git_global_opts() helper + 6 tests. Signed-off-by: Patrick szymkowiak <[email protected]> --------- Signed-off-by: Patrick szymkowiak <[email protected]>
…-ai#519) When running `rtk cargo clippy -p my-crate -- -D warnings`, Clap with `trailing_var_arg = true` preserves the `--` in parsed args when flags precede it. `restore_double_dash()` then added a second `--`, producing `cargo clippy -p my-crate -- -- -D warnings`. This caused rustc to interpret `-D` as a filename instead of a lint flag. Fix: skip restoration when args already contain `--` (Clap preserved it). Fixes rtk-ai#496 Signed-off-by: Ousama Ben Younes <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
- PR template reminds contributors to target develop - CI workflow labels PRs targeting master with 'wrong-base' and posts a comment - Excludes develop→master PRs (maintainer releases) Signed-off-by: Patrick <[email protected]> Signed-off-by: Patrick szymkowiak <[email protected]>
Add Language::Data variant for data formats (JSON, YAML, TOML, XML, CSV, etc.) with empty comment patterns to prevent comment stripping. AggressiveFilter falls back to MinimalFilter for data files. Fixes rtk-ai#464 Signed-off-by: Ousama Ben Younes <[email protected]> Co-authored-by: Claude Opus 4.6 <[email protected]>
…tk-ai#439) (rtk-ai#563) rtk find outputs a grouped format incompatible with pipe consumers like xargs, grep, wc, sort. Skip rewrite when find/fd is followed by a pipe, preserving native one-per-line output. Signed-off-by: Patrick szymkowiak <[email protected]>
…gh (rtk-ai#427) (rtk-ai#564) When compact_diff truncates output, append a hint line so Claude knows how to get the full diff: [full diff: rtk git diff --no-compact] Also fix --no-compact flag being passed to git (causing usage error) and remove decorative emoji from compact_diff output. Signed-off-by: Patrick szymkowiak <[email protected]>
rtk-ai#632) 4 P1 bugs where git exit codes were swallowed: - git diff: failure silently printed empty stat output - git status (with args): failure was filtered instead of propagated - git commit: failure printed "FAILED" but returned Ok(()) breaking pre-commit hooks - git branch (list mode): failure was silently ignored All now follow the established pattern: eprint stderr, track raw==raw, process::exit(code). Signed-off-by: Patrick szymkowiak <[email protected]>
…tk-ai#635) * feat: add 5 new TOML built-in filters (ollama, nx, gradle, spring-boot, jira) New filters for commands not covered by Rust modules: - ollama: strip ANSI spinners, keep final text response (rtk-ai#624) - nx: strip Nx monorepo noise, keep build results (rtk-ai#444) - gradle/gradlew: strip UP-TO-DATE tasks, keep build summary (rtk-ai#147) - spring-boot: strip banner and verbose logs, keep startup/errors (rtk-ai#147) - jira: strip blanks, truncate wide columns (rtk-ai#524) All 5 filters pass inline tests via rtk verify (123/123). Updated builtin filter count: 47 -> 52. Signed-off-by: Patrick szymkowiak <[email protected]> * feat: add 5 more TOML filters (turbo, mise, just, task, yadm) New filters for task runners and git wrapper: - turbo: strip cache/Tasks/Duration noise, keep task output (rtk-ai#531) - mise: strip install/download progress, keep task results (rtk-ai#607) - just: strip blanks and recipe headers, keep output (rtk-ai#607) - task: strip task headers and up-to-date lines, keep results (rtk-ai#607) - yadm: strip hint lines, compact git-like output (rtk-ai#567) All verified with fake binaries through catch-all TOML engine. 137/137 TOML tests pass, 934 Rust tests pass. Updated builtin filter count: 52 -> 57. Signed-off-by: Patrick szymkowiak <[email protected]> --------- Signed-off-by: Patrick szymkowiak <[email protected]>
…rtk-ai#638) Git status output used emojis (📌, 📝, ❓, ✅,⚠️ ) that confuse non-Claude LLMs (GPT, etc.) causing retry loops. Replace with plain text labels (branch:, modified:, staged:, untracked:, conflicts:). Also add "clean — nothing to commit" when working tree is clean, so LLMs understand the repo state without ambiguity. Before: 📌 master After: branch: master clean — nothing to commit Fixes rtk-ai#603 Signed-off-by: Patrick szymkowiak <[email protected]>
…ooks When Claude Code updates a plugin, both old and new version directories remain on disk. patch_plugin_caches() previously processed ALL versions, creating manifest entries for each. This caused double-execution: the active version's hook ran via direct match AND via fallthrough from the old version's manifest entry. Fix: collect version directories, sort by semver (descending via parse_semver()), process only the highest (active) version. Remove manifest entries for non-active versions of the same plugin using retain() with plugin_prefix/active_prefix path matching. Add parse_semver() helper (major.minor.patch tuple comparison). Add TDD tests: test_parse_semver, test_version_dedup_removes_older_entries, test_catch_all_not_registered_as_fallthrough.
Cargo.toml: add [profile.dev.package."*"] with debug=0, opt-level=1 to reduce target/ size (8.2GB → 0.73GB measured) and speed up dep builds. CLAUDE.md: add Debug & Profiling Builds section documenting debugging/ profiling named profiles. Append RTK command reference (rtk-instructions v2) with full command list organized by workflow category.
Merge 34 upstream commits (develop) including: - src/trust.rs: trust boundary for project-local TOML filters - src/git.rs: git log --oneline regression fix (user_format 4th param) - src/git.rs: nothing-to-commit detection in commit handler - 6 critical bug fixes: exit codes, unwrap, lazy regex (rtk-ai#626) - src/discover/registry.rs: find/fd pipe-skip ported to token-based lexer - cargo fmt + 54 clippy warnings fix (rtk-ai#663) - subcommand routing fix (rtk-ai#600), output cap raise (rtk-ai#617-620) - SQLite WAL, grep regex perf (rtk-ai#631) Conflict resolution (6 files, 11 hunks): - src/main.rs: kept Pipe (ours) + Trust/Untrust (upstream) - src/git.rs: pub(crate) (ours) + user_format param (upstream) - src/grep_cmd.rs: filter_grep_raw (ours) + context_re (upstream) - src/init.rs: remove-then-insert upgrade path (ours) - src/tracking.rs: merged doc comments - src/discover/registry.rs: token-based lexer (ours) + find/fd skip (upstream) - src/tee.rs: removed duplicate derive(Default) - src/pipe_cmd.rs: added user_format=false 4th arg Tests: 1388 passed (was 1358), 0 failures, 6 ignored
Author
|
I updated again to match upstream, it is ready to merge to the best of my knowledge! Additionally this pr fixes the multi-hook conflict bug that is now independently confirmed by #361 (comment) |
…s-v2-develop Merge 15 new upstream commits after force-push rebase including: - v0.31.0 release - Cline/Roo Code, Windsurf, Cursor Agent, Copilot, Codex CLI support - Gemini CLI support via rtk init --gemini - OpenClaw plugin for transparent exec rewriting - Emoji removal from all CLI output (rtk-ai#687) Conflict resolution (9 files, 29 hunks): - src/git.rs: accepted upstream plain-text labels, updated 8 test assertions - src/main.rs: merged Hook enum (Claude+Gemini+Copilot), kept Pipe + Init fields - src/init.rs: kept remove-then-insert + Result<()>, added HookType to new tests - src/discover/registry.rs: kept token-based lexer - src/grep_cmd.rs: kept filter_grep_raw/find_output, emoji removal - src/gh_cmd.rs, log_cmd.rs, wget_cmd.rs: accepted upstream changes - src/tracking.rs: kept doc comments - src/cargo_cmd.rs, pytest_cmd.rs: fixed stale emoji assertions in stream tests Caller/callee verification: 10/10 critical functions verified correct Tests: 1417 passed (was 1388), 0 failures, 6 ignored
ahundt
added a commit
to ahundt/rtk
that referenced
this pull request
Mar 19, 2026
Quantitative benchmark comparing PR rtk-ai#536 capabilities vs upstream develop. Measures token savings across 10 operations using live command output: - 5 shared e2e commands (both branches: 72-91% savings) - 5 pipe mode commands (PR rtk-ai#536 only: 78-97% savings, develop: 0%) - Token-based lexer correctness (background & operator: 2/2) Combined: develop 44.2% vs PR rtk-ai#536 93.0% (+48.7pp improvement) Run: cargo test --test pipe_and_hook_savings_benchmark -- --nocapture --include-ignored
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR #536 integrates the hooks-v2 engine (from PR #156) with the
developbranch (note that #537 is the same code as 536 and 156 but it is stripped of tests for easier reviewing), and #361 describes the bug fixes in this pr, this pr #536 demonstrates that:rewrite_compound()'s inline byte scanner — all 155 existing rewrite tests pass unchangedrtk hook claude) works alongside the shell hook path (rtk rewrite)stream.rs) enables bidirectional process shims with exit code transparencyKey components
src/cmd/lexer.rs— Shell-aware tokenizer (56 tests): handles quoting, escapes,$(), backticks, globs, redirects,2>&1src/cmd/analysis.rs— Command chain parser (61+ tests):needs_shell()detection,parse_chain()for&&/||/;chainssrc/cmd/hook/mod.rs— Binary hook engine (98 tests): conservativehook_lookup()whitelist, Claude Code JSON protocol, safe suffix handlingsrc/cmd/exec.rs— Native command executor: runs simple chains natively, delegates complex shell to/bin/shsrc/stream.rs— Streaming process execution (39 tests):StreamFiltertrait,StdinMode::Inheritfor pipe support, SIGPIPE handling, 1 MiB raw capsrc/pipe_cmd.rs—rtk pipe --filterdispatch for grep/rg/find/fd output filteringsrc/discover/registry.rs—rewrite_compound()replaced: lexer-based splitting instead of 130-line inline byte scannerLexer integration in
rewrite_compound()The byte-level quote tracker in
rewrite_compound()(~130 lines) is replaced withlexer::tokenize()calls (~80 lines). The lexer adds:\\)$()and backtick detection (Shellism tokens)*,?→ Shellism)&&operator (2>&1,&>/dev/null)Token byte offsets (
ParsedToken.offset) enable extracting original substrings from the command string, sorewrite_segment()receives exact original text — no reconstruction fidelity issues.Binary hook architecture
Two complementary routing paths:
rtk rewrite→registry::rewrite_command()→rewrite_compound()→rewrite_segment()(TOML filters + RULES table)rtk hook claude→check_for_hook()→lexer::tokenize()→analysis::parse_chain()→hook_lookup()→route_native_command()(conservative whitelist)The binary hook uses a strict whitelist (
hook_lookup()) that only routes subcommands RTK optimizes well (e.g.,git statusbut notgit rebase,cargo testbut notcargo publish).Init system (
src/init.rs)--hook-type binary|scriptflag to select hook modeBashfrom plugin matchers, writes manifestNoOpinionandAllowpathsbackup_file_once()+atomic_write()for safe file operations--install-opencodeflag preserved)Test plan
cargo test— 1309 passed, 0 failed, 6 ignoredcargo fmt --all— cleancargo clippy --all-targets— cleanrtk rewrite "cargo test && git status"producesrtk cargo test && rtk git statusrtk rewrite "cargo test | grep FAIL"producesrtk cargo test | grep FAILrtk rewrite "cargo test 2>&1"producesrtk cargo test 2>&1Stats
46 files changed, ~9500 insertions, ~450 deletions
Fixes #222