Skip to content

Next Release#1747

Merged
aeppling merged 47 commits into
masterfrom
develop
May 13, 2026
Merged

Next Release#1747
aeppling merged 47 commits into
masterfrom
develop

Conversation

@aeppling
Copy link
Copy Markdown
Contributor

@aeppling aeppling commented May 6, 2026

Features

Fix

Others

hed0rah and others added 30 commits April 19, 2026 01:07
Rebased onto current develop (binary-command era). Threads dry_run
through patch_settings_json_command, migrate_old_hook_script,
install_cursor_hooks, run_kilocode_mode, run_antigravity_mode,
run_gemini, run_copilot, and uninstall.

Fixes from PR #1032 review:
- --uninstall --dry-run no longer deletes files (uninstall() now takes
  dry_run, every fs::remove_file / fs::write / atomic_write guarded)
- Success messages ("installed", "configured", "Restart ...") gated on
  !dry_run in run_default_mode, run_hook_only_mode, run_codex_mode,
  run_copilot, install_cursor_hooks, run_gemini
- prompt_telemetry_consent() skipped in dry-run
- integrity::store_hash() in run_gemini guarded
- KiloCode and Antigravity modes now accept dry_run
- PatchResult::WouldPatch variant added for patch_settings_json_command
- [dry-run] Nothing written. footer printed by every sub-mode
- write_if_changed uses atomic_write (not fs::write)

Added integrity::hash_path_for() public wrapper so dry-run can check
sidecar existence without the destructive remove_hash.

Tests: write_if_changed(dry_run=true) creates nothing;
run_codex_mode_with_paths(dry_run=true) creates neither RTK.md nor
AGENTS.md. 1596 tests pass, clippy clean.
…removals

The two remove_file calls flagged by semgrep already existed pre-PR.
Wrapping them in if dry_run { print } else { remove_file } shifted
their context enough that --baseline-commit re-attributed them as new.
The rule's own message says deletion is expected in hooks/init cleanup.
Addresses three review items from PR #1032:

- Bundle verbose+dry_run into a Clone+Copy InitContext struct (mirrors
  RunOptions in src/core/runner.rs). Collapses 25+ function signatures
  that already carried both fields and makes future flags one struct
  field instead of N signature changes.
- Emit "[dry-run] Nothing written." exactly once from the top-level
  run() and uninstall() exit points instead of from every sub-mode.
  Fixes the double footer when --agent cursor combined with default
  mode.
- Reject --show with --dry-run via clap conflicts_with rather than
  silently ignoring --dry-run.
- Add regression tests for run_default_mode and uninstall dry-run paths
  using the existing with_claude_dir_override scaffolding.
Adds a "Preview without writing" subsection under Step 1 covering the
--dry-run flag, -v interaction for content preview, that telemetry
consent is skipped, and the --show conflict. Required by CONTRIBUTING.md
section 4 (new features need documentation).
Adds rtk gradlew command for build, test, lint, and dependency
operations on Gradle projects. Filters task progress noise, preserves
build scan URLs, test failures, lint violations, and compiler warnings.

Recognises ./gradlew, gradlew, gradlew.bat, and gradle invocations.
Surfaces unit-test report paths and shows a progress indicator for
long-running tasks.

Targets 75% savings (90% on test, 80% on build).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract a new_gradle_command() helper that uses string literals in
every Command::new() branch. The .semgrep.yml dynamic-command-execution
rule rejects Command::new(var) — semgrep needs to statically audit
the executable set.

Same runtime behaviour: prefer ./gradlew (or gradlew.bat on Windows),
fall back to gradle.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces custom run_streaming / run_batch / run_passthrough / ProgressIndicator
with the shared runner helpers used by cargo and other commands:

- Build → runner::run_streamed with a BuildLineFilter implementing StreamFilter
- Test/Lint/Connected/Deps → runner::run_filtered with the existing filter_*
  closures (filter_test, filter_connected, filter_lint, filter_dependencies)
- Other / verbose flags → runner::run_passthrough(tool, args, verbose)

Benefits inherited from runner.rs: ChildGuard zombie prevention, 10 MiB output
cap, broken pipe handling, proper Result<i32> exit-code propagation.

