Skip to content

Next Release#2512

Merged
aeppling merged 68 commits into
masterfrom
develop
Jun 28, 2026
Merged

Next Release#2512
aeppling merged 68 commits into
masterfrom
develop

Conversation

@aeppling

@aeppling aeppling commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Feats

Fix

wlami and others added 27 commits April 15, 2026 18:20
Adds 5 TOML filters for the Pulumi CLI, matching the existing OpenTofu/
Ansible/Terraform TOML-based pattern. Filters strip known noise (header
banners, View Live URLs, policy pack loading, @ progress spinners,
intermediate creating/updating/deleting rows, Duration, Node.js stack
trace frames) while preserving resource change rows, policy violations,
diagnostics, outputs, and summaries.

Measured savings on a real neo-migration AWS project (~30 resources,
Pulumi Cloud backend): pulumi up 65.8%, pulumi destroy 72.3%, preview
--refresh 40.7%, refresh 34.9%, stack 29.3%. Lower savings on already-
compact output (clean preview ~9%) because remaining content is signal
per the correctness-over-compression design philosophy.

Registers matching RtkRule in src/discover/rules.rs so the rewrite hook
transparently proxies `pulumi <subcmd>` through `rtk pulumi`. Bumps
BUILTIN_TOML filter count 59 -> 64 and updates the expected-filter list
and concat-discoverability test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Revert CHANGELOG.md (release-please CICD generates from commit msg)
- Narrow pulumi-stack match_command to explicit subcommand whitelist
  (bare stack, ls, output, history, select, init, rm, rename, tag,
  unselect, change-secrets-provider). Excludes export (JSON state) and
  graph (DOT), which now passthrough unfiltered.
- Lower savings_pct 70 -> 45 (measured mean across 5 subcommands).
  Populate subcmd_savings with measured values: up=66, destroy=72,
  refresh=35, preview=25, stack=29.

JSON-safety (self-found during review validation):
- All 5 pulumi subcommands support -j/--json; Rust regex crate has no
  lookahead so match_command can't negatively exclude --json.
- Remove max_lines from all 5 filters; strip patterns don't match JSON
  structure so JSON passes through unfiltered.
- Remove ^\\s*\\}\\s*$ strip pattern (was targeting Node.js stack trace
  trailers, but also matched legitimate JSON closing braces).

Verified on neo-migration stack:
- pulumi stack ls/output/history/export, preview --json, stack graph:
  byte-identical with RTK_NO_TOML=1 baseline (filter transparent).
- Human-readable preview still compresses (~20% on clean state).
- 10/10 inline pulumi tests pass; 1449/0 full cargo test suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…atterns

Four bugs in `extract_pattern_path` caused `rtk grep` to misparse
common invocations:

- `-A 2 error src` → `2` was taken as the pattern (value-taking
  flags not recognised)
- `-rn foo src` → `n` leaked to rg as `--replace` value (only
  standalone `-r` was stripped, not combined clusters like `-rn`)
- `TODO src tests` → second path silently dropped (only one path
  kept)
- `-i x agent -g '*.md'` → rg regex error (`*.md` became positional
  before `-g` could consume it)

New implementation:
- `VALUE_FLAGS_SHORT`: explicit byte-set of 2-char flags that consume
  one following token (`A`, `B`, `C`, `g`, `f`, `j`, `m`)
- `process_flag()`: strips `r`/`R` from any single-dash cluster,
  drops `--recursive`; replaces the old inline `if arg == "-r"` loop
- `extract_pattern_path` returns `(Vec<String>, Vec<String>, Vec<String>)`
  (patterns, paths, flags) — no more `Option<String>` for pattern
- `-e` values collected separately; if any `-e` is present, all
  positionals are paths; otherwise first positional is the pattern
- All patterns forwarded to rg as `-e` flags (BRE `\|`→`|` inline)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
A combined cluster like -rne PATTERN or -rA 2 had the last flag's
value consumed only if the cluster was exactly 2 chars. Longer
clusters (e.g. -rne) fell through to process_flag, which stripped
-r and emitted -ne — then -e ended up in flags without a value,
causing rg to error.

