Skip to content

Commit 588957e

Browse files
committed
feat(rules): add warn-curl-mutating-supabase-rest (close feedback_scripts_not_db.md)
Adds a single warn rule to the migration-discipline family that catches the last documented escape hatch from the "no ad-hoc DB writes" memory: direct curl mutations (POST / PATCH / PUT / DELETE) against the Supabase PostgREST endpoint. The neighbor warn-psql-against-supabase-remote already covers the psql / pg_dump / pg_restore vector; warn-curl-mutating-supabase-rest closes the final gap. Match is anchored on the explicit `-X <METHOD>` form and narrowed to `.supabase.co/rest/v1/` so Auth, Storage, and Edge Function endpoints are not flagged. Test fixtures use sanitized hosts (example.supabase.co) — same convention the migration-discipline rules adopted in PR #15 round-2. - 19 rules (was 18) - 93 / 93 tests pass (was 86, +7) - 1.7.0 -> 1.8.0 in package.json, plugin.json, marketplace.json (top-level + nested plugin entry) - README family entry extended with the new rule
1 parent ded6a12 commit 588957e

9 files changed

Lines changed: 292 additions & 15 deletions

File tree

.claude-plugin/marketplace.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
33
"name": "make-no-mistakes",
4-
"version": "1.7.0",
4+
"version": "1.8.0",
55
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, and stash secrets via OS-native prompts. One plugin to make no mistakes.",
66
"owner": {
77
"name": "Luis Andres Pena Castillo",
@@ -11,7 +11,7 @@
1111
{
1212
"name": "make-no-mistakes",
1313
"description": "Dev lifecycle orchestrator: disciplined Linear issue execution with worktree isolation, PR review with Greptile gating, team release sync, E2E test generation and execution, test suite previewer, security pentesting, MoSCoW + RICE prioritization, cross-platform secret stash via OS-native GUI prompts (zenity / kdialog / osascript / Get-Credential), and session management. 18 commands, 6 auto-activating skills, 2 specialized agents.",
14-
"version": "1.7.0",
14+
"version": "1.8.0",
1515
"author": {
1616
"name": "Luis Andres Pena Castillo",
1717
"email": "lapc506@users.noreply.github.com"

.claude-plugin/plugin.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "make-no-mistakes",
3-
"version": "1.7.0",
3+
"version": "1.8.0",
44
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks. One plugin to make no mistakes.",
55
"author": {
66
"name": "Luis Andres Pena Castillo",

hooks/rules/README.md

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,21 @@ Adding a new family is fine — just keep ids unique and follow the schema.
145145
- **Database / migration discipline**
146146
(`schema-sql-outside-migrations`, `warn-psql-against-supabase-remote`,
147147
`pr-create-with-migrations-needs-deploy-note`,
148-
`block-supabase-db-push-prod`) — keep schema mutations inside
149-
versioned `supabase/migrations/` files, nudge developers away from
150-
direct `psql` / `pg_dump` / `pg_restore` execution against
151-
`*.supabase.co` hosts, remind PR authors to document migration
152-
deployment, and hard-block `supabase db push` aimed at the production
153-
project ref or `--linked` (which transparently resolves to whichever
154-
project was last linked, possibly production). The production project
155-
ref is configured per install via the substitutions mechanism described
156-
above (`PROD_SUPABASE_REF` token). Added after a discussion surfaced
157-
drift between manually-applied SQL and the migrations directory when
158-
migrations failed to auto-run after a teammate's PR merged.
148+
`block-supabase-db-push-prod`, `warn-curl-mutating-supabase-rest`) —
149+
keep schema mutations inside versioned `supabase/migrations/` files,
150+
nudge developers away from direct `psql` / `pg_dump` / `pg_restore`
151+
execution against `*.supabase.co` hosts, remind PR authors to document
152+
migration deployment, hard-block `supabase db push` aimed at the
153+
production project ref or `--linked` (which transparently resolves to
154+
whichever project was last linked, possibly production), and warn on
155+
ad-hoc mutating `curl` (`POST` / `PATCH` / `PUT` / `DELETE`) against
156+
the Supabase PostgREST endpoint (drift risk equivalent to direct `psql`
157+
+ RLS bypass — same memory ref `feedback_scripts_not_db.md`). The
158+
production project ref is configured per install via the substitutions
159+
mechanism described above (`PROD_SUPABASE_REF` token). Added after a
160+
discussion surfaced drift between manually-applied SQL and the
161+
migrations directory when migrations failed to auto-run after a
162+
teammate's PR merged.
159163

160164
## Tier 2 — decomposing non-deterministic memories
161165

hooks/rules/rules.json

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1229,5 +1229,92 @@
12291229
"expected_exit": 0
12301230
}
12311231
]
1232+
},
1233+
{
1234+
"id": "warn-curl-mutating-supabase-rest",
1235+
"description": "Warn when curl issues a mutating HTTP method (POST/PATCH/PUT/DELETE) against the Supabase PostgREST endpoint (drift risk vs versioned migrations + RLS bypass)",
1236+
"applies_to": [
1237+
"Bash"
1238+
],
1239+
"match": [
1240+
{
1241+
"field": "command",
1242+
"pattern": "curl.*-X[[:space:]]+(POST|PATCH|PUT|DELETE).*\\.supabase\\.co/rest/v1/",
1243+
"flags": "i"
1244+
}
1245+
],
1246+
"action": "warn",
1247+
"bypass_marker": "curl-supabase-rest-mutation",
1248+
"memory_ref": "feedback_scripts_not_db.md",
1249+
"message": "WARNING: mutating curl against the Supabase PostgREST endpoint.\n\nDirect REST mutations bypass:\n - the versioned migrations workflow (drift across local DBs / envs),\n - the RLS + admin-role checks the Supabase clients enforce, and\n - the access pattern already encapsulated in src/services/api/.\n\nCorrect outlets:\n - Schema changes -> supabase/migrations/<timestamp>_<slug>.sql\n - Data mutations -> an Edge Function or a committed script\n - One-shot fixes -> use the API client from a versioned script\n\nUse the bypass marker (curl-supabase-rest-mutation) only for documented\nhotfixes that the team has explicitly approved.\n",
1250+
"tests": [
1251+
{
1252+
"name": "blocks-curl-post-supabase-rest",
1253+
"input": {
1254+
"tool_input": {
1255+
"command": "curl -X POST https://example.supabase.co/rest/v1/profiles -H 'apikey: x' -d '{\"id\":1}'"
1256+
}
1257+
},
1258+
"expected_exit": 0,
1259+
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
1260+
},
1261+
{
1262+
"name": "blocks-curl-patch",
1263+
"input": {
1264+
"tool_input": {
1265+
"command": "curl -X PATCH https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"a\"}'"
1266+
}
1267+
},
1268+
"expected_exit": 0,
1269+
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
1270+
},
1271+
{
1272+
"name": "blocks-curl-put",
1273+
"input": {
1274+
"tool_input": {
1275+
"command": "curl -X PUT https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"b\"}'"
1276+
}
1277+
},
1278+
"expected_exit": 0,
1279+
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
1280+
},
1281+
{
1282+
"name": "blocks-curl-delete",
1283+
"input": {
1284+
"tool_input": {
1285+
"command": "curl -X DELETE https://example.supabase.co/rest/v1/profiles?id=eq.1"
1286+
}
1287+
},
1288+
"expected_exit": 0,
1289+
"expected_stderr_contains": "warn-curl-mutating-supabase-rest"
1290+
},
1291+
{
1292+
"name": "allows-curl-get-supabase-rest",
1293+
"input": {
1294+
"tool_input": {
1295+
"command": "curl https://example.supabase.co/rest/v1/profiles?select=id,name"
1296+
}
1297+
},
1298+
"expected_exit": 0
1299+
},
1300+
{
1301+
"name": "allows-curl-non-supabase",
1302+
"input": {
1303+
"tool_input": {
1304+
"command": "curl -X POST https://example.com/api/v1/users -d '{\"id\":1}'"
1305+
}
1306+
},
1307+
"expected_exit": 0
1308+
},
1309+
{
1310+
"name": "allows-bypass-marker",
1311+
"input": {
1312+
"tool_input": {
1313+
"command": "curl -X POST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}' # hook-bypass: curl-supabase-rest-mutation"
1314+
}
1315+
},
1316+
"expected_exit": 0
1317+
}
1318+
]
12321319
}
12331320
]

