Skip to content

Next Release - #2774

Merged
aeppling merged 197 commits into
masterfrom
develop
Jul 26, 2026
Merged

Next Release#2774
aeppling merged 197 commits into
masterfrom
develop

Conversation

@github-actions

@github-actions github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Feats

  • feat(sbt): add SBT (Scala Build Tool) support #752
  • feat(hook): add support for Kimi AI agent #1478
  • feat(hooks): add Factory Droid integration #2267Closes #2099 (to verify)
  • feat: add git checkout support #2859
  • feat(hook): wire TOML filters + better tee tail hint #2748
  • feat(php): consolidated PHP tooling (php, artisan, phpunit, phpstan, pest, paratest, ecs, pint) #1649

Fix

  • fix(copilot): support IDE terminal hooks #3093Closes #2443 (to verify)
  • fix(permissions): stop extra whitespace from evading deny rules #3211
  • fix(hooks): emit permissionDecision allow for simple Copilot CLI rewrites #3149Closes #3037 (to verify)
  • fix(uv): preserve program stdout and restore inner-command filtering #3061
  • fix(rewrite): only rewrite safe final pipeline stages #3128
  • fix(benchmark): use deterministic curl and wget responses #3170
  • fix(sbt): filter testOnly/testQuick like sbt test #3140
  • fix(init): honor RTK_TELEMETRY_DISABLED in consent prompt (rtk init hangs in non-interactive environments (v0.36.0 telemetry consent gate) #1307) #2477Closes #1307 (to verify)
  • fix(copilot): add missing 'get' to kubectl example in init template #2472Closes #2471 (to verify)
  • fix: detect absolute rtk path in Claude hook settings #2885Closes #2884 (to verify)
  • fix(ccusage): accept period key from current ccusage #2732Closes #2731 (to verify)
  • fix(cargo): surface --message-format=json build errors instead of "compiled" #2422Closes #2419 (to verify)
  • fix(grep): preserve -- separator between non-adjacent match blocks #2836Closes #2795 (to verify)
  • fix(git): compact git stash show instead of forcing -p #2801
  • fix(analytics): truncate display strings on UTF-8 char boundaries #2789Closes #2787 (to verify)
  • fix(parser): use byte offsets instead of char indices in extract_json_object #2751Closes #2509 (to verify)
  • fix(cicd): next release pr target fork compatible #2771

Other

  • chore: declare RtkRule default to avoid repeating unecessary attributes #3181
  • docs: scope savings claims scope to bash output & clean savings docs #3156
  • doc: update instructions to reflect current project state #3103
  • tests: fix tests on non en env #3104
  • docs: add Takayuki Maeda as core contributor #3015
  • docs(install): pin cargo install to the master release branch #3110
  • chore(docs): clean contributing outdated #2911
  • chore(docs): add core contributor #2853
  • docs(windows): document native binary hook support #2791
  • chore(scripts): switch benches to mockhttp.org as httpbin.org is struggling at the moment #2775

williamkoller and others added 30 commits April 23, 2026 16:24
Adds `rtk sbt` command with three subcommands:

- `rtk sbt test`    — ScalaTest output filtering (90% token reduction):
                      compact single-line on pass, failure details on fail
- `rtk sbt compile` — Strips SBT noise, keeps errors (75% token reduction):
                      success summary with source count + time
- `rtk sbt run`     — Light filtering, strips SBT preamble, keeps program output
- `rtk sbt <other>` — Passthrough for unsupported subcommands

Implementation follows the established go_cmd.rs pattern with lazy_static
regex, tee output recovery on failure, and exit code propagation for CI/CD.

Includes 3 real-output fixtures (pass, fail, compile error) and 11 unit tests
with token savings assertions. Discovery rules updated with 80% savings estimate.

Signed-off-by: Ivan Severino <4858703+randomBrainstormer@users.noreply.github.com>
…dle Mockito/ScalaMock

- filter_sbt_test now uses a state machine to capture [info] detail lines
  that follow a *** FAILED *** marker — covers native ScalaTest assertion
  messages, Mockito Scala verification failures (WantedButNotInvoked,
  TooManyActualInvocations), and ScalaMock expectation failures
  (Unexpected call, Unsatisfied expectation)
- run_other detects integration test commands (it:test, IntegrationTest/test,
  integration-test/test, and any *:test / */test variant) and applies
  filter_sbt_test instead of raw passthrough
- sbt boilerplate cleaned from failure output: TestsFailedException,
  Total time, and compileIncremental noise removed; failed suite class
  names retained for navigation
- add fixtures: sbt_test_mockito_fail.txt, sbt_test_scalamock_fail.txt,
  sbt_it_test_pass.txt
- 18 tests (was 11), all passing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…quirement

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add MUNIT_SUMMARY_RE to recognize the munit/discipline-munit summary format:
  [info] Passed: Total N, Failed N, Errors N, Passed N
  [info] Failed: Total N, Failed N, Errors N, Passed N

munit is the default test framework in Scala 3 templates and is used by
major libraries (typelevel/cats via discipline-munit). Without this,
filter_sbt_test fell through to the error-recovery path for every munit
project, which also printed a misleading "sbt test: parse error" header.

Changes:
- Add MUNIT_SUMMARY_RE regex and parse branch in filter_sbt_test
- Change fallback label from "sbt test: parse error" to "sbt test: errors"
- Add munit pass/fail fixtures and tests covering both Scala 2 (ScalaTest)
  and Scala 3 (munit) output formats
…mpiled"

With --message-format=json, cargo emits diagnostics as NDJSON, so the block
handler that keys on human `error[..]` lines counted zero errors and
summarized a failing build as "cargo build (1 crates compiled)" — the exact
same wording as a successful build. The error was dropped from the inline
output and only the exit code (easily swallowed by an && / || chain) hinted
at failure.

Recover the NDJSON `compiler-message` diagnostics from the raw stream when
building the summary: count them and render them. A failed json build now
shows the real error[E..] plus "N errors", and the exit code is used as a
backstop so a non-zero build is never reported as compiled. Non-json output
is untouched (the helper returns nothing for it).

Closes #2419
Parse the --message-format=json stream once into errors and warnings
instead of scanning it twice per call site, and move the success and
failure summaries into two small helpers shared by the streaming and
batch paths. Also render json warnings, not just errors, so json mode
matches how the human path already surfaces warning blocks.
The COPILOT_INSTRUCTIONS template translated 'kubectl get pods' to
'rtk kubectl pods', dropping the 'get' subcommand. This caused Copilot
agents to run 'rtk kubectl pods' which fails since rtk expects
'rtk kubectl get pods'.

Fix: change 'rtk kubectl pods' to 'rtk kubectl get pods' in the
COPILOT_INSTRUCTIONS template string.

Add regression test to prevent this class of typo in the future.

Signed-off-by: K0IN <thisk0in@gmail.com>
The --message-format=json failure summary rendered every error and
warning in full, while the human-text path bounds blocks at CAP_ERRORS.
A json build with many errors could therefore print an unbounded wall of
diagnostics — exactly what RTK normally caps.

Cap the json error/warning lists at CAP_ERRORS/CAP_WARNINGS and append a
'… +N more' hint on overflow. The summary line still reports the real
total counts.
cargo_cmd had no token-savings test despite the repo's >=60% rule, so
nothing locked in the gain from extracting rendered diagnostics out of
the verbose json envelope and capping them. Add a savings assertion on a
large failing --message-format=json build.
The json-aware summary had tests for the failure and capped paths but
none for a clean --message-format=json build, where artifacts and
build-finished:true must still report crates compiled.
…pest, paratest, ecs, pint)

