34 Commits

Author SHA1 Message Date
Nicolas Le Cam cfe248c1f6 Merge pull request #3941 from iliaal/fix/faithful-head-tail-windows
fix(read): make head/tail rewrites faithful to the native commands
2026-09-14 00:59:57 +02:00
Nicolas Le Cam e224b159af docs(read): point the head/tail rewrite docs at --head-lines
The pipeline diagram, the discover walkthrough and the `rtk read` option
table still described the `--max-lines` mapping this branch replaced, and
the repo's own suggest hook still proposed `--max-lines N` for `head -N`
— the mapping this branch exists to correct.

Drop the `head -` disjunct in the rewrite guard: every string with that
prefix also has the `head ` prefix that sits next to it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-14 00:45:35 +02:00
Nicolas Le Cam 3d2d244406 fix(ast-grep): account for every dropped line and stop wrecking scan output
Two defects in `filter_ast_grep`, both verified against ast-grep 0.45.3.

`ast-grep scan` diagnostics parse only on their locator line, so filtering
line by line kept `  ┌─ a.rs:2:13` and dropped the rule id, severity,
message and source line — at exit code 0, so it read as success. The
whole-output fallback never fired because `order` was not empty.
`unparsed_signal()` now passes any shape through untouched when a single
non-blank line fails to parse, which is what `search.rs` already does for
grep/rg. This also covers `--heading` mode and Windows drive-letter paths,
where `[^:]+` cannot match `C:\src\a.rs`.

The per-file overflow hint was computed as `entries.len() - max_per_file`,
which ignores `max_total` cutting a file short: a file under its own cap
lost its remainder with no hint at all, and a file over it under-reported
the drop. Both now count against what was actually printed.

The hint said "matches" while counting lines. ast-grep prints one line per
matched source line and a structural match spans several, so a repo search
reported "19 more matches" where five matches remained.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-13 12:16:33 +02:00
Nicolas Le Cam 8b4c445815 Merge branch 'develop' into feat/ast-grep-support 2026-09-13 12:11:52 +02:00
Nicolas Le Cam 66eeb10bdf Merge pull request #3265 from breisnerlopez/feat/git-show-large-blob-preview
feat(git): filter large git show blob dumps with recovery and Latin-1 decoding
2026-09-11 17:21:52 +02:00
breisnerlopez ed106db06f docs(git): warn that a hand-written 'git show rev:path > file' can truncate
A large blob is windowed to a preview with a recovery hint, so redirecting a
manual 'rtk git show HEAD:x > file' can silently truncate (exit stays 0). Note
it in the git-show user docs and point at plain 'git show' / the recovery hint.
2026-09-08 16:19:21 +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 b158152ef7 feat(telemetry): report recall efficiency counters per filter family 2026-09-08 14:12:16 +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
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
Ilia Alshanetsky 844d6fa3d1 fix(git-diff): end hunks at their declared length, keep context adjacent
A hunk ran until the next header, so any trailing text was read as its body.
`gh pr diff --patch` routes here, and GitHub's `.patch` is an mbox: each patch
carries a bare `---` before the diffstat and a `-- ` signature after the diff,
both at column 0. With hunk bodies now anchoring, a two-commit mbox with two
real deletions reported `grep -c "^-"` = 5. `@@ -a,b +c,d @@` declares its own
line counts, so the hunk now ends when they are consumed and anything after it
is outside every hunk. That covers trailing prose and truncated input too,
rather than the shapes we happen to know about, and a heuristic on `---` / `-- `
could not have worked: a deleted line whose content is `- ` renders as `-- `.

`--patch` also joins `has_non_diff_format_flag`, so an mbox passes through
whole. Someone asking for a patch wants one that applies.

Leading context keeps the last lines before the change rather than the first.
With `-U10` or `--function-context` the first three sit ten lines above it, and
emitting those tells the reader that ctx3 precedes the deletion when ctx10 does.
A three-slot ring buffer costs the same lines. The diff-wide budget is charged
when the buffer drains, so a line the ring evicted costs nothing.

