feat(jvm): add Maven, Gradle, and Ant support with output filters#1241
feat(jvm): add Maven, Gradle, and Ant support with output filters#1241ryanmurf wants to merge 12 commits intortk-ai:developfrom
Conversation
feat(): batch fixs + aws extended + filter quality batch
…master--components--rtk chore(master): release 0.35.0
- New rtk mvn subcommand with build/package/clean/install/test/verify - src/cmds/jvm/mod.rs and src/cmds/jvm/mvn_cmd.rs - Expanded mvn-build.toml filter (Scanning, Reactor, Download, Progress, jansi/SLF4J/JDK warnings) - Combined mvn/mvnw rewrite rule in src/discover/rules.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New rtk gradle subcommand with build/test/assemble/clean/check/bootRun - src/cmds/jvm/gradle_cmd.rs (mod.rs already created by mvn agent) - Expanded gradle.toml filter (progress bars, daemon chatter, native-platform warnings) - Combined gradle/gradlew rewrite rule in src/discover/rules.rs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # src/cmds/mod.rs # src/main.rs
Adds native rtk mvn and rtk gradle subcommands with output filtering, plus wrapper script detection (./mvnw, ./gradlew, mvnw, gradlew). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rt, line dedup, frame trimming
Major improvements to JVM support after corpus benchmarking on 29 real
Maven/Gradle/Ant projects:
Critical fixes:
- Wrapper detection: rtk now uses ./mvnw and ./gradlew when present (was
silently falling back to system mvn/gradle, breaking projects pinned
to specific versions). resolved_build_command() in core/utils.rs.
- Maven version probe: --no-transfer-progress only injected on Maven >=
3.6.1 (older versions reject it and fail the build). Per-binary cache.
- strip_ansi() now strips C0 control chars including \x08 backspace
(script/pty wrappers were defeating ^anchored regex matches).
New features:
- Quiet flag injection: --no-transfer-progress + -B for Maven, --console=
plain for Gradle. Suppresses noise at the source.
- PGP signature verify collapse: pgpverify-maven-plugin's per-artifact
chatter (354 lines for 178 artifacts on htmlunit) collapses to a single
summary, preserving non-OK results verbatim.
- Stack-trace dedup: identical repeated traces (e.g. SpotBugs 340x flood)
collapse to first + '(... repeated N more times ...)'.
- Stack-frame trimming: noise frames (groovy internals, jdk reflection,
test runner harness) within a single trace collapse to '(... N frames
omitted ...)' while keeping the header, first frame, last frame, and
any user-code frames.
- Line-level dedup: adjacent runs of identical single lines collapse
(e.g. 26x '> Configure project :app').
- Ant build tool support: rtk ant {build,clean,test,compile,package,
install} with target chatter stripping.
New noise patterns added (corpus-discovered):
- JVM 25 sun.misc.Unsafe deprecation footer
- java.applet/legacy-deprecation compiler warnings
- Maven reactor artifact headers and Surefire decorations
- pgpverify-maven-plugin config block
- OpenAPI generator file-writing chatter
- Apache Cocoon resources-plugin lines
- Checkstyle audit start/done/0-violations messages
- Gradle release highlights and JVM forking hints
Test fixtures: 4 realistic captured-output files in tests/fixtures/jvm/
plus a 12-test integration suite (tests/jvm_filter_integration.rs) that
asserts >=50% compression with signal preservation.
Test count: 1386 unit tests passing, 12 integration tests passing.
Corpus benchmark (29 real projects, 14 Maven + 13 Gradle + 2 Ant):
- Maven: 70.0% mean / 78.2% median line compression
- Gradle: ~50% mean (Groovy stack noise, now addressed by stack_trim)
- Ant: 81.4% mean
- 100% exit-code parity (filter never masks failures)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
|
There was a problem hiding this comment.
Pull request overview
Adds first-class JVM build-tool support to rtk by introducing native rtk mvn, rtk gradle, and rtk ant subcommands plus supporting filter/rewriter infrastructure and fixtures/tests to validate output compression.
Changes:
- Added new JVM command modules (Maven/Gradle/Ant) with stack-trace trimming/dedup and line dedup helpers.
- Expanded/added built-in TOML filters for Maven/Gradle/Ant output and updated built-in filter counts.
- Added discovery rewrite rules plus new integration tests and realistic captured-output fixtures.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/jvm_filter_integration.rs | Adds JVM integration tests using captured fixtures and helper filters. |
| tests/common/mod.rs | Adds duplicated “test helper” filter logic for integration tests. |
| tests/fixtures/jvm/mvn_test_failure.txt | Maven failure fixture for integration testing. |
| tests/fixtures/jvm/mvn_clean_compile_with_pgp.txt | Maven multi-module + PGP fixture for integration testing. |
| tests/fixtures/jvm/gradle_test_with_spotbugs_flood.txt | Gradle failure + repeated stack traces fixture. |
| tests/fixtures/jvm/ant_compile.txt | Ant javac failure fixture. |
| src/main.rs | Wires new Mvn, Gradle, and Ant clap subcommands into the CLI. |
| src/cmds/mod.rs | Exposes new jvm command module namespace. |
| src/cmds/jvm/mod.rs | Adds automod entry for JVM command modules. |
| src/cmds/jvm/mvn_cmd.rs | Implements rtk mvn execution, quiet-flag injection, and Maven filtering (incl. PGP collapse). |
| src/cmds/jvm/gradle_cmd.rs | Implements rtk gradle execution, quiet-flag injection, and Gradle filtering pipeline. |
| src/cmds/jvm/ant_cmd.rs | Implements rtk ant execution and Ant filtering. |
| src/cmds/jvm/stack_dedup.rs | Adds repeated stack-trace block deduplication. |
| src/cmds/jvm/stack_trim.rs | Adds intra-trace “noise frame” trimming. |
| src/cmds/jvm/line_dedup.rs | Adds adjacent repeated-line deduplication. |
| src/core/utils.rs | Enhances strip_ansi() and adds resolved_build_command() wrapper preference logic. |
| src/discover/rules.rs | Adds rewrite rules for mvn/mvnw, gradle/gradlew, and ant. |
| src/filters/mvn-build.toml | Expands Maven TOML filter patterns and inline tests. |
| src/filters/gradle.toml | Expands Gradle TOML filter patterns and limits. |
| src/filters/ant.toml | Adds new Ant TOML filter and inline tests. |
| src/core/toml_filter.rs | Updates built-in filter-count assertions for the new filter(s). |
| //! Each test: | ||
| //! - Reads a fixture file from tests/fixtures/jvm/ | ||
| //! - Runs it through the appropriate filter function (via tests/common/mod.rs) | ||
| //! - Asserts filtered output is at least 50% smaller than raw input | ||
| //! - Asserts critical signal lines are preserved (errors, BUILD result) |
| #[test] | ||
| fn test_mvn_compile_with_pgp_reduces_by_50pct() { | ||
| let raw = fixture("mvn_clean_compile_with_pgp.txt"); | ||
| let filtered = common::filter_mvn_build(&raw); | ||
|
|
||
| assert!( | ||
| reduced_by_at_least(&raw, &filtered, 40), | ||
| "expected ≥40% reduction, got:\nraw={} bytes, filtered={} bytes\n---\n{}", | ||
| raw.len(), |
src/core/utils.rs
Outdated
| // Unix-style wrapper (works on macOS, Linux, and Git Bash on Windows). | ||
| let unix_wrapper = std::path::Path::new(&wrapper); | ||
| if unix_wrapper.exists() { | ||
| return Command::new(format!("./{}", wrapper)); | ||
| } | ||
|
|
||
| // Windows native wrappers. | ||
| #[cfg(target_os = "windows")] | ||
| { | ||
| for ext in &["cmd", "bat"] { | ||
| let win_wrapper = format!("{}.{}", wrapper, ext); | ||
| if std::path::Path::new(&win_wrapper).exists() { | ||
| return Command::new(format!(".\\{}", win_wrapper)); | ||
| } | ||
| } |
| # Plugin chatter that's never actionable | ||
| "^\\[INFO\\] skip non existing resourceDirectory", | ||
| "^\\[INFO\\] Finished at:", | ||
| # Java compiler legacy-deprecation noise (e.g. java.applet.*) | ||
| "^\\[WARNING\\].*java\\.applet\\.", | ||
| "^\\[WARNING\\].*has been deprecated and marked for removal", | ||
| # JVM 25 sun.misc.Unsafe deprecation footer block (4 lines) | ||
| "^WARNING: sun\\.misc\\.Unsafe::objectFieldOffset", |
| // Always preserve these. | ||
| if RE_KEEP_BUILD_RESULT.is_match(line) | ||
| || RE_KEEP_TOTAL_TIME.is_match(line) | ||
| || RE_KEEP_JAVAC_ERROR.is_match(line) | ||
| || RE_KEEP_ERROR.is_match(line) | ||
| || RE_KEEP_FAILED.is_match(line) | ||
| { | ||
| kept.push(truncate(line.trim_end(), 240).to_string()); | ||
| continue; | ||
| } | ||
|
|
||
| // Strip these patterns. | ||
| if RE_STRIP_BUILDFILE.is_match(line) | ||
| || RE_STRIP_TARGET.is_match(line) | ||
| || RE_STRIP_TASK_CHATTER.is_match(line) | ||
| || RE_STRIP_JAVAC_PREFIX.is_match(line) | ||
| { | ||
| continue; |
| // JVM-MVN BEGIN | ||
| RtkRule { | ||
| pattern: r"^mvn\s+(compile|package|clean|install)\b", | ||
| pattern: r"^(?:mvn|mvnw)\s+(compile|package|clean|install|test|verify)\b", |
There was a problem hiding this comment.
Thanks — verified empirically that this works as-is. strip_absolute_path() in src/discover/registry.rs:263 normalizes any first-word containing / (including ./mvnw) down to its basename (mvnw) before classification, so the regex never sees the ./ prefix:
$ rtk rewrite "./mvnw clean test"
rtk mvn clean test (exit 3)
$ rtk rewrite "mvnw verify"
rtk mvn verify (exit 3)
The same normalization handles ./gradlew (see the related comment thread). Leaving the patterns as-is to avoid regex duplication that would diverge from the existing absolute-path normalization.
| // Matches `gradle ...`, `./gradlew ...` and `gradlew ...` | ||
| // (strip_absolute_path normalizes `./gradlew` → `gradlew` for classify, | ||
| // but rewrite_segment uses the un-normalized cmd, so both prefixes are | ||
| // listed in rewrite_prefixes below). | ||
| pattern: r"^(?:gradle|gradlew)\s+(build|test|assemble|clean|check|bootRun)\b", |
There was a problem hiding this comment.
Same as the Maven thread above — verified working via strip_absolute_path():
$ rtk rewrite "./gradlew test"
rtk gradle test (exit 3)
$ rtk rewrite "gradlew assemble"
rtk gradle assemble (exit 3)
Note: I did fix a separate broken regex in src/filters/gradle.toml's match_command (was ^(gradle|gradlew|\./)gradlew?\b requiring gradlegradle, now ^(?:\./)?(?:gradle|gradlew)\b) — that was a real bug from a different code path.
| /// Patterns stripped from Maven output. Conservative — we never drop lines | ||
| /// starting with `[ERROR]`, `[WARNING]`, `Tests run:`, or `BUILD ...`. | ||
| fn is_mvn_noise(line: &str) -> bool { | ||
| lazy_static::lazy_static! { | ||
| static ref PATTERNS: Vec<Regex> = { | ||
| let raw = [ | ||
| // Initial scan / reactor announcements | ||
| r"^Scanning for projects", | ||
| r"^\[INFO\] Scanning for projects", | ||
| r"^\[INFO\] Reactor", | ||
| r"^\[INFO\] -+$", | ||
| r"^\[INFO\] =+$", | ||
| r"^\[INFO\] $", | ||
| r"^\[INFO\]\s*$", | ||
| r"^\[INFO\] ---", | ||
| r"^\[INFO\] Building\s", | ||
| // Dependency I/O | ||
| r"^Downloading from\s", | ||
| r"^Downloaded from\s", | ||
| r"^\[INFO\] Downloading\s", | ||
| r"^\[INFO\] Downloaded\s", | ||
| r"^Downloading:", | ||
| r"^Downloaded:", | ||
| r"^Progress \(\d+\)", | ||
| r"^Progress ", | ||
| // jansi / SLF4J / JDK restricted-method warnings | ||
| r"^WARNING: A restricted method", | ||
| r"^WARNING: java\.lang\.System::load", | ||
| r"^WARNING: Use --enable-native-access", | ||
| r"^WARNING: Restricted methods will be blocked", | ||
| // JVM 25 sun.misc.Unsafe deprecation footer block (4 lines) | ||
| r"^WARNING: A terminally deprecated method in sun\.misc\.Unsafe", | ||
| r"^WARNING: sun\.misc\.Unsafe::objectFieldOffset", | ||
| r"^WARNING: This is a critical method", | ||
| // pgpverify-maven-plugin config-block chatter | ||
| r"^\[INFO\] Key server\(s\)", | ||
| r"^\[INFO\] Create cache directory for PGP keys:", | ||
| r"^\[INFO\] Resolved \d+ artifact\(s\)", | ||
| r"^\[INFO\] Artifacts were already validated", | ||
| // Corpus-discovered patterns (resources, checkstyle, OpenAPI gen) |
tests/common/mod.rs
Outdated
| //! Thin test helpers that replicate filter logic from src/cmds/jvm/ for use | ||
| //! in integration tests. These are intentionally minimal — they use the same | ||
| //! regex patterns as the production code but don't depend on the binary crate's | ||
| //! internal module graph (which is inaccessible from tests/ in a bin-only crate). |
| [filters.gradle] | ||
| description = "Compact Gradle build output — strip progress, keep tasks and errors" | ||
| match_command = "^(gradle|gradlew|\\./)gradlew?\\b" | ||
| strip_ansi = true |
- jvm_filter_integration: rename test fns to *_reduces_by_40pct and update module doc to match the actual 40% threshold (1, 2) - core/utils: prefer .cmd/.bat wrappers FIRST on Windows (shebang ./mvnw doesn't execute under CreateProcess), fall through to Unix wrapper only on non-Windows (3) - mvn-build.toml: drop 'Finished at:' from inline test expected output to match the strip pattern (4) - ant_cmd: replace blanket [javac]-prefix strip with narrow informational pattern (Compiling/Note/Compiled/options-warning) so user-actionable diagnostic context (source snippets, carets, symbol/location, error count summaries) survives the filter (5) - main.rs: clarify 'rtk ant build' help text — it runs the literal 'build' target, not the project's default target (6) - mvn_cmd: align is_mvn_noise regex set with mvn-build.toml strip patterns (reactor headers, Surefire decorations, Finished at, applet deprecation, SLF4J) so the native filter and TOML filter behave consistently (9) - tests/common/mod.rs: docstring acknowledges this is an approximation of the production filter, not a byte-for-byte mirror — drift is possible (10) - gradle.toml: fix broken match_command alternation. Was '^(gradle|gradlew|\\./)gradlew?\\b' which required 'gradlegradle'; now '^(?:\\./)?(?:gradle|gradlew)\\b' (11) Items 7 and 8 (Copilot's claim that ./mvnw and ./gradlew patterns don't match) are false positives — strip_absolute_path() in src/discover/registry.rs already normalizes ./mvnw → mvnw before classification. Verified via 'rtk rewrite "./mvnw clean test"' returning 'rtk mvn clean test' (exit 3). Will leave a polite reply on those review threads. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds two new safeguards for flag injection: 1. RTK_NO_INJECT_FLAGS env var — global kill-switch. When set to any non-empty, non-'0' value, rtk passes args through unchanged. Intended as a one-line escape hatch for users hitting unexpected build-tool quirks where the injected flags break their build. 2. Gradle version probe (symmetric with the existing Maven probe) — only inject --console=plain when the resolved Gradle reports major version >= 5. Older versions had a less-mature plain console that didn't reliably suppress noise; falling back to no injection on Gradle <5 keeps the tiny number of legacy projects working (1 of 213 in the cataloged corpus). Both probes fail-CLOSED: if probing fails (binary missing, output unparseable), no flag is injected. Better to lose a small win than to break someone's build. Verified via smoke tests: - RTK_NO_INJECT_FLAGS=1 rtk mvn build → 'compile' (no flags) - Default rtk mvn build → '-B compile' on synthetic wrapper - Real Maven 3.9.8 (cve-doc-generator) → BUILD SUCCESS exit 0 - Real Gradle 8.12.1 (Kotlin compiler) → exit 0 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
Summary
Adds native
rtk mvn,rtk gradle, andrtk antsubcommands with outputfiltering, validated against 29 real-world Spring/Java/Kotlin projects.
The JVM ecosystem was the largest gap in rtk — Maven and Gradle builds are
notoriously verbose, and AI agents working in Java codebases waste enormous
amounts of context on download progress, reactor chatter, repeated stack
frames, and plugin noise.
What's added
Three new tools
rtk mvn {build,package,clean,install,test,verify}+ passthroughrtk gradle {build,test,assemble,clean,check,bootRun}+ passthroughrtk ant {build,clean,test,compile,package,install}+ passthroughWrapper detection
resolved_build_command()(incore/utils.rs) prefers project-local./mvnwand
./gradlewover system binaries — this matches what the developeractually runs and respects pinned tool versions.
Five compression mechanisms
--no-transfer-progress+-Bfor Maven (withper-binary version probe so it's not injected on Maven < 3.6.1),
--console=plainfor Gradle. Suppresses the bulk of noise at the source.pgpverify-maven-plugin's 354 lines (178artifacts × 2 lines) collapse to one summary; non-OK results preserved
verbatim for diagnosis.
stack_dedup.rs) — identical repeated trace blockscollapse with
(... repeated N more times ...).stack_trim.rs) — within a single trace, runsof noise frames (Groovy internals, JDK reflection, JUnit/Gradle/Surefire
harness) collapse to
(... N frames omitted ...)while keeping theheader, first frame, last frame, and any user-code frames.
line_dedup.rs) — adjacent runs of identicalsingle lines collapse (e.g.
> Configure project :app× 26).Noise patterns added (corpus-discovered)
sun.misc.Unsafedeprecation footerjava.applet/legacy-deprecation compiler warningsFinished at:timestamps,skip non existing resourceDirectoryno-opsBug fix in shared code
strip_ansi()incore/utils.rsnow strips C0 control chars including\x08backspace. Previouslyscript/pty-captured output bypassed^anchoredregex matches because lines began with\x08\x08.Real-world benchmark (29 projects)
100% exit-code parity across all 29 builds — the filter never masks a
real build failure.
Outliers in the bottom of the distribution were all tiny failed builds
(20–100 lines of nearly-pure error signal) where low compression is correct,
not a filter problem.
Test coverage
src/cmds/jvm/tests/jvm_filter_integration.rsagainst 4realistic captured-output fixtures (
tests/fixtures/jvm/*.txt)mvn-build.toml,gradle.toml,ant.tomlArchitecture
Test plan
cargo test --bin rtk— 1386 passedcargo test --test jvm_filter_integration— 12 passedcargo build --release— cleanrtk mvn buildagainst a real Spring Boot project — verify wrapper usedrtk gradle buildagainst a real Kotlin Gradle project — verify wrapper usedrtk ant buildagainst an Ant project (e.g. Apache Derby)rtk gainreports nonzero savings after running the above🤖 Generated with Claude Code