hooks/rules/rules.yaml

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,3 +985,74 @@
985985
tool_input:
986986
command: 'supabase db push --linked # hook-bypass: prod-db-push-approved'
987987
expected_exit: 0
988+
989+
- id: warn-curl-mutating-supabase-rest
990+
description: Warn when curl issues a mutating HTTP method (POST/PATCH/PUT/DELETE) against the Supabase PostgREST endpoint (drift risk vs versioned migrations + RLS bypass)
991+
applies_to: [Bash]
992+
match:
993+
# Anchor on the explicit `-X <METHOD>` form. Curl defaults to GET, so any
994+
# author issuing a mutation invariably types `-X POST/PATCH/PUT/DELETE`.
995+
# Match `.supabase.co/rest/v1/` (PostgREST) specifically, so this rule
996+
# does NOT fire for Supabase Auth / Storage / Edge Function endpoints —
997+
# those have separate guidance and Edge Functions are the intended outlet.
998+
- field: command
999+
pattern: 'curl.*-X[[:space:]]+(POST|PATCH|PUT|DELETE).*\.supabase\.co/rest/v1/'
1000+
flags: i
1001+
action: warn
1002+
bypass_marker: curl-supabase-rest-mutation
1003+
memory_ref: feedback_scripts_not_db.md
1004+
message: |
1005+
WARNING: mutating curl against the Supabase PostgREST endpoint.
1006+
1007+
Direct REST mutations bypass:
1008+
- the versioned migrations workflow (drift across local DBs / envs),
1009+
- the RLS + admin-role checks the Supabase clients enforce, and
1010+
- the access pattern already encapsulated in src/services/api/.
1011+
1012+
Correct outlets:
1013+
- Schema changes -> supabase/migrations/<timestamp>_<slug>.sql
1014+
- Data mutations -> an Edge Function or a committed script
1015+
- One-shot fixes -> use the API client from a versioned script
1016+
1017+
Use the bypass marker (curl-supabase-rest-mutation) only for documented
1018+
hotfixes that the team has explicitly approved.
1019+
tests:
1020+
- name: blocks-curl-post-supabase-rest
1021+
input:
1022+
tool_input:
1023+
command: "curl -X POST https://example.supabase.co/rest/v1/profiles -H 'apikey: x' -d '{\"id\":1}'"
1024+
expected_exit: 0
1025+
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
1026+
- name: blocks-curl-patch
1027+
input:
1028+
tool_input:
1029+
command: "curl -X PATCH https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"a\"}'"
1030+
expected_exit: 0
1031+
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
1032+
- name: blocks-curl-put
1033+
input:
1034+
tool_input:
1035+
command: "curl -X PUT https://example.supabase.co/rest/v1/profiles?id=eq.1 -d '{\"name\":\"b\"}'"
1036+
expected_exit: 0
1037+
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
1038+
- name: blocks-curl-delete
1039+
input:
1040+
tool_input:
1041+
command: 'curl -X DELETE https://example.supabase.co/rest/v1/profiles?id=eq.1'
1042+
expected_exit: 0
1043+
expected_stderr_contains: 'warn-curl-mutating-supabase-rest'
1044+
- name: allows-curl-get-supabase-rest
1045+
input:
1046+
tool_input:
1047+
command: 'curl https://example.supabase.co/rest/v1/profiles?select=id,name'
1048+
expected_exit: 0
1049+
- name: allows-curl-non-supabase
1050+
input:
1051+
tool_input:
1052+
command: "curl -X POST https://example.com/api/v1/users -d '{\"id\":1}'"
1053+
expected_exit: 0
1054+
- name: allows-bypass-marker
1055+
input:
1056+
tool_input:
1057+
command: "curl -X POST https://example.supabase.co/rest/v1/profiles -d '{\"id\":1}' # hook-bypass: curl-supabase-rest-mutation"
1058+
expected_exit: 0
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Design — `warn-curl-mutating-supabase-rest`
2+
3+
## Pattern
4+
5+
```
6+
'curl.*-X[[:space:]]+(POST|PATCH|PUT|DELETE).*\.supabase\.co/rest/v1/'
7+
```
8+
9+
Rationale:
10+
11+
- Anchor on the explicit `-X <METHOD>` form. `curl` defaults to `GET`, and
12+
any author using a mutating method invariably types it as `-X POST` /
13+
`-X PATCH` / `-X PUT` / `-X DELETE`. (Curl also accepts `--request`, but
14+
the long form is uncommon enough that we accept the false negative —
15+
authors using `--request` can be expected to know the rule and self-flag.)
16+
- Match `.supabase.co/rest/v1/` rather than just `.supabase.co` so we don't
17+
flag mutations against Supabase Auth / Storage endpoints (those have
18+
separate guidance) or Edge Functions (which are exempt — they're the
19+
intended outlet).
20+
- Use `i` flag for case-insensitivity on the method names.
21+
22+
## Action
23+
24+
`warn`, not `block`. The rule is a nudge, not a hard stop — there are
25+
legitimate reasons to issue a one-shot REST mutation (e.g. seeding a
26+
local-only fixture, debugging an Edge Function invocation chain). The
27+
`warn-psql-against-supabase-remote` neighbor uses the same severity; they
28+
are the same class of nudge.
29+
30+
## Bypass marker
31+
32+
`curl-supabase-rest-mutation` — kebab-case, unique. Documented as
33+
"hotfix only".
34+
35+
## Test fixtures
36+
37+
Use a **sanitized** Supabase host (`example.supabase.co`) — same convention
38+
as the migration-discipline rules added in PR #15 round-2. Real project
39+
refs would either leak in the public toolkit or trip the IP-leak guard.
40+
Tests required (≥5):
41+
42+
1. `blocks-curl-post-supabase-rest` — POST mutation, expects warn.
43+
2. `blocks-curl-patch` — PATCH mutation, expects warn.
44+
3. `blocks-curl-put` — PUT mutation, expects warn.
45+
4. `blocks-curl-delete` — DELETE mutation, expects warn.
46+
5. `allows-curl-get-supabase-rest` — GET (read), expects allow.
47+
6. `allows-curl-non-supabase` — POST against `example.com`, expects allow.
48+
7. `allows-bypass-marker` — POST + bypass comment, expects allow.
49+
50+
(Test names use the `blocks-*` prefix purely for readability symmetry with
51+
neighboring rules. Action is `warn`, so `expected_exit` is `0` everywhere
52+
and we use `expected_stderr_contains` to assert the warning fired.)
53+
54+
## Why warn, not block
55+
56+
A `block` would frustrate legitimate one-off REST debugging. The author of
57+
`feedback_scripts_not_db.md` is consistent: they prefer a warn for
58+
psql-against-remote (the documented neighbor rule), and the same severity
59+
logic applies here. If repeat offenders surface in code review, we can
60+
escalate the action to `block` in a follow-up PR without changing the
61+
schema.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Proposal — `warn-curl-mutating-supabase-rest`
2+
3+
## Why
4+
5+
`feedback_scripts_not_db.md` mandates that schema and data mutations always go
6+
through versioned migrations or committed scripts — never via ad-hoc `psql`,
7+
the SQL Editor, or direct REST calls against the live Supabase project. The
8+
existing `warn-psql-against-supabase-remote` rule covers the `psql` /
9+
`pg_dump` / `pg_restore` path, but `curl -X POST/PATCH/PUT/DELETE` against
10+
PostgREST (`https://<project>.supabase.co/rest/v1/...`) is an equivalent
11+
escape hatch that the manifest does not yet warn about.
12+
13+
PostgREST mutations:
14+
15+
- bypass the versioned migrations workflow (drift vs other devs' local DBs),
16+
- skip the RLS / role checks the Supabase clients enforce,
17+
- and circumvent the API-client layer in `src/services/api/` that already
18+
encapsulates the same access pattern.
19+
20+
## What
21+
22+
Add **one new warn-only rule** to the manifest:
23+
24+
```
25+
warn-curl-mutating-supabase-rest
26+
applies_to: Bash
27+
match: command contains `curl ... -X (POST|PATCH|PUT|DELETE) ... .supabase.co/rest/v1/`
28+
action: warn
29+
bypass_marker: curl-supabase-rest-mutation
30+
memory_ref: feedback_scripts_not_db.md
31+
```
32+
33+
`GET` requests (read-only inspection) are intentionally not flagged; the rule
34+
targets mutations only.
35+
36+
## Impact
37+
38+
- `hooks/rules/rules.yaml` — append the new rule + ≥5 tests
39+
- `hooks/rules/rules.json` — regenerated artifact
40+
- `hooks/rules/README.md` — extend the "Database / migration discipline"
41+
family entry to mention the new rule
42+
- 3 manifests bumped `1.7.0 → 1.8.0` (`package.json`, `.claude-plugin/plugin.json`,
43+
`.claude-plugin/marketplace.json` top-level + nested plugin entry)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Tasks — `warn-curl-mutating-supabase-rest`
2+
3+
- [x] Append rule to `hooks/rules/rules.yaml` with ≥5 tests
4+
- [x] Run `npm run build-rules` to regenerate `hooks/rules/rules.json`
5+
- [x] Run `npm run test-hooks` — all tests pass (target: 86 → 93+)
6+
- [x] Update `hooks/rules/README.md` family entry
7+
- [x] Bump version `1.7.0 → 1.8.0` in 3 manifests (package.json,
8+
.claude-plugin/plugin.json, .claude-plugin/marketplace.json)
9+
- [x] Commit + push + open PR + tag Greptile
10+
- [x] Loop until Greptile 5/5 (substantive findings only)
11+
- [x] `gh pr merge --squash --delete-branch`

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lapc506/make-no-mistakes",
3-
"version": "1.7.0",
3+
"version": "1.8.0",
44
"description": "The disciplined dev lifecycle — implement issues, review PRs, sync releases, test E2E, manage sessions, stash secrets, and enforce manifest-driven tool-call hooks (no SSH+DB, no manual prod, no minified build, no secret leaks, Slack format). OpenCode + Claude Code plugin.",
55
"type": "module",
66
"main": "./dist/index.js",

0 commit comments

Comments
 (0)