Combined diffs carry one marker column per parent, so a line changed against
only one of them holds its marker in column 2. Those were counted as context:
neither in the `+N -M` tally nor reachable by an anchored grep. The tally reads
both columns now. The grep gap is real and cannot be closed without rewriting
the line, so FEATURES.md states it alongside the per-hunk cap. Against
`git diff --numstat` on three complete commit ranges the tally is now exact,
where master under-counted one of them by +2 -1.

`diff_header_path` keeps paths containing spaces: git does not quote them, so
`diff --cc my file.txt` needs the whole remainder, not the last token.

FEATURES.md said 15 lines for a 24-line block and showed the stat's first line
with a leading space it does not have. It now also carries a measured example
with real compression, since the two-file one saves 11% in a document
advertising 60-90%.
2026-09-01 12:41:51 +02:00
Ilia Alshanetsky 5d4b5492cf fix(git-diff): reset hunk state on combined diffs, exempt leading context
Dropping the `+++` / `---` guards assumed those headers only appear before the
first `@@`. That holds for `diff --git` sections. It does not hold for the
`diff --cc` sections git emits for unmerged paths: `diff --cc` never matched
the `diff --git` branch, so `in_hunk` stayed set from the previous section and
the `--- a/z.txt` / `+++ b/z.txt` headers of every later file were printed at
column 0 and counted in the tally. On a two-file conflict with no deletions,
`rtk git diff | grep -c "^-"` returned 1. The state reset now fires on any
`diff --` prefix, and the path falls back to the trailing token for the
`diff --cc <path>` shape, so the headers land before the first `@@` again.
Same repo now returns 0.

Leading context no longer counts against `max_lines`, so it cannot displace
change lines. Its previous justification was wrong: before this branch, leading
context was never emitted, so it could not have crowded anything out of
`max_hunk_lines`. The cost it does carry is on the global budget, and on a
five-commit diff of this repo it cost 57 change lines (144 shown, against 201
with the exemption). A diff-wide cap of `max_lines / 10` bounds what the
exemption gives back: without it, a diff of many small hunks would spend three
exempt lines on every one of them. The same diff comes out at 71.7% byte
reduction against 71.8% on master, which showed zero anchorable change lines.

`docs/usage/FEATURES.md` showed change lines indented under a
`src/main.rs (+5/-2)` header, which no version of rtk emits. It now carries
real captured output, plus the two limits of the anchored audit: the 100-line
per-hunk cap, and that the output is not an applyable patch.
2026-09-01 12:41:51 +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 eb3f814872 refactor(grep): trim comments and align the siblings of the flag change
Condense the Grep variant and its tests to the comment rules in
CONTRIBUTING.md (explain why, not what), and carry the -l/-t change
through the files that still assumed the old behavior.

- scripts/test-aristote.sh: add -r to the two grep assertions, since
  `grep PATTERN <dir>` exits 2, and move the type filter to `rtk rg`,
  the engine where -t is valid.
- docs/usage/FEATURES.md: extra args go to the engine actually invoked,
  not always to rg; drop the per-row shortcut justifications.
- tests/search_compress_test.rs: guard grep-engine tests on
  grep_available() instead of rg_available(), which skipped them into a
  false green on a box without ripgrep.
- .claude/hooks/rtk-suggest.sh: reverted; the rg -> rtk rg fix is
  correct but belongs in its own PR.

BREAKING CHANGE: `rtk grep --file-type` and its `-t` short are removed.
The option never reached the engine, so it was a silent no-op; `-t` now
flows through, which means `rtk rg -t rust` filters by type while
`rtk grep -t rust` returns grep's own `invalid option -- 't'`. `-l` and
`-m` under `rtk grep` are likewise the native grep flags now rather than
rtk's --max-len and --max.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 01:58:35 +02:00
Brandon Harper 187228dc50 fix(grep): repair the smoke assertion this PR invalidated
scripts/test-all.sh asserted `rtk grep "pub fn" src/ -t rust` exits 0. That
only held because --file-type swallowed `-t` and never passed it to the
engine, so the assertion was pinning a silent no-op. With --file-type gone,
`-t` reaches GNU grep, which has no such flag:

    $ rtk grep "pub fn" src/ -t rust
    grep: invalid option -- t
    exit=2