New unified short-flag handler: for any single-dash cluster, inspect
the last byte. If it is -e or a VALUE_FLAGS_SHORT member, strip the
r/R chars from the prefix, emit the prefix as a flag (if non-empty),
and consume the next token as the value (pattern for -e, flag value
otherwise). Long flags (--foo) are still routed through process_flag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
rtk diff treated files whose changes were all classified as modified
(similar lines, e.g. "a: 1" vs "a: 2" in YAML/JSON) as identical,
because the identical check only looked at added/removed counts.
Report any non-empty change set as a difference, and exit 1 when
files differ per diff convention (0 when identical).

Fixes #2364
…hrough

Five regressions on grep_arg_parsing vs develop:

- `-ecarrot` matched "caot" — strip_r was applied to the inline value bytes.
  Fix: scan cluster left-to-right; stop at first value-taking letter and
  treat the remainder as the inline value without r/R stripping.

- `-g*.rs` glob became `*.s` — same root cause, inline glob value r-stripped.

- `--glob '*.md'` took `*.md` as a path — no VALUE_FLAGS_LONG existed; the
  value token fell through to the positional collector.
  Fix: add VALUE_FLAGS_LONG covering all rg long flags that take a value.

- `--max-count 1 fn file` took `1` as the pattern — same missing long flag.

- VALUE_FLAGS_SHORT expanded: add t/T (--type/--type-not), d (--max-depth),
  M (--max-columns) so inline short forms and space-separated forms parse
  correctly without mis-routing values to patterns or paths.

- --regexp (long form of -e) now routes to patterns like -e does.

- has_format_flag: add --count-matches, --json, --passthru, --files so
  these output-format modes get passthrough treatment instead of silent
  "0 matches in 0 files".

2183 tests pass.

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

The left-to-right cluster scan now calls strip_r() at emit time on the
accumulated flag-letter prefix (raw_prefix), never on inline value bytes.
This makes the safety contract explicit and testable: strip_r("carrot")
produces "caot", documenting exactly why it must not touch value bytes.

Also fix pre-existing unused-mut in aws_cmd.rs (has_output_flag).

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

The left-to-right scanning logic now lives entirely in parse_cluster(), which
returns a ClusterResult enum (Boolean or ValueTaking). extract_pattern_path
just dispatches on the result — no loop logic inline.

parse_cluster() is the only place that touches cluster bytes, so the full
contract is testable in isolation:

  parse_cluster("ecarrot") → ValueTaking { prefix: None, flag: 'e', inline: "carrot" }
  parse_cluster("rne")     → ValueTaking { prefix: Some("n"), flag: 'e', inline: "" }
  parse_cluster("g*.rs")   → ValueTaking { prefix: None, flag: 'g', inline: "*.rs" }
  parse_cluster("rn")      → Boolean(Some("n"))

strip_r() remains as the focused r/R-stripping helper used internally by
parse_cluster (and tested directly to document the "carrot"→"caot" danger).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fix(grep): stabilize argument parsing — trailing_var_arg, -v invert-match, --version passthrough, safe rg invocation
…se-identical

fix(diff): report modified-only diffs and follow diff exit convention
The system-grep fallback (used when ripgrep is not installed) passed
-rnHZ, relying on -Z for the NUL filename separator the match parser
requires. -Z only means --null on GNU grep; on BSD/macOS grep it is an
alias for --decompress (zgrep mode), so output is plain
file:line:content with no NUL. parse_match_line() then matches zero
filenames and every result collapses into "N matches in 0 files" with
all lines hidden behind [+N more].

Use the long option --null instead, which both GNU and BSD grep define
as "print a zero-byte after the file name".

Related to #2310

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fix(hook): rewrite pytest under uv run
Exit codes >= 2 signal real errors in grep/rg (bad regex, tool
crash, missing binary). rtk was only checking the narrow case
exit_code == 2 with non-empty stderr, letting any other error
exit masquerade as a clean "0 matches" false negative.

