- #345: rewrite_segment() now returns None when env_prefix contains
RTK_DISABLED= — prevents rewriting commands the user explicitly
opted out of
- #346: rewrite_compound() detects redirect operators (2>&1, >&2,
&>/dev/null, &>>) before treating bare & as background operator —
fixes corrupted compound commands containing stderr redirects
- #347: validate_json_extension() helper added to json_cmd.rs — early
rejection with clear error message for TOML/YAML/XML/CSV/etc. files,
with a dedicated tip for Cargo.toml (suggests rtk deps)
Tests: 16 new unit tests (4 + 6 + 6), 7 new hook integration tests.
All 672 tests pass. Quality gate clean.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: curl size guard (#297) + exclude_commands config (#243)
**#297 — curl JSON inflation guard**
- `rtk curl` no longer replaces small JSON responses with a type schema
that is larger than the original payload
- Size guard in `filter_curl_output()`: only return schema when
`schema.len() <= original.len()`
- Inspired by PR #324 (@rursache)
**#243 — exclude_commands config**
- Add `[hooks] exclude_commands = ["curl"]` in config.toml to prevent
the auto-rewrite hook from rewriting specific commands
- Survives `rtk init -g` re-runs (config.toml is user-owned)
- Implemented in `rewrite_cmd::run()` via `HooksConfig` struct
- Threaded through `rewrite_command()` → `rewrite_compound()` → `rewrite_segment()`
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: add #297 and #243 to CHANGELOG + exclude_commands in README
* docs: fix version references 0.24.0 → 0.25.0 across all docs
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add anonymous telemetry ping (1/day, opt-out)
Fire-and-forget daily ping to track active installs for BPI metrics.
- SHA-256 device hash (hostname:user), no PII
- Opt-out: RTK_TELEMETRY_DISABLED=1 or config.toml [telemetry] enabled=false
- Token injected at compile time via option_env!, not in source
- 23h throttle via marker file, 2s timeout, non-blocking thread
* ci: inject RTK_TELEMETRY_TOKEN at build time in release workflow
All 3 build jobs (binary, DEB, RPM) now receive the token from
GitHub secrets so option_env!() compiles it into release binaries.
* refactor: externalize telemetry URL via option_env!(RTK_TELEMETRY_URL)
URL and token are both injected at compile time — no hardcoded
server address in source code. Builds without the env var simply
skip telemetry silently.
* feat: add `rtk rewrite` command — single source of truth for hook rewrites
Implements `rtk rewrite <cmd>` as the canonical rewrite engine for all
LLM hook integrations (Claude Code, Gemini CLI, future tools).
- Add `rewrite_prefixes` field to `RtkRule` in discover/registry.rs
- Add public `rewrite_command()` with compound command support (&&, ||, ;, |)
- Add `rewrite_segment()`, `rewrite_compound()`, `strip_word_prefix()` helpers
- Handle already-rtk commands (exit 0, identical output)
- Handle unsupported/ignored commands (exit 1, no output)
- Add 20 unit tests covering all branches and edge cases
- Create `src/rewrite_cmd.rs` thin CLI wrapper
- Register `Commands::Rewrite` in main.rs
- Simplify `.claude/hooks/rtk-rewrite.sh` from 357 → 60 lines
Hooks no longer need duplicate mapping logic — a single
`REWRITTEN=$(rtk rewrite "$CMD") || exit 0` handles everything.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: fix version references and module count for 0.22.2
- README.md, CLAUDE.md, ARCHITECTURE.md: 0.20.1 → 0.22.2
- ARCHITECTURE.md: module count 48 → 51 (added rewrite_cmd + 2 from master)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: update hook coverage check to verify registry (not hook script)
Since PR #241, the hook delegates to `rtk rewrite` — command mappings
live in src/discover/registry.rs, not the bash hook script.
Update the "Verify hook coverage" CI step to:
- Check that the hook calls `rtk rewrite` (new architecture)
- Check that registry.rs has rewrite_prefixes for all Python/Go commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: support single `&` background operator in compound rewrites
Per feedback from @xDelph: AI agents increasingly use `cmd1 & cmd2`
for parallel execution. This commit adds support alongside existing
`&&`, `||`, `;`, and `|` operators.
Changes:
- rewrite_compound: add match arm for single `&` (after `&&` check)
- rewrite_command: add `" & "` to has_compound detection
- init: show "installed/updated" vs "already up to date" so users
know whether rtk init changed the hook on re-run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add Python/Go commands to rewrite registry
Add `ruff`, `pytest`, `pip`, `go`, and `golangci-lint` to both
PATTERNS and RULES in registry.rs so the CI coverage check passes
and `rtk rewrite` correctly identifies these commands.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update version reference to 0.23.0 in README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update version reference to 0.23.0 in CLAUDE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update version and module count to 0.23.0/52 in ARCHITECTURE.md
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: address P0 review feedback on rtk-rewrite PR
P0.1 - Replace 218-line bash hook with 58-line thin delegating hook.
All rewrite logic now lives exclusively in `rtk rewrite` (Rust registry).
`rtk init` installs the thin hook via include_str!.
P0.2 - Fix `head -20 file` crash at runtime.
Generic prefix replacement produced `rtk read -20 file` (invalid clap args).
Now translates `head -N file` → `rtk read file --max-lines N` and skips
unsupported head flags (e.g. -c) by returning exit 1.
P0.3 - Add version guard in hook for rtk < 0.23.0.
Prints a warning to stderr instead of silently doing nothing.
Also adds missing registry entries vs old hook:
- gh release, cargo install
- docker run/exec/build, kubectl describe/apply
- tree, diff
474 tests pass, 0 clippy warnings.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: cargo fmt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(discover): extract rules/patterns into rules.rs
Per aeppling review: registry.rs was too large (~1500 lines).
Extract all static data into src/discover/rules.rs:
- RtkRule struct (with pub fields)
- PATTERNS const array
- RULES const array
- IGNORED_PREFIXES and IGNORED_EXACT const arrays
registry.rs now contains only logic + tests.
rules.rs is the single place to add a new command mapping.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update version reference to 0.24.0 in README
* docs: update version references to 0.24.0 in CLAUDE.md and ARCHITECTURE.md
* fix(discover): add aws and psql to rewrite registry
rtk aws and rtk psql modules exist since PR #216 but were missing
from the registry rules — rewrite was silently skipping them.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* test(discover): exhaustive regression tests for rewrite registry
Cover all command categories added in PR #241 and missing from
the previous test suite:
- aws / psql (PR #216 modules, gap detected during non-reg run)
- Python: ruff, pytest, python -m pytest, pip, uv pip
- Go: go test/build/vet, golangci-lint
- JS/TS: vitest, pnpm vitest, prisma, prettier, pnpm list
- Compound operators: || and ; rewrites, 4-segment chains,
mixed supported/unsupported segments, all-unsupported → None
- sudo prefix rewrite, env var prefix rewrite
- find with native flags
- Registry invariants: PATTERNS/RULES aligned, all rules valid,
all patterns are valid regex
Before: 559 tests. After: 607 tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(migration): document breaking change + detect outdated hook in rtk config
CHANGELOG [Unreleased]:
- Migration required: rtk init --global needed after upgrade
- Documents upgrade path and explains no immediate breakage
rtk init --show / rtk config now detects old hook (inline if-else)
vs new thin delegator (rtk rewrite) and prints actionable warning:
⚠️ Hook: ~/.claude/hooks/rtk-rewrite.sh (outdated — inline logic, not thin delegator)
→ Run `rtk init --global` to upgrade to the single source of truth hook
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: update hook references for thin delegator (0.24.0)
- ARCHITECTURE.md: fix hook description (shell script → thin delegator)
- INSTALL.md: add upgrade path for old hook users (pre-0.24 breaking change)
- SECURITY.md: add registry.rs + hook files to Tier 1 critical files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs(arch): fix module count 52→55, add aws/psql/rewrite to module map
Adds aws_cmd, psql_cmd (PR #216) and rewrite_cmd (this PR) to the
Complete Module Map table. Updates total from 52 to 55 to match main.rs.
Fixes CI validate-docs.sh module count mismatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* ci: fix hook coverage check to search rules.rs instead of registry.rs
After the PR #241 refactor, rewrite_prefixes constants moved from
registry.rs to rules.rs. Update grep to search the whole discover/
directory so it finds them in the right file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Three bugs caused `rtk playwright test` to always fail with EXIT: 1:
1. **Wrong argument order**: `--reporter=json` was inserted before the
subcommand (e.g. `playwright --reporter=json test`), but Playwright
requires flags after the subcommand (`playwright test --reporter=json`).
2. **Float duration deserialization**: `PlaywrightStats.duration` was typed
as `u64`, but real Playwright output emits a float (e.g. `3519.703...`),
causing serde_json to fail with a type error.
3. **Wrong suite structure**: Suites contain `specs` (not `tests`). Each
spec has `ok: bool` and `tests` (per-browser executions). The old struct
used `tests: Vec<PlaywrightTest>` directly in suites, which never matched
real output and always failed JSON parsing.
Fixes:
- Move `--reporter=json` to after the first arg (the subcommand)
- Change `duration: u64` to `duration: f64` in `PlaywrightStats`
- Rewrite `PlaywrightSuite` → `specs: Vec<PlaywrightSpec>`
- Add `PlaywrightSpec` with `ok: bool` and `tests: Vec<PlaywrightExecution>`
- Add `PlaywrightExecution` with `status` and `results: Vec<PlaywrightAttempt>`
- Update `PlaywrightAttempt` to use `errors: Vec<PlaywrightError>` (array)
- Add tests covering float duration, real suite structure, and failure details
Co-authored-by: devkeruse <devkeruse@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: passthrough fallback when Clap parse fails
When RTK cannot parse a command (e.g. `rtk git -C /path status`),
instead of exiting with error code 2, it now falls back to running
the raw command directly. This keeps developer workflows unbroken.
- Replace Cli::parse() with try_parse() + run_fallback()
- Add parse_failures SQLite table for failure analytics
- Add `rtk gain --failures` / `-F` to view failure log
- Fallback preserves stdin/stdout/stderr via Stdio::inherit()
- --help and --version still work normally
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: guard RTK meta-commands from fallback to raw execution
When Clap fails to parse a meta-command like `rtk gain --badtypo`,
show the Clap error directly instead of trying to execute `gain`
as a binary from $PATH. Adds RTK_META_COMMANDS constant listing
gain, discover, learn, init, config, proxy, hook-audit, cc-economics.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: move timer start before command execution in fallback
TimedExecution::start() was called after the command finished,
so all fallback commands showed ~0ms in rtk gain --history.
Now the timer captures actual command runtime.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add cleanup_old() call to record_parse_failure()
The parse_failures table was never cleaned up because only record()
called cleanup_old(). Now parse failures also trigger 90-day retention
cleanup.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: strip ANSI codes from Clap error before SQLite storage
Clap errors may contain terminal color codes. Strip them with
utils::strip_ansi() before storing in the parse_failures table
to avoid garbled output and wasted space.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: smoke test cargo test assertion and missing skip_test calls
- cargo test check now matches RTK's filtered output format ("passed")
in addition to raw "test result:" and "FAILURES"
- Fix undefined `skip` calls to use existing `skip_test` function
with proper (name, reason) arguments
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: fmt upstream files after rebase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: update version refs to 0.23.0 and module count to 51
Upstream v0.23.0 release bumped Cargo.toml version and added mypy_cmd
module. Update docs to match for CI validation.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
run_push, run_pull, run_fetch, run_stash (pop/apply/drop/push and
default), and run_worktree all printed FAILED messages on error but
returned Ok(()) instead of propagating git's exit code.
This breaks CI/CD pipelines and shell scripts that rely on exit code
semantics (e.g. `git push || handle_error`).
Fix: call std::process::exit(output.status.code().unwrap_or(1)) in
each failure branch, matching the pattern already used in run_log,
run_diff, run_add, and run_branch.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the existing git global options support to accept boolean flags
that the hook already strips for subcommand matching but Clap was rejecting.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
git -C <path> and other global options were rejected because Clap
expected a subcommand immediately after `git`. Add these options to
the Git command variant and thread them through all git subcommand
handlers via a git_cmd() helper that prepends global args before
the subcommand.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(gain): add per-project token savings with -p flag
Record project_path (cwd) in tracking database and add filtered query
methods. `rtk gain -p` shows savings scoped to the current project
directory instead of global aggregates.
- tracking.rs: Add project_path column with auto-migration, index,
and filtered variants for all query methods (summary, daily, weekly,
monthly, recent)
- gain.rs: Add resolve_project_scope(), shorten_path(), scope-aware
header, pass project filter to all queries and exports
- main.rs: Add --project/-p flag to Gain command
Backward-compatible: existing rows get empty project_path, unfiltered
queries delegate to filtered(None) which returns all data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(tracking): use GLOB instead of LIKE for project path filtering
Address reviewer feedback on PR #128:
1. Replace SQL LIKE with GLOB in all project-scoped queries to prevent
`_` and `%` characters in path names from being interpreted as
wildcards (e.g., `my_project` matching `myXproject`). GLOB uses `*`
for wildcard matching which is safer for file system paths.
2. Guard the startup `UPDATE commands SET project_path = ''` migration
with an `EXISTS` check so it only runs when NULL rows actually exist,
avoiding a no-op UPDATE on every startup after the first migration.
3. Add `DEFAULT ''` to the ALTER TABLE migration so new installs never
create NULL project_path values.
4. Add 3 new unit tests for project_filter_params GLOB behavior.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add mypy command with grouped error output (80% token reduction)
Add rtk mypy module that parses mypy type checker output, groups errors
by file and error code, and produces compact summaries. Includes
discovery registry pattern, hook rewrites, and 11 new tests (324 total).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review - remove specs/ and clean CLAUDE.md
Remove process docs (spec, plan, tasks, research, checklists) from
specs/001-mypy-cmd/ and strip "Active Technologies" / "Recent Changes"
sections from CLAUDE.md per PR #109 review feedback.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address review - deduplicate mypy handler, delegate from lint_cmd
Make lint_cmd.rs delegate to mypy_cmd::filter_mypy_output() instead of
maintaining a separate, less capable implementation. The mypy_cmd version
handles column numbers, note continuations, file-less errors, and shows
full error details. Removes 145 lines of duplicate code + 2 redundant tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Robert Mohid <rmohid@Roberts-Mini.ht.home>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`go test -json` emits build failures as `build-output`/`build-fail`
actions with `ImportPath` (not `Package`/`Test`), followed by a
package-level `fail` with a `FailedBuild` field. The JSON parser
only counted failures when `event.test.is_some()`, so build errors
were silently dropped from the summary — showing "891 passed" when
a package failed to compile.
Now parses `build-output`, `build-fail`, and `FailedBuild` fields.
Build failures count toward the total and display with `[build failed]`
plus the compiler error lines.
Before:
✓ Go test: 891 passed in 74 packages
After:
Go test: 891 passed, 1 failed in 74 packages
═══════════════════════════════════════
📦 stream [build failed]
handler_test.go:64:9: undefined: undefinedVariable
Co-authored-by: Ricardo Liberato <ricardo@liberato.pt>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The shell keyword "fi" was listed as a bare prefix in IGNORED_PREFIXES,
causing classify_command("find ...") to return Ignored because
"find".starts_with("fi") is true. This prevented find commands from
being rewritten by rtk rewrite and the hook system.
Move "fi" and "done" from IGNORED_PREFIXES to IGNORED_EXACT so they
only match as exact standalone keywords, not as prefixes of other
commands.
Fixes#170, #204, #236 (partially — find classification was the root
blocker for rtk rewrite).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Three bugs fixed:
1. JSON struct mismatch: The parser expected `suites[].tests[]` but
Playwright's JSON reporter outputs `suites[].specs[].tests[]`.
Added PlaywrightSpec layer, use `spec.ok` for failure detection,
and `errors: Vec<PlaywrightError>` (array, not Option).
2. Float duration: Playwright emits `duration` as f64, not u64.
Changed struct field type to avoid deserialization failures.
3. pyenv shim collision: `which playwright` can resolve to a pyenv
shim (Python's playwright) which lacks a `test` subcommand.
Bypass `which` entirely and resolve through the detected package
manager (npx/pnpm/yarn).
Also strips user-supplied `--reporter` flags to avoid conflicts with
the forced `--reporter=json`.
rtk grep previously returned exit 0 for all failure modes (no match,
invalid regex, missing file) where rg returns non-zero. This broke
shell workflows relying on exit code semantics.
Now propagates rg exit codes: 1 (no match), 2 (error) and surfaces
stderr for error cases so users see why the command failed.
Fixes#162
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`rtk git branch <name>` fell into the listing code path which injected
`-a --no-color`, turning the creation into a pattern-filtered listing
(silent no-op with exit 0). Detect positional args without list-mode
flags and route them to direct passthrough so branch creation, including
`git branch <name> <start-point>`, works correctly.
Adds regression integration tests for both cases.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Two bugs when passing grep-idiom arguments to rtk grep:
1. Pattern alternation `\|` (BRE syntax) returns 0 results because rg
uses PCRE-style `|`, not BRE `\|`. Users coming from grep/awk muscle
memory naturally write `rtk grep "fn foo\|fn bar" src/` and get
empty output with no error.
Fix: translate `\|` → `|` in pattern before passing to rg.
2. `-r`/`--recursive` flag (grep idiom) silently breaks the search.
In rg, `-r` means `--replace` and requires an argument — passing it
alone causes rg to exit with an error. The `.or_else(|_| ...)` fallback
only catches IO errors (command not found), so the grep fallback never
triggers; stdout is empty and rtk reports "0 for '<pattern>'".
Fix: filter out `-r`/`--recursive` from extra_args before building
the rg command (rg is recursive by default, flag is a no-op).
Adds two regression tests documenting both behaviours.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
rtk lint was always returning exit code 0 regardless of the underlying
linter's exit status, causing false-green CI behavior when lint should fail.
Uses !output.status.success() pattern consistent with all other RTK modules,
which correctly handles both non-zero exit codes and signal termination.
Fixes#185
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Adds a new `wc` subcommand that compresses wc output by:
- Stripping alignment padding: ` 30 96 978 file.py` → `30L 96W 978B`
- Removing common path prefixes in multi-file output
- Returning bare numbers for single-flag modes (-l, -w, -c, -m)
- Showing Σ totals instead of verbose "total" lines
Includes 15 unit tests covering single/multi-file, stdin, all flag
modes, and common prefix detection.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
view_run() only passed the run ID to `gh run view`, silently
dropping all subsequent args. This made it impossible to inspect
CI failure logs when the rtk-rewrite hook was active.
Add should_passthrough_run_view() to detect flags that produce
output incompatible with the summary filter, and pass through
to gh directly when found.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(docker): add docker compose support with compact output
Add token-optimized output for docker compose commands:
- `docker compose ps`: compact service listing with status and ports
- `docker compose logs`: deduplicated log output via existing log engine
- `docker compose build`: summary with build time, services, and step count
- Unsupported compose subcommands pass through directly
Includes 11 tests (8 compose format tests + 3 for existing compact_ports).
Fixes#101
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address PR #110 review — use --format for compose ps, fix bytes/chars bugs
- Switch format_compose_ps to parse structured --format tab output
instead of fragile human-readable table (eliminates contains("Up")
false positives and byte/char confusion)
- Fix find('[') byte offset used with chars().skip() in build parsing
- Use .next_back() instead of .last() on DoubleEndedIterator
- Consistent error handling with .context()? in run_compose_ps
- Strengthen test assertion for compose logs
- Add tests for no-ports and long-image-path cases
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: propagate exit codes on compose failures + remove Vec allocation
Address PR #110 review feedback from pszymkowiak:
- Check output.status.success() after .output() in run_compose_ps,
run_compose_logs, and run_compose_build to prevent silent failures
when Docker daemon is not running
- Replace .collect::<Vec<&str>>().len() with .count() in
format_compose_build step counting to avoid unnecessary allocation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* docs: update version references to 0.20.1 after upstream rebase
Sync documentation version strings with Cargo.toml after rebasing
on upstream master. Fixes CI validate-docs check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: drop upstream workflow file from feature branch
This workflow was pulled in during rebase on upstream master.
Not part of the docker compose feature — let upstream manage it.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* chore: restore validate-docs workflow from upstream
Workflow was unintentionally removed while resolving a push
permission issue. Restored to match upstream master.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add opt-in audit logging to the RTK rewrite hook and a new `rtk hook-audit`
command to analyze the collected metrics. This provides verifiable data on
hook reliability (rewrite rate, skip reasons, top commands) instead of
relying on anecdotal claims.
- Add `_rtk_audit_log()` function to rtk-rewrite.sh (opt-in via RTK_HOOK_AUDIT=1)
- Log each invocation: timestamp | action | original_cmd | rewritten_cmd
- Actions: rewrite, skip:already_rtk, skip:heredoc, skip:no_match, skip:no_deps, skip:empty
- New `src/hook_audit_cmd.rs` module with log parser and stats display
- New `rtk hook-audit --since N` subcommand (default: last 7 days)
- 8 Rust unit tests + 7 hook bash tests for audit mode
- Make HOOK var overridable in test suite for repo-local testing
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: tee raw output to file for LLM re-read without re-run (#86)
When RTK filters command output, LLM agents lose failure details
(stack traces, assertions) and re-run the same command 2-3x. The tee
feature saves raw output to ~/.local/share/rtk/tee/ on failure and
prints a one-line hint so the agent can read the file instead.
- Add src/tee.rs: core module with tee_raw(), tee_and_hint(), rotation
- Add TeeConfig to config.rs: enabled, mode, max_files, max_file_size
- Integrate in 7 modules: cargo, runner, vitest, pytest, lint, tsc, go
- Default: failures only, skip <500 chars, 20 file rotation, 1MB cap
- Env overrides: RTK_TEE=0 (disable), RTK_TEE_DIR (custom directory)
- 14 unit tests, 352 total tests passing, zero regressions
Closes#86
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: add tee.rs to ARCHITECTURE.md module count (47 modules)
CI validate-docs requires main.rs module count == ARCHITECTURE.md count.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
`rtk grep -n "pattern" path` failed with "unexpected argument '-n'"
because clap didn't recognize -n before the positional arguments.
Users naturally place -n before the pattern (muscle memory from
grep/rg). The flag is a no-op since grep_cmd::run() already passes
-n to ripgrep unconditionally, but clap must accept it.
Adds -n/--line-numbers as an explicit bool field to the Grep command
and ignores it in the match arm. Test added in grep_cmd::tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(gain): add colored dashboard with efficiency meter and impact bars
Upgrade `rtk gain` output with rich terminal visualization:
- Colored percentage cells (green ≥70%, yellow ≥40%, red <40%)
- Efficiency meter: ASCII progress bar with savings percentage
- Impact bars: proportional block charts per command
- Styled section headers (bold green)
- Command names in bright cyan
- History tier indicators (▲/■/•)
- Dynamic column widths for clean table alignment
- KPI-style key-value layout for summary metrics
All visual features are TTY-aware via std::io::IsTerminal — output
degrades gracefully to plain text when piped or redirected.
No new dependencies (uses existing `colored` crate).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add dashboard visualization screenshot
Shows colored terminal output of `rtk gain -p` with:
- Project-scoped token savings header
- Efficiency meter with color gradient
- Per-command table with colored Avg% and Impact bars
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Replace naive append-or-skip logic in run_claude_md_mode with
upsert_rtk_block() that handles all 4 cases:
- Added: no existing block → append
- Updated: stale block → replace in-place
- Unchanged: current block → no-op
- Malformed: opening marker without closing → warn safely
Includes 4 unit tests covering each case.
Addresses all findings from FlorianBruniaux's review of PR #119:
C-02 (CRITICAL): Remove RTK_SKIP_INTEGRITY env var bypass
- Any process in the environment could disable verification
- If attacker can modify hook, they can also set env var
- Recovery path: `rtk init -g --auto-patch` to re-baseline
I-02 (IMPORTANT): Invert is_operational_command to whitelist
- Explicitly enumerate commands requiring integrity check
- New commands fail open (no check) rather than creating
false confidence about protection coverage
I-03 (IMPORTANT): Strict hash file parsing
- Validate exact sha256sum format: "<64 hex> <filename>"
- Reject single-space separator, missing filename, garbage
- Prevents silent acceptance of corrupted hash files
N-01 (NIT): Safe hash truncation in error output
- Use .get(..16).unwrap_or() instead of &[..16] slice
- Prevents potential panic on malformed stored hash
N-02 (NIT): Remove bypass hint from error message
- "To override: RTK_SKIP_INTEGRITY=1" leaked mechanism
- Resolved automatically by C-02 (env var removed entirely)
Tests: 14 → 16 (+hash_only_no_filename, +wrong_separator)
Add token-optimized filtering for `cargo nextest` output: strip
compilation noise, PASS lines, and post-summary recaps; show only
failure details and a compact summary. Extract flush_failure_block
helper to DRY up failure block collection (was repeated 4 times).
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(cargo): aggregate test output into single line (#83)
Problem: `cargo test` shows 24+ summary lines even when all pass.
An LLM only needs to know IF something failed, not 24x "ok".
Before (24 lines):
```
✓ test result: ok. 2 passed; 0 failed; ...
✓ test result: ok. 0 passed; 0 failed; ...
... (x24)
```
After (1 line):
```
✓ cargo test: 137 passed (24 suites, 1.45s)
```
Changes:
- Add AggregatedTestResult struct with regex parsing
- Merge multiple test summaries when all pass
- Format: "N passed, M ignored, P filtered out (X suites, Ys)"
- Fallback to original behavior if parsing fails
- Failures still show full details (no aggregation)
Tests: 6 new + 1 modified, covering all cases:
- Multi-suite aggregation
- Single suite (singular "suite")
- Zero tests
- With ignored/filtered out
- Failures → no aggregation (detail preserved)
- Regex fallback
Closes#83
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add Python and Go support (#88)
* feat(cargo): aggregate test output into single line (#83)
Problem: `cargo test` shows 24+ summary lines even when all pass.
An LLM only needs to know IF something failed, not 24x "ok".
Before (24 lines):
```
✓ test result: ok. 2 passed; 0 failed; ...
✓ test result: ok. 0 passed; 0 failed; ...
... (x24)
```
After (1 line):
```
✓ cargo test: 137 passed (24 suites, 1.45s)
```
Changes:
- Add AggregatedTestResult struct with regex parsing
- Merge multiple test summaries when all pass
- Format: "N passed, M ignored, P filtered out (X suites, Ys)"
- Fallback to original behavior if parsing fails
- Failures still show full details (no aggregation)
Tests: 6 new + 1 modified, covering all cases:
- Multi-suite aggregation
- Single suite (singular "suite")
- Zero tests
- With ignored/filtered out
- Failures → no aggregation (detail preserved)
- Regex fallback
Closes#83
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add Python and Go language support
Implements comprehensive support for Python and Go development tooling
with 70-90% token reduction across all commands.
Python commands (3):
- rtk ruff: Linter/formatter with JSON (check) and text (format) parsing (80%+)
- rtk pytest: Test runner with state machine text parser (90%+)
- rtk pip: Package manager with auto-detect uv (70-85%)
Go commands (4):
- rtk go test: NDJSON streaming parser for interleaved test events (90%+)
- rtk go build: Text filter showing errors only (80%)
- rtk go vet: Text filter for issues (75%)
- rtk golangci-lint: JSON parser grouped by rule (85%)
Architecture:
- Standalone Python commands (mirror lint/prettier pattern)
- Go sub-enum (mirror git/cargo pattern)
- 5 new modules: ruff_cmd, pytest_cmd, pip_cmd, go_cmd, golangci_cmd
- Hook integration in rtk-rewrite.sh for transparent rewrites
- Comprehensive tests (47 new tests, all passing)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(benchmark): add Python and Go commands
Add benchmark sections for Python (ruff, pytest, pip) and Go (go test/build/vet, golangci-lint) to validate >80% token savings in CI pipeline.
Sections conditionally execute based on project markers (pyproject.toml, go.mod) and tool availability.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: make install-local.sh self-contained (#89)
- Build from source automatically instead of requiring a pre-built binary
- Default install dir to ~/.cargo/bin
- Skip rebuild when binary is up to date
- Warn if install dir is not in PATH
* chore(master): release 0.15.0 (#90)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix(vitest): robust JSON extraction for pnpm/dotenv prefixes (#92)
* fix(vitest): robust JSON extraction for pnpm/dotenv prefixes
Problem: RTK's vitest parser forces --reporter=json but pnpm/dotenv prepend
non-JSON text to stdout (banners, env messages), causing 100% Tier 1 failure
and useless 500-char passthrough.
Solution:
- Add extract_json_object() to parser/mod.rs (shared utility)
- Algorithm: find "numTotalTests" or first standalone {, brace-balance forward
- VitestParser now tries direct parse → extract+parse → regex → passthrough
- Replace hardcoded Command::new("pnpm") with package_manager_exec("vitest")
- Delete orphan doc comment on line 203
Impact:
- Before: 100% Tier 3 passthrough with pnpm workflows
- After: Tier 1 success with prefixes, maintains 99.5% token savings
Tests:
- 6 tests for extract_json_object (clean, pnpm, dotenv, nested, no-json, strings)
- 3 tests for VitestParser with prefixes
- All 277 tests pass
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore(benchmark): add vitest, pnpm, and gh commands
Add benchmarks for recently implemented commands:
- vitest run (PR #92 - JSON extraction fix)
- pnpm list/outdated (PR #6)
- gh pr list/run list (existing gh support)
These commands are now tested in CI to ensure token savings are maintained.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix: improve CI reliability and hook coverage (#95)
* feat(cargo): aggregate test output into single line (#83)
Problem: `cargo test` shows 24+ summary lines even when all pass.
An LLM only needs to know IF something failed, not 24x "ok".
Before (24 lines):
```
✓ test result: ok. 2 passed; 0 failed; ...
✓ test result: ok. 0 passed; 0 failed; ...
... (x24)
```
After (1 line):
```
✓ cargo test: 137 passed (24 suites, 1.45s)
```
Changes:
- Add AggregatedTestResult struct with regex parsing
- Merge multiple test summaries when all pass
- Format: "N passed, M ignored, P filtered out (X suites, Ys)"
- Fallback to original behavior if parsing fails
- Failures still show full details (no aggregation)
Tests: 6 new + 1 modified, covering all cases:
- Multi-suite aggregation
- Single suite (singular "suite")
- Zero tests
- With ignored/filtered out
- Failures → no aggregation (detail preserved)
- Regex fallback
Closes#83
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(ci): prevent Python/Go benchmark sections from being silently skipped
**Problem:**
Python and Go benchmark sections were silently skipped in CI because
the RTK repository doesn't contain pyproject.toml or go.mod files.
The sections only ran when these project files existed.
**Solution:**
1. Create temporary fixtures with minimal project structure:
- Python: pyproject.toml + sample.py + test_sample.py
- Go: go.mod + main.go + main_test.go
2. Resolve RTK to absolute path to work after cd into temp dirs
3. Install required tools in CI workflow:
- Python: ruff, pytest
- Go: stable version + golangci-lint
**Impact:**
- Python/Go sections now appear in CI benchmark output
- Self-contained fixtures ensure consistent benchmarking
- No dependency on RTK project structure
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(hooks): add missing RTK command rewrites
Add 8 missing command rewrites to rtk-rewrite.sh and rtk-suggest.sh:
- cargo check/install/fmt
- tree, find, diff
- head → rtk read (with --max-lines transformation)
- wget
Fixes BSD sed compatibility for head transformation by using literal
spaces instead of \s+ (which doesn't work on macOS).
Impact: ~18.2K tokens saved on previously missed commands discovered
by `rtk discover`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
* chore(master): release 0.15.1 (#96)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat(python): add lint dispatcher + universal format command
Phase 1: Enhanced rtk lint
- Add pylint JSON2 parser (80-85% token savings)
- Add mypy text parser (75-80% token savings)
- Smart dispatcher: Python tools (pip) vs JS tools (npm)
- Reuse ruff_cmd JSON parser for rtk lint ruff
Phase 2: New rtk format command
- Universal formatter: black/ruff/prettier
- Auto-detect from pyproject.toml/package.json
- Implement black output parser (70-85% savings)
- Reuse existing prettier/ruff formatters
Phase 3: Hook integration
- Auto-rewrite: pylint → rtk lint pylint
- Auto-rewrite: mypy → rtk lint mypy
- Auto-rewrite: black --check → rtk format black
Files changed:
- src/lint_cmd.rs: +454 lines (pylint/mypy parsers, dispatcher)
- src/format_cmd.rs: +386 lines (NEW - universal formatter)
- src/ruff_cmd.rs: Export filter functions as pub
- src/prettier_cmd.rs: Export filter_prettier_output as pub
- src/main.rs: Add Commands::Format + routing
- hooks/rtk-rewrite.sh: Add Python tool rewrite rules
Testing: 10 new unit tests, all 313 tests passing
Impact: 80-90% token savings on Python workflows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Michael Coen <mhcoen@gmail.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: prevent UTF-8 panics on multi-byte characters (Thai, emoji, CJK)
Replace all byte-indexed string slicing (`&s[..n]`) with char-aware
operations across 10 locations in 7 files. Rust strings are UTF-8, so
byte slicing can land mid-character on Thai (3 bytes), emoji (4 bytes),
or CJK text and panic at runtime.
Fixes applied:
- git.rs: filter_log_output, format_status_output
- log_cmd.rs: error/warning message truncation (2 locations)
- env_cmd.rs: long value display, mask_value
- parser/mod.rs: truncate_output
- grep_cmd.rs: clean_line (pattern context + fallback truncation)
- wget_cmd.rs: compact_url, truncate_line, parse_error
Also adds safe_char_boundary() utility and 20 regression tests covering
Thai, emoji, and CJK input across all affected modules.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: collect chars once in compact_url to avoid redundant iteration
Address review feedback: collect chars into Vec once instead of
calling .chars() multiple times. Also fixes byte-vs-char length check.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: remove unused safe_char_boundary function and tests
Address review: function was defined but never called anywhere.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: convert remaining is_char_boundary to full .chars() approach
Address review: use consistent .chars() everywhere instead of
mixing byte-based is_char_boundary with char-based operations.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(cargo): aggregate test output into single line (#83)
Problem: `cargo test` shows 24+ summary lines even when all pass.
An LLM only needs to know IF something failed, not 24x "ok".
Before (24 lines):
```
✓ test result: ok. 2 passed; 0 failed; ...
✓ test result: ok. 0 passed; 0 failed; ...
... (x24)
```
After (1 line):
```
✓ cargo test: 137 passed (24 suites, 1.45s)
```
Changes:
- Add AggregatedTestResult struct with regex parsing
- Merge multiple test summaries when all pass
- Format: "N passed, M ignored, P filtered out (X suites, Ys)"
- Fallback to original behavior if parsing fails
- Failures still show full details (no aggregation)
Tests: 6 new + 1 modified, covering all cases:
- Multi-suite aggregation
- Single suite (singular "suite")
- Zero tests
- With ignored/filtered out
- Failures → no aggregation (detail preserved)
- Regex fallback
Closes#83
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat: add Python and Go language support
Implements comprehensive support for Python and Go development tooling
with 70-90% token reduction across all commands.
Python commands (3):
- rtk ruff: Linter/formatter with JSON (check) and text (format) parsing (80%+)
- rtk pytest: Test runner with state machine text parser (90%+)
- rtk pip: Package manager with auto-detect uv (70-85%)
Go commands (4):
- rtk go test: NDJSON streaming parser for interleaved test events (90%+)
- rtk go build: Text filter showing errors only (80%)
- rtk go vet: Text filter for issues (75%)
- rtk golangci-lint: JSON parser grouped by rule (85%)
Architecture:
- Standalone Python commands (mirror lint/prettier pattern)
- Go sub-enum (mirror git/cargo pattern)
- 5 new modules: ruff_cmd, pytest_cmd, pip_cmd, go_cmd, golangci_cmd
- Hook integration in rtk-rewrite.sh for transparent rewrites
- Comprehensive tests (47 new tests, all passing)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* feat(benchmark): add Python and Go commands
Add benchmark sections for Python (ruff, pytest, pip) and Go (go test/build/vet, golangci-lint) to validate >80% token savings in CI pipeline.
Sections conditionally execute based on project markers (pyproject.toml, go.mod) and tool availability.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: `cargo test` shows 24+ summary lines even when all pass.
An LLM only needs to know IF something failed, not 24x "ok".
Before (24 lines):
```
✓ test result: ok. 2 passed; 0 failed; ...
✓ test result: ok. 0 passed; 0 failed; ...
... (x24)
```
After (1 line):
```
✓ cargo test: 137 passed (24 suites, 1.45s)
```
Changes:
- Add AggregatedTestResult struct with regex parsing
- Merge multiple test summaries when all pass
- Format: "N passed, M ignored, P filtered out (X suites, Ys)"
- Fallback to original behavior if parsing fails
- Failures still show full details (no aggregation)
Tests: 6 new + 1 modified, covering all cases:
- Multi-suite aggregation
- Single suite (singular "suite")
- Zero tests
- With ignored/filtered out
- Failures → no aggregation (detail preserved)
- Regex fallback
Closes#83
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Strip dep compilation, downloading, locking noise from cargo install output.
Keep only installed/replaced/error lines with compact summary format.
Closes#69
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>