Skip to content

feat: binary hook engine with lexer, streaming, and rewrite_compound integration - #536

Closed
ahundt wants to merge 294 commits into
rtk-ai:developfrom
ahundt:feat/rust-hooks-v2-develop
Closed

ahundt wants to merge 294 commits into
rtk-ai:developfrom
ahundt:feat/rust-hooks-v2-develop

Conversation

@ahundt

@ahundt ahundt commented Mar 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR #536 integrates the hooks-v2 engine (from PR #156) with the develop branch (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:

  1. The lexer is a drop-in replacement for rewrite_compound()'s inline byte scanner — all 155 existing rewrite tests pass unchanged
  2. The binary hook path (rtk hook claude) works alongside the shell hook path (rtk rewrite)
  3. The streaming infrastructure (stream.rs) enables bidirectional process shims with exit code transparency

Key components

  • src/cmd/lexer.rs — Shell-aware tokenizer (56 tests): handles quoting, escapes, $(), backticks, globs, redirects, 2>&1
  • src/cmd/analysis.rs — Command chain parser (61+ tests): needs_shell() detection, parse_chain() for &&/||/; chains
  • src/cmd/hook/mod.rs — Binary hook engine (98 tests): conservative hook_lookup() whitelist, Claude Code JSON protocol, safe suffix handling
  • src/cmd/exec.rs — Native command executor: runs simple chains natively, delegates complex shell to /bin/sh
  • src/stream.rs — Streaming process execution (39 tests): StreamFilter trait, StdinMode::Inherit for pipe support, SIGPIPE handling, 1 MiB raw cap
  • src/pipe_cmd.rs — rtk pipe --filter dispatch for grep/rg/find/fd output filtering
  • src/discover/registry.rs — rewrite_compound() replaced: lexer-based splitting instead of 130-line inline byte scanner

Lexer integration in rewrite_compound()

The byte-level quote tracker in rewrite_compound() (~130 lines) is replaced with lexer::tokenize() calls (~80 lines). The lexer adds:

  • Escape sequence handling (\\)
  • $() and backtick detection (Shellism tokens)
  • Glob detection (*, ? → Shellism)
  • Redirect parsing — not confused with && operator (2>&1, &>/dev/null)

Token byte offsets (ParsedToken.offset) enable extracting original substrings from the command string, so rewrite_segment() receives exact original text — no reconstruction fidelity issues.

Binary hook architecture

Two complementary routing paths:

  • Shell hook: rtk rewrite → registry::rewrite_command() → rewrite_compound() → rewrite_segment() (TOML filters + RULES table)
  • Binary hook: 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 status but not git rebase, cargo test but not cargo publish).