Now any exit code >= 2 surfaces the error to stderr instead of
printing a false zero-match result.

Fixes #2461
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The test simulates a broken `rg` via a chmod'd shell script (from_mode,
PermissionsExt) which is unix-only. The unconditional `use std::os::unix`
broke compilation of the integration-test crate on the Windows CI target.
Gate the whole file so Windows stays green while preserving unix coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Wrap the rtk binary path expression per rustfmt to satisfy
`cargo fmt --all -- --check` on CI.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Address KuSh review on #2465:
- Move the `exit_code >= 2` decision into a pure `is_grep_error_exit`
  function and unit-test it directly (0/1 = normal, >=2 = error),
  instead of faking the rg binary in an integration test.
- Drop the GREP_ERROR_EXIT const; the doc comment on the function
  conveys the grep/rg exit convention.
- Remove tests/grep_error_test.rs (faked binary) in favour of the
  unit test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
run_commit had a branch that printed 'ok (nothing to commit)' and fell
through to Ok(0) whenever git exited non-zero with a 'nothing to commit'
message — so a no-op or hook-aborted commit was reported as success with
exit 0. Gate strictly on output.status.success() via a CommitOutcome
helper: any failure surfaces git's message and propagates the real exit
code, matching native git. Same bug class as #1581 (push) and #1535
(stash).

Fixes #2494
The compact status path only caught the "not a git repository" error and
let every other failure (corrupt index, lock contention, broken refs)
fall through to a formatted "Clean working tree" + Ok(0), masking a real
git error. The non-compact path already guards on result.success(); apply
the same guard to the compact path, keeping the friendly not-a-repo
message and propagating git's real exit code otherwise.

Part of #2497
run_worktree list mode never checked result.success() — a failed
`git worktree list` (e.g. run outside a repo) was flattened to empty
output + exit 0. The has_action branch already guards on success; apply
the same guard to list mode and surface git's error with its exit code.

