Skip to content

Commit 0800bbe

Browse files
feat(copilot): add Copilot hook support (VS Code + CLI) (#605)
Add `rtk hook copilot` command that handles both VS Code Copilot Chat (updatedInput rewrite) and GitHub Copilot CLI (deny-with-suggestion). - Auto-detects format: snake_case (VS Code) vs camelCase (Copilot CLI) - Delegates to `rtk rewrite` (single source of truth) - 14 hook tests (format detection, rewrite gating, output shape) - .github/hooks/rtk-rewrite.json for repo-scoped hook config - .github/copilot-instructions.md for RTK awareness - Test script: hooks/test-copilot-rtk-rewrite.sh Rebased on develop (includes Gemini #573, Codex #377, OpenClaw #358). Original work by @jeziellopes, cleaned up and rebased by maintainer. Signed-off-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu> Co-authored-by: Patrick szymkowiak <patrick.szymkowiak@innovtech.eu>
1 parent 7d04b48 commit 0800bbe

6 files changed

Lines changed: 705 additions & 2 deletions

File tree

‎.github/copilot-instructions.md‎

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Copilot Instructions for rtk
2+
3+
**rtk (Rust Token Killer)** is a CLI proxy that filters and compresses command outputs before they reach an LLM context, saving 60–90% of tokens. It wraps common tools (`git`, `cargo`, `grep`, `pnpm`, `go`, etc.) and outputs condensed summaries instead of raw output.
4+
5+
## Using rtk in this session
6+
7+
**Always prefix commands with `rtk` when running shell commands** — this is the entire point of the project and reduces token consumption for every operation you perform.
8+
9+
```bash
10+
# Instead of: Use:
11+
git status rtk git status
12+
git log -10 rtk git log -10
13+
cargo test rtk cargo test
14+
cargo clippy --all-targets rtk cargo clippy --all-targets
15+
grep -r "pattern" src/ rtk grep -r "pattern" src/
16+
```
17+
18+
**rtk meta-commands** (always use these directly, no prefix needed):
19+
```bash
20+
rtk gain # Show token savings analytics for this session
21+
rtk gain --history # Full command history with per-command savings
22+
rtk discover # Scan session history for missed rtk opportunities
23+
rtk proxy <cmd> # Run a command raw (no filtering) but still track it
24+
```
25+
26+
**Verify rtk is installed before starting:**
27+
```bash
28+
rtk --version # Should print: rtk X.Y.Z
29+
rtk gain # Should show a dashboard (not "command not found")
30+
```
31+
32+
> ⚠️ **Name collision**: `rtk gain` failing means you have `reachingforthejack/rtk` (Rust Type Kit) installed instead of this project. Run `which rtk` and check the binary source.
33+
34+
## Build, Test & Lint
35+
36+
```bash
37+
# Development build
38+
cargo build
39+
40+
# Run all tests
41+
cargo test
42+
43+
# Run a single test by name
44+
cargo test test_filter_git_log
45+
46+
# Run all tests in a module
47+
cargo test git::tests::
48+
49+
# Run tests with stdout
50+
cargo test -- --nocapture
51+
52+
# Pre-commit gate (must all pass before any PR)
53+
cargo fmt --all --check && cargo clippy --all-targets && cargo test
54+
55+
# Smoke tests (requires installed binary)
56+
bash scripts/test-all.sh
57+
```
58+
59+
PRs target the **`develop`** branch, not `main`. All commits require a DCO sign-off (`git commit -s`).
60+
61+
## Architecture
62+
63+
```
64+
main.rs ← Clap Commands enum → specialized module (git.rs, *_cmd.rs, etc.)
65+
↓
66+
execute subprocess
67+
↓
68+
filter/compress output
69+
↓
70+
tracking::TimedExecution → SQLite (~/.local/share/rtk/tracking.db)
71+
```
72+
73+
Key modules:
74+
- **`main.rs`** — Clap `Commands` enum routes every subcommand to its module. Each arm calls `tracking::TimedExecution::start()` before running, then `.track(...)` after.
75+
- **`filter.rs`** — Language-aware filtering with `FilterLevel` (`none` / `minimal` / `aggressive`) and `Language` enum. Used by `read` and `smart` commands.
76+
- **`tracking.rs`** — SQLite persistence for token savings, scoped per project path. Powers `rtk gain`.
77+
- **`tee.rs`** — On filter failure, saves raw output to `~/.local/share/rtk/tee/` and prints a one-line hint so the LLM can re-read without re-running the command.
78+
- **`utils.rs`** — Shared helpers: `truncate`, `strip_ansi`, `execute_command`, package-manager auto-detection (pnpm/yarn/npm/npx).
79+
80+
New commands follow this structure: one file `src/<cmd>_cmd.rs` with a `pub fn run(...)` entry point, registered in the `Commands` enum in `main.rs`.
81+
82+
## Key Conventions
83+
84+
### Error handling
85+
- Use `anyhow::Result` throughout (this is a binary, not a library).
86+
- Always attach context: `operation.context("description")?` — never bare `?` without context.
87+
- No `unwrap()` in production code; `expect("reason")` is acceptable only in tests.
88+
- Every filter must fall back to raw command execution on error — never break the user's workflow.
89+
90+
### Regex
91+
- Compile once with `lazy_static!`, never inside a function body:
92+
```rust
93+
lazy_static! {
94+
static ref RE: Regex = Regex::new(r"pattern").unwrap();
95+
}
96+
```
97+
98+
### Testing
99+
- Unit tests live **inside the module file** in `#[cfg(test)] mod tests { ... }` — not in `tests/`.
100+
- Fixtures are real captured command output in `tests/fixtures/<cmd>_raw.txt`, loaded with `include_str!("../tests/fixtures/...")`.
101+
- Each test module defines its own local `fn count_tokens(text: &str) -> usize` (word-split approximation) — there is no shared utility for this.
102+
- Token savings assertions use `assert!(savings >= 60.0, ...)`.
103+
- Snapshot tests use `assert_snapshot!()` from the `insta` crate; review with `cargo insta review`.
104+
105+
### Adding a new command
106+
1. Create `src/<cmd>_cmd.rs` with `pub fn run(...)`.
107+
2. Add `mod <cmd>_cmd;` at the top of `main.rs`.
108+
3. Add a variant to the `Commands` enum with `#[arg(trailing_var_arg = true, allow_hyphen_values = true)]` for pass-through flags.
109+
4. Route the variant in the `match` block, wrapping execution with `tracking::TimedExecution`.
110+
5. Write a fixture from real output, then unit tests in the module file.
111+
6. Update `README.md` (command list + savings %) and `CHANGELOG.md`.
112+
113+
### Exit codes
114+
Preserve the underlying command's exit code. Use `std::process::exit(code)` when the child process exits non-zero.
115+
116+
### Performance constraints
117+
- Startup must stay under 10ms — no async runtime (no `tokio`/`async-std`).
118+
- No blocking I/O at startup; config is loaded on-demand.
119+
- Binary size target: <5 MB stripped.
120+
121+
### Branch naming
122+
```
123+
fix(scope): short-description
124+
feat(scope): short-description
125+
chore(scope): short-description
126+
```
127+
`scope` is the affected component (e.g. `git`, `filter`, `tracking`).

‎.github/hooks/rtk-rewrite.json‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"type": "command",
6+
"command": "rtk hook",
7+
"cwd": ".",
8+
"timeout": 5
9+
}
10+
]
11+
}
12+
}

