Repository navigation
Conversation
|
Thank you so much for your important fix! ❤️ In case you want to update, |
|
Hey @ahundt, thanks for this contribution. I'm currently working on the rewrite, since models now often use multi-line statements: #2781 If you're available to align with my PR, I can merge mine and you adjust this one afterwards; otherwise I can try to retrieve your fixes into mine. Your call If you want to align:
Before merge
|
7efa13e to
ffd7b3b
Compare
ffd7b3b to
1f74d09
Compare
Before this change, rewrite_command re-tokenized each normalized command in has_heredoc, contains_shell_block, contains_compound_boundary, and rewrite_compound. Long multiline Bash input therefore paid repeated O(n) scans and repeated token-vector allocation, while shell-block detection used independent conditional branches. Tokenize the normalized command once in rewrite_command and pass the shared ParsedToken slice through heredoc, shell-block, compound-boundary, pipeline, and compound-rewrite analysis. Replace shell-block branching with an explicit match-based state transition that recognizes comments, escaped newlines, case alternatives, grouping, substitutions, and control keywords while preserving fail-open behavior for heredocs and control blocks. Preserve indentation and exact LF/CRLF separators when rewriting safe top-level command lists. Files: src/discover/lexer.rs (token analysis, comment/newline handling, case-pattern handling, and shell-block state transitions); src/discover/registry.rs (one-pass rewrite analysis and multiline command-list rendering). Verification: cargo test -q discover::lexer::tests (109 passed); cargo test -q discover::registry::tests (344 passed); HOME=/private/tmp/rtk-v4-home RTK_TEE=1 cargo test -q (2531 passed, 8 ignored); cargo check -q; cargo clippy --all-targets --all-features -- -D warnings; cargo fmt --all -- --check; git diff --check. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Before this change, rewrite_compound sent the final pipeline stage to rewrite_segment_inner. A command such as cargo test | grep FAILED could therefore rewrite the content-sensitive consumer and change the producer output seen by grep. The registry also had a second trailing-redirect parser, leaving file-target permission ownership split across helpers. Route only the producer when pipe_tail_accepts_filtered_output confirms that every remaining stage is an stdin-only head, tail, or cat invocation. Keep grep, rg, tee, wrapped consumers, stderr pipelines, and malformed stages raw. The decision walks the ParsedToken slice already created by rewrite_command, preserving one-pass command analysis for the pipeline path. Move redirect suffix ownership into src/discover/suffix.rs, preserve the original suffix bytes, leave file targets in the command core, and expose RewriteResult so rewrite_cmd and the native Claude, Copilot, and Droid hooks make file redirects ask-only while command substitutions remain pass-through. Remove the obsolete pipeline_final_safe rule metadata and its final-stage tests. Files: src/discover/lexer.rs (construct classification and fd-redirect ownership); src/discover/registry.rs (producer routing, shared policy result, and regression tests); src/discover/suffix.rs and src/discover/mod.rs (lexer-backed suffix handling); src/discover/rules.rs and src/discover/README.md (remove stale final-stage contract); src/hooks/rewrite_cmd.rs and src/hooks/hook_cmd.rs (protocol decisions and payload tests). Verification: discover::lexer::tests 109 passed; discover::registry::tests 343 passed; hooks::rewrite_cmd::tests 15 passed; hooks::hook_cmd::tests 98 passed; full cargo test 2533 passed, 8 ignored; cargo check -q; cargo clippy --all-targets --all-features -- -D warnings; cargo fmt --all -- --check; git diff --check. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Before this change, removing the hook-level substitution defer allowed a multiline command containing a substitution to reach the normal rewrite path. A simple command such as `git log $(git rev-parse HEAD~1)` could be rewritten, and an allowed multiline bundle could be auto-allowed even though its complete command was not attestable. Guard `rewrite_segment_inner` in `src/discover/registry.rs` so substitution-bearing segments remain verbatim while safe top-level statements in the same newline bundle can still be rewritten. Gate `decide_from_verdict` in `src/hooks/hook_cmd.rs` with the same whole-command attestability check used by `evaluate_with_verdict` in `src/hooks/rewrite_cmd.rs`, preserving ask-only handling for mixed bundles and file-target redirects. Refs rtk-ai#1155; aligns the multiline permission contract reviewed in PR rtk-ai#2781 without importing its unrelated branch history. Verification: `cargo test -q` (2535 passed, 8 ignored); `cargo test -q discover::registry::tests::test_rewrite` (192 passed); `cargo test -q hooks::hook_cmd::tests` (99 passed); `cargo test -q hooks::rewrite_cmd::tests` (16 passed); `cargo check -q`; `cargo fmt --all -- --check`; `git diff --check`. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Replace the removed struct-variant patterns in the multiline substitution and file redirect assertions in src/hooks/hook_cmd.rs. The tests now follow upstream/develop's tuple HookDecision::AskRewrite(String) while retaining exact rewritten-command checks.\n\nVerification: env HOME=/private/tmp/rtk-lexer-test-home-20260802 RUSTUP_HOME=/Users/athundt/.rustup CARGO_HOME=/Users/athundt/.cargo RUSTC_WRAPPER= cargo test -q -- --test-threads=1 (2541 passed, 8 ignored).
rewrite_multiline_block still assumed one-byte newline separators after the upstream multiline rewrite changes, and it passed continued shell lines to the single-line router with embedded newlines. Record each ParsedToken separator length so CRLF blocks preserve their bytes, join continued &&/||/| units with shell-equivalent spaces, and fail closed when a continued logical unit cannot be routed safely.\n\nThe stronger lexer treats apostrophes inside comments as inert while retaining the original comment text. Raw grep pipelines remain unmodified, while stdin-only head/tail display tails remain eligible for producer rewriting.\n\nFiles: src/discover/registry.rs.\nVerification: discover::registry::tests 372 passed; discover::lexer::tests 109 passed; hooks::rewrite_cmd::tests 16 passed; hooks::hook_cmd::tests 103 passed; cargo check -q; cargo fmt --all -- --check; git diff --check.\n\nSigned-off-by: Andrew Hundt <ATHundt@gmail.com>
1f74d09 to
968cf99
Compare
|
Addressed the multiline-rewrite feedback in the current head (968cf99). The branch now uses |
Why: PR rtk-ai#2555 must be current with develop without losing consumer-aware pipelines, multiline statement routing, or Ask-only redirects. What: Merge current upstream/develop and resolve hook conflicts by retaining the branch RewriteResult policy while adding upstream Copilot self-healing and Vibe support. Keep host-independent rewrite tests isolated from local permission settings. Verification: cargo fmt --all; cargo check --all-targets; 18 rewrite-command tests; 109 hook-command tests; git diff --check. Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Why: The combined integration proof must include the latest PR rtk-ai#2555 behavior on top of current upstream/develop without replacing either history. What: Merge v4/lexer-command-analysis-bash-blocks, preserving multiline Bash parsing, consumer-aware raw routing, suffix analysis, Ask-only safe rewrites, and upstream Copilot and Vibe behavior. Verification: - git diff --cached --check - inspected staged 8-file capability scope before commit Signed-off-by: Andrew Hundt <ATHundt@gmail.com>
Round 2 of 3 — DUPLICATE, recommend closingClaim: Lex shell structure before rewriting so pipeline/redirect content consumers receive the producer's original output (fixes #1560). Scope: duplicate — superseded by #3128 (with #3343 Ran: both binaries built from CI @9e8d1285: 10 green, Why develop already covers thisDevelop fixes #1560 by a different and strictly better mechanism: it leaves the producer raw and rewrites the consumer, instead of leaving the whole pipeline raw. Verified the survivor is genuinely correct, not just smaller: Every consumer this PR names is already handled on develop — The residual delta is a regressionBecause this PR drops the whole statement to raw where develop rewrites the consumer, it loses savings that develop keeps:
Against the 20% floor (CONTRIBUTING.md), these go from 99% to 0% on a very common shape. The one novel piece is unsafeThe file-redirect rewrite is the only behavior here develop lacks, and it reintroduces the content-sink hazard this PR exists to close: The user's file then holds RTK's compact rendering (57 B) instead of the raw output (123 B) — a content consumer being fed display output, the same class as #1560, just a file sink instead of a pipe. Relatedly, the PR body states "Input redirects … remain conservative", but Your questions
NextRecommend closing as superseded by #3128. The analysis in this PR was right about the bug and about the consumer/display-tail distinction — develop reached the same conclusions and kept the savings. Nothing in the remaining diff is worth rebasing through conflicts in all six files. Rounds: 2/3. Threads: 0 open, 0 resolved. |
Preserve pipeline input while rewriting safe shell commands
Summary
RTK now lexes shell command structure before rewriting it. Filters and other content consumers receive the producer's original output, while display-only tails can still use RTK's compact output. Redirections and shell-block boundaries remain attached to the command that owns them.
This fixes issue #1560, where rewriting the left side of a pipe changed the input seen by downstream filters. The lexer consolidates the boundary and suffix behavior developed in PR #536 and supports long, multi-line shell blocks without treating their internal operators as independent top-level commands.
Before and after
git log | grep featgrepread it.grepreceives the original Git stream.cargo test | tee /tmp/test.logteesaves the original output.git log | head -20headonly consumes a bounded display prefix.git status > /tmp/status.logrtk git status > /tmp/status.log.git status $(whoami)Implementation
src/discover/lexer.rstokenizes quotes, escapes, comments, CRLF/newline boundaries, pipes, redirects, case alternatives, and shell blocks using typed tokens and byte offsets.src/discover/registry.rsapplies consumer-aware producer routing.src/discover/suffix.rspreserves the exact trailing output suffix and assigns an ask-only policy to file-target redirects.The policy path is compatible with the multiline rewrite work in PR #2781: each statement is analyzed independently, and auto-allow requires both an attestable command and a rewrite that does not require confirmation. This also preserves the least-privilege behavior tracked in issue #1155.
Content consumers including
grep,rg,tee,xargs,wc,awk, andsedremain raw. Safe display tails are limited to supported stdin-only numericheadandtailforms, plaincat, and equivalent absolute paths. Input redirects, substitutions, heredocs, and opaque grouped commands remain conservative.Compatibility and scope
The existing string rewrite API remains available. Hook adapters use the policy-returning path when a file redirect requires confirmation. This change adds no parser dependency and does not execute native commands through the lexer.
Verification
cargo clippy --all-targets --all-features -- -D warnings,cargo fmt --all -- --check, andgit diff --checkpass.