From d28a76308bec6959c286a07318748cefe8dad5a2 Mon Sep 17 00:00:00 2001 From: Shun Kakinoki Date: Sun, 17 May 2026 21:40:15 +0800 Subject: [PATCH 1/3] fix: add Copilot toolArgs/modifiedArgs support to rtk-rewrite hook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tests added in #1788 check that rtk-rewrite.sh reads Copilot-format input (.toolArgs as object or stringified JSON) and emits .modifiedArgs output, but the hook itself was never updated, so the Shell workflow has been failing on every push to main. #1794 then added two new copilot hook files without updating the coverage list, adding a 5th failure. - Read CMD from .tool_input.command OR .toolArgs.command (object) OR .toolArgs | fromjson | .command (string). - Emit modifiedArgs alongside hookSpecificOutput when input used toolArgs, preserving original fields (e.g. timeout). - Mirror to claude/codex/copilot copies (sync_rtk_rewrite_spec.sh enforces byte equality between claude and codex). - Add config/copilot/hooks/{rtk-rewrite,security}.sh to coverage_spec.sh. Note: scripts/sync-rtk-rewrite.sh fetches from upstream and overwrites all three local copies — next sync will wipe this fix. Documented inline. --- config/claude/hooks/rtk-rewrite.sh | 64 +++++++++++++++++++---------- config/codex/hooks/rtk-rewrite.sh | 64 +++++++++++++++++++---------- config/copilot/hooks/rtk-rewrite.sh | 64 +++++++++++++++++++---------- spec/coverage_spec.sh | 2 + 4 files changed, 128 insertions(+), 66 deletions(-) diff --git a/config/claude/hooks/rtk-rewrite.sh b/config/claude/hooks/rtk-rewrite.sh index 3ec14e0a7..180dff173 100755 --- a/config/claude/hooks/rtk-rewrite.sh +++ b/config/claude/hooks/rtk-rewrite.sh @@ -11,6 +11,11 @@ # 1 No RTK equivalent → pass through unchanged # 2 Deny rule matched → pass through (Claude Code native deny handles it) # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user +# +# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads +# commands from Copilot's `.toolArgs` (object or string) and emits a top-level +# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will +# OVERWRITE these additions on next run — re-apply or upstream after syncing. # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- _rtk_audit_log() { @@ -32,7 +37,14 @@ fi set -euo pipefail INPUT=$(cat) -CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') +# Read command from either Claude (`.tool_input.command`) or Copilot +# (`.toolArgs` as object or stringified JSON) format. +CMD=$(echo "$INPUT" | jq -r ' + .tool_input.command + // (.toolArgs | if type == "object" then .command else empty end) + // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // empty +') if [ -z "$CMD" ]; then _rtk_audit_log "skip:empty" "-" @@ -79,29 +91,37 @@ esac _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" # Build the updated tool_input with all original fields preserved, only command changed. -ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') +# `// {}` handles pure-Copilot payloads that have no `.tool_input` field. +ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}') UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') +# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the +# original toolArgs structure (e.g. `timeout`) with `command` replaced. +TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') +case "$TOOL_ARGS_KIND" in + object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + *) MODIFIED_ARGS="null" ;; +esac + if [ "$EXIT_CODE" -eq 3 ]; then - # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "updatedInput": $updated - } - }' + DECISION="" else - # Allow: output the rewrite instruction in Claude Code hook format. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "allow", - "permissionDecisionReason": "RTK auto-rewrite", - "updatedInput": $updated - } - }' + DECISION="allow" fi + +jq -n \ + --argjson updated "$UPDATED_INPUT" \ + --argjson modified "$MODIFIED_ARGS" \ + --arg decision "$DECISION" \ + ' + ({ + hookSpecificOutput: ( + {hookEventName: "PreToolUse", updatedInput: $updated} + + (if $decision != "" then + {permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"} + else {} end) + ) + }) + + (if $modified != null then {modifiedArgs: $modified} else {} end) + ' diff --git a/config/codex/hooks/rtk-rewrite.sh b/config/codex/hooks/rtk-rewrite.sh index 3ec14e0a7..180dff173 100644 --- a/config/codex/hooks/rtk-rewrite.sh +++ b/config/codex/hooks/rtk-rewrite.sh @@ -11,6 +11,11 @@ # 1 No RTK equivalent → pass through unchanged # 2 Deny rule matched → pass through (Claude Code native deny handles it) # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user +# +# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads +# commands from Copilot's `.toolArgs` (object or string) and emits a top-level +# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will +# OVERWRITE these additions on next run — re-apply or upstream after syncing. # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- _rtk_audit_log() { @@ -32,7 +37,14 @@ fi set -euo pipefail INPUT=$(cat) -CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') +# Read command from either Claude (`.tool_input.command`) or Copilot +# (`.toolArgs` as object or stringified JSON) format. +CMD=$(echo "$INPUT" | jq -r ' + .tool_input.command + // (.toolArgs | if type == "object" then .command else empty end) + // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // empty +') if [ -z "$CMD" ]; then _rtk_audit_log "skip:empty" "-" @@ -79,29 +91,37 @@ esac _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" # Build the updated tool_input with all original fields preserved, only command changed. -ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') +# `// {}` handles pure-Copilot payloads that have no `.tool_input` field. +ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}') UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') +# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the +# original toolArgs structure (e.g. `timeout`) with `command` replaced. +TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') +case "$TOOL_ARGS_KIND" in + object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + *) MODIFIED_ARGS="null" ;; +esac + if [ "$EXIT_CODE" -eq 3 ]; then - # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "updatedInput": $updated - } - }' + DECISION="" else - # Allow: output the rewrite instruction in Claude Code hook format. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "allow", - "permissionDecisionReason": "RTK auto-rewrite", - "updatedInput": $updated - } - }' + DECISION="allow" fi + +jq -n \ + --argjson updated "$UPDATED_INPUT" \ + --argjson modified "$MODIFIED_ARGS" \ + --arg decision "$DECISION" \ + ' + ({ + hookSpecificOutput: ( + {hookEventName: "PreToolUse", updatedInput: $updated} + + (if $decision != "" then + {permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"} + else {} end) + ) + }) + + (if $modified != null then {modifiedArgs: $modified} else {} end) + ' diff --git a/config/copilot/hooks/rtk-rewrite.sh b/config/copilot/hooks/rtk-rewrite.sh index 3ec14e0a7..180dff173 100755 --- a/config/copilot/hooks/rtk-rewrite.sh +++ b/config/copilot/hooks/rtk-rewrite.sh @@ -11,6 +11,11 @@ # 1 No RTK equivalent → pass through unchanged # 2 Deny rule matched → pass through (Claude Code native deny handles it) # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user +# +# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads +# commands from Copilot's `.toolArgs` (object or string) and emits a top-level +# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will +# OVERWRITE these additions on next run — re-apply or upstream after syncing. # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- _rtk_audit_log() { @@ -32,7 +37,14 @@ fi set -euo pipefail INPUT=$(cat) -CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') +# Read command from either Claude (`.tool_input.command`) or Copilot +# (`.toolArgs` as object or stringified JSON) format. +CMD=$(echo "$INPUT" | jq -r ' + .tool_input.command + // (.toolArgs | if type == "object" then .command else empty end) + // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // empty +') if [ -z "$CMD" ]; then _rtk_audit_log "skip:empty" "-" @@ -79,29 +91,37 @@ esac _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" # Build the updated tool_input with all original fields preserved, only command changed. -ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') +# `// {}` handles pure-Copilot payloads that have no `.tool_input` field. +ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}') UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') +# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the +# original toolArgs structure (e.g. `timeout`) with `command` replaced. +TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') +case "$TOOL_ARGS_KIND" in + object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + *) MODIFIED_ARGS="null" ;; +esac + if [ "$EXIT_CODE" -eq 3 ]; then - # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "updatedInput": $updated - } - }' + DECISION="" else - # Allow: output the rewrite instruction in Claude Code hook format. - jq -n \ - --argjson updated "$UPDATED_INPUT" \ - '{ - "hookSpecificOutput": { - "hookEventName": "PreToolUse", - "permissionDecision": "allow", - "permissionDecisionReason": "RTK auto-rewrite", - "updatedInput": $updated - } - }' + DECISION="allow" fi + +jq -n \ + --argjson updated "$UPDATED_INPUT" \ + --argjson modified "$MODIFIED_ARGS" \ + --arg decision "$DECISION" \ + ' + ({ + hookSpecificOutput: ( + {hookEventName: "PreToolUse", updatedInput: $updated} + + (if $decision != "" then + {permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"} + else {} end) + ) + }) + + (if $modified != null then {modifiedArgs: $modified} else {} end) + ' diff --git a/spec/coverage_spec.sh b/spec/coverage_spec.sh index 63db6d375..7b62c5404 100644 --- a/spec/coverage_spec.sh +++ b/spec/coverage_spec.sh @@ -374,6 +374,8 @@ config/codex/hooks/notify.sh config/codex/hooks/pushover.sh config/codex/hooks/rtk-rewrite.sh config/codex/hooks/security.sh +config/copilot/hooks/rtk-rewrite.sh +config/copilot/hooks/security.sh config/shared/hooks/block-gh-settings.sh config/shared/hooks/block-git-push.sh config/cursor/activate.sh From 8bc29d155d6e525a5f8154de9dbd35cc54ccc7f6 Mon Sep 17 00:00:00 2001 From: Shun Kakinoki Date: Sun, 17 May 2026 22:09:43 +0800 Subject: [PATCH 2/3] fix(hooks): tolerant fromjson, exclude copilot from shfmt, preserve sync Entire-Checkpoint: 9d826b214b88 --- config/claude/hooks/rtk-rewrite.sh | 4 +- config/codex/hooks/rtk-rewrite.sh | 4 +- config/copilot/hooks/rtk-rewrite.sh | 4 +- scripts/rtk-rewrite.copilot.patch | 89 +++++++++++++++++++++++++++++ scripts/sync-rtk-rewrite.sh | 10 ++++ spec/sync_rtk_rewrite_spec.sh | 5 ++ treefmt.toml | 1 + 7 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 scripts/rtk-rewrite.copilot.patch diff --git a/config/claude/hooks/rtk-rewrite.sh b/config/claude/hooks/rtk-rewrite.sh index 180dff173..6dadd97ad 100755 --- a/config/claude/hooks/rtk-rewrite.sh +++ b/config/claude/hooks/rtk-rewrite.sh @@ -42,7 +42,7 @@ INPUT=$(cat) CMD=$(echo "$INPUT" | jq -r ' .tool_input.command // (.toolArgs | if type == "object" then .command else empty end) - // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // (.toolArgs | if type == "string" then (fromjson? | if type == "object" then .command else empty end) else empty end) // empty ') @@ -100,7 +100,7 @@ UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $ TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') case "$TOOL_ARGS_KIND" in object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; - string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | ((fromjson? | if type == "object" then .command = $cmd else null end) // null)') ;; *) MODIFIED_ARGS="null" ;; esac diff --git a/config/codex/hooks/rtk-rewrite.sh b/config/codex/hooks/rtk-rewrite.sh index 180dff173..6dadd97ad 100644 --- a/config/codex/hooks/rtk-rewrite.sh +++ b/config/codex/hooks/rtk-rewrite.sh @@ -42,7 +42,7 @@ INPUT=$(cat) CMD=$(echo "$INPUT" | jq -r ' .tool_input.command // (.toolArgs | if type == "object" then .command else empty end) - // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // (.toolArgs | if type == "string" then (fromjson? | if type == "object" then .command else empty end) else empty end) // empty ') @@ -100,7 +100,7 @@ UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $ TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') case "$TOOL_ARGS_KIND" in object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; - string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | ((fromjson? | if type == "object" then .command = $cmd else null end) // null)') ;; *) MODIFIED_ARGS="null" ;; esac diff --git a/config/copilot/hooks/rtk-rewrite.sh b/config/copilot/hooks/rtk-rewrite.sh index 180dff173..6dadd97ad 100755 --- a/config/copilot/hooks/rtk-rewrite.sh +++ b/config/copilot/hooks/rtk-rewrite.sh @@ -42,7 +42,7 @@ INPUT=$(cat) CMD=$(echo "$INPUT" | jq -r ' .tool_input.command // (.toolArgs | if type == "object" then .command else empty end) - // (.toolArgs | if type == "string" then (fromjson? | .command) else empty end) + // (.toolArgs | if type == "string" then (fromjson? | if type == "object" then .command else empty end) else empty end) // empty ') @@ -100,7 +100,7 @@ UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $ TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') case "$TOOL_ARGS_KIND" in object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; - string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | fromjson | .command = $cmd') ;; + string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | ((fromjson? | if type == "object" then .command = $cmd else null end) // null)') ;; *) MODIFIED_ARGS="null" ;; esac diff --git a/scripts/rtk-rewrite.copilot.patch b/scripts/rtk-rewrite.copilot.patch new file mode 100644 index 000000000..b28dfa963 --- /dev/null +++ b/scripts/rtk-rewrite.copilot.patch @@ -0,0 +1,89 @@ +--- /tmp/rtk-upstream.sh 2026-05-17 22:04:22 ++++ config/claude/hooks/rtk-rewrite.sh 2026-05-17 22:03:57 +@@ -11,6 +11,11 @@ + # 1 No RTK equivalent → pass through unchanged + # 2 Deny rule matched → pass through (Claude Code native deny handles it) + # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user ++# ++# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads ++# commands from Copilot's `.toolArgs` (object or string) and emits a top-level ++# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will ++# OVERWRITE these additions on next run — re-apply or upstream after syncing. + + # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- + _rtk_audit_log() { +@@ -32,7 +37,14 @@ + set -euo pipefail + + INPUT=$(cat) +-CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') ++# Read command from either Claude (`.tool_input.command`) or Copilot ++# (`.toolArgs` as object or stringified JSON) format. ++CMD=$(echo "$INPUT" | jq -r ' ++ .tool_input.command ++ // (.toolArgs | if type == "object" then .command else empty end) ++ // (.toolArgs | if type == "string" then (fromjson? | if type == "object" then .command else empty end) else empty end) ++ // empty ++') + + if [ -z "$CMD" ]; then + _rtk_audit_log "skip:empty" "-" +@@ -79,29 +91,37 @@ + _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" + + # Build the updated tool_input with all original fields preserved, only command changed. +-ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') ++# `// {}` handles pure-Copilot payloads that have no `.tool_input` field. ++ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}') + UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') + ++# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the ++# original toolArgs structure (e.g. `timeout`) with `command` replaced. ++TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') ++case "$TOOL_ARGS_KIND" in ++ object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; ++ string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | ((fromjson? | if type == "object" then .command = $cmd else null end) // null)') ;; ++ *) MODIFIED_ARGS="null" ;; ++esac ++ + if [ "$EXIT_CODE" -eq 3 ]; then +- # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. +- jq -n \ +- --argjson updated "$UPDATED_INPUT" \ +- '{ +- "hookSpecificOutput": { +- "hookEventName": "PreToolUse", +- "updatedInput": $updated +- } +- }' ++ DECISION="" + else +- # Allow: output the rewrite instruction in Claude Code hook format. +- jq -n \ +- --argjson updated "$UPDATED_INPUT" \ +- '{ +- "hookSpecificOutput": { +- "hookEventName": "PreToolUse", +- "permissionDecision": "allow", +- "permissionDecisionReason": "RTK auto-rewrite", +- "updatedInput": $updated +- } +- }' ++ DECISION="allow" + fi ++ ++jq -n \ ++ --argjson updated "$UPDATED_INPUT" \ ++ --argjson modified "$MODIFIED_ARGS" \ ++ --arg decision "$DECISION" \ ++ ' ++ ({ ++ hookSpecificOutput: ( ++ {hookEventName: "PreToolUse", updatedInput: $updated} ++ + (if $decision != "" then ++ {permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"} ++ else {} end) ++ ) ++ }) ++ + (if $modified != null then {modifiedArgs: $modified} else {} end) ++ ' diff --git a/scripts/sync-rtk-rewrite.sh b/scripts/sync-rtk-rewrite.sh index a5982fa37..310ae2f0d 100755 --- a/scripts/sync-rtk-rewrite.sh +++ b/scripts/sync-rtk-rewrite.sh @@ -1,15 +1,25 @@ #!/usr/bin/env bash # sync-rtk-rewrite.sh — Sync rtk-rewrite.sh from upstream rtk repo via raw GitHub. +# +# After fetching upstream, applies scripts/rtk-rewrite.copilot.patch so the +# local Copilot-format support (reads .toolArgs, emits .modifiedArgs) survives +# the sync. If you upstream the patch to rtk-ai/rtk, delete the patch file and +# the apply step here. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" URL="https://raw.githubusercontent.com/rtk-ai/rtk/master/.claude/hooks/rtk-rewrite.sh" +PATCH="$ROOT/scripts/rtk-rewrite.copilot.patch" tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT curl -fsSL "$URL" -o "$tmpfile" +if [ -f "$PATCH" ]; then + patch "$tmpfile" <"$PATCH" +fi + cp "$tmpfile" "$ROOT/config/claude/hooks/rtk-rewrite.sh" cp "$tmpfile" "$ROOT/config/codex/hooks/rtk-rewrite.sh" cp "$tmpfile" "$ROOT/config/copilot/hooks/rtk-rewrite.sh" diff --git a/spec/sync_rtk_rewrite_spec.sh b/spec/sync_rtk_rewrite_spec.sh index c11379ef0..f4f0819e8 100644 --- a/spec/sync_rtk_rewrite_spec.sh +++ b/spec/sync_rtk_rewrite_spec.sh @@ -17,4 +17,9 @@ It 'keeps Claude and Codex rtk rewrite hooks in sync' When run cmp -s "$PWD/config/claude/hooks/rtk-rewrite.sh" "$PWD/config/codex/hooks/rtk-rewrite.sh" The status should be success End + +It 'keeps Claude and Copilot rtk rewrite hooks in sync' +When run cmp -s "$PWD/config/claude/hooks/rtk-rewrite.sh" "$PWD/config/copilot/hooks/rtk-rewrite.sh" +The status should be success +End End diff --git a/treefmt.toml b/treefmt.toml index bc0450815..b5d2de3e8 100644 --- a/treefmt.toml +++ b/treefmt.toml @@ -22,6 +22,7 @@ includes = ["*.sh"] excludes = [ "config/claude/hooks/rtk-rewrite.sh", "config/codex/hooks/rtk-rewrite.sh", + "config/copilot/hooks/rtk-rewrite.sh", ] [formatter.lua] From 5e7930972bdac55989752d636d91697ca89227ac Mon Sep 17 00:00:00 2001 From: Shun Kakinoki Date: Sun, 17 May 2026 22:14:19 +0800 Subject: [PATCH 3/3] refactor(sync): drop patch file, make sync drift-check-only Entire-Checkpoint: 8c976510b69a --- scripts/rtk-rewrite.copilot.patch | 89 ------------------------------- scripts/sync-rtk-rewrite.sh | 27 ++++++---- 2 files changed, 16 insertions(+), 100 deletions(-) delete mode 100644 scripts/rtk-rewrite.copilot.patch diff --git a/scripts/rtk-rewrite.copilot.patch b/scripts/rtk-rewrite.copilot.patch deleted file mode 100644 index b28dfa963..000000000 --- a/scripts/rtk-rewrite.copilot.patch +++ /dev/null @@ -1,89 +0,0 @@ ---- /tmp/rtk-upstream.sh 2026-05-17 22:04:22 -+++ config/claude/hooks/rtk-rewrite.sh 2026-05-17 22:03:57 -@@ -11,6 +11,11 @@ - # 1 No RTK equivalent → pass through unchanged - # 2 Deny rule matched → pass through (Claude Code native deny handles it) - # 3 + stdout Ask rule matched → rewrite but let Claude Code prompt the user -+# -+# LOCAL DIVERGENCE FROM UPSTREAM (rtk-ai/rtk @ master): this copy also reads -+# commands from Copilot's `.toolArgs` (object or string) and emits a top-level -+# `modifiedArgs` alongside `hookSpecificOutput`. scripts/sync-rtk-rewrite.sh will -+# OVERWRITE these additions on next run — re-apply or upstream after syncing. - - # --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) --- - _rtk_audit_log() { -@@ -32,7 +37,14 @@ - set -euo pipefail - - INPUT=$(cat) --CMD=$(echo "$INPUT" | jq -r '.tool_input.command // empty') -+# Read command from either Claude (`.tool_input.command`) or Copilot -+# (`.toolArgs` as object or stringified JSON) format. -+CMD=$(echo "$INPUT" | jq -r ' -+ .tool_input.command -+ // (.toolArgs | if type == "object" then .command else empty end) -+ // (.toolArgs | if type == "string" then (fromjson? | if type == "object" then .command else empty end) else empty end) -+ // empty -+') - - if [ -z "$CMD" ]; then - _rtk_audit_log "skip:empty" "-" -@@ -79,29 +91,37 @@ - _rtk_audit_log "rewrite" "$CMD" "$REWRITTEN" - - # Build the updated tool_input with all original fields preserved, only command changed. --ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input') -+# `// {}` handles pure-Copilot payloads that have no `.tool_input` field. -+ORIGINAL_INPUT=$(echo "$INPUT" | jq -c '.tool_input // {}') - UPDATED_INPUT=$(echo "$ORIGINAL_INPUT" | jq --arg cmd "$REWRITTEN" '.command = $cmd') - -+# Build Copilot-format `modifiedArgs` when input used `.toolArgs`. Preserves the -+# original toolArgs structure (e.g. `timeout`) with `command` replaced. -+TOOL_ARGS_KIND=$(echo "$INPUT" | jq -r '.toolArgs | type') -+case "$TOOL_ARGS_KIND" in -+ object) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | .command = $cmd') ;; -+ string) MODIFIED_ARGS=$(echo "$INPUT" | jq -c --arg cmd "$REWRITTEN" '.toolArgs | ((fromjson? | if type == "object" then .command = $cmd else null end) // null)') ;; -+ *) MODIFIED_ARGS="null" ;; -+esac -+ - if [ "$EXIT_CODE" -eq 3 ]; then -- # Ask: rewrite the command, omit permissionDecision so Claude Code prompts. -- jq -n \ -- --argjson updated "$UPDATED_INPUT" \ -- '{ -- "hookSpecificOutput": { -- "hookEventName": "PreToolUse", -- "updatedInput": $updated -- } -- }' -+ DECISION="" - else -- # Allow: output the rewrite instruction in Claude Code hook format. -- jq -n \ -- --argjson updated "$UPDATED_INPUT" \ -- '{ -- "hookSpecificOutput": { -- "hookEventName": "PreToolUse", -- "permissionDecision": "allow", -- "permissionDecisionReason": "RTK auto-rewrite", -- "updatedInput": $updated -- } -- }' -+ DECISION="allow" - fi -+ -+jq -n \ -+ --argjson updated "$UPDATED_INPUT" \ -+ --argjson modified "$MODIFIED_ARGS" \ -+ --arg decision "$DECISION" \ -+ ' -+ ({ -+ hookSpecificOutput: ( -+ {hookEventName: "PreToolUse", updatedInput: $updated} -+ + (if $decision != "" then -+ {permissionDecision: $decision, permissionDecisionReason: "RTK auto-rewrite"} -+ else {} end) -+ ) -+ }) -+ + (if $modified != null then {modifiedArgs: $modified} else {} end) -+ ' diff --git a/scripts/sync-rtk-rewrite.sh b/scripts/sync-rtk-rewrite.sh index 310ae2f0d..29cfda23a 100755 --- a/scripts/sync-rtk-rewrite.sh +++ b/scripts/sync-rtk-rewrite.sh @@ -1,25 +1,30 @@ #!/usr/bin/env bash -# sync-rtk-rewrite.sh — Sync rtk-rewrite.sh from upstream rtk repo via raw GitHub. +# sync-rtk-rewrite.sh — check upstream rtk-rewrite.sh for drift. # -# After fetching upstream, applies scripts/rtk-rewrite.copilot.patch so the -# local Copilot-format support (reads .toolArgs, emits .modifiedArgs) survives -# the sync. If you upstream the patch to rtk-ai/rtk, delete the patch file and -# the apply step here. +# This repo's hook copies have local Copilot-format support (reads .toolArgs, +# emits .modifiedArgs) that upstream rtk-ai/rtk lacks. To protect those local +# additions, this script no longer overwrites the local copies — it just +# fetches upstream and prints a diff so changes can be ported in manually. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" URL="https://raw.githubusercontent.com/rtk-ai/rtk/master/.claude/hooks/rtk-rewrite.sh" -PATCH="$ROOT/scripts/rtk-rewrite.copilot.patch" tmpfile=$(mktemp) trap 'rm -f "$tmpfile"' EXIT curl -fsSL "$URL" -o "$tmpfile" -if [ -f "$PATCH" ]; then - patch "$tmpfile" <"$PATCH" +if diff -q "$tmpfile" "$ROOT/config/claude/hooks/rtk-rewrite.sh" >/dev/null; then + echo "No drift from upstream." + exit 0 fi -cp "$tmpfile" "$ROOT/config/claude/hooks/rtk-rewrite.sh" -cp "$tmpfile" "$ROOT/config/codex/hooks/rtk-rewrite.sh" -cp "$tmpfile" "$ROOT/config/copilot/hooks/rtk-rewrite.sh" +echo "Upstream drift detected (vs config/claude/hooks/rtk-rewrite.sh):" +diff -u "$tmpfile" "$ROOT/config/claude/hooks/rtk-rewrite.sh" || true +echo +echo "Local copies have Copilot-format support not in upstream. Review the" +echo "diff above and port any non-Copilot changes into all three hook files:" +echo " config/claude/hooks/rtk-rewrite.sh" +echo " config/codex/hooks/rtk-rewrite.sh" +echo " config/copilot/hooks/rtk-rewrite.sh"