84 Commits

Author SHA1 Message Date
lvdeqing ceae215ff7 test(hooks): scope Trae audit assertion to Unix as reviewed 2026-09-18 15:23:56 +08:00
lvdeqing 0184ba2425 fix(hooks): address Trae portability and partial install diagnostics 2026-09-18 15:08:49 +08:00
lvdeqing 651441cb52 chore: merge latest develop into Trae hook integration 2026-09-18 14:47:34 +08:00
lvdeqing dbecb980f9 fix(hooks): honor audit directory override for portable Trae tests 2026-09-18 14:26:58 +08:00
Nicolas Le Cam d2e906a0fe fix(gain): honour suppress_hook_warning, and pin the env/config composition
`rtk gain` printed its own missing-hook line from a bare `status()` match,
outside `maybe_warn`, so neither `hooks.suppress_hook_warning` nor
`RTK_SUPPRESS_HOOK_WARNING` reached it — on the report the no-hook audience
reads most. Gate that arm on the same helper; the outdated-hook prompt stays
visible, as it does everywhere else.

The new unit tests exercise the parser alone, so folding env and config
together with `||` — which drops the falsy force-off — left the whole suite
green. Drive the composition through the binary instead, and give the env
table the `=0` row that overrides the config flag.

The suite pins `XDG_CONFIG_HOME`/`XDG_DATA_HOME` to the temporary home and
seeds the config in both the XDG and the macOS location, so the loader cannot
resolve past it: `dirs::config_dir` is `~/.config` on Linux but
`~/Library/Application Support` on macOS, and a runner that exports either
`XDG_*` reached the developer's own config before.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 02:16:12 +02:00
Matt Van Horn 4e53f760fe fix: scope suppress_hook_warning to missing hooks and parse the env override
Parse RTK_SUPPRESS_HOOK_WARNING as a truthy/falsy override that falls
back to config when unset or unrecognised, keep the HookStatus::Ok fast
path free of config loading, and suppress only the missing-hook warning
so the outdated-hook upgrade prompt stays visible.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CNmJZVV9u6pBhcwVhwkPp6
2026-09-16 07:46:02 -07:00
Matt Van Horn 0e7a41eb52 feat(config): add suppress_hook_warning option
Add hooks.suppress_hook_warning config option and RTK_SUPPRESS_HOOK_WARNING
env var to disable "No hook installed" and "Hook outdated" warnings.

Users running rtk via CLAUDE.md instructions instead of hooks, or with
tools like OpenCode, get these warnings on every command. The warnings
waste tokens and confuse AI agents since rtk is working correctly.

RTK_SUPPRESS_HOOK_WARNING=1 suppresses; any other value forces the warning
on; unset falls back to the config file. Both default to false so existing
behavior is unchanged. The env parse follows the repo's
as_deref() == Ok("1") convention.

The suppression check runs before status() so the suppressed path does not
pay for the hook probe.

Documented in configuration.md (the hooks block and the env var table) and
README.md.

Fixes #682

Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
2026-09-16 07:27:24 -07:00
lvdeqing ca24862484 chore: merge latest develop into Trae hook integration 2026-09-15 20:24:01 +08:00
Nicolas Le Cam 7c18567155 chore: migrate to Rust edition 2024
Bump edition 2021 -> 2024. rust-version stays at 1.91, already well above
the 1.85 floor the edition needs; docs/guide/resources/troubleshooting.md
still claimed 1.70+, which is where a failed `cargo install --git` lands.

Four things the edition forces:

- std::env::{set_var,remove_var} are unsafe in 2024 with no safe std
  replacement. Rather than wrap the test call sites in unsafe -- which the
  crate denies and .semgrep.yml flags -- route them through temp-env, a
  dev-only dependency whose closure API is safe and which restores the
  previous value even when the body panics. The hand-rolled CLAUDE_DIR_LOCK
  and PI_DIR_LOCK guards existed only to serialise those mutations and are
  now redundant; CWD_LOCK and TEST_ENV_LOCK stay, they order more than the
  env var itself.

- unsafe_op_in_unsafe_fn is on by default, so the libc calls in the proxy
  signal handler and in stream.rs's relay handler need explicit unsafe
  blocks, scoped to the libc calls themselves.

- `gen` is a reserved keyword, so the closure by that name in diff_cmd.rs
  becomes make_lines.

- Tightened tail-expression temporary scopes let clippy prove the binding in
  setup_test_env is inlinable, so let_and_return now fires there.

if_let_rescope changes when the scrutinee temporary drops in an if let/else.
The two sites in show_claude_config take cargo fix --edition's match rewrite,
which keeps the 2021 drop timing.