Consolidates the PHP-tooling work from three upstream PRs plus a new
Pint module, leaving phpt to its own PR (#1503).

- rtk php / rtk artisan: syntax check (-l) and Laravel artisan wrapper.
- rtk phpunit: structured-state parser, aggregate counts, bounded
  failure list. Uses runner::run_filtered.
- rtk phpstan: typed serde::Deserialize parser for --error-format=json,
  groups errors by file, sorts by count desc. Utility commands
  (--version, list, clear-result-cache) pass through unchanged.
- rtk pest / rtk paratest: shared test_output helper.
- rtk ecs / rtk pint: code-style fixers; pint uses --format=json for
  structured per-file rule counts.

Composer custom-bin-dir detection: composer_bin_dirs() reads
COMPOSER_BIN_DIR and composer.json config.bin-dir, so tools/bin/phpunit
classifies identically to vendor/bin/phpunit. registry.rs normalizes
tool paths before matching.

Sources:
- #1246 (aaronflorey, self-closed): php, artisan, ecs, pest,
  paratest, test_output, utils, composer_bin_dirs, registry normalization.
- #874 (Beninho, open): phpunit state-machine parser.
- #1110 (LucianoVandi, open): phpstan typed parser.
- New: pint_cmd.rs.

Tests: discover::registry 253 pass; cmds::php 36 pass; cargo build
--release 0 errors; cargo fmt --check clean.
`Commands::Run` used `status.code().unwrap_or(1)` which silently
maps signal kills (SIGTERM, SIGKILL, OOM) to exit code 1 instead
of the POSIX-conventional 128+signal. Use the existing
`exit_code_from_status()` helper already used by Commands::Proxy.

Fixes #2680
fix(run): propagate signal exit code instead of unwrap_or(1)
Upstream develop changed `rewrite_command` to take 3 args
(cmd, excluded, transparent_prefixes). The PHP tooling tests that
don't care about transparent prefixes now use the existing
`rewrite_command_no_prefixes` helper instead.
The PHP runners (phpunit, pest/paratest, ecs, phpstan, pint) only
applied their compact filters when rtk itself launched the process.
Output produced elsewhere — most commonly a tool run inside a Docker
container and piped back to the host — bypassed them entirely, since
the visible command is `docker ...`, not the PHP tool.

Wire the existing filter functions into `rtk pipe`:

- resolve_filter: phpunit, pest|paratest|php-test, ecs, phpstan, pint
- phpstan/pint pipe wrappers sniff JSON-vs-text by content, since the
  runners force --format=json but piped output may be either
- auto_detect_filter: route the "by Sebastian Bergmann" banner to the
  phpunit filter (no -f needed)
- bump the four backing fns to pub(crate)

Also fix filter_phpstan_text matching the summary line case-sensitively
("found"), which missed phpstan's actual "[ERROR] Found N errors".

Tests: phpunit banner auto-detect, phpstan case-insensitive summary.
pint: Pint >=1.14 renamed JSON keys name->path and appliedFixers->fixers.
The struct fields were required with no aliases, so serde rejected current
output and the filter fell back to raw (no compression). Add backward-
compatible aliases so both schemas parse.

phpstan: the "ok" gate and summary line read totals.errors, which counts
only non-file-specific (global) errors. A normal failing run reports
errors=0 with the count in file_errors, so runs with real errors were
reported as "phpstan: ok", silently hiding failures. Gate on both counts
and report file_errors in the summary.

Both regressions slipped past the suite because the fixtures set
errors == file_errors (phpstan) and used the old key names (pint). Added
regression tests using the current-version schemas.

Reported by @evaldnet (verified against Pint 1.27.1, PHPStan 2.1.40).

Co-authored-by: Aaron Florey <azza@jcaks.net>
Co-authored-by: Eli White <1153183+EliW@users.noreply.github.com>
Co-authored-by: Benjamin LETELLIER <bletellier@audencia.com>
Co-authored-by: Luciano <vandi.luciano@gmail.com>
…ecs/pint

`./vendor/bin/<tool>` is the common Laravel invocation form. classify_command
normalizes the leading `./`, so these classify as supported, but the rewrite
strips literal `rewrite_prefixes` from the raw command and the five rules only
carried `vendor/bin/<tool>` and bare `<tool>`. So `./vendor/bin/pint` ran raw
with no compression while `vendor/bin/pint` rewrote to `rtk pint`. phpstan
already carried `./vendor/bin/phpstan`. Add the `./vendor/bin/` prefix to the
other five, with a regression test.

Reported by @evaldnet (verified against pint 1.29.1, phpstan 2.1.40).

Co-authored-by: Aaron Florey <azza@jcaks.net>
Co-authored-by: Eli White <1153183+EliW@users.noreply.github.com>
Co-authored-by: Benjamin LETELLIER <bletellier@audencia.com>
Co-authored-by: Luciano <vandi.luciano@gmail.com>
develop's `test_every_subcommand_is_classified` requires every CLI subcommand
to appear in `RTK_META_COMMANDS` or `PASSTHROUGH`. The consolidated php
subcommands (php, phpunit, phpstan, pest, paratest, ecs, pint) wrap real
tools, so they belong in `PASSTHROUGH`. Without this the PR-into-develop merge
fails the test.
The `.semgrep.yml` `dynamic-command-execution` rule forbids `Command::new` on a
variable. `php_tool_command` built the command directly from the resolved tool
path; route it through `resolved_command` (the sanctioned PATHEXT-aware
constructor) instead. Clears both blocking findings with no behavior change.
… path compaction)

