Skip to content

fix(hooks/init): preserve user content in copilot-instructions.md#1976

Merged
aeppling merged 5 commits into
rtk-ai:developfrom
YOMXXX:fix/copilot-instructions-preserve
May 20, 2026
Merged

fix(hooks/init): preserve user content in copilot-instructions.md#1976
aeppling merged 5 commits into
rtk-ai:developfrom
YOMXXX:fix/copilot-instructions-preserve

Conversation

@YOMXXX

@YOMXXX YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor

Summary

`rtk init --copilot` previously called `write_if_changed()` which truncated any pre-existing `.github/copilot-instructions.md`, destroying user-authored Copilot rules. This PR routes copilot-instructions through the same idempotent marker-block helper (`upsert_rtk_block`) already used for CLAUDE.md and AGENTS.md, so RTK-owned content lives between markers and user content outside the markers is preserved verbatim.

Reproduction

```bash
mkdir -p /tmp/repro/.github && cd /tmp/repro
printf '# My rules\nAlways answer in Spanish.\n' > .github/copilot-instructions.md
rtk init --copilot
grep -c 'Spanish' .github/copilot-instructions.md

Before this PR: 0 (file truncated)

After this PR: 1 (user content preserved)

```

Root cause

`src/hooks/init.rs:3728-3734`: `write_if_changed(&instructions_path, COPILOT_INSTRUCTIONS, …)` performs an atomic write of `COPILOT_INSTRUCTIONS` regardless of pre-existing content.

Fix approach

  • Wrap `COPILOT_INSTRUCTIONS` in the existing ` … ` markers.
  • Add `upsert_copilot_instructions()` (mirrors the proven `run_claude_md` pattern at lines 1485-1545).
  • `upsert_rtk_block()` already handles all four cases: Added, Updated, Unchanged, Malformed.
  • Malformed pre-existing files (opening marker without closing) are refused with an explicit error rather than rewritten.
  • Extract `run_copilot_at(base, ctx)` private helper so tests pass a TempDir path instead of mutating process-global cwd — eliminates parallel-test flakes.

Behavior changes

  • Preserves pre-existing user content in `.github/copilot-instructions.md` — previously destroyed.
  • Adds RTK marker-block fences inside the file (one-time additive change).
  • Refuses to modify malformed files (opening marker without closing) — previously silently rewrote.

Public API unchanged: `pub fn run_copilot(ctx)` keeps the same signature; only an internal seam (`run_copilot_at`) was added for testability.

Test plan

  • `test_copilot_init_preserves_existing_instructions` — headline regression test.
  • `test_copilot_init_idempotent_repeats` — second init is a no-op.
  • `test_copilot_init_updates_stale_block` — stale RTK block replaced, surrounding user content preserved.
  • `test_copilot_init_dry_run_no_write` — dry-run never touches disk.
  • `test_copilot_init_fresh_install_creates_file` — first run on empty repo creates marker-block-wrapped file.
  • `test_copilot_init_refuses_malformed_block` — opening marker without closing → hard error.
  • `cargo fmt --all` clean.
  • `cargo clippy --all-targets` zero warnings.
  • `cargo test --bin rtk -- --test-threads=8` 1909 passed (all parallel-safe).

Fixes #1964, #1891

YOMXXX added 4 commits May 20, 2026 12:13
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
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
…formed 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
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
@CLAassistant

CLAassistant commented May 20, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@YOMXXX

YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@CLAassistant recheck

@YOMXXX

YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

recheck

@YOMXXX YOMXXX closed this May 20, 2026
@YOMXXX YOMXXX reopened this May 20, 2026
@YOMXXX

YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@CLAassistant recheck

@aeppling aeppling self-assigned this May 20, 2026
@aeppling

aeppling commented May 20, 2026

Copy link
Copy Markdown
Contributor

Hey @YOMXXX

Thanks for contributing, here is a quick review, but no blockers look good to me

Before merge

Share the upsert dispatch instead of duplicating it. upsert_copilot_instructions copies the four-arm RtkBlockUpsert match from run_claude_md_mode (init.rs:3731-3781 vs 1490-1542), and within it the Added and Updated arms are identical except for the verb (init.rs:3732-3759). That duplication is why the function lands at 65 lines, just over the ~60 guideline. Extracting one write_rtk_block(path, content, label, ctx) that both call removes the copy and gives the malformed case a single policy. CODING_PRACTICES asks for reusable logic to be lifted out rather than copied per-file, and to prefer that over mirroring older code.

