fix(git): preserve full status paths and untracked files#991
Conversation
1b1f947 to
359daa5
Compare
2b61126 to
481103d
Compare
|
Could a maintainer please approve and run workflows so CI can start for review? |
|
Hello @em0t Thanks for those contributions ! I'll review this today , currently focusing on bugs and filters quality. Did you check if there was any issues related to your 4 PRs ? |
|
Thanks @aeppling. I didn’t find existing upstream issues specifically tracking these 4 cases. These 4 PRs came from an internal local A/B evaluation we ran while assessing RTK for rollout in our team. We used Claude Code + Codex, wrote up a report, and these were the main issues we found. I split them into 4 small PRs by issue category to keep review clearer and each fix easier to reason about. Also yes, all 4 PRs include automated regression tests for the behavior they change, and I ran To be transparent, I haven’t manually reproduced every case end-to-end by hand yet. |
|
Ok thanks, passing by this i see the output of git status is : But actually, isn't "modified" and "untracked" enough for LLM to understand and compress a bit more ? |
|
Yes, that makes sense. I’ll follow up with a smaller optimization for the |
|
Thank you ! |
|
@aeppling Follow-up pushed in this PR. Default |
|
Thanks for the review. I narrowed this PR further to keep the scope as small as possible. Changes since the last round:
Would appreciate another look when you have a moment. If anything still feels broader than it should be, I can trim it further. |
|
@aeppling I thought about it more and removed the What’s left is just the two Would appreciate another review when you have a chance. |
|
Hey @em0t Additional thoughts , you bypass the porcelain flag addition when user run a "git status" cmd without arguments. |
|
@aeppling I pushed a follow-up for your last comment.
Would appreciate another look when you have time. |
|
Hey @em0t Sorry for the response time, yes this is clean, you just got conflicts i think because we now use exec_capture, could you resolve please ? i'll come back to this during the week end |
|
@aeppling Sorry for the delay, I was away for a long holiday. I’ve updated the PR based on your request and resolved the conflicts against the latest |
|
Hey @em0t No problem, LGTM Thanks again for contributing to RTK :) |
fix(git): preserve full status paths and untracked files
The compact `git status` path ran `git status --porcelain -b -uall`. The `-uall` flag expands fully-untracked directories into every file, while raw `git status` collapses them (e.g. `node_modules/`). This made rtk output larger than raw — measured ~29x on a 200-file untracked dir (5500B vs 191B) — violating RTK's compress-or-match-raw invariant and inflating tokens. Remove `-uall` so untracked directories collapse exactly like raw. This keeps rtk-ai#991's actual fix intact: all modified/staged/renamed/conflict paths are still shown with no grouped summaries or `... +N more` overflow markers (`-uall` never affected those lines). Untracked files in partially-tracked dirs and any paths git itself expands are still preserved by the formatter. Measured (rtk vs raw git status): - node_modules/ (200 files): 5500B (-2780%) -> 25B (+87% savings) - normal (8 mod + 2 untracked): +71% -> +76% - 17 modified (rtk-ai#991 case): unchanged at +58%, all 17 shown, 0 overflow markers Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The compact `git status` path ran `git status --porcelain -b -uall`. The `-uall` flag expands fully-untracked directories into every file, while raw `git status` collapses them (e.g. `node_modules/`). This made rtk output larger than raw — measured ~29x on a 200-file untracked dir (5500B vs 191B) — violating RTK's compress-or-match-raw invariant and inflating tokens. Remove `-uall` so untracked directories collapse exactly like raw. This keeps rtk-ai#991's actual fix intact: all modified/staged/renamed/conflict paths are still shown with no grouped summaries or `... +N more` overflow markers (`-uall` never affected those lines). Untracked files in partially-tracked dirs and any paths git itself expands are still preserved by the formatter. Measured (rtk vs raw git status): - node_modules/ (200 files): 5500B (-2780%) -> 25B (+87% savings) - normal (8 mod + 2 untracked): +71% -> +76% - 17 modified (rtk-ai#991 case): unchanged at +58%, all 17 shown, 0 overflow markers Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(uninstall): uninstall removes --claude-md artifacts on Windows Core fix (src/init.rs — uninstall()): - Added step 3b after existing @RTK.md removal: checks for <!-- rtk-instructions marker in CLAUDE.md and removes the block using the existing remove_rtk_block() helper - Handles both artifacts in a single pass (covers edge case where both @RTK.md and the instructions block exist, e.g. after running both rtk init -g and rtk init -g --claude-md) - If CLAUDE.md becomes empty after cleanup, deletes the file Hardening: - Extracted RTK_BLOCK_START and RTK_BLOCK_END constants — all marker matching across uninstall(), upsert_rtk_block(), remove_rtk_block(), patch_claude_md(), and show_config() now uses these instead of duplicated string literals - When uninstall finds nothing to remove, prints which paths were checked to help users diagnose path issues - resolve_claude_dir() error message now shows %USERPROFILE% on Windows instead of $HOME Signed-off-by: Adrien Eppling <adrien.eppling@supinfo.com> * fix(init-uninstall): trailing newline, test refactor, changelog Signed-off-by: aesoft <43991222+aeppling@users.noreply.github.com> * fix(pnpm): install don't take a list of packages Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com> * fix(ls): add LC_ALL=C and fallback to raw on unrecognized locale - Force LC_ALL=C so ls always outputs English month names regardless of system locale - When no lines are parsed (e.g., non-English locale where regex fails to match), fall back to raw output instead of returning '(empty)' - This prevents silent data loss for users in zh_CN/ja/ko/etc. locales - Fixes rtk-ai#1276 * chore(master): release 0.37.0 * fix(install): reject archive with path traversal before extraction (rtk-ai#1250) The installer previously ran `tar -xzf` on the downloaded archive with no pre-extraction verification. A malicious mirror could ship a tarball with `../` components or absolute paths and write files anywhere on the user's filesystem (CWE-22). Add a pre-extraction check that lists archive contents with `tar -tzf` and rejects any entry whose name starts with `/` or contains a `..` path component. The check is POSIX-compliant and adds negligible overhead for the single-binary RTK release tarball. Covered by scripts/test-install.sh, which exercises one safe archive and four crafted malicious archives (leading `..`, absolute path, mid-path `..`, trailing `..`) plus a regression guard that ensures the check remains in install.sh. Co-Authored-By: Claude <noreply@anthropic.com> * fix(discover): skip head/tail rewrite when multiple files are passed (rtk-ai#1362) `head -N file1 file2 ...` was rewritten to `rtk read file1 file2 ... --max-lines N`. The capture regex was greedy (`(.+)$`), so every file argument was folded into a single string. `rtk read` then silently mis-handled the shape and fell through to the system `read` builtin (`/usr/bin/read`) which rejects paths starting with `/` as "not a valid identifier". Tighten each head/tail regex to a single non-whitespace file argument (`\S+$`). When >1 file is provided the regex misses, `rewrite_line_range` returns `None`, and the command falls through to the native `head` / `tail` binary — which already handles multi-file input with the expected `==> name <==` banners. Same class as the cat multi-file fix in rtk-ai#989. Added six regression tests covering the `-N`, `--lines=N`, `-n N`, and `--lines N` variants for both `head` and `tail`. * fix(ls): distinguish empty dir from unparseable locale content - Move . and .. detection before date parsing (is_dotdir) for non-English locale compatibility - Add dotdirs counter to distinguish empty dir (only . and ..) from real content that failed to parse - Fix test_compact_empty to use real ls -la output (includes . and ..) - Add test_compact_empty_chinese_locale for Chinese locale empty dir case - Closes regression where fallback falsely triggered on empty directories * refactor(ls): remove unused parse_failed variable * chore(master): release 0.37.1 * feat(init): add --dry-run flag to preview changes without writing Rebased onto current develop (binary-command era). Threads dry_run through patch_settings_json_command, migrate_old_hook_script, install_cursor_hooks, run_kilocode_mode, run_antigravity_mode, run_gemini, run_copilot, and uninstall. Fixes from PR rtk-ai#1032 review: - --uninstall --dry-run no longer deletes files (uninstall() now takes dry_run, every fs::remove_file / fs::write / atomic_write guarded) - Success messages ("installed", "configured", "Restart ...") gated on !dry_run in run_default_mode, run_hook_only_mode, run_codex_mode, run_copilot, install_cursor_hooks, run_gemini - prompt_telemetry_consent() skipped in dry-run - integrity::store_hash() in run_gemini guarded - KiloCode and Antigravity modes now accept dry_run - PatchResult::WouldPatch variant added for patch_settings_json_command - [dry-run] Nothing written. footer printed by every sub-mode - write_if_changed uses atomic_write (not fs::write) Added integrity::hash_path_for() public wrapper so dry-run can check sidecar existence without the destructive remove_hash. Tests: write_if_changed(dry_run=true) creates nothing; run_codex_mode_with_paths(dry_run=true) creates neither RTK.md nor AGENTS.md. 1596 tests pass, clippy clean. * chore(master): release 0.37.2 * fix(tests): update init.rs test calls for dry_run parameter * feat(cicd): add auto next release parser Create Next Release PR and set up the description with fix / feat PRs and issues to be closed * fix(ls): LC_ALL=C + fallback to raw on unrecognized locale - Force LC_ALL=C so ls always outputs English month names - When zero lines parsed but directory has content, fallback to raw output - Add is_dotdir() to distinguish empty dirs (only . and ..) from unparseable content - Fix empty directory regression for both English and non-English locales - Closes rtk-ai#1276 * chore(init): suppress semgrep filesystem-deletion on dry-run-wrapped removals The two remove_file calls flagged by semgrep already existed pre-PR. Wrapping them in if dry_run { print } else { remove_file } shifted their context enough that --baseline-commit re-attributed them as new. The rule's own message says deletion is expected in hooks/init cleanup. * test(dotnet): try to lessen test flakiness Fix seems counter intuitive but it helps a lot on my machine. Before, I had 234 errors (23%) out of 1,000 tests run; now, I only have one (0.1%) It's not totaly fixed but it reduce error a lot and avoids having one almost every time I test a PR. Another way to completly fix it would be to use std::fs::set_times on old.trx to set each time in the past, but it requires unstable fs_set_times feature, so I'm not sure you'll agree with that Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com> * fix(grep): adjust the command to fall through if the output would already be as small as possible * refactor(init): introduce InitContext and centralize dry-run footer Addresses three review items from PR rtk-ai#1032: - Bundle verbose+dry_run into a Clone+Copy InitContext struct (mirrors RunOptions in src/core/runner.rs). Collapses 25+ function signatures that already carried both fields and makes future flags one struct field instead of N signature changes. - Emit "[dry-run] Nothing written." exactly once from the top-level run() and uninstall() exit points instead of from every sub-mode. Fixes the double footer when --agent cursor combined with default mode. - Reject --show with --dry-run via clap conflicts_with rather than silently ignoring --dry-run. - Add regression tests for run_default_mode and uninstall dry-run paths using the existing with_claude_dir_override scaffolding. * docs(init): document --dry-run in quick-start guide Adds a "Preview without writing" subsection under Step 1 covering the --dry-run flag, -v interaction for content preview, that telemetry consent is skipped, and the --show conflict. Required by CONTRIBUTING.md section 4 (new features need documentation). * fix(stream): route to respective fd * fix(benchmark): capture all fd for stream cmd benchmark * fix(benchmark): benchmark capture all fd only stream * refactor(git): Fix stash status detection for all cases Consolidate stash default handlers to ensure "no local changes" is properly detected across all stash operations, not just the default push case. Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com> * fix(stream): add semgrep flag for sh tests * hotfix(cicd): add git app token for release please * Update cd.yml * hotfix(cicd): authorize dispatch for release please + git app for bump * hotfix(cicd): git app token for artefact * chore(master): release 0.38.0 * fix(git): surface in-progress state in compact `rtk git status` Default `rtk git status` runs `git status --porcelain -b` to build its compact view, but porcelain v1 omits the state header git prints when a rebase, merge, cherry-pick, revert, bisect, am, or sparse checkout is in progress. That header is correctness-critical — hiding "You are currently editing a commit while rebasing..." leaves the user thinking the repo is clean when it isn't. `run_status` already captures plain `git status` output as `raw_output` for tracking, so this fix adds `extract_state_header` to pull the state block out of it and prepends the block to the compact output. Returns `None` when nothing is in progress so clean repos are unchanged. Covered states: interactive / regular rebase, merge-with-unmerged-paths, "still merging after conflicts fixed", cherry-pick, revert, bisect, am, sparse checkout. Preserves the directive hints git prints alongside (`git rebase --continue`, `git commit --amend`, `git bisect reset`, etc.), while still filtering generic `(use "git add")` / `(use "git restore")` noise. Design principle: Correctness vs Token Savings — never hide information that changes the user's understanding of repo state. * fix(git): compact in-progress status state * fix(git): address review feedback on status state surfacing - Drop redundant LANG=C env (LC_ALL=C is sufficient). - Replace nested if/else chains in detect_status_state and extract_state_hint with const slices + iter for readability. - Drop the 2-space indent prefix on hint lines — adds nothing for machine consumption. * fix(git): drop state-hint extraction in compact status KuSh: the in-progress state line ("rebase in progress", "merge in progress. unresolved conflicts", etc.) is enough — LLMs know which git commands resolve each state, so the per-state hint list was noise. Removes STATE_HINTS and extract_state_hint, and reduces extract_state_header to a short walk that returns the first detected state summary or None. Existing per-state tests now assert the exact compact summary. * chore: fix clippy warnings - **Path Centralization:** Hardcoded directory and file paths across all hook logic () are replaced with dedicated, exported constants in . This prevents magic strings and simplifies maintenance when system directories change. - **Code Cleanup:** Move all code only used by tests behind cfg(test) attribute - **Refactoring:** Apply Clippy fixes and address remaining warnings Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com> * build(deps): bump rustls-webpki from 0.103.9 to 0.103.13 Bumps [rustls-webpki](https://github.com/rustls/webpki) from 0.103.9 to 0.103.13. - [Release notes](https://github.com/rustls/webpki/releases) - [Commits](rustls/webpki@v/0.103.9...v/0.103.13) --- updated-dependencies: - dependency-name: rustls-webpki dependency-version: 0.103.13 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> * fix(json): use char boundary when truncating long string values * fix(json): expand char boundary truncation test * test(json): assert truncated value byte length for multibyte strings * feat(cicd): target develop branch * fix(curl): JSON passthrough + IsTerminal gate to prevent invalid JSON output `rtk curl` was unconditionally truncating any response >=500 bytes when a tee hint was available, inserting the literal `... (N bytes total)` marker mid-stream. For JSON bodies this produces invalid, unparseable output and silently breaks every downstream `jq`, `python -m json.tool`, or agent pipeline that consumes `rtk curl`. Two changes in `filter_curl_output`: 1. JSON heuristic passthrough — if the trimmed body starts/ends with matching JSON brackets, return it unchanged. The body is already buffered in memory so the check is essentially free. 2. `is_terminal()` gate — only truncate non-JSON output when stdout is a TTY. Pipes and shell redirects (`> file`, `| jq`) now receive the full body, matching what `curl -o file` already does and closing the silent-data-loss path described in rtk-ai#1282. The tee file is still written for inspection, but the hint line is suppressed in the passthrough cases so it never leaks into pipes. Tests: existing 5 cases updated to pass `is_tty=true` (preserve TTY-mode assertions); 4 new cases cover JSON object/array passthrough at >500 bytes and pipe-mode (non-TTY) full-body delivery. Verified end-to-end against the public repro from the issue: `rtk curl -s https://jsonplaceholder.typicode.com/posts | python3 -c 'import sys,json; print(len(json.load(sys.stdin)))'` now returns 100. Closes rtk-ai#1536 Refs rtk-ai#1282 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(curl): gate force_tee_hint, extend JSON heuristic, avoid full-body alloc Follow-up to the initial rtk-ai#1536 fix after a deeper review pass. - force_tee_hint() is no longer called on the passthrough path. Previously every non-TTY pipe invocation (curl | jq, curl | python -m json.tool — the exact scenarios this PR fixes) was still writing a tee log file as a side effect, which is wasteful: the tee file's purpose is to give the LLM a recovery path when output is truncated, and we are no longer truncating in those cases. - looks_like_json now also matches bare top-level JSON strings (e.g. /api/token endpoints returning a long quoted token). Previously a >500-byte bare JSON string on a TTY would still get truncated mid-stream and produce invalid output. - FilterResult.content is now Cow<'_, str> so the passthrough path no longer copies the whole body. For multi-MB curl responses piped through rtk this eliminates a full-size allocation. - Exit code from curl is propagated via Ok(result.exit_code) instead of the previously hardcoded Ok(0). On the success path the value is 0 anyway, but the inconsistency with the early-exit branch above made the intent unclear. Tests: 9 -> 11 (added bare-JSON-string passthrough and a Cow::Borrowed assertion verifying the passthrough paths don't allocate). cargo test --all: 1693 passed, 6 ignored. Verified end-to-end against jsonplaceholder.typicode.com/posts (~27 KB, 100 entries, parseable) and a randomly-generated 100-entry / 52 KB / 1450-line JSON served from a local http.server (rtk output byte-identical to source modulo the trailing newline added by println!). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ls): handle device files (block, char, pipe, socket) in ls filter Character devices (c), block devices (b), pipes (p), and sockets (s) were silently dropped by compact_ls because only regular files (-), symlinks (l), and directories (d) were handled. This caused `rtk ls /dev/ttyACM*` to return "(empty)". Closes rtk-ai#844 * fix(cicd): match ":" for body prefix to catch Co-authored-by: Nicolas Le Cam <niko.lecam@gmail.com> * fix(cicd): match allowed repo list in pr bodies for next release body auto write create a regex with pipes of list of allowed repo to be matched added allowed_repos in cicd env * feat(gradlew): add Gradle/gradlew support with streaming filters Adds rtk gradlew command for build, test, lint, and dependency operations on Gradle projects. Filters task progress noise, preserves build scan URLs, test failures, lint violations, and compiler warnings. Recognises ./gradlew, gradlew, gradlew.bat, and gradle invocations. Surfaces unit-test report paths and shows a progress indicator for long-running tasks. Targets 75% savings (90% on test, 80% on build). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(hooks): add Pi coding agent integration * refactor: handling of uninstallation * test(cli): add init parse coverage for --pi, --agent pi, and --uninstall --pi * fix(gradlew): satisfy semgrep dynamic-command-execution rule Extract a new_gradle_command() helper that uses string literals in every Command::new() branch. The .semgrep.yml dynamic-command-execution rule rejects Command::new(var) — semgrep needs to statically audit the executable set. Same runtime behaviour: prefer ./gradlew (or gradlew.bat on Windows), fall back to gradle. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(gradlew): align with shared runner pattern Replaces custom run_streaming / run_batch / run_passthrough / ProgressIndicator with the shared runner helpers used by cargo and other commands: - Build → runner::run_streamed with a BuildLineFilter implementing StreamFilter - Test/Lint/Connected/Deps → runner::run_filtered with the existing filter_* closures (filter_test, filter_connected, filter_lint, filter_dependencies) - Other / verbose flags → runner::run_passthrough(tool, args, verbose) Benefits inherited from runner.rs: ChildGuard zombie prevention, 10 MiB output cap, broken pipe handling, proper Result<i32> exit-code propagation. run() now returns Result<i32> like cargo_cmd / golangci_cmd. main.rs updated to forward the exit code with `?` directly (no manual `0` wrapper). All Command::new() invocations remain string literals (semgrep dynamic-command-execution rule). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(master): release 0.39.0 * fix(gradlew): use resolved_command for system gradle fallback new_gradle_command() now uses resolved_command("gradle") for the two fallback branches (Windows + Unix when no local wrapper is present), matching how cargo / golangci-lint / etc. resolve system binaries. Local wrappers (./gradlew, gradlew.bat) stay as string literals — they are relative paths, not on PATH, and semgrep's dynamic-command-execution rule needs literals here. Net effect: PATHEXT-aware resolution on Windows (.CMD/.BAT shims work), no behavioural change on Unix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat: rm rtk awareness injection * chore: docs cleanup * Update telemetry documentation link to use 'master' branch * fix(dotnet): move build/test/restore status line to the bottom The `ok` / `fail` verdict line was emitted at the top of the filtered output, before errors and warnings. Consumers reading the tail of the stream — `| tail -N`, IDE log followers, agent watch/monitor loops, bounded-context-window readers — saw error noise followed by EOF with no verdict anywhere near the end, silently breaking any agent loop that gates the next step on build success. Reorder `format_build_output`, `format_test_output`, and `format_restore_output` to emit the body first, then the separator (only when the body is non-empty), then the verdict header last, matching native `dotnet` which ends with `Build succeeded.` or `Build FAILED.`. Add three regression tests using strict `output.lines().last()` plus a `tail -5` inclusion assertion so future regressions that append a trailing context line get caught. Closes rtk-ai#1574 Signed-off-by: Artiom Tofan <arto@queue-it.com> * fix(dotnet): 🐛 format build/test/restore output sections - Separate warnings and errors into distinct sections. - Ensure status line is emitted last for clarity. - Maintain consistency with native `dotnet` output behavior. * fix(dotnet): 🐛 format build/test/restore output summaries - Improve formatting of output summaries for build, test, and restore commands. - Ensure status lines are emitted last for better stream consumption. * fix(dotnet): 🐛 format warnings section in build/test/restore outputs - Refactor warning formatting to improve consistency across output sections. - Ensure warnings are displayed correctly in all relevant output formats. * fix: correct ARCHITECTURE.md path in README links The file lives at docs/contributing/ARCHITECTURE.md, not at the repo root — both the nav header and Documentation section pointed to the wrong location. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(init): make --pi route to Pi-only mode and skip CLAUDE.md injection * chore: update hooks/pi/rtk.ts to reflect new package name Co-authored-by: Jean du Plessis <jeandp@gmail.com> * feat(hooks): add transparent_prefixes config for wrapper commands Command wrappers like shadowenv (https://github.com/Shopify/shadowenv), `direnv exec .`, `nix develop --command`, `docker exec <container>`, `poetry run`, and `bundle exec` sit in front of every command in their respective project layouts. When rtk is driving a Claude Code / Gemini / Copilot / opencode hook, these wrappers defeat the rewrite — `rtk rewrite "shadowenv exec -- git status"` returns exit 1 (no rewrite) because the registry can't see past the wrapper to the inner `git status`. Every build tool, test runner, linter, and `git` invocation in such a layout stays uncompressed. rtk already solves exactly this shape for shell builtins via SHELL_PREFIX_BUILTINS (`noglob`, `command`, `builtin`, `exec`, `nocorrect`): strip the prefix, recurse on the inner command, re-prepend the prefix to the rewrite. This commit extends that pattern with a new [hooks].transparent_prefixes config field so users can register multi-word wrappers that don't change what runs, only how. [hooks] transparent_prefixes = ["shadowenv exec --", "direnv exec ."] With that config, `rtk rewrite "shadowenv exec -- cargo test"` becomes `shadowenv exec -- rtk cargo test` and exits 3 (ask, matching the existing ask semantics for inner commands). Implementation: - New `rewrite_command_with_prefixes(cmd, excluded, transparent_prefixes)` public entry. The original `rewrite_command(cmd, excluded)` is kept as a thin back-compat wrapper that passes an empty prefix slice, so the ~150 existing unit-test call sites don't need updating. - `transparent_prefixes: &[String]` threaded through `rewrite_compound`, `rewrite_segment`, `rewrite_segment_inner`. - In `rewrite_segment_inner`, a second strip loop runs after the SHELL_PREFIX_BUILTINS loop, with identical strip-recurse-reprepend semantics. Matching is whole-word via the existing `strip_word_prefix` helper (multi-word prefixes like `"shadowenv exec --"` are handled by-value — byte-level `starts_with` plus a space check). - Recursion is bounded by the existing MAX_PREFIX_DEPTH so pathological or self-referential configs cannot stack-overflow. - Production callers (`hooks/rewrite_cmd.rs`, `hooks/hook_cmd.rs`, `main.rs`) now load both `exclude_commands` and `transparent_prefixes` from config in the same `Config::load()` call — no new I/O. Tests (14 new): - config: 3 TOML roundtrip tests (present, missing, mixed with older fields). - registry: 11 behavior tests — strip/reprepend, unknown inner returns None, unmatched passthrough, composed with shell builtin, multiple prefixes configured, whole-word matching, empty-rest None, blank-entry skip, compound `&&` with prefix on both segments, excluded-inner returns None, recursion bounded. Design principles (from CONTRIBUTING.md): - Extensibility: extends an existing, documented pattern. - Zero Overhead: config load already happens on every hook invocation; this reads one additional vec. Empty `transparent_prefixes` is the default and adds one cheap loop-over-zero-elements on the hot path. - Correctness vs Token Savings: without this, hook rewrites silently miss every command run through a wrapper — exactly the kind of correctness gap the principle warns against. * fix(hooks): compose env and transparent prefixes * fix(hooks): address transparent prefix review * fix(hooks): address transparent prefix review comments * fix: minor code cleanup, avoid duplicating logic * fix: new rewite_command test call after rebase * fix: don't inject -json for go test -bench runs When -bench is present, benchmark output is already compact and useful. Injecting -json causes the filter to discard benchmark results since they don't produce pass/fail events the JSON parser expects. Fixes rtk-ai#1609 * refactor(warn): src/core/utils.rs has an unused error warning at compilation * fix(cicd): pr-target clean msg + git app token inform user how to solve CI pr-target failing check * fix(git): resolve status completeness conflicts * fix(hooks): make Cursor preToolUse rewrites work and stay visible Three related issues prevented RTK from working with the Cursor preToolUse hook on Windows. Each one alone produced the same user-visible symptom (`Output: {}` and the original command running unmodified), so they're fixed together: 1. Cursor preToolUse only enforces allow/deny — `permission: "ask"` is accepted by the schema but ignored at runtime, so `updated_input` rewrites could silently be dropped. Always return `allow` for rewritten commands (deny rules still take precedence and are evaluated before this point). 2. Cursor's preToolUse panel renders the JSON returned by the hook. Without a `continue: true` field the panel collapses to `Output: {}` even when the rewrite ran successfully, which makes the hook look broken from the user's perspective. Every other Cursor hook (`afterShellExecution`, `beforeSubmitPrompt`, `stop`, ...) returns `continue: true`; mirror that shape here so the panel surfaces the actual `permission` and `updated_input` payload. 3. Cursor on Windows prepends one or two UTF-8 BOMs (`EF BB BF`, sometimes doubled) to the JSON it pipes into the hook process. serde_json refuses to parse BOM-prefixed input, so `run_cursor` bailed out into the "no command" branch and returned `{}` for every invocation. Strip leading BOMs before parsing. This was the root cause of the long-standing "RTK silently no-ops in Cursor" reports — the rewrite path was never reached. Tests cover the flat allow output, `continue: true` on both single and compound (`cd ... && git status`) rewrites, the BOM-strip on double-BOM payloads matching what Cursor actually sends, and assert that the legacy hookSpecificOutput envelope is not emitted. Verified end to end on Windows by tracing real Cursor stdin payloads: the hook now returns `{"continue":true,"permission":"allow", "updated_input":{"command":"rtk git status"}}` for `git status` and the panel renders it instead of `Output: {}`. * fix: resolve merge conflict artifacts in init.rs * chore: fix clippy 0.1.95 warnings * chore(ci): deny warnings and make clippy pass mandatory in ci * feat(hermes): add rtk integration Signed-off-by: Kayphoon <109347466+Kayphoon@users.noreply.github.com> * style(hooks): format BOM helper assertion * feat(init): remove --pi flag, canonicalize Pi install to --agent pi chore: sync the codebase after mergew * fix(security): replace insecure tmp, lock git workflow perm * fix(security): pin workflow actions to SHA, clean up tempfile on failure * Update pr-target-check.yml * fix(cicd): set release-please target-branch to master [skip ci] * chore(master): release 0.40.0 * fix(docker): forward --tail flag in compose logs Previously `rtk docker compose logs --tail=20 web` would fall through to the passthrough handler because clap had no `--tail` field on the `Logs` subcommand, resulting in 0% token savings. Additionally, `run_compose_logs` always fetched 100 lines regardless of user input. - Add `#[arg(long, default_value_t = 100)] tail: u32` to `ComposeCommands::Logs` - Thread the value through to the docker invocation - Default unchanged (100 lines), so bare `rtk docker compose logs` behaves as before Ports the core fix from rtk-ai#580, retargeted onto current develop layout (`src/cmds/cloud/container.rs`). Relates to rtk-ai#578. Co-authored-by: Mihir Dash <137862945+slice-mihird@users.noreply.github.com> * fix(filters): address adversarial test-suite findings on aggressive filtering Several filters dropped or rewrote information an agent needs, producing output that was misleading rather than merely compressed. This fixes the clear-cut cases surfaced by the TheDecipherist/rtk-test suite: - ls: stop hiding the `.env` file (removed `.env`/`env` from NOISE_DIRS). - git status: restore the explicit "HEAD detached at <sha>" line instead of the opaque "* HEAD (no branch)". - log/docker logs: recognise CRITICAL/FATAL/ALERT/EMERGENCY/SEVERE (and NOTICE) as severities so those lines are no longer silently filtered; expose `log` as a `rtk pipe` filter and accept a positional filter name. - wc: forward stdin to the child so `cat file | rtk wc` counts the pipe instead of reporting 0 (new RunOptions::inherit_stdin). - jest/vitest: include skipped/pending test count in the compact summary. - pytest: surface xfailed/xpassed counts and list XFAIL/XPASS entries with their reasons (adds `-rxX`). - ruff/eslint: list individual violations with file:line:col, not just rule/file group counts. - pnpm/npm list: render the actual package list instead of the false-positive "All packages up-to-date" when there's no upgrade info. - git add: stay silent on a no-op instead of printing an ambiguous "ok". - git stash: pass git's own message through instead of collapsing to "ok". - docker ps: keep the Status column (health/restart info) and list stopped/exited containers; docker images: show all images with full names; docker compose ps: use `-a` so crashed services stay visible. All 1870 unit tests pass; cargo fmt + clippy clean. * chore(filters): remove filter-level annotations and restore compose logs tail arg * fix(filters): add test for aggressive filter batch fix * fix(git): stream push output via FilterMode::Streaming (rtk-ai#963) ## Problem (rtk-ai#963) `rtk git push` reportedly times out: users see `bash tool terminated command after exceeding timeout 30000 ms` while plain `git push` to the same remote completes fine. P1-critical because every Claude Code git push goes through rtk. ## Root cause `run_push` used `cmd.stdin(Stdio::inherit()).output()`. `Command::output()` captures both stdout and stderr until the child exits. Git push prints its progress (`Counting objects` / `Compressing objects` / `Writing objects`) to stderr and may prompt for SSH passphrases or HTTPS credentials. With stderr captured, Claude Code's bash tool saw zero output for 30+ seconds and killed the command — exactly the 30000 ms message in the issue. ## Fix Rewrite `run_push` on top of the streaming infrastructure that already exists for this exact purpose (`stream::run_streaming` + `FilterMode::Streaming`, added in 0.37.0). Add a counterpart to `BlockStreamFilter<H: BlockHandler>` in `src/core/stream.rs`: `LineStreamFilter<H: LineHandler>`. Where `BlockStreamFilter` defaults to DROP and emits only collected blocks, `LineStreamFilter` defaults to KEEP and lets handlers opt into dropping noise. Trait surface mirrors `BlockHandler`: - `should_skip(&mut self, line: &str) -> bool` — default false - `observe_line(&mut self, line: &str)` — default no-op - `format_summary(&self, exit_code, raw) -> Option<String>` This lets future streaming commands reuse the line-oriented pattern. `GitPushLineHandler` then becomes a tiny `LineHandler` impl: - `should_skip` drops the high-volume progress phases (Enumerating / Counting / Compressing / Writing objects, Delta compression, Total) and blank lines. - `observe_line` captures the up-to-date sentinel and the first ref update target (e.g. `master`) for the summary. - `format_summary` emits `ok <ref>` / `ok (up-to-date)` / `ok` on success; nothing on failure (raw error lines already flowed through). Stdin is inherited (`StdinMode::Inherit`) so SSH passphrase and HTTPS credential prompts still reach the user. Tracking now records the real raw output and the filtered output. ## Test plan - [x] `cargo fmt --all -- --check` - [x] `cargo clippy --all-targets -- -D warnings` — clean - [x] `cargo test --all` — 1880 passed, 0 failed, 6 ignored - Six unit tests cover the push handler: progress-prefix drop, up-to-date summary, remote message passthrough, no-summary-on-failure, first-ref-wins, and token-savings (>=60% on a representative payload). - Four unit tests cover the new `LineStreamFilter` trait: default-keep-all, skip-drops-matching, summary-propagates-exit-code, observe-only-called-for-kept-lines. ## Notes - Behaviour change: users now see git's native output line-by-line (with progress phases stripped) plus a final `ok <ref>` summary, instead of just the compact summary. This matches plain `git push` more closely and is what the issue reporter expects. - No regression for other filters: `run_pull`, `run_fetch`, `run_clone` are untouched; only `run_push` is modified. Closes rtk-ai#963 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Vibe Coded by Ousama Ben Younes_ _Developed With Ora Studio (Claude Code)_ Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(hints): add tail hints for tee & hints + address reviews - fix signal truncation gaps + kush reviews - added a new tee and hints function to give an hint with tail (avoid re-ingest of the head) - extracted patterns in constants * fix: re-add env python as noisy dir * fix: '...' ascii to unicode, remove some comments * fix(kubectl): compact get pods and services aliases Co-authored-by: Cursor <cursoragent@cursor.com> * fix(filters): split docker ps/-a paths, cap ruff violations at 50 - docker ps reverts to plain (no -a augmentation) - docker ps -a / compose ps -a wired to the running/stopped split filter - ruff Violations: capped at 50 + force_tee_tail_hint recovery - tests for the ruff and pytest xfail caps * fix(init): honor dry-run for Pi install and uninstall paths * fix(tee): safe truncation caps and compose-ps tee content fix Replace all bare magic offset literals (e.g. 21 for cap=20) with named constants (MAX_XXX + 1) so offsets stay in sync if caps change. Fix compose_ps to tee pre-formatted lines instead of raw tab-separated input, so tail -n +N gives the agent readable content. All 13 affected modules updated: container, dotnet, gh, glab, go, lint, pnpm, pytest, rubocop, cargo, aws. 1884 tests pass. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(rust): multi-line blocks used with tail hint * docs(cmds): truncations hints and recovery guidelines * test(hooks/init): add red test for copilot-instructions preservation Adds a TDD red test that asserts pre-existing user content in .github/copilot-instructions.md must survive 'rtk init --copilot'. Currently fails because run_copilot() uses write_if_changed() which truncates the file. Refs rtk-ai#1964 * fix(hooks/init): preserve user content in copilot-instructions.md run_copilot() previously called write_if_changed() which truncated any pre-existing .github/copilot-instructions.md, destroying user-authored Copilot rules. This routes copilot-instructions.md through upsert_rtk_block() (the same idempotent marker-block helper already used for CLAUDE.md and AGENTS.md). The COPILOT_INSTRUCTIONS constant is wrapped in <!-- rtk-instructions v2 --> ... <!-- /rtk-instructions --> markers so subsequent inits replace only RTK-owned content; the rest of the file is preserved verbatim. Malformed pre-existing blocks (opening marker without closing) are refused with a remediation message rather than silently rewritten. Fixes rtk-ai#1964, rtk-ai#1891 * test(hooks/init): cover idempotency, stale-block, dry-run, fresh, malformed paths Adds five additional tests around copilot-instructions.md upsert: - idempotent re-init (no duplicated blocks, identical content) - stale RTK block in-place update with surrounding user content preserved - dry-run never creates or modifies the file - fresh install creates file with RTK marker block when no file exists - malformed pre-existing block (opening marker without closing) is refused with a clear error rather than silently rewritten Refs rtk-ai#1964 * refactor(hooks/init): extract run_copilot_at for parallel-safe tests run_copilot(ctx) is now a thin wrapper around run_copilot_at(base, ctx); tests pass a TempDir path so they no longer mutate process-global cwd. This eliminates flakes when cargo test runs in parallel (the default). The public API is unchanged; only the internal seam moves. Refs rtk-ai#1964 * docs(readme): fix license references to match LICENSE (Apache-2.0) The README badge and footer claimed MIT, but the LICENSE file is Apache License 2.0. Updates both references so the README matches the authoritative LICENSE file. Closes rtk-ai#1996 Co-Authored-By: Claude <noreply@anthropic.com> * refacto(truncations): Set global CAPS for truncation Following tee and hint refacto Add global CAP constant to be inherited , to enable easier global configuration later * refactor(hooks/init): share write_rtk_block, unify malformed handling Address review feedback on rtk-ai#1976: - Extract write_rtk_block() shared dispatcher: eliminates the duplicated 4-arm RtkBlockUpsert match between run_claude_md_mode and the former upsert_copilot_instructions (now inlined). Both callers stay under the ~60-line guideline. - Unify malformed handling: both paths now bail!() with a diagnostic and the exact recovery command. CLAUDE.md previously warned and exited 0, silently skipping the OpenCode plugin step; behaviour is now consistent. - Reorder run_copilot_at: upsert copilot-instructions.md BEFORE writing the hook config so a malformed file aborts the install without leaving a stale .github/hooks/rtk-rewrite.json on disk. Regression coverage: - test_claude_md_mode_refuses_malformed_block mirrors the existing copilot malformed test against the shared dispatcher contract. - test_copilot_init_malformed_leaves_no_hook_on_disk pins the new write order so a future re-order regression is caught. cargo fmt / clippy --all-targets / test --bin rtk: clean (1909 passed). * fix(truncate): global caps reduce (avoid underflow and 0 results) - Avoid underflow + 0 results caused by overwrite of global constant CAPS - Soon user will be able to apply global configuration, this will change global CAPS and should not cause any underflow or 0 results, if overwrite sub >= CAPS -> use CAPS. * fix: honor explicit -n N limit for git log on merge commits When user runs 'git log -1 --format='%H' HEAD' where HEAD is a merge commit, rtk was adding --no-merges which filtered out the merge commit itself and returned the second parent instead. This made 'git log -1' return wrong SHAs for merge commits. Fix: don't add --no-merges when user explicitly passes -n N or --max-count=N. When a user specifies an exact count they expect exactly that many commits, not filtered results. Also skip --no-merges if user already passed --merges or --no-merges explicitly. Fixes rtk-ai#2009. * chore: regroup agent init tests by agent section * fix(git): drop -uall from compact status so output never exceeds raw The compact `git status` path ran `git status --porcelain -b -uall`. The `-uall` flag expands fully-untracked directories into every file, while raw `git status` collapses them (e.g. `node_modules/`). This made rtk output larger than raw — measured ~29x on a 200-file untracked dir (5500B vs 191B) — violating RTK's compress-or-match-raw invariant and inflating tokens. Remove `-uall` so untracked directories collapse exactly like raw. This keeps rtk-ai#991's actual fix intact: all modified/staged/renamed/conflict paths are still shown with no grouped summaries or `... +N more` overflow markers (`-uall` never affected those lines). Untracked files in partially-tracked dirs and any paths git itself expands are still preserved by the formatter. Measured (rtk vs raw git status): - node_modules/ (200 files): 5500B (-2780%) -> 25B (+87% savings) - normal (8 mod + 2 untracked): +71% -> +76% - 17 modified (rtk-ai#991 case): unchanged at +58%, all 17 shown, 0 overflow markers Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Update git.rs * docs(git): sync status README with --porcelain -b (drop -uall) * chore(master): release 0.41.0 * feat: address pr suggestions * chore: sync with pi api * chore: minor cleanup * chore(master): release 0.42.0 * fix(clippy): factor mvn version-cache triple into type alias Pre-existing clippy::type_complexity on the probe_mvn_version CACHE static (deny-warnings in CI's `cargo clippy --all-targets` gate). Extract the (u32,u32,u32) into a VersionTriple alias per clippy's own suggestion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Signed-off-by: Adrien Eppling <adrien.eppling@supinfo.com> Signed-off-by: aesoft <43991222+aeppling@users.noreply.github.com> Signed-off-by: Nicolas Le Cam <niko.lecam@gmail.com> Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: Artiom Tofan <arto@queue-it.com> Signed-off-by: Kayphoon <109347466+Kayphoon@users.noreply.github.com> Co-authored-by: aesoft <43991222+aeppling@users.noreply.github.com> Co-authored-by: Nicolas Le Cam <niko.lecam@gmail.com> Co-authored-by: Lumin Cui <vincenthcui@qq.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Ousama Ben Younes <benyounes.ousama@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: hed0rah <18272116+hed0rah@users.noreply.github.com> Co-authored-by: Joshua <joshua.french@hey.com> Co-authored-by: rtk-release-bot[bot] <280461666+rtk-release-bot[bot]@users.noreply.github.com> Co-authored-by: Victor Sumner <victor.sumner@shopify.com> Co-authored-by: Victor Sumner <vsumner@gmail.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: swithek <52840391+swithek@users.noreply.github.com> Co-authored-by: patrick <patrick@rtk-ai.app> Co-authored-by: Maxence Bombeeck <maxence.bombeeck@supinfo.com> Co-authored-by: Adrien Eppling <adrien.eppling@supinfo.com> Co-authored-by: Kevin <kevin.herembourg@gmail.com> Co-authored-by: gitbluf <gh+git@gitbluf.xyz> Co-authored-by: gitbluf <git+gh@gitbluf.xyz> Co-authored-by: SADIK KUZU <sadikkuzu@hotmail.com> Co-authored-by: Artiom Tofan <arto@queue-it.com> Co-authored-by: Sam Severance <swseverance@gmail.com> Co-authored-by: Marko Petrovic <22802784+gitbluf@users.noreply.github.com> Co-authored-by: Jean du Plessis <jeandp@gmail.com> Co-authored-by: Yaniv Michael Kaul <yaniv.kaul@scylladb.com> Co-authored-by: xdm67x <m-ozkan@outlook.fr> Co-authored-by: em0t <10153971+em0t@users.noreply.github.com> Co-authored-by: kamilkaczmareksolutions <kamil.kaczmarek.us@gmail.com> Co-authored-by: patrick szymkowiak <52030887+pszymkowiak@users.noreply.github.com> Co-authored-by: Kayphoon <109347466+Kayphoon@users.noreply.github.com> Co-authored-by: Mihir Dash <137862945+slice-mihird@users.noreply.github.com> Co-authored-by: pagarsky <opaharskyi@chartbeat.com> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: 李冠辰 <liguanchen@xiaomi.com> Co-authored-by: Tylere Zimmerman <tylere.zimmerman@delinea.com> Co-authored-by: okwn <root@okwn.cc> Co-authored-by: tron-coord <coord@murphytek.com>
Summary
rtk git statusto--porcelain -b -uallso dirty paths are no longer hidden behind grouped summaries, overflow markers, or directory-level collapse* branch,clean — nothing to commit) while keeping every dirty path visible-b,--branch,-sb,--short --branch) on the same compact path sortk git status -bstill gains tokens without hiding filesMinimal Cases
rtk git statusHidden Dirty PathsRaw
Previous RTK (
develop)This PR
Untracked Directory Expansion
Raw
Previous RTK (
develop)This PR
Branch-Oriented Explicit Flags Stay Compact
Raw
Previous RTK (
develop)This PR
Scope note: this PR keeps the explicit-flag support intentionally narrow. Only branch-oriented status flags (
-b,--branch,-sb,--short --branch) reuse the compact path here; other explicit args stay on their existing path so we do not change semantics like-salone gaining a branch line or-unobeing overridden to-uall.Testing