尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

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

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 Chatsnake_case 的tool_name/tool_input与 Copilot CLIcamelCase 的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.rsGITHUB_DIR .github、HOOKS_SUBDIR hooks、COPILOT_HOOK_FILE rtk-rewrite.jsonCOPILOT_INSTRUCTIONS_FILE copilot-instructions.mdCOPILOT_USER_DIR .copilot可用COPILOT_HOME环境变量覆盖2.1 Hook 配置文件项目级安装会把下面这份COPILOT_HOOK_JSONinit.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 Kitreachingforthejack/rtk而非本项目用which rtk排查后重新安装即可。3. 输入协议detect_format如何区分两种 Copilot 格式Hook 入口 run_copilot() 的管线是限额读取 stdin上限 1 MiBSTDIN_CAP超限即报错退出剥离前导 BOM 并 trim——部分 Windows 宿主如 Cursor会往 Hook stdin 前置 UTF-8 BOMserde_json 对此直接报错JSON 解析失败时仅写 stderr 警告、静默放行保证 Hook 永不阻塞命令交给detect_format()分派到对应处理分支。格式识别逻辑detect_format()可归纳为一张表输入键匹配工具名解析路径归类tool_namesnake_caserunTerminalCommand、run_in_terminal、Bash、bash/tool_input/commandVsCodeupdatedInput透明重写toolNamecamelCasebash、powershelltoolArgsJSON 字符串反序列化后取commandCopilotCli保留给未升级的旧安装toolNamecamelCaserun_in_terminal同上CopilotIdeJetBrains/IntelliJ 插件只认顶层 deny 决策其余工具/格式——PassThrough完全静默几个值得注意的实现细节run_in_terminal是 VS Code Copilot Chat 真实的终端工具名经线上 payload 抓取确认。若匹配不上detect_format会落入 PassThroughHook 对 VS Code 永远不触发——这是该分支注释里特别强调的坑toolArgs是 JSON 编码的字符串而非嵌套对象CopilotCli变体会携带完整解析后的args对象改写时只替换command字段保留宿主附带元数据description、initial_wait、mode等命令为空、非 shell 工具如editFiles、view、edit一律走 PassThroughHook 不产生任何输出。// VS Code Copilot Chat 输入snake_case { tool_name: Bash, tool_input: { command: git status } } // Copilot CLI 输入camelCasetoolArgs 为 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_commands与transparent_prefixes配置交给 src/discover/registry.rs 的模式注册表匹配返回rtk cmd或 None原样。4.1 VS Code Copilot ChatupdatedInput透明重写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 CLIdeny-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_AUDIT1后所有 rewrite/deny/skip 决策会追加到~/.local/share/rtk/hook-audit.logaudit_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 status、cargo test、gh pr list。分区 3 — 静默放行test_allow断言 Hook 输出为空exit 0已是 rtk 命令rtk git status——保证不会出现rtk rtk双重前缀heredoc 输入cat EOF ... EOF——heredoc 不做自动重写对应源码中has_heredoc检查未知命令htop、echo hello world——注册表未覆盖的命令绝不干预非 bash 工具view、edit、editFiles——工具类型闸门生效。分区 4 — 输出格式契约逐字段断言 Copilot CLI 输出是合法 JSON、permissionDecision deny、reason 含反引号包裹的 rtk 命令VS Code 输出是合法 JSON、hookSpecificOutput.permissionDecision allow、updatedInput.command以rtk开头。脚本退出码即失败用例数exit $FAIL可直接挂进 CI。注意一个易混淆点测试脚本依赖jq构造 payload但被测试的 Hook二进制本身零 jq 依赖——这正是 README 特意强调的特性。Rust 侧另有单元级覆盖hook_cmd.rs 的 tests 模块 对detect_format的四种归类逐一断言Bash、runTerminalCommand、run_in_terminal、camelCasebash/powershell、JetBrainsrun_in_terminal与上面的端到端脚本互补。6. 与其他 Agent 集成的对照rtk-awareness.md 给出的集成对照表转换为仓库内路径ToolMechanismHook 输出文件Claude CodePreToolUsehook updatedInput透明重写hooks/claude/rtk-rewrite.shVS 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.mdCopilot 与 Cursor 同属Rust 二进制 Hook家族但协议不同Cursor 要求所有路径返回 JSON无重写时回{}见 run_cursor()而 Copilot 的放行语义是空输出 exit 0——这与 hooks/README.md 的退出码契约一致Hook 在任何错误路径二进制缺失、JSON 非法、重写失败都必须 exit 0绝不能阻塞用户命令。7. 小结与适用前提协议rtk hook copilot从 stdin 读取 preToolUse JSON1 MiB 上限、BOM 免疫按 snake_case/camelCase 自动分流VS Code 走updatedInput透明重写不支持改写的宿主走 deny-with-suggestion。安装rtk init --copilot项目级.github/或rtk init --global --copilot用户级~/.copilot/COPILOT_HOME可覆盖卸载只清理 RTK 管理的两个文件。配置逃生舱RTK_DISABLED1单次跳过、~/.config/rtk/config.toml的hooks.exclude_commands永久排除、RTK_HOOK_AUDIT1审计日志。验证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),仅供参考
返回列表