| 1 | #!/usr/bin/env bash |
| 2 | set -euo pipefail |
| 3 | |
| 4 | usage() { |
| 5 | cat <<'USAGE' |
| 6 | Usage: scripts/check-cache-impact.sh [changed-file ...] |
| 7 | |
| 8 | Checks that PRs touching cache-sensitive prompt or tool surfaces include an |
| 9 | explicit cache-impact note and guard-test note in the pull request body. |
| 10 | |
| 11 | Inputs: |
| 12 | CACHE_IMPACT_PR_BODY or PR_BODY Pull request body text. |
| 13 | CACHE_IMPACT_PR_BODY_FILE File containing the pull request body. |
| 14 | CACHE_IMPACT_CHANGED_FILES Newline-separated changed files. |
| 15 | CACHE_IMPACT_CHANGED_FILES_FILE File containing newline-separated changed files. |
| 16 | CACHE_IMPACT_BASE_SHA / BASE_SHA Diff base when files are not supplied. |
| 17 | CACHE_IMPACT_HEAD_SHA / HEAD_SHA Diff head when files are not supplied. |
| 18 | USAGE |
| 19 | } |
| 20 | |
| 21 | if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then |
| 22 | usage |
| 23 | exit 0 |
| 24 | fi |
| 25 | |
| 26 | body="${CACHE_IMPACT_PR_BODY:-${PR_BODY:-}}" |
| 27 | if [[ -n "${CACHE_IMPACT_PR_BODY_FILE:-}" ]]; then |
| 28 | body="$(cat "$CACHE_IMPACT_PR_BODY_FILE")" |
| 29 | fi |
| 30 | |
| 31 | changed_input="" |
| 32 | if [[ "$#" -gt 0 ]]; then |
| 33 | changed_input="$(printf '%s\n' "$@")" |
| 34 | elif [[ -n "${CACHE_IMPACT_CHANGED_FILES_FILE:-}" ]]; then |
| 35 | changed_input="$(cat "$CACHE_IMPACT_CHANGED_FILES_FILE")" |
| 36 | elif [[ -n "${CACHE_IMPACT_CHANGED_FILES:-}" ]]; then |
| 37 | changed_input="$CACHE_IMPACT_CHANGED_FILES" |
| 38 | else |
| 39 | base="${CACHE_IMPACT_BASE_SHA:-${BASE_SHA:-}}" |
| 40 | head="${CACHE_IMPACT_HEAD_SHA:-${HEAD_SHA:-HEAD}}" |
| 41 | if [[ -z "$base" ]]; then |
| 42 | base="$(git merge-base origin/main-v2 "$head" 2>/dev/null || git merge-base main-v2 "$head")" |
| 43 | fi |
| 44 | diff_base="$base" |
| 45 | if merge_base="$(git merge-base "$base" "$head" 2>/dev/null)"; then |
| 46 | diff_base="$merge_base" |
| 47 | fi |
| 48 | changed_input="$(git diff --name-only "$diff_base" "$head")" |
| 49 | fi |
| 50 | |
| 51 | changed_files=() |
| 52 | while IFS= read -r file; do |
| 53 | [[ -z "$file" ]] && continue |
| 54 | changed_files+=("$file") |
| 55 | done <<< "$changed_input" |
| 56 | |
| 57 | cache_sensitive=() |
| 58 | system_prompt_sensitive=() |
| 59 | |
| 60 | for file in "${changed_files[@]:-}"; do |
| 61 | case "$file" in |
| 62 | desktop/session_prompt.go|\ |
| 63 | internal/agent/agent.go|\ |
| 64 | internal/agent/ask.go|\ |
| 65 | internal/agent/cache*|\ |
| 66 | internal/agent/compact*|\ |
| 67 | internal/agent/parallel_tasks.go|\ |
| 68 | internal/agent/prune*|\ |
| 69 | internal/agent/subagent_registry*|\ |
| 70 | internal/agent/task.go|\ |
| 71 | internal/boot/*|\ |
| 72 | internal/command/slashtool.go|\ |
| 73 | internal/config/config.go|\ |
| 74 | internal/config/system_prompt*|\ |
| 75 | internal/environment/*|\ |
| 76 | internal/history/tool.go|\ |
| 77 | internal/installsource/*|\ |
| 78 | internal/lsp/tool.go|\ |
| 79 | internal/memory/*|\ |
| 80 | internal/outputstyle/*|\ |
| 81 | internal/plugin/*|\ |
| 82 | internal/provider/*|\ |
| 83 | internal/skill/*|\ |
| 84 | internal/tool/*|\ |
| 85 | scripts/cache-guard.sh|\ |
| 86 | scripts/check-cache-impact.sh) |
| 87 | cache_sensitive+=("$file") |
| 88 | ;; |
| 89 | esac |
| 90 | |
| 91 | case "$file" in |
| 92 | desktop/session_prompt.go|\ |
| 93 | internal/agent/task.go|\ |
| 94 | internal/boot/*|\ |
| 95 | internal/config/config.go|\ |
| 96 | internal/config/system_prompt*|\ |
| 97 | internal/environment/*|\ |
| 98 | internal/memory/*|\ |
| 99 | internal/outputstyle/*|\ |
| 100 | internal/skill/*) |
| 101 | system_prompt_sensitive+=("$file") |
| 102 | ;; |
| 103 | esac |
| 104 | done |
| 105 | |
| 106 | if [[ "${#cache_sensitive[@]}" -eq 0 ]]; then |
| 107 | echo "No cache-sensitive prompt/tool files changed." |
| 108 | exit 0 |
| 109 | fi |
| 110 | |
| 111 | failures=() |
| 112 | |
| 113 | trim() { |
| 114 | local s="$1" |
| 115 | s="${s#"${s%%[![:space:]]*}"}" |
| 116 | s="${s%"${s##*[![:space:]]}"}" |
| 117 | printf '%s' "$s" |
| 118 | } |
| 119 | |
| 120 | field_value() { |
| 121 | local label="$1" |
| 122 | local line |
| 123 | line="$(printf '%s\n' "$body" | grep -Eim1 "^[[:space:]>#*_-]*${label}[[:space:]]*:" || true)" |
| 124 | [[ -z "$line" ]] && return 1 |
| 125 | trim "${line#*:}" |
| 126 | } |
| 127 | |
| 128 | require_field() { |
| 129 | local label="$1" |
| 130 | local value |
| 131 | if ! value="$(field_value "$label")"; then |
| 132 | failures+=("missing ${label}: line") |
| 133 | return |
| 134 | fi |
| 135 | if [[ -z "$value" || "$value" =~ ^[Tt][Oo][Dd][Oo]($|[[:space:]:-]) || "$value" =~ ^[Tt][Bb][Dd]($|[[:space:]:-]) ]]; then |
| 136 | failures+=("${label}: must be filled out") |
| 137 | fi |
| 138 | } |
| 139 | |
| 140 | require_review_field() { |
| 141 | local label="$1" |
| 142 | local value |
| 143 | if ! value="$(field_value "$label")"; then |
| 144 | failures+=("missing ${label}: line") |
| 145 | return |
| 146 | fi |
| 147 | local lower |
| 148 | lower="$(printf '%s' "$value" | tr '[:upper:]' '[:lower:]')" |
| 149 | if [[ -z "$value" || "$lower" =~ ^todo($|[[:space:]:-]) || "$lower" =~ ^tbd($|[[:space:]:-]) || "$lower" =~ ^n/?a($|[[:space:]:-]) || "$lower" =~ ^none($|[[:space:]:-]) ]]; then |
| 150 | failures+=("${label}: must name the explicit system-prompt review/approval") |
| 151 | fi |
| 152 | } |
| 153 | |
| 154 | require_field "Cache-impact" |
| 155 | require_field "Cache-guard" |
| 156 | |
| 157 | if [[ "${#system_prompt_sensitive[@]}" -gt 0 ]]; then |
| 158 | require_review_field "System-prompt-review" |
| 159 | fi |
| 160 | |
| 161 | if [[ "${#failures[@]}" -gt 0 ]]; then |
| 162 | { |
| 163 | echo "Cache impact check failed." |
| 164 | echo |
| 165 | echo "Cache-sensitive files changed:" |
| 166 | printf ' - %s\n' "${cache_sensitive[@]}" |
| 167 | if [[ "${#system_prompt_sensitive[@]}" -gt 0 ]]; then |
| 168 | echo |
| 169 | echo "System-prompt-sensitive files changed:" |
| 170 | printf ' - %s\n' "${system_prompt_sensitive[@]}" |
| 171 | fi |
| 172 | echo |
| 173 | echo "Required PR body lines:" |
| 174 | echo " Cache-impact: <none|low|medium|high> - <reason>" |
| 175 | echo " Cache-guard: <focused guard test/command or existing guard rationale>" |
| 176 | if [[ "${#system_prompt_sensitive[@]}" -gt 0 ]]; then |
| 177 | echo " System-prompt-review: <reviewer/approval note>" |
| 178 | fi |
| 179 | echo |
| 180 | echo "Failures:" |
| 181 | printf ' - %s\n' "${failures[@]}" |
| 182 | } >&2 |
| 183 | exit 1 |
| 184 | fi |
| 185 | |
| 186 | echo "Cache impact check passed." |
| 187 | echo "Cache-sensitive files:" |
| 188 | printf ' - %s\n' "${cache_sensitive[@]}" |
| 189 |