run() now returns Result<i32> like cargo_cmd / golangci_cmd. main.rs updated
to forward the exit code with `?` directly (no manual `0` wrapper).

All Command::new() invocations remain string literals (semgrep
dynamic-command-execution rule).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
new_gradle_command() now uses resolved_command("gradle") for the two
fallback branches (Windows + Unix when no local wrapper is present),
matching how cargo / golangci-lint / etc. resolve system binaries.

Local wrappers (./gradlew, gradlew.bat) stay as string literals — they
are relative paths, not on PATH, and semgrep's dynamic-command-execution
rule needs literals here.

Net effect: PATHEXT-aware resolution on Windows (.CMD/.BAT shims work),
no behavioural change on Unix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
feat(gradlew): Gradle support for Android/Kotlin developers
The `ok` / `fail` verdict line was emitted at the top of the filtered
output, before errors and warnings. Consumers reading the tail of the
stream — `| tail -N`, IDE log followers, agent watch/monitor loops,
bounded-context-window readers — saw error noise followed by EOF with
no verdict anywhere near the end, silently breaking any agent loop that
gates the next step on build success.

Reorder `format_build_output`, `format_test_output`, and
`format_restore_output` to emit the body first, then the separator
(only when the body is non-empty), then the verdict header last,
matching native `dotnet` which ends with `Build succeeded.` or
`Build FAILED.`.

Add three regression tests using strict `output.lines().last()` plus a
`tail -5` inclusion assertion so future regressions that append a
trailing context line get caught.

Closes #1574

