tool-match-before-tool-match 1.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. #!/usr/bin/env bash
  2. # Verify any Bash call with command matching a regex fires before any other Bash call
  3. # matching a second regex.
  4. #
  5. # Usage: tool-match-before-tool-match <tool-name> <earlier-regex> <tool-name> <later-regex>
  6. # Example: tool-match-before-tool-match Bash 'pytest' Bash 'git[[:space:]]+commit'
  7. #
  8. # Semantics:
  9. # - If no call matches the "later" regex, PASS (vacuously — the gated event never happened).
  10. # - If the "later" call fires but no "earlier" call preceded it, FAIL.
  11. set -euo pipefail
  12. command -v jq >/dev/null || { echo "jq required"; exit 127; }
  13. TOOL_A="$1"
  14. REGEX_A="$2"
  15. TOOL_B="$3"
  16. REGEX_B="$4"
  17. FILE="tool_calls.jsonl"
  18. if [ ! -s "$FILE" ]; then
  19. echo "FAIL: tool_calls.jsonl missing or empty"
  20. exit 1
  21. fi
  22. IDX_A=$(
  23. jq -s --arg tool "$TOOL_A" --arg re "$REGEX_A" \
  24. 'to_entries | map(select(.value.tool == $tool and ((.value.args.command // "") | test($re)))) | first | (.key // -1)' \
  25. "$FILE"
  26. )
  27. IDX_B=$(
  28. jq -s --arg tool "$TOOL_B" --arg re "$REGEX_B" \
  29. 'to_entries | map(select(.value.tool == $tool and ((.value.args.command // "") | test($re)))) | first | (.key // -1)' \
  30. "$FILE"
  31. )
  32. if [ "$IDX_B" -lt 0 ]; then
  33. echo "PASS: no $TOOL_B call matched /$REGEX_B/ — assertion is vacuous"
  34. exit 0
  35. fi
  36. if [ "$IDX_A" -lt 0 ]; then
  37. echo "FAIL: $TOOL_B /$REGEX_B/ fired at line $((IDX_B + 1)) but no $TOOL_A /$REGEX_A/ preceded it"
  38. exit 1
  39. fi
  40. if [ "$IDX_A" -lt "$IDX_B" ]; then
  41. echo "PASS: $TOOL_A /$REGEX_A/ at line $((IDX_A + 1)) before $TOOL_B /$REGEX_B/ at line $((IDX_B + 1))"
  42. exit 0
  43. else
  44. echo "FAIL: $TOOL_A /$REGEX_A/ at line $((IDX_A + 1)) fired after $TOOL_B /$REGEX_B/ at line $((IDX_B + 1))"
  45. exit 1
  46. fi