Replaced with an assert_fails pinning that rejection, plus an assert_ok
against rg (which does have -t), guarded on rg being installed. Verified:

    rtk grep "pub fn" src/ -t rust  -> exit=2  (assert_fails wants non-zero)
    rtk rg   "pub fn" src/ -t rust  -> exit=0  (assert_ok wants 0)
    $ bash -n scripts/test-all.sh
    syntax OK

Also drops a `--line-numbers | -n` row from the FEATURES.md grep table. No
such option exists on Commands::Grep -- its fields are max_len, max,
context_only, extra_args. The `line_numbers` field at src/main.rs:118
belongs to Read. The row predates this PR but sits in the table it edits.

Note, NOT fixed here as it is pre-existing and unrelated: the adjacent
`assert_ok "rtk grep pattern" rtk grep "pub fn" src/` on the line above
also fails, because grep without -r on a directory exits 2. The smoke
suite is not wired into CI (referenced only from
.github/copilot-instructions.md), which is how it rotted unnoticed.
2026-08-30 11:22:22 -06:00
Brandon Harper 16228b74b0 fix(grep): stop -l and -t shadowing native grep flags
rtk's own tuning options on `Commands::Grep` declared short forms that
collide with native grep/rg flags of the same letter. This removes the two
that remain, and deletes one option outright.

`--max-len` loses `-l`. `-l` is grep's --files-with-matches, and the old
binding split behavior on whether the PATTERN parsed as a usize:

  - Non-numeric: clap rejected it, run_fallback re-ran raw grep, and the
    user got the right answer. Wasteful, not harmful.
  - Numeric: clap ACCEPTED it, and the result was silently wrong.

        # hit.txt contains "listen on port 8080 today"; miss.txt does not
        $ rtk grep -l 8080 hit.txt miss.txt
        before:  (no output)   exit=1     <- claims nothing matched
        after:   hit.txt       exit=0

    `-l 8080` set max_len=8080, leaving hit.txt as the pattern and miss.txt
    as the only path. With a single file the filename becomes the pattern,
    no path is left, and rtk falls back to stdin: exit 1 against /dev/null,
    hangs on a terminal. Port numbers, years and error codes are ordinary
    patterns, and the Claude Code hook rewrites plain `grep` into `rtk grep`
    transparently -- so this reached users who never opted in.

    Note this is not a savings story. `-l` is a has_format_flag passthrough
    on the fixed path too (search.rs:548), so there is no compaction for
    this flag either way. The bug was a wrong answer.

`--file-type` is removed entirely, with its `-t` short, rather than left as
a parse-only no-op. It was destructured as `file_type: _` at the call site
and never reached search::run, so it parsed and then did nothing.
`rtk rg -t rust` is unaffected: Rg has no such field and forwards `-t` to
real ripgrep.

Deliberately typed `fix`, not `fix!`, even though a documented long option
disappears. The option was inert -- it never filtered anything -- so no
working behavior is lost, and #3259 set the precedent for removing a
colliding grep short under plain `fix`. Recording the choice here because
silence would read as an oversight.

User-visible consequence: `rtk grep -t rust` goes from silently returning
unfiltered results to surfacing the engine's own error. BSD/macOS grep
prints `invalid option -- t`; GNU grep prints `invalid option -- 't'`.

`--max`/`-m` is NOT part of this change. #3259 removed that short already;
this branch is rebased onto it and preserves its rationale comment verbatim.

The struct NOTE deliberately does not claim to finish the job. clap's auto
`-h` still shadows grep/rg's --no-filename (that is #2532's scope), and
removing a clap short does not make a letter safe end to end: search.rs
shares one VALUE_FLAGS_SHORT table across both engines, so a letter taking
a value in rg but not grep still eats the next token inside a cluster --
`rtk grep -rt FOO .` consumes FOO as -t's value regardless of clap.

Adds clap-layer tests pinning the routing, including characterization tests
for behavior that was previously untested.
2026-08-30 11:22:22 -06: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
breisnerlopez 3b6b812ac0 fix(grep): free -m for GNU --max-count instead of rtk --max
`-m` was derived as the short for RTK's `--max` (a display cap), colliding
with GNU grep where `-m N` = `--max-count` (stop after N matches per file).
`rtk grep -m N` set RTK's display cap and never forwarded --max-count, so
grep didn't actually stop early — a silent semantic mismatch (no parse error).

Drop the short. `-m` now flows to extra_args and is forwarded verbatim to
grep/rg (it is not a has_format_flag letter, so it takes the normal engine
path, not passthrough), applying genuine --max-count while RTK still compacts.
This makes Grep consistent with Rg, which never had the short and already
forwarded `-m` correctly. `--max` keeps its long form and default 200.

- src/main.rs: drop `short` from Grep::max + clap-parse regression test.
- tests/grep_faithful_format_test.rs: end-to-end `rtk grep -m N` == `grep -n
  -m N` (leading, trailing, N >= total) plus the per-file multi-file case.
- docs/usage/FEATURES.md: drop the `-m` short; fix stale default 50 -> 200.
2026-08-01 13:44:45 +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 ead80a3283 docs: name the metric "reduction sortie bash" throughout FEATURES.md
Drop the 33 standalone "**Economies :** ~N%" claims, which asserted a
number with no context next to each command.

Rename the metric everywhere it remains. "Economies" on its own reads as
money saved; the tables measure bash output bytes removed. Column headers,
the section heading and its anchor, the rtk gain description, the telemetry
field list and the category summary now all say so explicitly.
2026-07-22 19:16:10 +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 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
Adrien Eppling 8c7f0dc97f docs(install): pin cargo install to the master release branch 2026-07-20 16:58:58 +02:00
aesoft 492aa76ed3 Merge pull request #1009 from KuSh/broken-pnpm-i
fix(pnpm): install don't take a list of packages
2026-05-03 16:46:49 +02:00
Joshua 021827c3c9 fix(grep): adjust the command to fall through if the output would already be as small as possible 2026-04-25 20:38:33 -05:00
aesoft e3149cb7fb Merge pull request #1056 from vzwjustin/develop
feat(gains): add --reset flag
2026-04-21 19:00:59 +02:00
Joshua 80bc7fbd61 documentation 2026-04-15 14:45:30 -05:00
aesoft 9e96caa0a1 Merge pull request #355 from KuSh/pnpx+dlx
feat(discover): handle more npm/npx/pnpm/pnpx patterns
2026-04-13 20:31:12 +02:00
Nicolas Le Cam 45938b2a4d feat(js): distinguish between jest and vitest and don't rewrite npm test commands as we don't know which test framework is used under the hood
Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com>
2026-04-12 22:03:39 +02:00
Nicolas Le Cam 9a50efadc3 fix(pnpm): install don't take a list of packages
Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com>
2026-04-11 17:31:10 +02:00
aesoft 6a5bc847e0 fix(telemetry): RGPD-compliant, consent gate, erasure, privacy controls
+ docs update
2026-04-10 16:04:08 +02:00
Justin Adams 3f15b89227 Merge branch 'develop' into develop 2026-04-08 17:54:07 -05:00
Claude 4db7e86c26 docs: add --reset/--yes flags to docs/usage/AUDIT_GUIDE.md
Adds reset flag documentation to the upstream file path:
- Quick Reference: reset usage examples with and without --yes
- Command Options: Reset Flag table with --reset and --yes descriptions
- Warning note about irreversibility and non-interactive behaviour

https://claude.ai/code/session_015WXbDdxEU3pXPBtheDkXcd
2026-04-08 22:51:55 +00:00
aesoft deda44f736 fix(docs): clean some docs + disclaimer 2026-04-02 21:33:38 +02:00