Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 42 additions & 22 deletions config/claude/hooks/rtk-rewrite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale warning after the sync refactor: this banner still says scripts/sync-rtk-rewrite.sh will OVERWRITE these additions on next run, but after 5e79309 the sync script no longer overwrites — it only curls upstream and prints a diff. Worth updating the wording (e.g. “Run scripts/sync-rtk-rewrite.sh to diff against upstream and port any non-Copilot changes in manually”) so future readers don't think their local edits are fragile. Remember to mirror the edit byte-for-byte into the codex and copilot copies so spec/sync_rtk_rewrite_spec.sh stays green.


# --- Audit logging (opt-in via RTK_HOOK_AUDIT=1) ---
_rtk_audit_log() {
Expand All @@ -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? | if type == "object" then .command else empty end) else empty end)
// empty
')
Comment thread
shunkakinoki marked this conversation as resolved.

if [ -z "$CMD" ]; then
_rtk_audit_log "skip:empty" "-"
Expand Down Expand Up @@ -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? | 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)
'
Comment on lines 93 to +127
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# 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)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

64 changes: 42 additions & 22 deletions config/codex/hooks/rtk-rewrite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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? | if type == "object" then .command else empty end) else empty end)
// empty
')
Comment thread
shunkakinoki marked this conversation as resolved.

if [ -z "$CMD" ]; then
_rtk_audit_log "skip:empty" "-"
Expand Down Expand Up @@ -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? | 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)
'
Comment on lines 93 to +127
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# 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)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

64 changes: 42 additions & 22 deletions config/copilot/hooks/rtk-rewrite.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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? | if type == "object" then .command else empty end) else empty end)
// empty
')
Comment thread
shunkakinoki marked this conversation as resolved.

if [ -z "$CMD" ]; then
_rtk_audit_log "skip:empty" "-"
Expand Down Expand Up @@ -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? | 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)
'
Comment on lines 93 to +127
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation uses multiple jq calls to construct the output JSON, which is inefficient in a shell script due to process forking. Additionally, the fromjson call on line 103 is potentially unsafe if .toolArgs is a string but not valid JSON.

Consolidating these into a single jq call improves performance and simplifies the logic while adding robustness to the JSON parsing.

Suggested change
# 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)
'
# Build the final output JSON, consolidating Claude and Copilot formats.
jq -n \
--argjson input "$INPUT" \
--arg rewritten "$REWRITTEN" \
--argjson is_ask "$([ "$EXIT_CODE" -eq 3 ] && echo true || echo false)" \
'
($input.tool_input // {} | .command = $rewritten) as $updated |
($input.toolArgs | if type == "object" then .command = $rewritten elif type == "string" then (fromjson? | if type == "object" then .command = $rewritten else null end) else null end) as $modified |
{
hookSpecificOutput: (
{hookEventName: "PreToolUse", updatedInput: $updated}
+ (if $is_ask then {} else {permissionDecision: "allow", permissionDecisionReason: "RTK auto-rewrite"} end)
)
}
+ (if $modified != null then {modifiedArgs: $modified} else {} end)
'

23 changes: 19 additions & 4 deletions scripts/sync-rtk-rewrite.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
#!/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.
#
# 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)"
Expand All @@ -10,6 +15,16 @@ trap 'rm -f "$tmpfile"' EXIT

curl -fsSL "$URL" -o "$tmpfile"

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"
if diff -q "$tmpfile" "$ROOT/config/claude/hooks/rtk-rewrite.sh" >/dev/null; then
echo "No drift from upstream."
exit 0
fi

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"
2 changes: 2 additions & 0 deletions spec/coverage_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions spec/sync_rtk_rewrite_spec.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions treefmt.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading