gw is a CLI tool designed to wrap ./gradlew and other Gradle wrappers, filtering output to suppress unnecessary noise for AI agents while keeping contexts small and focused. It forwards critical information such as errors, warnings, test failures, and build status, ensuring that developers receive only essential data.
Key Features:
Noise Suppression: Automatically filters out daemon banners, dependency downloads, task lifecycle messages, and deprecation notices.
Error Forwarding: Ensures that all errors, warnings, and test failures are clearly visible, complete with stack traces for easier debugging.
Heartbeat Monitoring: Provides a progress update every 3 seconds, showing the active task, its duration, and cumulative progress. Tasks exceeding a predefined threshold (default 60s) are flagged as slow.
Log Preservation: Saves the full build log to ./build-logs/gw-.log for detailed review.
Audience & Benefit:
Ideal for developers working with Gradle projects, gw is particularly beneficial in AI-driven environments where output noise can hinder efficiency. By reducing irrelevant information, gw allows for faster iteration and easier debugging. The tool's heartbeat feature offers real-time monitoring of long-running builds, enhancing productivity and focus on critical issues.
gw streamlines the build process, making it more efficient and user-friendly for developers seeking clarity in their development workflows.
README
gw — Gradle output filter for AI agents
Wraps ./gradlew (and any wrapper around it: ./mainframer ./gradlew, ssh host './gradlew ...', etc.) to keep agent contexts tiny:
Forwards: errors (Kotlin e:, Java error:, Lint), failure blocks, test failures with stacktraces, BUILD SUCCESSFUL / BUILD FAILED.
Heartbeat on stderr every ~3s (▸ :app:compileKotlin (45s) [12 tasks]) showing the active task, age, cumulative task progress, and a — slow marker once a single task exceeds GW_HEARTBEAT_SLOW_SECS (default 60s).
Final summary on stderr with task counts, test counts, error/warning totals, full-log path.
Full log preserved at ./build-logs/gw-.log for drill-down.
gw init defaults to Claude Code. Pick another agent — or several at once:
gw init --gemini-cli # ~/.gemini/settings.json (BeforeTool hook)
gw init --cursor # ~/.cursor/hooks.json (beforeShellExecution)
gw init --opencode # ~/.config/opencode/plugin/gw.ts
gw init --agent codex --local # ./AGENTS.md (rules block)
gw init --claude-code --gemini-cli # multi-target in one run
gw init --all --local # everything supported in this project
Per-agent integration mechanism:
Agent
Mechanism
Default file (global)
Local file
claude-code
PreToolUse hook + companion CLAUDE.md note
~/.claude/settings.json
.claude/settings.local.json
gemini-cli
BeforeTool hook + companion GEMINI.md note
~/.gemini/settings.json
.gemini/settings.json
cursor
beforeShellExecution hook + AGENTS.md note
~/.cursor/hooks.json
.cursor/hooks.json
opencode
TS plugin (tool.execute.before) shelling out to gw rewrite
~/.config/opencode/plugin/gw.ts
.opencode/plugin/gw.ts
codex
Markdown rules block
~/.codex/AGENTS.md
AGENTS.md
kilocode
Markdown rules block
~/.kilocode/rules/gw.md
.kilocode/rules/gw.md
cline
Markdown rules block
— (local-only)
.clinerules
windsurf
Markdown rules block
— (local-only)
.windsurfrules
antigravity
Markdown rules block
— (local-only)
AGENTS.md
copilot
Markdown rules block
— (local-only)
.github/copilot-instructions.md
Cursor lacks an in-band rewrite mechanism, so its hook denies the call with a userMessage instructing the agent to retry the command prefixed with gw . Other hook-based agents auto-rewrite transparently.
Rules-based agents (codex/cline/windsurf/...) get an instructional block delimited by / markers — only that range is rewritten on update or removed on uninstall, so any surrounding content stays untouched.
Uninstall:
gw uninstall # Claude Code, global
gw uninstall --gemini-cli --cursor # multiple agents at once
gw uninstall --all --local # everything in this project
Audit (gw doctor)
Read-only check of every supported agent at both global and local scope — useful when you're not sure whether the hook landed, or whether you're still on the legacy gw hook claude command.
gw doctor
gw 0.3.0
Claude Code:
✓ global installed /Users/me/.claude/settings.json
- local no file .claude/settings.local.json
Gemini CLI:
✗ global not installed /Users/me/.gemini/settings.json
...
Glyphs: ✓ installed, ⚠ legacy command (re-run gw init --claude-code to migrate), ✗ file present but no gw hook, - no file.
Usage
Once gw init is run, Claude Code's Bash tool calls get auto-rewritten when the command contains gradlew:
gw ./gradlew assembleDebug # default streaming filter
gw --full ./gradlew assembleDebug # passthrough, no filter
gw --quiet ./gradlew test # errors only
gw --warnings ./gradlew build # also stream warnings
gw --no-heartbeat ./gradlew lint # no progress lines on stderr
gw --no-log ./gradlew test # do not write build-logs/ file
gw --no-console-plain ./gradlew build # don't auto-inject --console=plain
By default gw injects --console=plain into the Gradle invocation
(detected by the gradle/gradlew/gradle.bat basename, even when
nested under a wrapper such as ./mainframer.sh ./gradlew …). This
keeps output deterministic when the build runs without a real PTY (over
SSH, on CI, inside a remote-builder wrapper) — without it, ANSI cursor
moves and progress redraws can corrupt parsing and tty layout. Pass
--no-console-plain to opt out, or set --console=... yourself and gw
will leave it alone.
The exit code matches the wrapped command's exit code.
Stats (gw gain)
Each run is appended as a JSONL record at ~/.local/share/gw/runs.jsonl. Aggregate
the savings and per-build stats:
gw gain # aggregate all-time
gw gain --since 7d # last 7 days (s/m/h/d/w units)
gw gain --history # table of recent runs
gw gain --history --limit 20 # last 20 runs
Example output:
gw stats (47 runs, last 7d)
─────────────────────────────────────
Lines: in 142,538 → out 1,247 (99.1% suppressed)
Bytes: in 9.2M → out 68.0K (99.3% saved)
Time: avg 1m24s/build, 3 failed
Errors: 5, warnings: 142
Stats writes are best-effort: a failing stats file never breaks a build.
Detection rules
The hook rewrites a command iff:
After stripping leading sudo/env/nice/time and VAR=value env prefixes,
it does not already start with gw ,
and it contains the token gradlew (word-boundary match, so ./mygradlewhatever is not touched).
Anything else passes through untouched.
Output format
While the build runs, gw writes to stdout only what an agent needs to act on:
build-logs/ may contain sensitive build output (signing keys, API tokens leaked by Gradle plugins). The directory is in .gitignore; ensure your project's .gitignore also excludes it. Use --no-log to disable file logging entirely.
--log-dir is treated as fully trusted; do not pass attacker-controlled values.
The Claude Code hook only prepends gw to commands containing the gradlew token. The rewritten command is exactly the user's original command with one extra prefix; no new attack surface beyond what would otherwise execute.