Init system (src/init.rs)

  • --hook-type binary|script flag to select hook mode
  • Plugin cache patching: removes Bash from plugin matchers, writes manifest
  • Manifest-based handler dispatch for both NoOpinion and Allow paths
  • backup_file_once() + atomic_write() for safe file operations
  • OpenCode plugin support (develop's --install-opencode flag preserved)

Test plan

  • cargo test — 1309 passed, 0 failed, 6 ignored
  • cargo fmt --all — clean
  • cargo clippy --all-targets — clean
  • All 155 registry rewrite tests pass (including develop's TOML rule tests)
  • All 56 lexer tests pass
  • All 98 hook tests pass
  • All 39 streaming tests pass
  • Manual: rtk rewrite "cargo test && git status" produces rtk cargo test && rtk git status
  • Manual: rtk rewrite "cargo test | grep FAIL" produces rtk cargo test | grep FAIL
  • Manual: rtk rewrite "cargo test 2>&1" produces rtk cargo test 2>&1

Stats

46 files changed, ~9500 insertions, ~450 deletions

Fixes #222

pszymkowiak and others added 30 commits January 30, 2026 14:24
…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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
- 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 <noreply@anthropic.com>
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 <noreply@anthropic.com>
feat: discover command, auto-rewrite hook, git show
…s--master--components--rtk

chore(master): release 0.7.0
pszymkowiak and others added 12 commits March 17, 2026 17:30
…#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 <patrick.szymkowiak@innovtech.eu>

* 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 <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
…-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 <benyounes.ousama@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- 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 <patrick@rtk-ai.com>
Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
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 <benyounes.ousama@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…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 <patrick.szymkowiak@innovtech.eu>
…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 <patrick.szymkowiak@innovtech.eu>
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 <patrick.szymkowiak@innovtech.eu>
…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 <patrick.szymkowiak@innovtech.eu>

* 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 <patrick.szymkowiak@innovtech.eu>

---------

Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
…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 <patrick.szymkowiak@innovtech.eu>
…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
@ahundt

ahundt commented Mar 18, 2026 •

Copy link
Copy Markdown
Contributor 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
@CLAassistant

CLAassistant commented Mar 20, 2026 •

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
3 out of 4 committers have signed the CLA.

✅ ousamabenyounes
✅ pszymkowiak
✅ FlorianBruniaux
❌ ahundt
You have signed the CLA already but the status is still pending? Let us recheck it.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you all sign our Contributor License Agreement before we can accept your contribution.
0 out of 4 committers have signed the CLA.

❌ ahundt
❌ pszymkowiak
❌ ousamabenyounes
❌ FlorianBruniaux
You have signed the CLA already but the status is still pending? Let us recheck it.

@ahundt

ahundt commented Mar 21, 2026

Copy link
Copy Markdown
Contributor Author

i signed the cla, but note all the other names listed are from contributions already merged in the repository

@KuSh

KuSh commented Mar 21, 2026

Copy link
Copy Markdown
Collaborator

i signed the cla, but note all the other names listed are from contributions already merged in the repository

Using rebase instead of merge should fix that

@aeppling

Copy link
Copy Markdown
Contributor

Hey

We are cleaning up the codebase and improving the project structure for better onboarding. As part of this effort, PR #826 reorganizes src/ from a flat layout into subfolders.

No logic changes — only file moves and import path updates.

What you need to do

Rebase your branch on develop when receiving this comment:

git fetch origin && git rebase origin/develop

Git detects renames automatically. If you get import conflicts, update the paths:

use crate::git;        // now: use crate::cmds::git::git;
use crate::tracking;   // now: use crate::core::tracking;
use crate::config;     // now: use crate::core::config;
use crate::init;       // now: use crate::hooks::init;
use crate::gain;       // now: use crate::analytics::gain;

Need help rebasing? Tag @aeppling

@aeppling

Copy link
Copy Markdown
Contributor

Hello @ahundt

First we're sorry about the delays, RTK has grown quicker than ever in the last 2 month, and being only 3 maintainers (with each having their jobs), it was hard for us to catch on everything.

We are currently organizing many aspect of this project, including codebase, contributions, and so on....

All this so RTK can keep growing and scale.

This include also a refacto of some part of the codebase.

This PR include some very good ideas, but that will need to be implemented as part of the refactoring.
If you're interested i'll mention you, if you want we can even discuss together in discord server about the routing refactoring, streaming etc..

Again, sorry for the time to answer you, but i prefer to rush any core features or update, but i did not forget this PR.

I'll be back to you soonly, once i've completed the first parts of my refactoring plan, so i can implement your ideas in the refactoring plan.

Thanks Andrew !

@aeppling

aeppling commented May 4, 2026

Copy link
Copy Markdown
Contributor

Hey, all ideas have been implement in the codebase during 0.36 and 0.37 releases , closing this.

If any request , please reach me on Discord

Thanks again Andrew !

@aeppling aeppling closed this May 4, 2026
@ahundt

ahundt commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

@aeppling unfortunately, while I very much appreciate what has been implemented, that is not correct, they were not implemented, and some of the core bugs remain to the best of my knowledge. This is the actual important branch with the fixes iirc.

@KuSh

KuSh commented May 4, 2026

Copy link
Copy Markdown
Collaborator

@ahundt would you be able to summarize what's still missing and or buggy in rtk 0.37+?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.