The section landed in docs/TROUBLESHOOTING.md, a flat file removed in
a94e9493 when the docs were consolidated. Content there does not reach
the published guide, and the copy reintroduced install guidance that
develop had already pinned to --branch master.
Move the answer into docs/guide/resources/troubleshooting.md and drop the
duplicated Type Kit collision section, which that guide already covers.
The cache write/read breakdown is reported by `rtk cc-economics`, not by
`rtk gain`, so point readers at the command that actually shows it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds `rtk sqlfluff lint` for SQL linting workflows, particularly dbt
projects. Discovered as a savings opportunity via `rtk discover`.
- Injects `--format json` automatically; passes through if user sets it
- Groups violations by rule (top N) and by file with per-file breakdown
- Surfaces first violation location per rule and line numbers per file
- Shows fixable count and suggests `sqlfluff fix` when relevant
- Passes fix/format/version/parse subcommands through unchanged (capped)
- Registers hook rule in `discover/rules.rs` to auto-rewrite `sqlfluff lint`
- Preserves exit codes for CI/CD compatibility
- dbt-aware path compaction: keeps models/, macros/, seeds/ prefixes
Real-world tested on a production dbt project. Tolerates both v3+
`start_line_no` and legacy `line_no` schemas.
17 tests: happy path, edge cases, JSON parse fallback, real schema
regression, hook classification/rewrite, and token savings assertion (≥60% verified).
Failure detection, so a green run is never reported red:
- a failure marker vouches for the block it closes, so a frameless failure
such as a timeout keeps the line that says why it failed
- bun's marker and run footer are matched with the duration and test count
they carry, since a test is free to log a line that starts the same way
- deno's FAILURES entries are matched by shape, because a bare " => " also
occurs in arrow functions and assertion messages
- deno fences output it did not write, and names the fence for when it was
printed, so the rule is matched by shape and gates both the failures list
and the block engine
Diagnostics that were reaching nobody:
- deno's type-check errors are kept with the frame under them: they open no
block and carry no section, so a type error left the caller with a bare
"Type checking failed."
- bun's frames are kept, since two failing tests that share a name, and a
module error that has no marker at all, cannot otherwise be located
Runs whose output rtk must not touch:
- watch mode on every filtered entry point goes through unfiltered, since the
runners buffer the whole stream until a child that never exits
- a deno reporter the user named is left alone, because --reporter=junit and
--junit-path write the report to stdout
- bun build and deno compile run unfiltered: without an output flag the bundle
is stdout, with one the emitted-file summary is the point of the run, and on
failure the diagnostics are, none of which an errors-only filter keeps
Tool resolution:
- a package runner the user named wins over lockfile detection, and over a
tool that merely happens to be on PATH
- lockfile detection never resolves through bunx, which always fetches a
missing tool and cannot be told not to
- a tool rtk may fetch resolves through npx, the only runner that can
Input and output:
- bunx forwards the caller's stdin when it is a pipe, since the filtered path
buffers output and an inherited terminal would let a prompt wait invisibly
- strip the resolution lines bun actually prints when piped, count the tree
levels drawn with a leading pipe, and stop injecting --json into a command
the user typed
- the lint label records the arguments once, and the shell runners' placeholder
tool name stays out of their verbose line
Fixtures are real bun 1.2.20 and deno 2.9.6 captures for each case.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- bun_cmd.rs: bun build passes through unless an output flag sends the bundle
to disk, and bunx uses the light npm filter instead of errors-only
- core/runner.rs: the err and test runners record the tool that actually ran,
with a per-tool tee slug
- utils.rs, tsc_cmd.rs, main.rs: resolve tools via the project package manager,
and keep the tool name when routing eslint
- tracking.rs, README.md: categorize bun and deno as js, and describe what the
deno check and bun build filters really do
`exclude_commands` entries name a tool, but a command can spell that tool with a
wrapper (`npx playwright test`), an interpreter (`python3 -m pytest tests/`) or a
path (`vendor/bin/phpunit tests/`). Those spellings are absorbed by each rule's own
pattern rather than stripped beforehand, so the anchored `^playwright($|\s)` never
matched and the exclusion silently did nothing — the README shipped
`exclude_commands = ["curl", "playwright"]` as the example, and `playwright` is a
tool almost nobody invokes bare.
Peel the wrapper off the command and match what remains, alongside the existing
check on the typed command. The peeled form keeps the arguments, so an anchored
entry still narrows the way it was written: `"^ls$"` excludes a bare `ls` without
swallowing `ls -la`.
Peeling uses the rule's own `rewrite_prefixes`, taking the shortest token-suffix of
the matched prefix that is itself a prefix of that rule. That drops `npx` and
`python3 -m` while keeping a subcommand the rule treats as part of the tool, so
`golangci-lint run` does not collapse to `run`.
Peeling reuses the PHP normalization the rewrite path already applies (`php`
wrapper and ini flags, leading `./`, vendor/composer bin dir), extracted into
`php_tool_form` and shared by both, so `php vendor/bin/phpunit tests/` is excluded
by `["phpunit"]` the same way `vendor/bin/phpunit tests/` is.
The peeled check is gated on a non-empty `exclude_commands`, keeping the default
config off the `RULES` scan on the hook rewrite path.
Matching the resolved `rtk` target instead would have been shorter but wrong in
both directions: it misses tools whose target differs from the binary (`["eslint"]`
would still rewrite `npx eslint .`, since the target is `lint`), and it leaks
across tools sharing a target (`["read"]` would exclude `cat`, `["git"]` would
exclude `yadm`). Peeling has neither failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add `--agent omp` to `rtk init` (with `-g`, `--uninstall`, `--show`)
for the Oh My Pi coding agent (https://github.com/can1357/oh-my-pi).
OMP loads the same `hooks/pi/rtk.ts` extension via its built-in
legacy-pi-compat layer, which remaps the Pi package imports to OMP's
bundled equivalent — so no separate OMP implementation is needed and
the rewrite behavior stays byte-identical (mutualization).
- Local scope: <project>/.omp/extensions/rtk.ts
- Global scope: ~/.omp/agent/extensions/rtk.ts
- `--uninstall` is three-way safe: missing → no-op, stock content →
removed, modified RTK content → bail with manual-removal guidance
- `--show` reports both scopes (installed / stock / modified / absent)
Co-authored-by: makoMakoGo <makoMakoGo@users.noreply.github.com>
Review follow-up for #3199: real captured mvnd 1.0.6 fixtures plus
regression tests, as requested.
The fixtures surfaced a daemon-specific bug: mvnd routes all output
through the daemon logger, which prefixes even blank lines with
`[INFO] `. The Surefire failure-trail terminator keyed on a truly
empty line, so under mvnd the trail never closed and everything after
the last failing class passed through verbatim, help boilerplate
included (35% savings instead of the >=60% floor). is_blank_separator
now treats `[INFO]`-only lines as blank in the trail and re-arm states.
- mvnd_reactor_pass_raw.txt: warmed `mvnd clean install` on the
multi-module skeleton — SmartBuilder parallel reactor with
`[module] [INFO]` prefixes and daemon chatter
- mvnd_test_fail_raw.txt: warmed failing `mvnd test` (exit 1) on the
multifail skeleton
- mvnd_compile_error_raw.txt: `mvnd compile` syntax error
- multifail-skeleton: six passing test classes so the failing-run
capture is representative of real projects (an all-failures run is
all signal and makes any savings floor meaningless)
- six regression tests: reactor summary survives, failure names,
messages and user stack frames survive, compile diagnostics survive,
daemon chatter and module-prefixed noise dropped, >=60% token savings
asserted on the success and failure runs
- README: `rtk mvnd` added to the root command list
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses @aeppling's review on #3391:
Blocking fixes:
- run_vibe now returns Ok(()) on malformed JSON (matches run_droid /
run_copilot / run_cursor pattern). Prior code violated the exit-code
contract documented at src/hooks/README.md:100 — a bad payload exited
non-zero and blocked the agent's command. Fixed via a match on
serde_json::from_str with a stderr warning fallback.
- Extract run_vibe_inner(input: &str) -> Option<String> from run_vibe so
the hook contract is unit-testable (mirrors run_droid_inner). Public
run_vibe becomes a thin stdin/stdout wrapper.
- Add 6 runtime tests exercising the hook contract: bash rewrite happy
path, non-bash tool passthrough, empty command passthrough, malformed
JSON returns None, unknown binary passthrough, substitution defers.
Should-fix:
- Telemetry agent detection: add ~/.vibe/hooks.toml to detect_hook_type()
checks in src/core/telemetry.rs, plus the two test enum arrays so Vibe
sessions no longer report as 'unknown' in rtk gain history.
- Dead deny arm: add a comment on Host::Vibe in permissions.rs
documenting that the empty-rules branch is defensive scaffolding for
when Vibe ships native denylist/allowlist config we can honor.
- Broken link: patch_vibe_hooks_toml skip-message now points at
https://www.rtk-ai.app/guide/getting-started/supported-agents#mistral-vibe
instead of a fragment that doesn't resolve.
Nits addressed:
- Install summary no longer prints 'hook installed' when the user chose
PatchMode::Skip or declined the interactive prompt. patch_vibe_hooks_toml
now returns a VibeHookPatchOutcome enum (Installed / AlreadyPresent /
Skipped) and the caller gates the summary on it.
- Document the string-spacing tradeoff on vibe_hooks_toml_has_rtk: a
reformatted 'name="rtk-rewrite"' would defeat idempotency, acceptable
because we control the writer and toml_edit round-trip would clobber
user comments.
- Fix stale line in src/hooks/README.md 'Adding New Functionality':
hook_check.rs::maybe_warn() only checks the Claude Code hook now,
not every agent.
Documentation:
- docs/guide/getting-started/supported-agents.md: frontmatter now lists
Mistral Vibe, drop 'planned' from the intro, tier table row flipped
from 'Planned (#800)' to 'Rust binary (pre_tool) / Yes', replace the
### Mistral Vibe (planned) placeholder with a full user-facing section
modeled on Factory Droid (install/uninstall commands, hook mechanism,
permission semantics, idempotency contract).
- hooks/README.md: agent count 9 -> 10, add Vibe entry to Directory
Structure list, add Vibe row to Supported Agents table, add
'### Mistral Vibe (Rust Binary)' entry to the JSON Formats section
showing the pre_tool input shape and rewrite response shape.
- src/hooks/README.md: agent count 5 -> 6, add Vibe row to per-host
ask-support table.
- README.md: '15 AI coding tools' -> '16'.
No behavior change for existing agents.
Add `rtk init -g --agent vibe` and `rtk hook vibe` to route bash tool
calls through the RTK proxy via Vibe's newly-shipped pre_tool hook.
Implementation follows the Gemini / Droid pattern:
- Native binary hook (`rtk hook vibe`), no shell script dependency.
- Global-only install (`~/.vibe/hooks.toml`); user-scope only.
- Idempotent install: detects existing `name = "rtk-rewrite"` entry.
- Uninstall is surgical: strips only the RTK `[[hooks]]` block and the
`~/.vibe/prompts/rtk.md` prompt file, preserving any other user hooks
byte-for-byte. Removes hooks.toml only when it becomes empty.
- Hook response uses Vibe's documented `hook_specific_output.tool_input`
rewrite contract with a `system_message` for UI visibility.
Vibe hook API reference:
https://docs.mistral.ai/vibe/code/cli/hooksCloses#800.
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.
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.
RTK was documented as delivering "60-90% token savings", which reads as a
cost reduction. What RTK actually reduces is bash output bytes. Those are
one contributor to input tokens, which are themselves only part of a bill
that also counts output tokens, so the reduction dilutes at every step.
- add docs/guide/resources/savings-explained.md as the canonical explainer:
the savings chain, both estimators, and what RTK does not reduce
- rescope the headline claim across README (7 languages), the guide, hook
rules, agent definitions and module READMEs
- relabel per-command tables as bash output reduction, keeping every figure
- document that reported tokens are estimates: rtk gain uses bytes/4
(src/core/tracking.rs), filter tests use split_whitespace().count().
Neither is a real tokenizer, so ratios hold but absolute counts do not
Remove figures that had no source: the $3/Mtok constant and its $36
example, the +/-10% tokenization accuracy claim, the 99.5% hook-install
figure, the invented session tables in README and INSTALL, and the 30-50%
parser range.
CHANGELOG is untouched. Shipped release notes stay as a historical record.
Both READMEs claimed uv's own noise is filtered out. Nothing strips it: on a
cold run the raw output and rtk's output carry the same Resolved/Installed
lines. The module header already documented this correctly, so the user-facing
docs contradicted both the code and the header.
Wraps `php run-tests.php` and collapses its per-test PASS chatter into
one summary line plus a bounded list of failure diffs. On php-src's
5322-test suite with 467 failures, output drops from 1.3 MB to 6 KB
(99.5% reduction). An all-pass run collapses to two lines.
Handles PASS/FAIL/SKIP/BORK/WARN/LEAK/XFAIL/XLEAK, ANSI color codes, the
========DIFF======== / ========DONE======== blocks, and the trailing
FAILED TEST SUMMARY block (which must not be re-counted). XFAIL and
XLEAK are expected results (reported under "Expected fail"/"Expected
leak" by run-tests.php) and do not count as failures.
The hook rewrite maps `php run-tests.php …` to `rtk phpt …` via a rule
in discover/rules.rs, with `phpt` added to the passthrough classifier.
Diff buffering is capped at the display limit while a separate counter
keeps the "+N more diff lines" note accurate. When the summary block
reports failures but per-test lines were truncated out of the capture,
the FAILURES header is still emitted with a note rather than dropped.
Tests: inline units plus an insta snapshot and a count_tokens-based
token-savings assertion (>=60%), per .claude/rules/cli-testing.md.
Adversarial verification against Droid v0.164.0 (shipped code + docs)
found two broken assumptions in the original integration:
- Droid's canonical hooks file is hooks.json (root event map, no wrapper);
the hooks key of settings.json is only a fallback that hooks.json shadows
per event key. Droid's own /hooks UI writes hooks.json, so an RTK entry
in settings.json silently dies as soon as the user adds any PreToolUse
hook there. Install now targets the file whose PreToolUse array Droid
actually reads (live hooks.json > live settings.json fallback > create
canonical hooks.json), migrates stale copies, and uninstall sweeps root
hooks.json, legacy hooks/hooks.json, and settings.json.
- The config-home env var is FACTORY_HOME_OVERRIDE (replaces $HOME, with
.factory appended), not FACTORY_HOME pointing at the config dir.
Also corrects the 'legacy Bash matcher' comment (Droid never had a Bash
tool; the acceptance is purely defensive) and records that the
omit-permissionDecision rewrite path is confirmed in v0.164.0: decisions
resolve as find(first result with a decision) and updatedInput applies
via a separate path even when no decision is set.
Claude-Session: https://claude.ai/code/session_01Vp4p5YroP2cfFvThhQE4Rv
Register RTK with Factory Droid so shell commands the agent runs are
transparently rewritten to their token-saving `rtk` equivalents.
- `rtk init --agent droid`: install a native PreToolUse hook into
~/.factory/settings.json (matcher "Execute"); supports global (-g) and
project scope, the FACTORY_HOME override, idempotent install with backup +
atomic write, and a clean uninstall round-trip.
- `rtk hook droid`: process Droid's PreToolUse payload (BOM strip, stdin cap,
JSON-parse fallback), mirroring the existing provider hooks.
- Deny verdict steps aside (no output) so Droid's native deny handling fires,
matching Claude/Cursor/Copilot and the PermissionVerdict::Deny contract.
- Rewrites are auto-allowed: Droid only applies a hook's updatedInput when the
decision is "allow", so we mirror run_cursor to avoid silently dropping the
rewrite (and its savings) on the default verdict.
Native Windows has had full auto-rewrite support since v0.37.2 via the
`rtk hook claude` native binary command (no bash/jq/Unix shell). The
README still described the pre-v0.37.2 "CLAUDE.md fallback" behavior,
which is now inaccurate and a recurring source of confusion (#330).
- Rewrite the Windows section: native binary hook is the primary path,
WSL secondary; drop the "limited support / CLAUDE.md fallback" framing
- Add an upgrade note (re-run `rtk init -g` to migrate off rtk-rewrite.sh)
- Add a ripgrep prerequisite note (`rg` on PATH)
- Fix the install note and Supported AI Tools table (bash -> native binary)
Refs #330
Co-Authored-By: Claude Opus 4.8 <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