- run() now uses php_tool_command("phpstan") instead of hardcoding
  vendor/bin/phpstan, so COMPOSER_BIN_DIR and composer.json's config.bin-dir
  are respected, matching every other php tool in this module.
- filter_phpstan_text only treats shell-level "binary missing" lines as a
  failure. The previous contains("not found") matched real analysis messages
  ("Class X not found") and swallowed the summary; also dropped a stray Ruby
  LoadError string ("cannot load such file"). Adds a regression test.
- compact_php_path reduced to last-two-components for all frameworks, dropping
  the Laravel-specific prefix list. Tests updated to the compacted output.
is_numbered_failure_heading matched any line starting with a digit and
containing ')', which split a failure block on detail lines like
"5 of 10 assertions passed in Foo::bar()". Anchor to `^\d+\) \S` via a
lazy_static regex. Adds a regression test.
short_path() called current_dir() on every file (up to MAX_FILES_SHOWN per
invocation). Hoist the cwd prefix out of the loop and strip it inline.
…ewrite

The six Composer-tool rules had inconsistent patterns, and their
rewrite_prefixes only worked because classify normalizes the command while
rewrite stripped literal prefixes off the raw text. A form the pattern
accepted but the prefix list missed (e.g. ./bin/phpunit) would classify yet
silently fail to rewrite.