Part of #2497
feat(oc): add Openshift CLI support with shared k8s filtering
On failing `dotnet test` runs the orchestrator prepended the full raw
stdout ahead of the filtered summary. The `Failed Tests:` section already
reproduces each failure (name + message + clipped stack) parsed from
TRX/console, so every failure was printed twice — inflating output +65%
vs raw, scaling linearly with failure count (issue #2501).

Gate the raw-stdout prepend behind `test_needs_raw_fallback`: skip it when
the structured section carries detail, keep it when the filter is blind
(no failures parsed, or a parsed failure has empty detail — e.g. a
self-closing <UnitTestResult> with no <ErrorInfo>, or a build failure /
crash where nothing reaches failed_tests).

Extracts two pure, unit-tested fns (`test_needs_raw_fallback`,
`compose_failure_output`) so the orchestration decision is covered without
running dotnet.

Closes #2501

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@aeppling

aeppling commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

Tests are currently fail because oc added on develop isn't registered in the PASSTHROUGH allowlist checked by test_every_subcommand_is_classified.

This test was introduced on master for a security fix.

Fix: resync with master, then add "oc" to the PASSTHROUGH list.

Fixed : #2514

aeppling and others added 28 commits June 23, 2026 21:53
RTK could emit more tokens than the underlying command on small inputs:
filters that add headers, summaries, re-indentation, or a tee hint, plus
synthetic no-result messages ("0 matches", "No stashes", "[docker] 0
containers") printed where the raw command emitted nothing. Both break the
Transparency principle and inflate tokens instead of saving them.

- core::guard::never_worse(raw, filtered) returns raw when the filtered form
  has more tokens (reuses tracking::estimate_tokens), so RTK output is never
  larger than the real command.
- runner::emit_guarded(filtered, hint, raw) composes body + tee hint, guards
  the whole, prints, and returns what was shown so printed == tracked.
- run_captured_filter guards the run_filtered* family centrally; per-site
  guards cover the remaining single-string filters.
- On empty raw, emit empty and preserve the exit code instead of a synthetic
  no-result message (the messages were cosmetic with no dependents; #2461
  reports the grep one as actively harmful).
- git stash show now propagates its exit code instead of masking a real
  failure as Ok(0).

Resolves #2551.
fix(grep): correctly handle all flag shapes and never exceed raw output
Masking env secrets is not what core RTK (OSS) is for -- it compresses command
output, it does not redact what the command itself already reveals. It also
broke raw-vs-filtered consistency, which the never-worse guard relies on.
pipe filters piped stdin but bypassed the guard; route its output through
never_worse like every other compressor.
Compare filtered output against the raw the agent's command would
emit (numbered for -n), not against unnumbered content, so the
guard keeps requested line numbers instead of dropping to plain.
…eline

docker ps/ps -a/images ran a plain command for the raw baseline and a
separate --format command for parsing, capturing the plain run with
unwrap_or_default(). A failed plain run while --format succeeded left
raw empty, and never_worse("", summary) dropped real output.

Run the agent's actual command first as the source of truth: on failure
show its output/exit faithfully; on success use its stdout as raw and
treat --format as a best-effort parse aid that falls back to raw. The
guard stays pure and always sees a real baseline. Parsers unchanged.
…orse-guard

# Conflicts:
#	src/cmds/system/grep_cmd.rs
Per review (#2498): the guard is self-explanatory; remove the comment
to avoid noise.
Per review (#2498): self-explanatory guard; remove comment to avoid noise.
…-code

fix(git): propagate exit code on git status failure in compact path
develop already propagates the exit code for failed git stash list/show
(via the empty-stdout guard). Add regression tests so the masking-failure
behavior (#2497) can't creep back.
…t-code

test(git): cover exit-code propagation for git stash list/show failure
fix(git): propagate exit code when commit fails instead of reporting ok
…code

fix(git): propagate exit code on git worktree list failure
- grep runs grep, rg runs rg: drop the substitution, forced --no-ignore-vcs, and BRE-to-rg translation
- add `rtk rg` command (native ripgrep, sharing the same output filter)
- split rewrite rule: grep to rtk grep, rg to rtk rg
- record the agent's real command in tracking (was synthesized as "grep -rn")
- emit nothing on a clean no-match (never-worse parity with the shared guard)
- rename grep_cmd.rs to search.rs, now hosting both engines
- cover engine faithfulness, ignore semantics, and rg savings with issue-referenced tests
- benchmark the grep and rg paths
- line number always (the openable position); filename only when grep/rg prints one (multi-file, dir, -r, -H)
- single file and stdin equal `grep -n` byte-for-byte: no synthetic filename, header, or lossy truncation
- read piped stdin instead of injecting `.`; rg no longer searches the cwd on a pipe
- pattern-less and format-flag commands run verbatim (rg --type-list, --files, grep -c/-o)
- grouped/capped form only when capping actually shrinks the output
- byte-for-byte tests vs grep -n across flags, pipes and edge cases (incl #1436)
- leading ^ anchor must stay scoped to the path (tenequm)
- line ending in `:` keeps full content (BTCAlchemist)
- rg surfaces a regex parse error as exit 2, not a silent 0 (jhagberg)
- grep_compress_test.rs -> search_compress_test.rs (tests the shared grep/rg filter)
- binary match skipped as noise by default, -a opts back in
- fix parse_flags doc: -I is an intentional binary skip, not a no-op
- drop the synthetic "search failed with exit code N" line (an rtk addition)
- surface the engine stderr verbatim in every path, incl. partial match + error
- remove the now-vestigial is_grep_error_exit (the "0 matches" it guarded is gone)
- error/exit matrix tests vs real grep/rg (#2465: an error is never a silent no-match)
refacto(search): run the invoked engine instead of substituting rg for grep

@KuSh KuSh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 🚀

@aeppling
aeppling merged commit 431145c into master Jun 28, 2026
32 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.

10 participants