rustfmt.toml is kept rather than dropped: cargo fmt passes --edition from
Cargo.toml, but a bare rustfmt invocation has no crate context and falls
back to edition 2015, which cannot parse the let-chains the next commit
introduces. Pinning it there keeps format-on-save and pre-commit hooks in
agreement with CI.

clippy::collapsible_if is allowed crate-wide for now; the follow-up commit
adopts let-chains and removes the allow.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 01:26:43 +02:00
Nicolas Le Cam cb5e5994e1 fix(tracking): align the telemetry rates with rtk gain and pin them in memory
`get_by_command` moved to the volume-weighted rate while this branch was
open (#891), summing every call and guarding the division at group level.
The two telemetry queries now use the same shape: the row-level
`WHERE input_tokens > 0` is gone, so a call with no input that still
printed something counts against its command exactly as it does in the
By Command table, and `HAVING SUM(input_tokens) > 0` keeps commands that
never had any input out of both, as before.

`low_savings_commands` lists net-regressing commands: `sav > 0.0` became
`sav <> 0.0`, so only exact 0% stays with `passthrough_top`. A command
that emits more than it saves is the filter to fix first, and with the
weighted rate the old bound also dropped commands the unweighted query
used to list.

The two regression tests opened the on-disk tracker, so every `cargo test`
wrote ten synthetic rows (one at 100k input tokens) into whichever history
database `Tracker::new()` resolves to, on a developer machine the real one.
They now use `Tracker::new_in_memory()` like the rest of the module, and
`test_avg_savings_per_command_inner_weighted` only asserted a `0..=100`
range that the unweighted query also satisfied. Both tests now assert the
exact weighted figures, check each listed rate against `get_summary()`,
and fail when either query goes back to `AVG`, to the row filter, or to
the positive bound.

Tests are numbered 18 and 19 after the ones develop already carries as
14-17. The `low_savings_commands` doc no longer points at a PR, and both
telemetry field tables describe the new semantics.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-13 21:05:32 +02:00
Nicolas Le Cam 8b4c445815 Merge branch 'develop' into feat/ast-grep-support 2026-09-13 12:11:52 +02:00
Yijie Xu 285fb68717 fix(hooks): address Codex integration review feedback 2026-09-13 16:50:19 +08:00
Yijie Xu 7f2788a1b4 feat(hooks): add direct Codex command rewrite 2026-09-13 16:50:19 +08:00
Nicolas Le Cam 22d49e327f Merge pull request #1318 from kerta1n/feat/winget-automation
feat(ci): add winget manifest automation
2026-09-11 12:37:41 +02:00
kerta1n 7be287958e docs: list winget in the installation page description
The frontmatter description renders as the page's meta description and
search snippet, so it advertised an install method list that omitted the
one the page now documents.
2026-09-09 14:21:43 -04:00
Adrien Eppling 7c10f7791c fix(benchmark): ignore the recall hints when counting find entries
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
2026-09-08 16:53:02 +02:00
Adrien Eppling feb8aeb644 revert(recall): drop tee_on_success, recovery stays failure and truncation driven
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
2026-09-08 14:12:27 +02:00
Adrien Eppling 8969fa49c5 feat(recall): tee_on_success restores legacy always behavior, accurate migration notices
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MNqtCagEUryc1fgw62APkm
2026-09-08 14:12:25 +02:00
Adrien Eppling a53373191d feat(recall): content-addressed recall store with selectable [retriever] mode
sqlite (default) queried by 'rtk recall'; tee (legacy files) and disabled modes retained.
2026-09-08 14:12:15 +02:00
Nicolas Le Cam c81c09775d docs: move the prompt-caching answer into the published guide
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>
2026-09-08 01:54:14 +02:00
kerta1n 2f905f7b62 docs: complete winget coverage, normalise install headings
- README_pt.md: add the winget section (only translation missing it)
- docs/guide/getting-started/installation.md: add winget install and
  uninstall entries
- README_{es,fr,ja,ko,zh}.md: drop the superlative from the winget
  heading so all seven READMEs read "winget (Windows)", matching the
  existing "Homebrew (recommended)" convention
- README.md: shorten the Native Windows heading to "(manual install)",
  move the winget pointer into prose

Deliberately out of scope: docs/guide/getting-started/installation.md
still tells Windows users to use WSL "for full hook support". README.md
contradicts this - native Windows hooks have worked since v0.37.2.
Correcting it means rewriting text this PR does not otherwise touch, so
it is left for a separate change.
2026-09-07 17:26:55 -04:00
Adrien Eppling 9bc4323916 Merge origin/develop into clean/awareness-file
Resolved: awareness constants vs Pi/OMP rework, InitContext destructures,
bun/deno CLI additions, write_if_changed split.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DVD4ZD5wiSjSsNpKCK6h7q
2026-09-07 11:06:29 +02:00
Christian Marbach ba802ac82d docs(ast-grep): document rtk ast-grep in README and usage guides
Adds the ast-grep row/section to README.md, what-rtk-covers.md, and
FEATURES.md, matching how rtk grep/rg are documented there.
2026-09-07 10:34:00 +02:00
Nicolas Le Cam e53ec1cf18 Merge pull request #3707 from alvins82/omp-shared
feat(omp)!: add Oh My Pi (OMP) support
2026-09-05 01:54:17 +02:00
patrick a8bb67a853 fix(rewrite): stop rewriting sudo commands (pass them through)
The env-prefix stripper treated `sudo` like `env` / `VAR=val` and rewrote
`sudo docker ps` into `sudo rtk docker ps`. That breaks at runtime: `rtk`
lives in ~/.local/bin, which is not on sudo's secure_path, so the rewritten
command fails with "rtk: command not found" under root (reported in #146).
And where rtk *is* on secure_path, `sudo rtk` would run the whole rtk binary
as root — an unnecessary-privilege footgun.

Drop `sudo` from the env-prefix regex so sudo commands pass through
untouched. The permission verdict path is unaffected (it never used this
regex and already matches sudo commands as-is, e.g. `sudo:*` rules). env /
VAR= prefixes and transparent builtins (noglob, command, …) still rewrite
normally.

Verified: `sudo docker ps` / `sudo -u root docker ps` / `sudo noglob git
status` are no longer rewritten; `env FOO=bar docker ps`, `FOO=bar docker
ps`, `noglob git status` still are. fmt/clippy clean, full test suite green.

Refs #146

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-03 22:35:01 +02:00
alvins82 d302797c51 fix(hooks)!: harden aliased Pi and OMP extension lifecycle
Treat pre-existing extensions without ownership state as uncertain, cover project-scope aliases, clean canonical sidecars after symlink removal, and classify protected extension overwrites as breaking.

BREAKING CHANGE: non-interactive installs of modified or unrelated Pi/OMP extensions now require --auto-patch to approve overwrites.
2026-09-03 11:04:19 +12:00
alvins82 ba6a1f57a3 fix: harden shared extension ownership handling 2026-09-03 09:35:36 +12:00
Nicolas Le Cam 976a1d7630 docs(config): scope the exclude_commands coverage claim to what is peeled
`configuration.md` said an excluded tool "is covered however it is invoked".
The stated limits then covered only tools without their own filter, and exactness
— leaving three invocation forms of *filtered* tools promised but not delivered.

Scope the sentence to the wrapper, interpreter and path spellings that are
actually peeled, and table the three that are not, each with its cause:

- `head -20 f` / `tail -n 5 f` — the line-range fast path returns before the
  exclusion is consulted (#2823); `head f` is excluded normally
- `gradlew.bat build` / `mvnw.cmd test` — path stripping splits on `/`, so a
  `.bat`/`.cmd` spelling never reduces to the tool name (#3617)
- `golangci run ./...` — `golangci run` is one of the rule's own aliases and is
  kept whole, so it misses a `golangci-lint` entry

Each row verified against a build on `develop` at e533c40, including the
documented workaround: `["golangci"]` excludes the alias form but not
`golangci-lint run`, so both entries are needed.

Docs only, no behaviour change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 12:42:04 +02:00
aesoft e533c40901 Merge pull request #3749 from KuSh/fix/exclude-commands-wrapper-forms
fix(hooks): match exclude_commands against the peeled command form
2026-09-01 10:04:32 +02:00
Nicolas Le Cam 2a49e529bb Merge pull request #3125 from derrik-fleming/docs/update-config-example
docs(config): update example to include `max_file_size`
2026-08-31 20:23:52 +02:00
Nicolas Le Cam 9ba523960b fix(hooks): match exclude_commands against the peeled command form
`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>
2026-08-30 15:28:30 +02:00
alvins82 8c25ada388 fix: harden shared extension ownership 2026-08-29 13:22:28 +12:00
alvins82 42a91474e9 fix: track shared agent ownership safely 2026-08-29 11:29:50 +12:00
alvins82 48ee59ec1b fix: address latest OMP review feedback 2026-08-29 09:08:48 +12:00
alvins82 256d4a77bb fix: handle extension safety review feedback 2026-08-28 13:53:15 +12:00
alvins82 002248839e fix: address second OMP review feedback 2026-08-28 13:07:56 +12:00
alvins82 d268e8f282 fix: address follow-up review feedback 2026-08-28 12:11:51 +12:00
lvdeqing 436f2d5bb3 Merge upstream/develop into feat/trae-hook-integration 2026-08-26 15:11:34 +08:00
alvins82 1664772215 feat(omp): add Oh My Pi (OMP) support
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>
2026-08-25 19:04:07 +12:00
Adrien Eppling cddcecd76d docs(awareness): shorten awareness level section 2026-08-20 10:14:35 +02:00
Adrien Eppling bc751926cc docs(awareness): explain levels, upgrade path, and hookless agents 2026-08-20 10:12:03 +02:00
Adrien Eppling aefea91a21 feat(init): add awareness.level config with default/high/full instruction files 2026-08-20 10:10:26 +02:00
Xavier Pestel 1847b07f7a fix(vibe): address PR review — exit code contract, tests, telemetry, docs
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.
2026-08-05 13:55:09 +02:00
lvdeqing 82454cc766 fix(hooks): harden Trae hook integration 2026-07-28 19:51:20 +08:00
Nicolas Le Cam b754b85009 fix(hooks): drop redundant camelCase preToolUse entry from Copilot hook config
rtk init --copilot registered both a PascalCase PreToolUse entry and a
camelCase preToolUse entry in the same rtk-rewrite.json, on the assumption
that VS Code Copilot Chat needs the former and Copilot CLI needs the latter.

Live testing showed Copilot CLI treats PreToolUse/preToolUse as two
independent, sequentially-run hooks — a redundant second `rtk hook copilot`
process spawn per tool call, chaining the first hook's rewrite into the
second's input (confirmed via raw stdin capture, and confirmed independent
of declaration order in the file). Also confirmed Copilot CLI honors the
PascalCase-only schema perfectly well on its own, receiving the same
tool_name/tool_input.command shape either way — so the camelCase entry buys
nothing for Copilot CLI, while adding process overhead and an extra,
harder-to-reason-about execution path.

Drop the camelCase preToolUse entry, keeping the single PascalCase
PreToolUse entry shared by both hosts. Existing installs are not upgraded
automatically — re-running `rtk init --copilot` / `rtk init --global
--copilot` overwrites the old dual-schema file with the new one
(write_if_changed overwrites unconditionally on content diff), verified
by test_copilot_init_upgrades_old_dual_schema_install and
test_copilot_global_install_upgrades_old_dual_schema_install, which seed
the old dual-schema content and assert it gets replaced.
2026-07-27 13:59:47 +02:00
Adrien Eppling f02f5b1f82 docs: keep the cost breakdown diagram in one place
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.
2026-07-22 19:23:58 +02:00
Adrien Eppling 3f009d188c docs: revert internal and low-value files, drop the two-estimator table
Restore to develop the files where the rescoping added noise without
helping a reader: all of .claude/, the src/ module READMEs that only
described their own filters, ARCHITECTURE.md, TELEMETRY.md, quick-start,
configuration and troubleshooting.

Also drop the "Two estimators, one caveat" table from savings-explained.md.
The page already states that rtk gain estimates bytes/4 and ships no
tokenizer; enumerating the test-side estimator was detail no reader needs.

The user-facing surface keeps the rescoping: READMEs in seven languages,
the guide, hooks, and the analytics and usage pages.
2026-07-22 19:02:16 +02:00
Adrien Eppling c1f6ede36e docs: show the cost breakdown as containment, not a transformation chain
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.
2026-07-22 18:52:34 +02:00
Adrien Eppling b1047583c6 docs: lower the filter gate to 20% and trim redundant explainer links
The documented release blocker was ">=60% savings", but the code never
enforced that. 23 of the 63 threshold assertions in src/ already sit below
60% (15, 20, 30, 40 and 50% appear across aws, gh, git, glab and mvn), so
the gate was aspirational rather than real.

Set the floor to 20%, pointing at the existing "Correctness VS Token
Savings" section for the reasoning rather than restating it: a modest, safe
reduction beats an aggressive one that drops information the agent needed.

Descriptive "60-90%" ranges are left alone where they report what filters
actually achieve. Only requirement statements moved to the 20% floor.

Also reduce links to savings-explained.md down to one per file, and only
from root docs and user-facing pages. Removed from docs/contributing/ and
.claude/rules/, where the surrounding text already carries the caveat.
2026-07-22 18:44:50 +02:00
Adrien Eppling a1673f7428 docs: scope savings claims to bash output and document the estimator
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.
2026-07-22 18:33:54 +02:00