rewrite_segment_inner now normalizes the leading invocation for these tools
(php wrapper, ./, vendor/bin, composer bin-dir) the same way classify does, so
the prefix list collapses to the residual canonical forms: bin/<tool> + bare
name for phpunit/phpstan, bare name for pest/paratest/ecs/pint. Patterns
standardized to ^(?:php\s+)?(?:\./)?(?:(?:vendor/)?bin/)?<tool> for
phpunit/phpstan and ^(?:\./)?(?:vendor/bin/)?<tool> for the rest. Adds a
form-coverage test asserting every accepted spelling maps to one rewrite.
When no explicit permission rules exist in ~/.cursor/cli-config.json
(the default for fresh installs), every command gets Default verdict
which maps to AskRewrite. The Cursor hook only handled AllowRewrite,
silently dropping all rewrites and making RTK non-functional.

Now treat AskRewrite as allow when no rules are configured — Cursor
has no ask-the-user UX, so deferring is indistinguishable from
dropping. When explicit rules exist, preserve the conservative
behavior of deferring mixed/unmatched commands.

Also fix the legacy shell script (rtk-rewrite.sh) which treated
exit code 3 (ask) as failure via || short-circuit.

Fixes #2372
Align cursor_has_explicit_rules() with the inline has_rules check in
run_cursor_inner_with_rules() — both now consider deny, ask, and allow
rules when determining whether the user has configured explicit
permission policies.
Address PR #2609 review feedback:
- RC=3 in shell script now emits "permission": "ask" (future-proof)
- Add cursor_ask() and use it for all AskRewrite decisions
- Remove has_rules guard (unnecessary with ask semantics)
- Remove dead cursor_has_explicit_rules() function
aeppling and others added 22 commits July 22, 2026 18:52
The arrow diagram read as a pipeline where bash output becomes input tokens
which become cost. The real relationship is containment: bash output is
part of input tokens, and input tokens are part of cost alongside output
tokens.

