news 2026/9/7 7:24:39

rtk 的 GitHub Copilot 集成:PreToolUse 命令重写 Hook 的实现与验证

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
rtk 的 GitHub Copilot 集成:PreToolUse 命令重写 Hook 的实现与验证

rtk 的 GitHub Copilot 集成:PreToolUse 命令重写 Hook 的实现与验证

【免费下载链接】rtkCLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies项目地址: https://gitcode.com/GitHub_Trending/rtk4/rtk

本文以 hooks/copilot/README.md 为骨架,深入剖析 rtk(一个将常见开发命令输出压缩 60-90% token 的 CLI 代理)如何为 GitHub Copilot 生态(VS Code Copilot Chat + Copilot CLI)安装一个纯 Rust 二进制的 PreToolUse Hook。读完后,你将掌握 Copilot Hook 的双输入格式协议、updatedInput透明重写与 deny-with-suggestion 两种响应策略的取舍、rtk init --copilot的完整安装/卸载流程,以及如何用仓库自带的测试脚本对 Hook 的 allow/deny/rewrite 决策进行端到端验证。

1. 集成定位:为什么 Copilot Hook 不用 Shell 脚本

rtk 的 hooks/ 目录存放的是"部署到用户机器上的 Hook 工件"。多数 Agent 集成(如 OpenCode、Hermes)采用"薄委托"脚本:解析 Agent 专属 JSON 后调用rtk rewrite子进程做决策。但 Copilot 是例外,其 README 明确列出了两条特有设计约束:

  • 使用rtk hook copilot这个 Rust 二进制(而非 shell 脚本)——jq依赖
  • 自动识别两种输入格式:VS Code Copilot Chat(snake_case 的tool_name/tool_input)与 Copilot CLI(camelCase 的toolName/toolArgs,其中 args 是 JSON 字符串);
  • VS Code 格式:返回updatedInput实现透明重写
  • Copilot CLI 格式:返回permissionDecision: "deny"加替代命令建议(当时的 CLI API 不支持updatedInput)。

选择 Rust 直读 stdin/stdout 的原因在 src/hooks/hook_cmd.rs 的模块注释中解释得很直接:Hook 输出走严格的 JSON 协议,脚本里任何多余的 stdout/stderr 输出都会污染协议(文件头注释甚至点名了 Claude Code 的一个相关 bug:意外输出会静默禁用 Hook)。此外,Rust 二进制天然跨平台,rtk hook copilot无需jq即可在 Windows 上原生工作。

2. 安装流程:rtk init --copilot到底写了什么

Copilot 集成的安装入口是 src/main.rs 中的rtk init --copilot(项目级)与rtk init --global --copilot(用户级),对应 src/hooks/init.rs 里的run_copilot/run_copilot_global。关键路径常量定义在 src/hooks/constants.rs:

  • GITHUB_DIR = ".github"HOOKS_SUBDIR = "hooks"COPILOT_HOOK_FILE = "rtk-rewrite.json"
  • COPILOT_INSTRUCTIONS_FILE = "copilot-instructions.md"
  • COPILOT_USER_DIR = ".copilot"(可用COPILOT_HOME环境变量覆盖)

2.1 Hook 配置文件

项目级安装会把下面这份COPILOT_HOOK_JSON(init.rs 第 4846-4859 行)写入.github/hooks/rtk-rewrite.json

{ "version": 1, "hooks": { "PreToolUse": [ { "type": "command", "command": "rtk hook copilot", "cwd": ".", "timeout": 5 } ] } }

源码注释记录了一个重要的演进:该文件早期还声明过一条 camelCase 的preToolUse条目以覆盖 Copilot CLI 的原生 schema,但实测发现 Copilot CLI 会把两个键当作独立 Hook 顺序执行,导致每次工具调用白白多起一个进程,而 PascalCase schema 本身就够 Copilot CLI 消费——因此现在只注册单条 PascalCasePreToolUse

2.2 指令文件(Prompt 层引导)

安装同时会向.github/copilot-instructions.mdupsert 一个带<!-- rtk-instructions v2 -->标记的指令块(init.rs 第 4861-4888 行),内容即 hooks/copilot/rtk-awareness.md 所描述的行为规范:该文件在会话启动时被 VS Code Copilot Chat 与 Copilot CLI 共同加载,指示 Agent 自动为命令加rtk前缀。核心规则与示例:

# Instead of: Use: git status rtk git status git log -10 rtk git log -10 cargo test rtk cargo test docker ps rtk docker ps kubectl get pods rtk kubectl get pods