‎hooks/copilot-rtk-awareness.md‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# RTK — Copilot Integration (VS Code Copilot Chat + Copilot CLI)
2+
3+
**Usage**: Token-optimized CLI proxy (60-90% savings on dev operations)
4+
5+
## What's automatic
6+
7+
The `.github/copilot-instructions.md` file is loaded at session start by both Copilot CLI and VS Code Copilot Chat.
8+
It instructs Copilot to prefix commands with `rtk` automatically.
9+
10+
The `.github/hooks/rtk-rewrite.json` hook adds a `PreToolUse` safety net via `rtk hook` —
11+
a cross-platform Rust binary that intercepts raw bash tool calls and rewrites them.
12+
No shell scripts, no `jq` dependency, works on Windows natively.
13+
14+
## Meta commands (always use directly)
15+
16+
```bash
17+
rtk gain # Token savings dashboard for this session
18+
rtk gain --history # Per-command history with savings %
19+
rtk discover # Scan session history for missed rtk opportunities
20+
rtk proxy <cmd> # Run raw (no filtering) but still track it
21+
```
22+
23+
## Installation verification
24+
25+
```bash
26+
rtk --version # Should print: rtk X.Y.Z
27+
rtk gain # Should show a dashboard (not "command not found")
28+
which rtk # Verify correct binary path
29+
```
30+
31+
> ⚠️ **Name collision**: If `rtk gain` fails, you may have `reachingforthejack/rtk`
32+
> (Rust Type Kit) installed instead. Check `which rtk` and reinstall from rtk-ai/rtk.
33+
34+
## How the hook works
35+
36+
`rtk hook` reads `PreToolUse` JSON from stdin, detects the agent format, and responds appropriately:
37+
38+
**VS Code Copilot Chat** (supports `updatedInput` — transparent rewrite, no denial):
39+
1. Agent runs `git status` → `rtk hook` intercepts via `PreToolUse`
40+
2. `rtk hook` detects VS Code format (`tool_name`/`tool_input` keys)
41+
3. Returns `hookSpecificOutput.updatedInput.command = "rtk git status"`
42+
4. Agent runs the rewritten command silently — no denial, no retry
43+
44+
**GitHub Copilot CLI** (deny-with-suggestion — CLI ignores `updatedInput` today, see [issue #2013](https://github.com/github/copilot-cli/issues/2013)):
45+
1. Agent runs `git status` → `rtk hook` intercepts via `PreToolUse`
46+
2. `rtk hook` detects Copilot CLI format (`toolName`/`toolArgs` keys)
47+
3. Returns `permissionDecision: deny` with reason: `"Token savings: use 'rtk git status' instead"`
48+
4. Copilot reads the reason and re-runs `rtk git status`
49+
50+
When Copilot CLI adds `updatedInput` support, only `rtk hook` needs updating — no config changes.
51+
52+
## Integration comparison
53+
54+
| Tool | Mechanism | Hook output | File |
55+
|-----------------------|-----------------------------------------|--------------------------|------------------------------------|
56+
| Claude Code | `PreToolUse` hook with `updatedInput` | Transparent rewrite | `hooks/rtk-rewrite.sh` |
57+
| VS Code Copilot Chat | `PreToolUse` hook with `updatedInput` | Transparent rewrite | `.github/hooks/rtk-rewrite.json` |
58+
| GitHub Copilot CLI | `PreToolUse` deny-with-suggestion | Denial + retry | `.github/hooks/rtk-rewrite.json` |
59+
| OpenCode | Plugin `tool.execute.before` | Transparent rewrite | `hooks/opencode-rtk.ts` |
60+
| (any) | Custom instructions | Prompt-level guidance | `.github/copilot-instructions.md` |

0 commit comments

Comments
 (0)