Replace the arrow chain with a tree in all 12 places it appeared, including
the six translated READMEs:

  Cost
  ├─ Input tokens
  │  ├─ Bash output           <- the only part RTK filters
  │  ├─ Your prompt
  │  ├─ System prompt
  │  └─ Conversation history
  └─ Output tokens            <- what the model writes

This also makes the dilution self-evident: RTK shrinks one leaf, so the
effect on the root is bounded by that leaf's share.
Restore to develop the files where the rescoping added noise without
helping a reader: all of .claude/, the src/ module READMEs that only
described their own filters, ARCHITECTURE.md, TELEMETRY.md, quick-start,
configuration and troubleshooting.

Also drop the "Two estimators, one caveat" table from savings-explained.md.
The page already states that rtk gain estimates bytes/4 and ships no
tokenizer; enumerating the test-side estimator was detail no reader needs.

The user-facing surface keeps the rescoping: READMEs in seven languages,
the guide, hooks, and the analytics and usage pages.
The revert restored "$36.0 (at ~$3/Mtok input pricing, Claude Sonnet)" for
estimated_savings_usd_30d. The constant is hardcoded in
src/core/telemetry.rs and applied to a bytes/4 estimate, so it is not a
measured cost and does not track any provider's pricing.

Keep the field documented, since TELEMETRY.md exists to disclose what
leaves the machine, but state no price. This matches the wording already in
docs/guide/resources/telemetry.md, which the revert left untouched.
Drop the 33 standalone "**Economies :** ~N%" claims, which asserted a
number with no context next to each command.

Rename the metric everywhere it remains. "Economies" on its own reads as
money saved; the tables measure bash output bytes removed. Column headers,
the section heading and its anchor, the rtk gain description, the telemetry
field list and the category summary now all say so explicitly.
The tree was copied into 11 files, so every future correction to it meant
11 edits in 7 languages. It now lives only in savings-explained.md, which
each of those pages already links to.

The surrounding prose stays: it carries the dilution point in the reader's
own language, which is the part that matters at a glance. Three pages
introduced the diagram with a trailing colon, reflowed into the following
paragraph. TRACKING.md gained the link it was missing.
docs: scope savings claims scope to bash output & clean savings docs
…marks

fix(benchmark): use deterministic curl and wget responses
Co-authored-by: Nicolas Le Cam <niko.lecam@gmail.com>
fix(uv): preserve program stdout and restore inner-command filtering
chore: declare RtkRule default to avoid repeating unecessary attributes
Change AskRewrite(String) to AskRewrite { rewritten, explicit } so the
Copilot path only auto-allows when the user didn't explicitly configure
"ask" for a command. Addresses KuSh's review feedback on #3149.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix(hooks): emit permissionDecision allow for simple Copilot CLI rewrites
aeppling and others added 5 commits July 26, 2026 00:28
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BwRRhncpyLA9AybUU8KvVh
Resolve a semantic (non-textual) merge conflict: develop's 0df6929
changed HookDecision::AskRewrite from a tuple variant AskRewrite(String)
to a struct variant AskRewrite { rewritten, explicit }. The auto-merge
left two copilot-IDE usages in the old tuple form, breaking the build:

- copilot_ide_response_from_decision match arm: bind `rewritten` from
  the struct variant via `AskRewrite { rewritten, .. }`.
- test_copilot_ide_rewrite_returns_deny_with_suggestion: construct the
  struct variant with `explicit: false` (a default, non-user-configured
  rewrite; the IDE path ignores the field).

Gate green: cargo fmt --all --check, cargo clippy --all-targets
--all-features -D warnings, cargo test --all-features (2497 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01G8b9dEaJtpvydzz4ttM7JH
…ization

fix(permissions): stop extra whitespace from evading deny rules
fix(copilot): support IDE terminal hooks
@aeppling
aeppling merged commit 63bcebd into master Jul 26, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.