Make the malformed handling consistent and avoid the half-install. On the same malformed input, copilot calls bail! and exits 1 (init.rs:3776) while claude-md warns and returns Ok, exit 0 (init.rs:1540), confirmed by running both.

And because run_copilot_at writes the hook config (init.rs:3809) before the upsert (init.rs:3813), a malformed file leaves the hook installed even though the command fails, confirmed the hook JSON is on disk after the exit-1. Pick one behavior for both paths; if it stays a hard fail, move the upsert ahead of the hook write so it aborts with nothing written.

Summary

All look good, just need some consistence on the point i mentionned, then should be ready for merge!

@aeppling

Copy link
Copy Markdown
Contributor

My bad #1891 is indeed solved as well, made a mistake

YOMXXX added a commit to YOMXXX/rtk that referenced this pull request May 20, 2026
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.lock re-synced to 0.34.3 to match Cargo.toml (it was stale at 0.36.0
on this branch, unrelated drift picked up by `cargo check`).

cargo fmt / clippy --all-targets / test --bin rtk: clean (1909 passed).
@YOMXXX

YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

Hey @aeppling, thanks for the careful read — all three points are addressed in 9dc8f8a. A quick map back to your review:

1. Drop Fixes #1891 — done. The trailer is now Fixes #1964 only, with a short note in the PR body flagging the still-live GEMINI.md path (run_geminiwrite_if_changed at init.rs:3458) as a follow-up.

2. Share the upsert dispatch instead of duplicating it — extracted write_rtk_block(path, block, label, recovery_cmd, ctx) -> Result<RtkBlockUpsert> next to upsert_rtk_block (right where its helpers belong). It owns the full 4-arm Added/Updated/Unchanged/Malformed match plus the atomic write, and it returns the action so callers can branch on Unchanged (CLAUDE.md needs to short-circuit before the OpenCode plugin step). The old upsert_copilot_instructions is gone — its 65 lines collapse into a single call. run_claude_md_mode shrinks from 56 lines of dispatch down to a 12-line call site.

3. Consistent malformed handling, no half-install — picked the hard-fail behaviour for both paths (it's the safer side of the trade-off, and it preserves the data-protection intent of #1976). Two follow-up moves:

  • run_claude_md_mode now propagates the bail!() from write_rtk_block instead of warning + Ok(()). CLAUDE.md no longer silently skips OpenCode plugin install when the file is malformed.
  • run_copilot_at was reordered: the upsert runs before write_if_changed(hook config), so a malformed file aborts the install with nothing written. Pinned by a new regression test (test_copilot_init_malformed_leaves_no_hook_on_disk) that asserts .github/hooks/rtk-rewrite.json does not exist after the bail. Added a matching test_claude_md_mode_refuses_malformed_block so both paths have symmetric coverage.

Build status: cargo fmt --all / cargo clippy --all-targets / cargo test --bin rtk -- --test-threads=8 → 1909 passed, 0 warnings.

(Cargo.lock got resynced from 0.36.0 back to 0.34.3 by cargo check to match Cargo.toml — pre-existing drift on this branch, unrelated to the fix.)

@aeppling

Copy link
Copy Markdown
Contributor

Hey, didn't saw that cargo.lock file, can you drop it from this PR please ?

Lockfile is generated on develop branch

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).
@YOMXXX YOMXXX force-pushed the fix/copilot-instructions-preserve branch from 9dc8f8a to 35a540c Compare May 20, 2026 16:47
@YOMXXX

YOMXXX commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

Both addressed in 35a540c (force-pushed):

  • Cargo.lock: dropped from the PR. The 0.36.0 → 0.34.3 churn was an unrelated resync cargo check did when I touched the worktree — now amended out, the only file changed in this PR is src/hooks/init.rs.
  • Fixes #1891: restored in the PR body (along with #1964). Sorry for the back-and-forth there — I'd taken it out after the first pass.

Ready when you are.

@aeppling

Copy link
Copy Markdown
Contributor

LGTM !

Thanks for contributing to RTK :)

@aeppling aeppling merged commit a04aa7e into rtk-ai:develop May 20, 2026
12 checks passed
@aeppling aeppling mentioned this pull request May 20, 2026
Olyno pushed a commit to Olyno/rtk that referenced this pull request May 24, 2026
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).
ryanmurf added a commit to murphytek/rtk that referenced this pull request May 30, 2026
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

copilot-instructions.md overwrite

3 participants