|
982 | 982 | }, |
983 | 983 | { |
984 | 984 | "id": "warn-psql-against-supabase-remote", |
985 | | - "description": "Warn when psql/SQL is executed directly against a *.supabase.co host (drift risk vs versioned migrations)", |
| 985 | + "description": "Warn when psql / pg_dump / pg_restore is executed directly against a *.supabase.co host (drift risk vs versioned migrations)", |
986 | 986 | "applies_to": [ |
987 | 987 | "Bash" |
988 | 988 | ], |
989 | 989 | "match": [ |
990 | 990 | { |
991 | 991 | "field": "command", |
992 | | - "pattern": "psql.*\\.supabase\\.co", |
| 992 | + "pattern": "\\b(psql|pg_dump|pg_restore|pg_dumpall)\\b.*\\.supabase\\.co", |
993 | 993 | "flags": "i" |
994 | 994 | } |
995 | 995 | ], |
996 | 996 | "action": "warn", |
997 | 997 | "bypass_marker": "psql-supabase-remote", |
998 | 998 | "memory_ref": "feedback_scripts_not_db.md", |
999 | | - "message": "WARNING: direct psql execution against a Supabase remote host.\n\nSQL Editor / psql-against-remote workflows create drift between the\nversioned migrations directory and other devs' local databases.\n\nCorrect workflow:\n 1. Add a migration in supabase/migrations/<timestamp>_<slug>.sql\n 2. Test locally: supabase db reset --local\n 3. Push to develop — CI applies the migration to staging automatically\n 4. For production: William runs the create-release SOP\n\nUse the bypass marker only for legitimate hotfixes where the PR cycle\nis too slow.\n", |
| 999 | + "message": "WARNING: direct psql / pg_dump / pg_restore execution against a Supabase\nremote host.\n\nSQL Editor / direct-Postgres-CLI workflows create drift between the\nversioned migrations directory and other devs' local databases.\n\nCorrect workflow:\n 1. Add a migration in supabase/migrations/<timestamp>_<slug>.sql\n 2. Test locally: supabase db reset --local\n 3. Push to develop — CI applies the migration to staging automatically\n 4. For production: follow the create-release SOP\n\nUse the bypass marker only for legitimate hotfixes where the PR cycle\nis too slow.\n", |
1000 | 1000 | "tests": [ |
1001 | 1001 | { |
1002 | 1002 | "name": "warns-on-psql-supabase-remote", |
1003 | 1003 | "input": { |
1004 | 1004 | "tool_input": { |
1005 | | - "command": "psql postgresql://user:pass@db.xepaexmpawtpqtilhcpw.supabase.co:5432/postgres -c 'SELECT 1'" |
| 1005 | + "command": "psql postgresql://user:pass@db.example1.supabase.co:5432/postgres -c 'SELECT 1'" |
1006 | 1006 | } |
1007 | 1007 | }, |
1008 | 1008 | "expected_exit": 0, |
|
1012 | 1012 | "name": "warns-on-psql-different-supabase-host", |
1013 | 1013 | "input": { |
1014 | 1014 | "tool_input": { |
1015 | | - "command": "psql -h db.ukwovawzehnebuoowcec.supabase.co -d postgres -c 'CREATE INDEX foo_idx ON foo (id)'" |
| 1015 | + "command": "psql -h db.example2.supabase.co -d postgres -c 'CREATE INDEX foo_idx ON foo (id)'" |
| 1016 | + } |
| 1017 | + }, |
| 1018 | + "expected_exit": 0, |
| 1019 | + "expected_stderr_contains": "warn-psql-against-supabase-remote" |
| 1020 | + }, |
| 1021 | + { |
| 1022 | + "name": "warns-on-pg-dump-supabase-remote", |
| 1023 | + "input": { |
| 1024 | + "tool_input": { |
| 1025 | + "command": "pg_dump -h db.example1.supabase.co -d postgres -t public.foo > foo.sql" |
| 1026 | + } |
| 1027 | + }, |
| 1028 | + "expected_exit": 0, |
| 1029 | + "expected_stderr_contains": "warn-psql-against-supabase-remote" |
| 1030 | + }, |
| 1031 | + { |
| 1032 | + "name": "warns-on-pg-restore-supabase-remote", |
| 1033 | + "input": { |
| 1034 | + "tool_input": { |
| 1035 | + "command": "pg_restore -h db.example1.supabase.co -d postgres backup.dump" |
1016 | 1036 | } |
1017 | 1037 | }, |
1018 | 1038 | "expected_exit": 0, |
|
1031 | 1051 | "name": "allows-non-psql-supabase-references", |
1032 | 1052 | "input": { |
1033 | 1053 | "tool_input": { |
1034 | | - "command": "curl https://xepaexmpawtpqtilhcpw.supabase.co/rest/v1/foo" |
| 1054 | + "command": "curl https://example1.supabase.co/rest/v1/foo" |
1035 | 1055 | } |
1036 | 1056 | }, |
1037 | 1057 | "expected_exit": 0 |
|
1040 | 1060 | "name": "allows-bypass-marker", |
1041 | 1061 | "input": { |
1042 | 1062 | "tool_input": { |
1043 | | - "command": "psql -h db.xepaexmpawtpqtilhcpw.supabase.co -c 'SELECT 1' # hook-bypass: psql-supabase-remote" |
| 1063 | + "command": "psql -h db.example1.supabase.co -c 'SELECT 1' # hook-bypass: psql-supabase-remote" |
1044 | 1064 | } |
1045 | 1065 | }, |
1046 | 1066 | "expected_exit": 0 |
|
1049 | 1069 | }, |
1050 | 1070 | { |
1051 | 1071 | "id": "pr-create-with-migrations-needs-deploy-note", |
1052 | | - "description": "Warn when gh pr create body does not mention migrations (caller should verify the PR has no migration files)", |
| 1072 | + "description": "Warn when gh pr create body (inline --body) does not mention migrations (caller should verify the PR has no migration files)", |
1053 | 1073 | "applies_to": [ |
1054 | 1074 | "Bash" |
1055 | 1075 | ], |
1056 | 1076 | "match": [ |
1057 | 1077 | { |
1058 | 1078 | "field": "command", |
1059 | | - "pattern": "gh[[:space:]]+pr[[:space:]]+create.*--body", |
| 1079 | + "pattern": "gh[[:space:]]+pr[[:space:]]+create.*--body([[:space:]]|=|$)", |
| 1080 | + "not_pattern": "--body-file", |
1060 | 1081 | "flags": "i" |
1061 | 1082 | }, |
1062 | 1083 | { |
|
1068 | 1089 | "action": "warn", |
1069 | 1090 | "bypass_marker": "pr-create-no-migration-note", |
1070 | 1091 | "memory_ref": "feedback_scripts_not_db.md", |
1071 | | - "message": "WARNING: gh pr create body does not mention migrations.\n\nIf your PR modifies files under supabase/migrations/, the body MUST\ninclude a \"Migration deployment\" section that lists:\n 1. Which envs need supabase db push (staging auto via CI; prod via release)\n 2. Backfill order if the migrations depend on existing data\n 3. Post-merge verification steps\n\nThis rule does NOT inspect the diff — it only reads the command. Use\nthe bypass marker if your PR has no migrations.\n", |
| 1092 | + "message": "WARNING: gh pr create body does not mention migrations.\n\nIf your PR modifies files under supabase/migrations/, the body MUST\ninclude a \"Migration deployment\" section that lists:\n 1. Which envs need supabase db push (staging auto via CI; prod via release)\n 2. Backfill order if the migrations depend on existing data\n 3. Post-merge verification steps\n\nThis rule does NOT inspect the diff — it only reads the command, and it\nintentionally skips --body-file invocations because it cannot read the\nreferenced file. Use the bypass marker if your PR has no migrations.\n", |
1072 | 1093 | "tests": [ |
1073 | 1094 | { |
1074 | 1095 | "name": "warns-on-pr-create-body-without-migration-mention", |
|
1098 | 1119 | }, |
1099 | 1120 | "expected_exit": 0 |
1100 | 1121 | }, |
| 1122 | + { |
| 1123 | + "name": "allows-pr-create-with-body-file", |
| 1124 | + "input": { |
| 1125 | + "tool_input": { |
| 1126 | + "command": "gh pr create --base develop --title \"feat: foo\" --body-file ./pr-body.md" |
| 1127 | + } |
| 1128 | + }, |
| 1129 | + "expected_exit": 0 |
| 1130 | + }, |
1101 | 1131 | { |
1102 | 1132 | "name": "allows-bypass-marker", |
1103 | 1133 | "input": { |
|
1111 | 1141 | }, |
1112 | 1142 | { |
1113 | 1143 | "id": "block-supabase-db-push-prod", |
1114 | | - "description": "Block supabase db push targeting the PRODUCTION project ref without an explicit bypass marker", |
| 1144 | + "description": "Block supabase db push that targets the PRODUCTION project ref or uses --linked (the linked project may transparently be production)", |
1115 | 1145 | "applies_to": [ |
1116 | 1146 | "Bash" |
1117 | 1147 | ], |
1118 | 1148 | "match": [ |
1119 | 1149 | { |
1120 | 1150 | "field": "command", |
1121 | | - "pattern": "supabase[[:space:]]+db[[:space:]]+push.*ukwovawzehnebuoowcec" |
| 1151 | + "pattern": "supabase[[:space:]]+db[[:space:]]+push.*(__PROD_SUPABASE_REF__|--linked\\b)" |
1122 | 1152 | } |
1123 | 1153 | ], |
1124 | 1154 | "action": "block", |
1125 | 1155 | "bypass_marker": "prod-db-push-approved", |
1126 | 1156 | "memory_ref": "feedback_never_touch_prod_without_approval.md", |
1127 | | - "message": "BLOCKED: supabase db push targeting the PRODUCTION project ref\n(ukwovawzehnebuoowcec).\n\nNEVER run against production without explicit user approval — see\nfeedback_never_touch_prod_without_approval.md.\n\nCorrect workflow:\n 1. Merge to main\n 2. William runs the release per the SOP at\n https://github.com/DojoCodingLabs/dojo-os/blob/develop/.claude/commands/create-release.md\n\nUse the bypass marker ONLY when the user has verbally approved the\noperation.\n", |
| 1157 | + "message": "BLOCKED: supabase db push targeting either the PRODUCTION project ref\nor the currently-linked project (which may be production).\n\nNEVER run against production without explicit user approval — see\nfeedback_never_touch_prod_without_approval.md.\n\nCorrect workflow:\n 1. Merge to main\n 2. The release owner runs the create-release SOP that ships\n migrations to production through CI, not from a developer machine.\n\nUse the bypass marker ONLY when the user has verbally approved the\noperation. For --linked, double-check the active link first:\n supabase status --output json | jq -r '.project_ref'\n\nMarketplace consumers: the production project ref is configured per\ninstall via .private/substitutions.json (PROD_SUPABASE_REF token). See\nhooks/rules/README.md for the substitution workflow.\n", |
1128 | 1158 | "tests": [ |
1129 | 1159 | { |
1130 | 1160 | "name": "blocks-supabase-db-push-prod", |
1131 | 1161 | "input": { |
1132 | 1162 | "tool_input": { |
1133 | | - "command": "supabase db push --db-url postgresql://postgres:pwd@db.ukwovawzehnebuoowcec.supabase.co:5432/postgres" |
| 1163 | + "command": "supabase db push --db-url postgresql://postgres:pwd@db.__PROD_SUPABASE_REF__.supabase.co:5432/postgres" |
1134 | 1164 | } |
1135 | 1165 | }, |
1136 | 1166 | "expected_exit": 2 |
|
1139 | 1169 | "name": "blocks-supabase-db-push-prod-with-project-ref-flag", |
1140 | 1170 | "input": { |
1141 | 1171 | "tool_input": { |
1142 | | - "command": "supabase db push --project-ref ukwovawzehnebuoowcec" |
| 1172 | + "command": "supabase db push --project-ref __PROD_SUPABASE_REF__" |
| 1173 | + } |
| 1174 | + }, |
| 1175 | + "expected_exit": 2 |
| 1176 | + }, |
| 1177 | + { |
| 1178 | + "name": "blocks-supabase-db-push-linked", |
| 1179 | + "input": { |
| 1180 | + "tool_input": { |
| 1181 | + "command": "supabase db push --linked" |
| 1182 | + } |
| 1183 | + }, |
| 1184 | + "expected_exit": 2 |
| 1185 | + }, |
| 1186 | + { |
| 1187 | + "name": "blocks-supabase-db-push-linked-with-extra-flags", |
| 1188 | + "input": { |
| 1189 | + "tool_input": { |
| 1190 | + "command": "supabase db push --linked --include-all" |
1143 | 1191 | } |
1144 | 1192 | }, |
1145 | 1193 | "expected_exit": 2 |
|
1148 | 1196 | "name": "allows-supabase-db-push-staging", |
1149 | 1197 | "input": { |
1150 | 1198 | "tool_input": { |
1151 | | - "command": "supabase db push --project-ref xepaexmpawtpqtilhcpw" |
| 1199 | + "command": "supabase db push --project-ref __STAGING_SUPABASE_REF__" |
1152 | 1200 | } |
1153 | 1201 | }, |
1154 | 1202 | "expected_exit": 0 |
|
1163 | 1211 | "expected_exit": 0 |
1164 | 1212 | }, |
1165 | 1213 | { |
1166 | | - "name": "allows-bypass-marker", |
| 1214 | + "name": "allows-bypass-marker-prod-ref", |
| 1215 | + "input": { |
| 1216 | + "tool_input": { |
| 1217 | + "command": "supabase db push --project-ref __PROD_SUPABASE_REF__ # hook-bypass: prod-db-push-approved" |
| 1218 | + } |
| 1219 | + }, |
| 1220 | + "expected_exit": 0 |
| 1221 | + }, |
| 1222 | + { |
| 1223 | + "name": "allows-bypass-marker-linked", |
1167 | 1224 | "input": { |
1168 | 1225 | "tool_input": { |
1169 | | - "command": "supabase db push --project-ref ukwovawzehnebuoowcec # hook-bypass: prod-db-push-approved" |
| 1226 | + "command": "supabase db push --linked # hook-bypass: prod-db-push-approved" |
1170 | 1227 | } |
1171 | 1228 | }, |
1172 | 1229 | "expected_exit": 0 |
|
0 commit comments