Closes#1564.
`rtk hook check` rejected commands that began with a bash line
continuation (`\<NL>`) because the matcher saw the literal `\`
as the first token and bailed out:
$ rtk hook check $'\\\ngit diff HEAD~1'
No rewrite for: \\ngit diff HEAD~1
Bash collapses `\<NL>` (and `\<CRLF>`) plus surrounding horizontal
whitespace to a single space before dispatching the command, so
the matcher should do the same. Internal continuations (e.g.
`git diff \<NL>HEAD~1`) are equally common when Claude Code
formats long invocations.
Add a small `collapse_line_continuations` helper that runs a
single regex over the input before `cmd.trim()`. The regex eats
the trailing whitespace on the previous line and the leading
indentation on the next line so the no-double-space invariant
holds. The fast path returns `Cow::Borrowed` when no continuation
is present, so commands without `\<NL>` allocate nothing.
Six regression tests pin: leading \\<NL\>, leading \\<CRLF\>, internal
\\<NL\> between subcommand and args, \\<NL\> followed by indentation,
the no-op fast path, and the helper-level Cow::Borrowed contract.
- rg: switch to `-nH --null` for robust file/content separation
- grep fallback: switch to `-rnHZ` to match rg's --null contract
- Parser regex: `^([^\x00]+)\x00(\d+):(.*)$` so filenames or content
containing `:digits:` (e.g. `badly_named:52:file.txt`,
`debug: counter is :42: now`) can no longer fool the parser
- Use `Captures::extract` for cleaner destructuring
Adds tests for the badly-named-filename and digit-colons-in-content
cases; updates existing tests to use NUL-separated fixtures.
Addresses review on #1554.
Signed-off-by: Artiom Tofan <arto@queue-it.com>
Single-file `rtk grep` returned synthetic `[file] N` buckets when a
matching line contained `:`. ripgrep emits `<line>:<content>` for
single-file searches; the parser used `splitn(3, ':')` length to
disambiguate, which fails when content has `:` (e.g. `Foo::bar(...)`).
Force `--with-filename` on rg (`-H` on the grep fallback) so output is
always `file:line:content`, and replace the inline parser with a
regex-based `parse_match_line` that also handles paths with embedded
`:` (Windows drive letters).
Fixes the parser/rendering portion of #1436. The BRE-translation
portion is addressed in a follow-up PR.
Signed-off-by: Artiom Tofan <arto@queue-it.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
#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 (#991 case): unchanged at +58%, all 17 shown, 0 overflow markers
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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.
Fixesrtk-ai/rtk#2009.
- 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.
Address review feedback on #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).
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#1996
Co-Authored-By: Claude <noreply@anthropic.com>
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 #1964
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 #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#1964, #1891
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 #1964
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>