因此 Copilot 集成实际是双层防线:指令文件做 Prompt 层引导,PreToolUse Hook 做执行层兜底(safety net)——即使 Agent 忘了加前缀,Hook 也会在命令执行前拦截并改写。

2.3 用户级安装与卸载

  • rtk init --global --copilot将同一份 Hook 配置与指令块写入~/.copilot/(目录由 copilot_user_dir() 解析,COPILOT_HOME环境变量优先),使本机所有 Copilot CLI 会话生效;
  • rtk init --uninstall --copilot只移除 RTK 管理的工件:删除rtk-rewrite.json、并从copilot-instructions.md中精确摘除 rtk-instructions 标记块(用户自己的指令内容原样保留,见 uninstall_copilot_at())。

安装完成后按 rtk-awareness.md 的建议验证:

rtk --version # 应输出 rtk X.Y.Z rtk gain # 应显示 token 节省仪表盘(而不是 command not found) which rtk # 确认二进制路径

命名冲突警示(原文档保留):如果rtk gain报命令错误,可能装到的是同名的 Rust Type Kit(reachingforthejack/rtk)而非本项目,用which rtk排查后重新安装即可。

3. 输入协议:detect_format如何区分两种 Copilot 格式

Hook 入口 run_copilot() 的管线是:

  1. 限额读取 stdin(上限 1 MiB,STDIN_CAP),超限即报错退出;
  2. 剥离前导 BOM 并 trim——部分 Windows 宿主(如 Cursor)会往 Hook stdin 前置 UTF-8 BOM,serde_json 对此直接报错;
  3. JSON 解析失败时仅写 stderr 警告、静默放行(保证 Hook 永不阻塞命令);
  4. 交给detect_format()分派到对应处理分支。

格式识别逻辑(detect_format())可归纳为一张表:

输入键匹配工具名解析路径归类
tool_name(snake_case)runTerminalCommandrun_in_terminalBashbash/tool_input/commandVsCodeupdatedInput透明重写)
toolName(camelCase)bashpowershelltoolArgs(JSON 字符串)反序列化后取commandCopilotCli(保留给未升级的旧安装)
toolName(camelCase)run_in_terminal同上CopilotIde(JetBrains/IntelliJ 插件,只认顶层 deny 决策)
其余工具/格式PassThrough(完全静默)

几个值得注意的实现细节:

  • run_in_terminal是 VS Code Copilot Chat 真实的终端工具名(经线上 payload 抓取确认)。若匹配不上,detect_format会落入 PassThrough,Hook 对 VS Code 永远不触发——这是该分支注释里特别强调的坑;
  • toolArgs是 JSON 编码的字符串而非嵌套对象,CopilotCli变体会携带完整解析后的args对象,改写时只替换command字段,保留宿主附带元数据(descriptioninitial_waitmode等);
  • 命令为空、非 shell 工具(如editFilesviewedit)一律走 PassThrough,Hook 不产生任何输出。
// VS Code Copilot Chat 输入(snake_case) { "tool_name": "Bash", "tool_input": { "command": "git status" } } // Copilot CLI 输入(camelCase,toolArgs 为 JSON 字符串) { "toolName": "bash", "toolArgs": "{\"command\": \"git status\"}" }

4. 输出协议:透明重写 vs deny-with-suggestion

识别格式后,run_copilot()分派到三个 handler,最终都收敛到同一个决策函数decide_hook_action(cmd, Host):先查权限规则(Deny 直接拒绝;含"不可证明"语法的命令 Defer 放行),再调用 get_rewritten() 执行真正的重写——它先跳过 heredoc 命令,再从~/.config/rtk/config.toml读取hooks.exclude_commandstransparent_prefixes配置,交给 src/discover/registry.rs 的模式注册表匹配,返回rtk <cmd>或 None(原样)。

4.1 VS Code Copilot Chat:updatedInput透明重写

vscode_response_from_decision() 输出的 JSON:

{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecisionReason": "RTK auto-rewrite", "updatedInput": { "command": "rtk git status" } } }

Agent 随即静默执行被改写的命令——无拒绝、无重试。注释里记录了两个克制的边界条件:

  • permissionDecision: "allow"在用户显式配置了 Allow 规则时断言(AllowRewrite分支);
  • Default 判定或 Ask 规则命中的重写(AskRewrite省略该字段,把权限判定留给宿主自身的原生流程。原因是曾出现过的回归:无条件断言"ask"会让 Copilot CLI 对每条被改写的命令弹出不带"记住"选项的阻塞对话框。

4.2 Copilot CLI:deny-with-suggestion(及源码中的后续演进)

关联文档描述的历史行为是:检测到 camelCase 格式时返回

{ "permissionDecision": "deny", "permissionDecisionReason": "Token savings: use `rtk git status` instead" }

Copilot 读到 reason 后自行改跑rtk git status。需要说明的是,从 hook_cmd.rs 的HookFormat枚举注释看,当前源码已演进:新的rtk init --copilot不再注册 camelCase 条目(Copilot CLI 自身遵循 PascalCase schema),因此CopilotCli分支主要服务于升级前未重新 init 的旧安装,其响应也已改为返回modifiedArgs(携带保留宿主元数据的完整改写参数)的透明重写;而"只认顶层 deny 决策"的JetBrains/IntelliJ Copilot 插件toolName: run_in_terminal)走的CopilotIde分支则仍严格采用 deny-with-suggestion 策略,输出形如:

{ "permissionDecision": "deny", "permissionDecisionReason": "RTK token optimization: re-run this command as `rtk git status` instead." }

也就是说,文档中"VS Code 透明重写 + CLI 拒绝带建议"的二分法依然是理解这套协议的最佳心智模型,只是当前代码把"拒绝带建议"精确地保留给了不支持updatedInput/modifiedArgs的 IDE 宿主。

4.3 自愈机制:清理历史遗留的 camelCase 注册

一个很工程化的细节:当请求落入CopilotCli分支时,run_copilot()会先执行 heal_legacy_copilot_configs()——检查项目级.github/hooks/rtk-rewrite.json与全局~/.copilot/hooks/rtk-rewrite.json中是否残留旧版preToolUsecamelCase 条目,若 PascalCase 条目仍在则原子性地移除旧条目(临时文件 + rename,写失败自动清理),并对每次自愈写一条self_heal审计记录。

4.4 可观测性:审计日志

设置RTK_HOOK_AUDIT=1后,所有 rewrite/deny/skip 决策会追加到~/.local/share/rtk/hook-audit.log(audit_log_inner()),格式为时间戳 | 动作 | 原命令 | 改写后命令,字段内换行与|会被转义以防日志注入——排查"Hook 为什么没改写我的命令"时这是第一手依据。

5. 测试套件:用rtk hook copilot验证全部决策路径

README 的 Testing 一节给出的入口是bash hooks/test-copilot-rtk-rewrite.sh;仓库中该测试脚本实际位于 hooks/copilot/test-rtk-rewrite.sh(脚本自身的 Usage 注释仍沿用旧路径名),直接运行该文件即可。脚本支持RTK环境变量覆盖被测二进制(RTK="${RTK:-rtk}"),用jq构造 mock 的 preToolUse 输入,共四个断言分区:

分区 1 — Copilot CLI 应 deny 的命令test_deny:断言permissionDecision == "deny"且 reason 包含期望的 rtk 命令):

git status → 期望 reason 含 "rtk git status" git log --oneline -10 → 期望 reason 含 "rtk git log" git diff HEAD → 期望 reason 含 "rtk git diff" cargo test / cargo build → 期望 "rtk cargo test" / "rtk cargo build" cargo clippy --all-targets → 期望 "rtk cargo clippy" grep -rn pattern src/ → 期望 "rtk grep" gh pr list → 期望 "rtk gh"

分区 2 — VS Code Copilot Chat 应重写test_vscode_rewrite:断言hookSpecificOutput.permissionDecision == "allow"updatedInput.command含 rtk 命令):git statuscargo testgh pr list

分区 3 — 静默放行test_allow:断言 Hook 输出为空,exit 0):

  • 已是 rtk 命令(rtk git status)——保证不会出现rtk rtk双重前缀;
  • heredoc 输入(cat <<'EOF' ... EOF)——heredoc 不做自动重写(对应源码中has_heredoc检查);
  • 未知命令(htopecho hello world)——注册表未覆盖的命令绝不干预;
  • 非 bash 工具(viewediteditFiles)——工具类型闸门生效。

分区 4 — 输出格式契约:逐字段断言 Copilot CLI 输出是合法 JSON、permissionDecision == "deny"、reason 含反引号包裹的 rtk 命令;VS Code 输出是合法 JSON、hookSpecificOutput.permissionDecision == "allow"updatedInput.commandrtk开头。

脚本退出码即失败用例数(exit $FAIL),可直接挂进 CI。注意一个易混淆点:测试脚本依赖jq构造 payload,但被测试的 Hook二进制本身零 jq 依赖——这正是 README 特意强调的特性。

Rust 侧另有单元级覆盖:hook_cmd.rs 的 tests 模块 对detect_format的四种归类逐一断言(BashrunTerminalCommandrun_in_terminal、camelCasebash/powershell、JetBrainsrun_in_terminal),与上面的端到端脚本互补。