Signed-off-by: Artiom Tofan <arto@queue-it.com>
- Separate warnings and errors into distinct sections.
- Ensure status line is emitted last for clarity.
- Maintain consistency with native `dotnet` output behavior.
- Improve formatting of output summaries for build, test, and restore commands.
- Ensure status lines are emitted last for better stream consumption.
- Refactor warning formatting to improve consistency across output sections.
- Ensure warnings are displayed correctly in all relevant output formats.
The file lives at docs/contributing/ARCHITECTURE.md, not at the repo
root — both the nav header and Documentation section pointed to the
wrong location.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Command wrappers like shadowenv (https://github.com/Shopify/shadowenv),
`direnv exec .`, `nix develop --command`, `docker exec <container>`,
`poetry run`, and `bundle exec` sit in front of every command in their
respective project layouts. When rtk is driving a Claude Code / Gemini /
Copilot / opencode hook, these wrappers defeat the rewrite — `rtk
rewrite "shadowenv exec -- git status"` returns exit 1 (no rewrite)
because the registry can't see past the wrapper to the inner `git
status`. Every build tool, test runner, linter, and `git` invocation
in such a layout stays uncompressed.

rtk already solves exactly this shape for shell builtins via
SHELL_PREFIX_BUILTINS (`noglob`, `command`, `builtin`, `exec`,
`nocorrect`): strip the prefix, recurse on the inner command, re-prepend
the prefix to the rewrite. This commit extends that pattern with a new
[hooks].transparent_prefixes config field so users can register
multi-word wrappers that don't change what runs, only how.

  [hooks]
  transparent_prefixes = ["shadowenv exec --", "direnv exec ."]

With that config, `rtk rewrite "shadowenv exec -- cargo test"` becomes
`shadowenv exec -- rtk cargo test` and exits 3 (ask, matching the
existing ask semantics for inner commands).

Implementation:

- New `rewrite_command_with_prefixes(cmd, excluded, transparent_prefixes)`
  public entry. The original `rewrite_command(cmd, excluded)` is kept as
  a thin back-compat wrapper that passes an empty prefix slice, so the
  ~150 existing unit-test call sites don't need updating.
- `transparent_prefixes: &[String]` threaded through `rewrite_compound`,
  `rewrite_segment`, `rewrite_segment_inner`.
- In `rewrite_segment_inner`, a second strip loop runs after the
  SHELL_PREFIX_BUILTINS loop, with identical strip-recurse-reprepend
  semantics. Matching is whole-word via the existing `strip_word_prefix`
  helper (multi-word prefixes like `"shadowenv exec --"` are handled
  by-value — byte-level `starts_with` plus a space check).
- Recursion is bounded by the existing MAX_PREFIX_DEPTH so pathological
  or self-referential configs cannot stack-overflow.
- Production callers (`hooks/rewrite_cmd.rs`, `hooks/hook_cmd.rs`,
  `main.rs`) now load both `exclude_commands` and `transparent_prefixes`
  from config in the same `Config::load()` call — no new I/O.

Tests (14 new):

- config: 3 TOML roundtrip tests (present, missing, mixed with older
  fields).
- registry: 11 behavior tests — strip/reprepend, unknown inner returns
  None, unmatched passthrough, composed with shell builtin, multiple
  prefixes configured, whole-word matching, empty-rest None, blank-entry
  skip, compound `&&` with prefix on both segments, excluded-inner
  returns None, recursion bounded.

Design principles (from CONTRIBUTING.md):
- Extensibility: extends an existing, documented pattern.
- Zero Overhead: config load already happens on every hook invocation;
  this reads one additional vec. Empty `transparent_prefixes` is the
  default and adds one cheap loop-over-zero-elements on the hot path.
- Correctness vs Token Savings: without this, hook rewrites silently
  miss every command run through a wrapper — exactly the kind of
  correctness gap the principle warns against.
When -bench is present, benchmark output is already compact and useful.
Injecting -json causes the filter to discard benchmark results since they
don't produce pass/fail events the JSON parser expects.

Fixes #1609
fix: don't inject -json for go test -bench runs
inform user how to solve CI pr-target failing check
fix(cicd): pr-target clean msg + git app token
Three related issues prevented RTK from working with the Cursor
preToolUse hook on Windows. Each one alone produced the same
user-visible symptom (`Output: {}` and the original command running
unmodified), so they're fixed together:

1. Cursor preToolUse only enforces allow/deny — `permission: "ask"` is
   accepted by the schema but ignored at runtime, so `updated_input`
   rewrites could silently be dropped. Always return `allow` for
   rewritten commands (deny rules still take precedence and are
   evaluated before this point).

2. Cursor's preToolUse panel renders the JSON returned by the hook.
   Without a `continue: true` field the panel collapses to
   `Output: {}` even when the rewrite ran successfully, which makes
   the hook look broken from the user's perspective. Every other
   Cursor hook (`afterShellExecution`, `beforeSubmitPrompt`, `stop`,
   ...) returns `continue: true`; mirror that shape here so the panel
   surfaces the actual `permission` and `updated_input` payload.

3. Cursor on Windows prepends one or two UTF-8 BOMs (`EF BB BF`,
   sometimes doubled) to the JSON it pipes into the hook process.
   serde_json refuses to parse BOM-prefixed input, so `run_cursor`
   bailed out into the "no command" branch and returned `{}` for
   every invocation. Strip leading BOMs before parsing. This was the
   root cause of the long-standing "RTK silently no-ops in Cursor"
   reports — the rewrite path was never reached.

Tests cover the flat allow output, `continue: true` on both single
and compound (`cd ... && git status`) rewrites, the BOM-strip on
double-BOM payloads matching what Cursor actually sends, and assert
that the legacy hookSpecificOutput envelope is not emitted.

Verified end to end on Windows by tracing real Cursor stdin payloads:
the hook now returns `{"continue":true,"permission":"allow",
"updated_input":{"command":"rtk git status"}}` for `git status` and
the panel renders it instead of `Output: {}`.
aeppling and others added 15 commits May 10, 2026 00:17
feat(init): add --dry-run flag to preview changes without writing
fix: correct ARCHITECTURE.md path in README links
refactor(warn): replace #[cfg] with cfg! macro to fix unused variable warning
Update telemetry documentation link to use 'master' branch
chore(ci): makes clippy pass, warnings included, mandatory
…-permission-ask

fix(hooks): make Cursor preToolUse rewrites work and stay visible
Signed-off-by: Kayphoon <109347466+Kayphoon@users.noreply.github.com>
feat(hermes): add Hermes Agent support via rtk init --agent hermes
…ck-workflow

fix(security): replace insecure tmp, lock git perm, set sha for actions
@aeppling
Copy link
Copy Markdown
Contributor Author

Release Notes

Layer 1 — Install / Uninstall Lifecycle (pre-release artifact)

Test Status Notes
Download tarball PASS rtk-x86_64-unknown-linux-musl.tar.gz 4.2MB → 9.5MB extracted
Extract + version PASS rtk 0.34.3 confirmed
User install (no sudo) PASS ~/.local/bin/ install works
Basic smoke tests PASS --version, gain, git status, git log -5, ls, grep all work
rtk init --dry-run PASS Correctly previews without writing
rtk verify PASS 145/145 filter tests pass
rtk proxy PASS Raw passthrough confirmed, tracked at 0%
rtk gain / --history PASS Live metrics, proxy commands tracked
DEB package PARTIAL Metadata valid, glibc >= 2.39 requirement met, no sudo available for live install
Uninstall path NOTE rtk uninstall is not a top-level command, use rtk init -g --uninstall

Layer 2 — Core Command Filters

Test Status Notes
rtk git status PASS 80% savings
rtk git log -10 PASS 60% savings
rtk git diff HEAD -- file PASS -- separator re-injection confirmed
rtk git push -u PASS (critical fix) -u not consumed by --ultra-compact, unit test test_git_push_u_flag_passes_through passing
rtk git stash list PASS
rtk git log --graph PASS ASCII graph preserved
rtk cargo check PASS 60% savings
rtk cargo test --all PASS ~100% savings (138KB → 52B)
rtk cargo clippy PASS (full block fix) Full multi-line error blocks with context, ^~~~, notes
rtk gh pr list PASS 40% savings

Layer 3 — New Features & Recent Fixes

Test Status Notes
NEW gradlew support PASS Commands::Gradlew routed, 63 unit tests pass, graceful fail when gradle absent
FIX go test -bench no -json PASS skip_json on a.starts_with("-bench") at go_cmd.rs:49
NEW transparent_prefixes PASS Config field present, 17 unit tests, wired into all 3 hook paths
FIX json multibyte truncation PASS floor_char_boundary(77), no panic on Japanese/emoji strings
FIX json --keys-only PASS Renamed from --schema, new flag working, old flag gone
FIX ls device files PASS /dev/null, /dev/zero, block/char/pipe/socket, no crash
FIX curl JSON passthrough PASS IsTerminal gate + JSON heuristic working
FIX dotnet build/test/restore formatting PASS Warnings section + status line ordering, 108 unit tests
FIX git compact in-progress state PASS merge/cherry-pick/rebase states surfaced, 3 unit tests
NEW hermes integration PASS --agent hermes in rtk init, 40 unit tests
FIX Cursor preToolUse visibility PASS "decision" field now emitted in JSON response

Layer 4 — System Filters

Test Status Notes
rtk ls PASS 90% savings
rtk ls /dev PASS Device files, no crash
rtk tree PASS Graceful degradation with install hint when binary absent
rtk grep PASS 60% savings (single file), per-file cap by design on large searches
rtk find PASS 71% savings
rtk json PASS Compact, multibyte-safe
rtk wc PASS Multi-file totals
rtk env PASS Categorized, PATH truncated
rtk log PASS Errors/warnings surfaced, DEBUG collapsed
rtk read PASS --level minimal, multi-file, --max-lines
rtk deps PASS Detects Rust project, shows 21 deps

Layer 5 — Hook System, Analytics, Permissions

Test Status Notes
rtk gain / --history PASS 97.5% savings dashboard correct
rtk discover PASS 82 sessions scanned, top missed commands identified
rtk proxy tracking PASS Proxy commands appear at 0% in history
rtk init --dry-run PASS All agents listed: claude, cursor, kilocode, antigravity, hermes
rtk rewrite exit codes PASS 0=allow, 1=passthrough, 2=deny, 3=ask all correct
rtk hook check PASS
Permissions PASS 39 unit tests, deny/ask/allow/default verdicts
Hook system PASS 256 unit tests
pnpm --filter support PASS Argument support confirmed
pytest -q mode fix PASS Quiet flag detection + bare summary line
vitest / playwright / prisma PASS All unit tests pass
ruff / mypy PASS All unit tests pass

Release Decision

READY FOR MERGE — no blockers found.

@aeppling aeppling merged commit 90525ac into master May 13, 2026
31 checks passed
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.

rtk for 'go test -bench' fails - since it adds '-json' to it, but benchmarks produce a different output that is not filtered properly