6. 与其他 Agent 集成的对照

rtk-awareness.md 给出的集成对照表(转换为仓库内路径):

ToolMechanismHook 输出文件
Claude CodePreToolUsehook +updatedInput透明重写hooks/claude/rtk-rewrite.sh
VS Code Copilot ChatPreToolUsehook +updatedInput透明重写.github/hooks/rtk-rewrite.json(由 rtk init 生成)
GitHub Copilot CLIPreToolUsedeny-with-suggestion拒绝 + 重试同上(单条 PascalCase 注册)
OpenCode插件tool.execute.before透明重写hooks/opencode/rtk.ts
(任意 Agent)自定义指令Prompt 层引导.github/copilot-instructions.md

Copilot 与 Cursor 同属"Rust 二进制 Hook"家族,但协议不同:Cursor 要求所有路径返回 JSON(无重写时回{},见 run_cursor()),而 Copilot 的放行语义是空输出 + exit 0——这与 hooks/README.md 的退出码契约一致:Hook 在任何错误路径(二进制缺失、JSON 非法、重写失败)都必须 exit 0,绝不能阻塞用户命令。

7. 小结与适用前提

  • 协议rtk hook copilot从 stdin 读取 preToolUse JSON(1 MiB 上限、BOM 免疫),按 snake_case/camelCase 自动分流;VS Code 走updatedInput透明重写,不支持改写的宿主走 deny-with-suggestion。
  • 安装rtk init --copilot(项目级.github/)或rtk init --global --copilot(用户级~/.copilot/COPILOT_HOME可覆盖);卸载只清理 RTK 管理的两个文件。
  • 配置逃生舱RTK_DISABLED=1单次跳过、~/.config/rtk/config.tomlhooks.exclude_commands永久排除、RTK_HOOK_AUDIT=1审计日志。
  • 验证bash hooks/copilot/test-rtk-rewrite.sh覆盖 deny/rewrite/pass-through/输出格式四类断言;安装后按 hooks/copilot/rtk-awareness.md 的三条命令验证二进制可用性,注意同名工具冲突。
  • 适用前提:Hook 二进制需已安装且在 PATH 中(rtk未安装时 Hook 静默放行,命令原样执行);camelCase 分支的行为差异取决于宿主版本,升级后建议重新执行一次rtk init --copilot,残留的旧注册也会由自愈逻辑自动清理。

【免费下载链接】rtkCLI proxy that reduces LLM token consumption by 60-90% on common dev commands. Single Rust binary, zero dependencies项目地址: https://gitcode.com/GitHub_Trending/rtk4/rtk

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 7:22:45

STM8外部中断从原理到实战:寄存器配置与避坑指南

简介&#xff1a;STM8外部中断程序开发包&#xff0c;面向使用IAR环境的嵌入式初学者与开发者&#xff0c;系统梳理了外部中断的触发源、模式选择、优先级设置、嵌套处理及标志清除等关键知识点&#xff0c;并结合实验工程演示MCU如何对外部事件做出实时响应&#xff0c;帮助读…

作者头像 李华
网站建设 2026/9/7 7:21:46

BiSeNet语义分割实战:从ZIP包到完整训练推理

简介&#xff1a;BiSeNet.zip 是一份针对实时语义分割任务、基于 BiSeNet 的完整工程包&#xff0c;面向需要快速构建和训练自定义数据集的深度学习开发者&#xff0c;解决了从数据准备、模型训练到测试推理的流程适配问题。压缩包内共149个文件&#xff0c;主要包含 Python 脚…

作者头像 李华
网站建设 2026/9/7 7:21:06

QMK固件开发环境完整搭建指南

QMK固件开发环境完整搭建指南 【免费下载链接】qmk_firmware Open-source keyboard firmware for Atmel AVR and Arm USB families 项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware QMK固件是面向 Atmel AVR 和 Arm USB 芯片族的开源键盘固件&#xff0…

作者头像 李华
网站建设 2026/9/7 7:21:03

视觉定位新范式:从弱标签到城市级地图的规模化之路

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/7 7:19:38

电力负荷预测实战:数据清洗、特征工程与模型选型全解析

简介&#xff1a;电力负荷预测分析资源包&#xff0c;面向电力系统从业人员、数据分析与机器学习学习者&#xff0c;聚焦短期、中期、长期负荷预测任务。负荷预测是电网规划与调度的重要基础&#xff0c;短期结果影响机组启停&#xff0c;中长期结果支撑运营检修与扩容决策。包…

作者头像 李华
网站建设 2026/9/7 7:18:57

豆包AI漫剧全流程实战:从脚本、分镜到新海诚风格成片

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华