Initial setup: PostgreSQL best practices repository

Skeleton structure for Supabase PostgreSQL experts to add performance
optimization rules. Modeled after Vercel's react-best-practices-build.

Includes:
- Build system (parser, validator, builder)
- Skill manifest and metadata
- Rule templates and writing guidelines
- CI workflow for validation
- Getting started guide for Postgres team

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Pedro Rodrigues
2026-01-16 09:52:32 +07:00
commit 0a543e1b4a
18 changed files with 2143 additions and 0 deletions

43
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,43 @@
name: PostgreSQL Best Practices CI
on:
push:
branches: [main]
paths:
- 'skills/postgresql-best-practices/**'
- 'packages/postgresql-best-practices-build/**'
pull_request:
paths:
- 'skills/postgresql-best-practices/**'
- 'packages/postgresql-best-practices-build/**'
jobs:
validate-and-build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
working-directory: packages/postgresql-best-practices-build
run: npm install
- name: Validate rule files
working-directory: packages/postgresql-best-practices-build
run: npm run validate
- name: Build AGENTS.md
working-directory: packages/postgresql-best-practices-build
run: npm run build
- name: Check for uncommitted changes
run: |
if [[ -n $(git status --porcelain skills/postgresql-best-practices/AGENTS.md) ]]; then
echo "Error: AGENTS.md is not up to date"
echo "Run 'npm run build' and commit the changes"
git diff skills/postgresql-best-practices/AGENTS.md
exit 1
fi

4
.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
node_modules/
dist/
*.log
.DS_Store

85
GETTING_STARTED.md Normal file
View File

@@ -0,0 +1,85 @@
# Getting Started - Postgres Team
Quick guide to start adding PostgreSQL best practice rules.
## Setup
```bash
cd packages/postgresql-best-practices-build
npm install
```
## Add a Rule
1. Copy template:
```bash
cp skills/postgresql-best-practices/rules/_template.md \
skills/postgresql-best-practices/rules/query-your-rule.md
```
2. Edit the file with your rule content
3. Validate & build:
```bash
cd packages/postgresql-best-practices-build
npm run validate
npm run build
```
4. Check `skills/postgresql-best-practices/AGENTS.md` for output
## File Prefixes → Sections
| Prefix | Section |
|--------|---------|
| `query-` | 1. Query Performance (CRITICAL) |
| `conn-` | 2. Connection Management (CRITICAL) |
| `schema-` | 3. Schema Design (HIGH) |
| `lock-` | 4. Concurrency & Locking (MEDIUM-HIGH) |
| `security-` | 5. Security & RLS (MEDIUM-HIGH) |
| `data-` | 6. Data Access Patterns (MEDIUM) |
| `monitor-` | 7. Monitoring & Diagnostics (LOW-MEDIUM) |
| `advanced-` | 8. Advanced Features (LOW) |
## Rule Structure
```markdown
---
title: Action-Oriented Title
impact: CRITICAL|HIGH|MEDIUM-HIGH|MEDIUM|LOW-MEDIUM|LOW
impactDescription: 10x faster queries
tags: indexes, performance
---
## Title
Brief explanation.
**Incorrect (why it's bad):**
```sql
-- Bad pattern
```
**Correct (why it's better):**
```sql
-- Good pattern
```
**Supabase Note:** Optional platform guidance.
Reference: [Link](url)
```
## Key Files
| File | Purpose |
|------|---------|
| `rules/_template.md` | Copy this to create new rules |
| `rules/_contributing.md` | Writing guidelines |
| `rules/_sections.md` | Section definitions (editable) |
| `AGENTS.md` | Generated output (don't edit directly) |
## Questions?
- Writing guidelines: `rules/_contributing.md`
- Full contributor guide: `skills/postgresql-best-practices/README.md`

View File

@@ -0,0 +1,590 @@
{
"name": "postgresql-best-practices-build",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "postgresql-best-practices-build",
"version": "0.1.0",
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openharmony-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@types/node": {
"version": "20.19.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz",
"integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
}
},
"node_modules/esbuild": {
"version": "0.27.2",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.27.2",
"@esbuild/android-arm": "0.27.2",
"@esbuild/android-arm64": "0.27.2",
"@esbuild/android-x64": "0.27.2",
"@esbuild/darwin-arm64": "0.27.2",
"@esbuild/darwin-x64": "0.27.2",
"@esbuild/freebsd-arm64": "0.27.2",
"@esbuild/freebsd-x64": "0.27.2",
"@esbuild/linux-arm": "0.27.2",
"@esbuild/linux-arm64": "0.27.2",
"@esbuild/linux-ia32": "0.27.2",
"@esbuild/linux-loong64": "0.27.2",
"@esbuild/linux-mips64el": "0.27.2",
"@esbuild/linux-ppc64": "0.27.2",
"@esbuild/linux-riscv64": "0.27.2",
"@esbuild/linux-s390x": "0.27.2",
"@esbuild/linux-x64": "0.27.2",
"@esbuild/netbsd-arm64": "0.27.2",
"@esbuild/netbsd-x64": "0.27.2",
"@esbuild/openbsd-arm64": "0.27.2",
"@esbuild/openbsd-x64": "0.27.2",
"@esbuild/openharmony-arm64": "0.27.2",
"@esbuild/sunos-x64": "0.27.2",
"@esbuild/win32-arm64": "0.27.2",
"@esbuild/win32-ia32": "0.27.2",
"@esbuild/win32-x64": "0.27.2"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/get-tsconfig": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"resolve-pkg-maps": "^1.0.0"
},
"funding": {
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
}
},
"node_modules/resolve-pkg-maps": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
}
},
"node_modules/tsx": {
"version": "4.21.0",
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
"dev": true,
"license": "MIT",
"dependencies": {
"esbuild": "~0.27.0",
"get-tsconfig": "^4.7.5"
},
"bin": {
"tsx": "dist/cli.mjs"
},
"engines": {
"node": ">=18.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.3"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
}
}
}

View File

@@ -0,0 +1,18 @@
{
"name": "postgresql-best-practices-build",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsx src/build.ts",
"build-agents": "tsx src/build.ts --agents-only",
"validate": "tsx src/validate.ts",
"extract-tests": "tsx src/extract-tests.ts",
"dev": "npm run validate && npm run build"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}

View File

@@ -0,0 +1,247 @@
import { readdirSync, readFileSync, writeFileSync, existsSync } from "fs";
import { join, basename } from "path";
import { parseRuleFile } from "./parser.js";
import { validateRuleFile } from "./validate.js";
import { RULES_DIR, AGENTS_OUTPUT, METADATA_FILE } from "./config.js";
import type { Rule, Metadata, Section } from "./types.js";
/**
* Parse section definitions from _sections.md
*/
function parseSections(): Section[] {
const sectionsFile = join(RULES_DIR, "_sections.md");
if (!existsSync(sectionsFile)) {
console.warn("Warning: _sections.md not found, using default sections");
return getDefaultSections();
}
const content = readFileSync(sectionsFile, "utf-8");
const sections: Section[] = [];
const sectionMatches = content.matchAll(
/##\s+(\d+)\.\s+([^\n(]+)\s*\((\w+)\)\s*\n\*\*Impact:\*\*\s*(\w+(?:-\w+)?)\s*\n\*\*Description:\*\*\s*([^\n]+)/g
);
for (const match of sectionMatches) {
sections.push({
number: parseInt(match[1], 10),
title: match[2].trim(),
prefix: match[3].trim(),
impact: match[4].trim() as Section["impact"],
description: match[5].trim(),
});
}
return sections.length > 0 ? sections : getDefaultSections();
}
/**
* Default sections if _sections.md is missing or unparseable
*/
function getDefaultSections(): Section[] {
return [
{ number: 1, title: "Query Performance", prefix: "query", impact: "CRITICAL", description: "Slow queries, missing indexes, inefficient plans" },
{ number: 2, title: "Connection Management", prefix: "conn", impact: "CRITICAL", description: "Pooling, limits, serverless strategies" },
{ number: 3, title: "Schema Design", prefix: "schema", impact: "HIGH", description: "Table design, indexes, partitioning, data types" },
{ number: 4, title: "Concurrency & Locking", prefix: "lock", impact: "MEDIUM-HIGH", description: "Transactions, isolation, deadlocks" },
{ number: 5, title: "Security & RLS", prefix: "security", impact: "MEDIUM-HIGH", description: "Row-Level Security, privileges, auth patterns" },
{ number: 6, title: "Data Access Patterns", prefix: "data", impact: "MEDIUM", description: "N+1 queries, batch operations, pagination" },
{ number: 7, title: "Monitoring & Diagnostics", prefix: "monitor", impact: "LOW-MEDIUM", description: "pg_stat_statements, EXPLAIN, metrics" },
{ number: 8, title: "Advanced Features", prefix: "advanced", impact: "LOW", description: "Full-text search, JSONB, extensions" },
];
}
/**
* Load metadata from metadata.json
*/
function loadMetadata(): Metadata {
if (!existsSync(METADATA_FILE)) {
return {
version: "0.1.0",
organization: "Supabase",
date: new Date().toLocaleDateString("en-US", { month: "long", year: "numeric" }),
abstract: "PostgreSQL performance optimization guide for developers.",
references: [],
};
}
return JSON.parse(readFileSync(METADATA_FILE, "utf-8"));
}
/**
* Generate anchor from title
*/
function toAnchor(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, "")
.replace(/\s+/g, "-");
}
/**
* Build AGENTS.md from all rule files
*/
function buildAgents(): void {
console.log("Building AGENTS.md...\n");
// Load metadata and sections
const metadata = loadMetadata();
const sections = parseSections();
// Get all rule files
const ruleFiles = readdirSync(RULES_DIR)
.filter((f) => f.endsWith(".md") && !f.startsWith("_"))
.map((f) => join(RULES_DIR, f));
if (ruleFiles.length === 0) {
console.log("No rule files found. Generating empty AGENTS.md template.");
}
// Parse and validate all rules
const rules: Rule[] = [];
for (const file of ruleFiles) {
const validation = validateRuleFile(file);
if (!validation.valid) {
console.error(`Skipping invalid file ${basename(file)}:`);
validation.errors.forEach((e) => console.error(` - ${e}`));
continue;
}
const result = parseRuleFile(file);
if (result.success && result.rule) {
rules.push(result.rule);
}
}
// Group rules by section and assign IDs
const rulesBySection = new Map<number, Rule[]>();
for (const rule of rules) {
const sectionRules = rulesBySection.get(rule.section) || [];
sectionRules.push(rule);
rulesBySection.set(rule.section, sectionRules);
}
// Sort rules within each section and assign IDs
for (const [sectionNum, sectionRules] of rulesBySection) {
sectionRules.sort((a, b) => a.title.localeCompare(b.title));
sectionRules.forEach((rule, index) => {
rule.id = `${sectionNum}.${index + 1}`;
});
}
// Generate markdown output
const output: string[] = [];
// Header
output.push("# PostgreSQL Best Practices\n");
output.push(`**Version ${metadata.version}**`);
output.push(`${metadata.organization}`);
output.push(`${metadata.date}\n`);
output.push("> This document is optimized for AI agents and LLMs. Rules are prioritized by performance impact.\n");
output.push("---\n");
// Abstract
output.push("## Abstract\n");
output.push(`${metadata.abstract}\n`);
output.push("---\n");
// Table of Contents
output.push("## Table of Contents\n");
for (const section of sections) {
const sectionRules = rulesBySection.get(section.number) || [];
output.push(`${section.number}. [${section.title}](#${toAnchor(section.title)}) - **${section.impact}**`);
for (const rule of sectionRules) {
output.push(` - ${rule.id} [${rule.title}](#${toAnchor(rule.id + "-" + rule.title)})`);
}
output.push("");
}
output.push("---\n");
// Sections and Rules
for (const section of sections) {
const sectionRules = rulesBySection.get(section.number) || [];
output.push(`## ${section.number}. ${section.title}\n`);
output.push(`**Impact: ${section.impact}**\n`);
output.push(`${section.description}\n`);
if (sectionRules.length === 0) {
output.push("*No rules defined yet. See rules/_template.md for creating new rules.*\n");
}
for (const rule of sectionRules) {
output.push(`### ${rule.id} ${rule.title}\n`);
if (rule.impactDescription) {
output.push(`**Impact: ${rule.impact} (${rule.impactDescription})**\n`);
} else {
output.push(`**Impact: ${rule.impact}**\n`);
}
output.push(`${rule.explanation}\n`);
for (const example of rule.examples) {
if (example.description) {
output.push(`**${example.label} (${example.description}):**\n`);
} else {
output.push(`**${example.label}:**\n`);
}
output.push("```" + (example.language || "sql"));
output.push(example.code);
output.push("```\n");
if (example.additionalText) {
output.push(`${example.additionalText}\n`);
}
}
if (rule.supabaseNotes) {
output.push(`**Supabase Note:** ${rule.supabaseNotes}\n`);
}
if (rule.references && rule.references.length > 0) {
if (rule.references.length === 1) {
output.push(`Reference: ${rule.references[0]}\n`);
} else {
output.push("References:");
for (const ref of rule.references) {
output.push(`- ${ref}`);
}
output.push("");
}
}
output.push("---\n");
}
}
// References section
if (metadata.references && metadata.references.length > 0) {
output.push("## References\n");
for (const ref of metadata.references) {
output.push(`- ${ref}`);
}
output.push("");
}
// Write output
writeFileSync(AGENTS_OUTPUT, output.join("\n"));
console.log(`Generated: ${AGENTS_OUTPUT}`);
console.log(`Total rules: ${rules.length}`);
}
// Run build when executed directly
const isMainModule = process.argv[1]?.endsWith("build.ts") || process.argv[1]?.endsWith("build.js");
if (isMainModule) {
buildAgents();
}
export { buildAgents };

View File

@@ -0,0 +1,42 @@
import { fileURLToPath } from "url";
import { dirname, join } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Build package directory
export const BUILD_DIR = join(__dirname, "..");
// Skill directory (relative to build package)
export const SKILL_DIR = join(BUILD_DIR, "../../skills/postgresql-best-practices");
// Rules directory
export const RULES_DIR = join(SKILL_DIR, "rules");
// Output files
export const AGENTS_OUTPUT = join(SKILL_DIR, "AGENTS.md");
export const METADATA_FILE = join(SKILL_DIR, "metadata.json");
export const TEST_CASES_OUTPUT = join(BUILD_DIR, "test-cases.json");
// Section prefix to number mapping
export const SECTION_MAP: Record<string, number> = {
query: 1,
conn: 2,
connection: 2,
schema: 3,
lock: 4,
security: 5,
data: 6,
monitor: 7,
advanced: 8,
};
// Valid impact levels in priority order
export const IMPACT_LEVELS = [
"CRITICAL",
"HIGH",
"MEDIUM-HIGH",
"MEDIUM",
"LOW-MEDIUM",
"LOW",
] as const;

View File

@@ -0,0 +1,120 @@
import { readdirSync, writeFileSync } from "fs";
import { join, basename } from "path";
import { parseRuleFile } from "./parser.js";
import { validateRuleFile } from "./validate.js";
import { RULES_DIR, TEST_CASES_OUTPUT } from "./config.js";
import type { TestCase } from "./types.js";
/**
* Check if an example label indicates a "bad" pattern
*/
function isBadExample(label: string): boolean {
const lower = label.toLowerCase();
return lower.includes("incorrect") || lower.includes("wrong") || lower.includes("bad");
}
/**
* Check if an example label indicates a "good" pattern
*/
function isGoodExample(label: string): boolean {
const lower = label.toLowerCase();
return (
lower.includes("correct") ||
lower.includes("good") ||
lower.includes("usage") ||
lower.includes("implementation") ||
lower.includes("example") ||
lower.includes("recommended")
);
}
/**
* Extract test cases from all rule files
*/
function extractTestCases(): TestCase[] {
const testCases: TestCase[] = [];
// Get all rule files
const ruleFiles = readdirSync(RULES_DIR)
.filter((f) => f.endsWith(".md") && !f.startsWith("_"))
.map((f) => join(RULES_DIR, f));
// Track rule IDs by section for assignment
const ruleCountBySection = new Map<number, number>();
for (const file of ruleFiles) {
const validation = validateRuleFile(file);
if (!validation.valid) {
continue;
}
const result = parseRuleFile(file);
if (!result.success || !result.rule) {
continue;
}
const rule = result.rule;
// Assign rule ID
const sectionCount = (ruleCountBySection.get(rule.section) || 0) + 1;
ruleCountBySection.set(rule.section, sectionCount);
const ruleId = `${rule.section}.${sectionCount}`;
// Extract test cases from examples
for (const example of rule.examples) {
if (!example.code || example.code.trim().length === 0) {
continue;
}
let type: "bad" | "good" | null = null;
if (isBadExample(example.label)) {
type = "bad";
} else if (isGoodExample(example.label)) {
type = "good";
}
if (type) {
testCases.push({
ruleId,
ruleTitle: rule.title,
type,
code: example.code,
language: example.language || "sql",
description: example.description || `${example.label} example for ${rule.title}`,
});
}
}
}
return testCases;
}
// Run extraction when executed directly
const isMainModule = process.argv[1]?.endsWith("extract-tests.ts") || process.argv[1]?.endsWith("extract-tests.js");
if (isMainModule) {
console.log("Extracting test cases from rules...\n");
const testCases = extractTestCases();
if (testCases.length === 0) {
console.log("No test cases extracted (no valid rules found).");
console.log("This is expected for initial setup.\n");
// Write empty array
writeFileSync(TEST_CASES_OUTPUT, JSON.stringify([], null, 2));
console.log(`Generated: ${TEST_CASES_OUTPUT} (empty)`);
process.exit(0);
}
// Write test cases
writeFileSync(TEST_CASES_OUTPUT, JSON.stringify(testCases, null, 2));
console.log(`Generated: ${TEST_CASES_OUTPUT}`);
console.log(`Total test cases: ${testCases.length}`);
console.log(` Bad examples: ${testCases.filter((t) => t.type === "bad").length}`);
console.log(` Good examples: ${testCases.filter((t) => t.type === "good").length}`);
}
export { extractTestCases };

View File

@@ -0,0 +1,271 @@
import { readFileSync } from "fs";
import { basename } from "path";
import type { Rule, CodeExample, ImpactLevel, ParseResult } from "./types.js";
import { SECTION_MAP, IMPACT_LEVELS } from "./config.js";
/**
* Parse YAML-style frontmatter from markdown content
*/
function parseFrontmatter(content: string): {
frontmatter: Record<string, string>;
body: string;
} {
const frontmatter: Record<string, string> = {};
if (!content.startsWith("---")) {
return { frontmatter, body: content };
}
const endIndex = content.indexOf("---", 3);
if (endIndex === -1) {
return { frontmatter, body: content };
}
const frontmatterContent = content.slice(3, endIndex).trim();
const body = content.slice(endIndex + 3).trim();
for (const line of frontmatterContent.split("\n")) {
const colonIndex = line.indexOf(":");
if (colonIndex === -1) continue;
const key = line.slice(0, colonIndex).trim();
let value = line.slice(colonIndex + 1).trim();
// Strip quotes
if ((value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))) {
value = value.slice(1, -1);
}
frontmatter[key] = value;
}
return { frontmatter, body };
}
/**
* Extract section number from filename prefix
*/
function getSectionFromFilename(filename: string): number | null {
const base = basename(filename, ".md");
const prefix = base.split("-")[0];
return SECTION_MAP[prefix] ?? null;
}
/**
* Extract code examples from markdown body
*/
function extractExamples(body: string): CodeExample[] {
const examples: CodeExample[] = [];
const lines = body.split("\n");
let currentLabel = "";
let currentDescription = "";
let inCodeBlock = false;
let codeBlockLang = "";
let codeBlockContent: string[] = [];
let additionalText: string[] = [];
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// Check for example label: **Label:** or **Label (description):**
const labelMatch = line.match(/^\*\*([^*]+?)(?:\s*\(([^)]+)\))?\s*:\*\*\s*$/);
if (labelMatch && !inCodeBlock) {
// Save previous example if exists
if (currentLabel && codeBlockContent.length > 0) {
examples.push({
label: currentLabel,
description: currentDescription || undefined,
code: codeBlockContent.join("\n"),
language: codeBlockLang || undefined,
additionalText: additionalText.length > 0 ? additionalText.join("\n").trim() : undefined,
});
}
currentLabel = labelMatch[1].trim();
currentDescription = labelMatch[2]?.trim() || "";
codeBlockContent = [];
codeBlockLang = "";
additionalText = [];
continue;
}
// Check for code block start
if (line.startsWith("```") && !inCodeBlock) {
inCodeBlock = true;
codeBlockLang = line.slice(3).trim();
continue;
}
// Check for code block end
if (line.startsWith("```") && inCodeBlock) {
inCodeBlock = false;
continue;
}
// Collect code block content
if (inCodeBlock) {
codeBlockContent.push(line);
continue;
}
// Collect additional text after code block (before next label)
if (currentLabel && codeBlockContent.length > 0 && line.trim()) {
// Stop collecting if we hit a heading or reference
if (line.startsWith("#") || line.startsWith("Reference")) {
continue;
}
additionalText.push(line);
}
}
// Save last example
if (currentLabel && codeBlockContent.length > 0) {
examples.push({
label: currentLabel,
description: currentDescription || undefined,
code: codeBlockContent.join("\n"),
language: codeBlockLang || undefined,
additionalText: additionalText.length > 0 ? additionalText.join("\n").trim() : undefined,
});
}
return examples;
}
/**
* Extract title from first ## heading
*/
function extractTitle(body: string): string | null {
const match = body.match(/^##\s+(.+)$/m);
return match ? match[1].trim() : null;
}
/**
* Extract explanation (content between title and first example)
*/
function extractExplanation(body: string): string {
const lines = body.split("\n");
const explanationLines: string[] = [];
let foundTitle = false;
for (const line of lines) {
if (line.startsWith("## ")) {
foundTitle = true;
continue;
}
if (!foundTitle) continue;
// Stop at first example label or code block
if (line.match(/^\*\*[^*]+:\*\*/) || line.startsWith("```")) {
break;
}
explanationLines.push(line);
}
return explanationLines.join("\n").trim();
}
/**
* Extract references from body
*/
function extractReferences(body: string): string[] {
const references: string[] = [];
const lines = body.split("\n");
for (const line of lines) {
// Match "Reference: [text](url)" or "- [text](url)" after "References:"
const refMatch = line.match(/Reference:\s*\[([^\]]+)\]\(([^)]+)\)/);
if (refMatch) {
references.push(refMatch[2]);
continue;
}
// Match list items under References section
const listMatch = line.match(/^-\s*\[([^\]]+)\]\(([^)]+)\)/);
if (listMatch) {
references.push(listMatch[2]);
}
}
return references;
}
/**
* Extract Supabase notes
*/
function extractSupabaseNotes(body: string): string | undefined {
const match = body.match(/\*\*Supabase Note:\*\*\s*(.+?)(?=\n\n|\n\*\*|$)/s);
return match ? match[1].trim() : undefined;
}
/**
* Parse a rule file and return structured data
*/
export function parseRuleFile(filePath: string): ParseResult {
const errors: string[] = [];
const warnings: string[] = [];
try {
const content = readFileSync(filePath, "utf-8");
const { frontmatter, body } = parseFrontmatter(content);
// Extract section from filename
const section = getSectionFromFilename(filePath);
if (section === null) {
errors.push(`Could not determine section from filename: ${basename(filePath)}`);
return { success: false, errors, warnings };
}
// Get title from frontmatter or body
const title = frontmatter.title || extractTitle(body);
if (!title) {
errors.push("Missing title in frontmatter or body");
return { success: false, errors, warnings };
}
// Get impact level
const impact = frontmatter.impact as ImpactLevel;
if (!impact || !IMPACT_LEVELS.includes(impact)) {
errors.push(`Invalid or missing impact level: ${impact}. Must be one of: ${IMPACT_LEVELS.join(", ")}`);
return { success: false, errors, warnings };
}
// Extract other fields
const explanation = extractExplanation(body);
const examples = extractExamples(body);
const references = extractReferences(body);
const supabaseNotes = extractSupabaseNotes(body);
const tags = frontmatter.tags?.split(",").map((t) => t.trim()) || [];
// Validation warnings
if (!explanation || explanation.length < 20) {
warnings.push("Explanation is very short or missing");
}
if (examples.length === 0) {
warnings.push("No code examples found");
}
const rule: Rule = {
id: "", // Will be assigned during build
title,
section,
impact,
impactDescription: frontmatter.impactDescription,
explanation,
examples,
references: references.length > 0 ? references : undefined,
tags: tags.length > 0 ? tags : undefined,
supabaseNotes,
};
return { success: true, rule, errors, warnings };
} catch (error) {
errors.push(`Failed to parse file: ${error}`);
return { success: false, errors, warnings };
}
}

View File

@@ -0,0 +1,68 @@
export type ImpactLevel =
| "CRITICAL"
| "HIGH"
| "MEDIUM-HIGH"
| "MEDIUM"
| "LOW-MEDIUM"
| "LOW";
export interface CodeExample {
label: string;
description?: string;
code: string;
language?: string;
additionalText?: string;
}
export interface Rule {
id: string;
title: string;
section: number;
subsection?: number;
impact: ImpactLevel;
impactDescription?: string;
explanation: string;
examples: CodeExample[];
references?: string[];
tags?: string[];
supabaseNotes?: string;
}
export interface Section {
number: number;
title: string;
prefix: string;
impact: ImpactLevel;
description: string;
}
export interface Metadata {
version: string;
organization: string;
date: string;
abstract: string;
references: string[];
maintainers?: string[];
}
export interface ParseResult {
success: boolean;
rule?: Rule;
errors: string[];
warnings: string[];
}
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
export interface TestCase {
ruleId: string;
ruleTitle: string;
type: "bad" | "good";
code: string;
language: string;
description?: string;
}

View File

@@ -0,0 +1,186 @@
import { readdirSync } from "fs";
import { join, basename } from "path";
import { parseRuleFile } from "./parser.js";
import { RULES_DIR, IMPACT_LEVELS } from "./config.js";
import type { ValidationResult } from "./types.js";
/**
* Check if an example label indicates a "bad" pattern
*/
function isBadExample(label: string): boolean {
const lower = label.toLowerCase();
return lower.includes("incorrect") || lower.includes("wrong") || lower.includes("bad");
}
/**
* Check if an example label indicates a "good" pattern
*/
function isGoodExample(label: string): boolean {
const lower = label.toLowerCase();
return (
lower.includes("correct") ||
lower.includes("good") ||
lower.includes("usage") ||
lower.includes("implementation") ||
lower.includes("example") ||
lower.includes("recommended")
);
}
/**
* Validate a single rule file
*/
export function validateRuleFile(filePath: string): ValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
const result = parseRuleFile(filePath);
// Add parser errors and warnings
errors.push(...result.errors);
warnings.push(...result.warnings);
if (!result.success || !result.rule) {
return { valid: false, errors, warnings };
}
const rule = result.rule;
// Validate title
if (!rule.title || rule.title.trim().length === 0) {
errors.push("Missing or empty title");
}
// Validate explanation
if (!rule.explanation || rule.explanation.trim().length === 0) {
errors.push("Missing or empty explanation");
} else if (rule.explanation.length < 50) {
warnings.push("Explanation is shorter than 50 characters");
}
// Validate examples
if (rule.examples.length === 0) {
errors.push("Missing examples (need at least one bad and one good example)");
} else {
const hasBad = rule.examples.some((e) => isBadExample(e.label));
const hasGood = rule.examples.some((e) => isGoodExample(e.label));
if (!hasBad && !hasGood) {
errors.push("Missing bad/incorrect and good/correct examples");
} else if (!hasBad) {
warnings.push("Missing bad/incorrect example (recommended for clarity)");
} else if (!hasGood) {
errors.push("Missing good/correct example");
}
// Check for code in examples
const hasCode = rule.examples.some((e) => e.code && e.code.trim().length > 0);
if (!hasCode) {
errors.push("Examples have no code");
}
// Check for language specification
for (const example of rule.examples) {
if (example.code && !example.language) {
warnings.push(`Example "${example.label}" missing language specification`);
}
}
}
// Validate impact level
if (!IMPACT_LEVELS.includes(rule.impact)) {
errors.push(`Invalid impact level: ${rule.impact}. Must be one of: ${IMPACT_LEVELS.join(", ")}`);
}
// Warning for missing impact description
if (!rule.impactDescription) {
warnings.push("Missing impactDescription (recommended for quantifying benefit)");
}
return {
valid: errors.length === 0,
errors,
warnings,
};
}
/**
* Validate all rule files in the rules directory
*/
export function validateAllRules(): {
totalFiles: number;
validFiles: number;
invalidFiles: number;
results: Map<string, ValidationResult>;
} {
const results = new Map<string, ValidationResult>();
let validFiles = 0;
let invalidFiles = 0;
// Get all markdown files (excluding _ prefixed files)
const files = readdirSync(RULES_DIR)
.filter((f) => f.endsWith(".md") && !f.startsWith("_"))
.map((f) => join(RULES_DIR, f));
for (const file of files) {
const result = validateRuleFile(file);
results.set(basename(file), result);
if (result.valid) {
validFiles++;
} else {
invalidFiles++;
}
}
return {
totalFiles: files.length,
validFiles,
invalidFiles,
results,
};
}
// Run validation when executed directly
const isMainModule = process.argv[1]?.endsWith("validate.ts") || process.argv[1]?.endsWith("validate.js");
if (isMainModule) {
console.log("Validating PostgreSQL best practices rules...\n");
const { totalFiles, validFiles, invalidFiles, results } = validateAllRules();
if (totalFiles === 0) {
console.log("No rule files found (this is expected for initial setup).");
console.log("Create rule files in: skills/postgresql-best-practices/rules/");
console.log("Use the _template.md as a starting point.\n");
process.exit(0);
}
let hasErrors = false;
for (const [filename, result] of results) {
if (!result.valid || result.warnings.length > 0) {
console.log(`\n${filename}:`);
for (const error of result.errors) {
console.log(` ERROR: ${error}`);
hasErrors = true;
}
for (const warning of result.warnings) {
console.log(` WARNING: ${warning}`);
}
}
}
console.log(`\n${"=".repeat(50)}`);
console.log(`Total: ${totalFiles} files | Valid: ${validFiles} | Invalid: ${invalidFiles}`);
if (hasErrors) {
console.log("\nValidation failed. Please fix the errors above.");
process.exit(1);
} else {
console.log("\nValidation passed!");
process.exit(0);
}
}

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"outDir": "dist",
"rootDir": "src",
"declaration": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}

View File

@@ -0,0 +1,128 @@
# PostgreSQL Best Practices - Contributor Guide
This repository contains PostgreSQL performance optimization rules optimized for AI agents and LLMs.
## Quick Start
```bash
# Install dependencies
cd packages/postgresql-best-practices-build
npm install
# Validate existing rules
npm run validate
# Build AGENTS.md
npm run build
```
## Creating a New Rule
1. **Choose a section prefix** based on the category:
- `query-` Query Performance (CRITICAL)
- `conn-` Connection Management (CRITICAL)
- `schema-` Schema Design (HIGH)
- `lock-` Concurrency & Locking (MEDIUM-HIGH)
- `security-` Security & RLS (MEDIUM-HIGH)
- `data-` Data Access Patterns (MEDIUM)
- `monitor-` Monitoring & Diagnostics (LOW-MEDIUM)
- `advanced-` Advanced Features (LOW)
2. **Copy the template**:
```bash
cp rules/_template.md rules/query-your-rule-name.md
```
3. **Fill in the content** following the template structure
4. **Validate and build**:
```bash
npm run validate
npm run build
```
5. **Review** the generated `AGENTS.md`
## Repository Structure
```
skills/postgresql-best-practices/
├── SKILL.md # Agent-facing skill manifest
├── AGENTS.md # [GENERATED] Compiled rules document
├── README.md # This file
├── metadata.json # Version and metadata
└── rules/
├── _template.md # Rule template
├── _sections.md # Section definitions
├── _contributing.md # Writing guidelines
└── *.md # Individual rules
packages/postgresql-best-practices-build/
├── src/ # Build system source
├── package.json # NPM scripts
└── test-cases.json # [GENERATED] Test artifacts
```
## Rule File Structure
See `rules/_template.md` for the complete template. Key elements:
```markdown
---
title: Clear, Action-Oriented Title
impact: CRITICAL|HIGH|MEDIUM-HIGH|MEDIUM|LOW-MEDIUM|LOW
impactDescription: Quantified benefit (e.g., "10-100x faster")
tags: relevant, keywords
---
## [Title]
[1-2 sentence explanation]
**Incorrect (description):**
```sql
-- Comment explaining what's wrong
[Bad SQL example]
```
**Correct (description):**
```sql
-- Comment explaining why this is better
[Good SQL example]
```
**Supabase Note:** [Optional platform-specific guidance]
Reference: [Link](url)
```
## Writing Guidelines
See `rules/_contributing.md` for detailed guidelines. Key principles:
1. **Show concrete transformations** - "Change X to Y", not abstract advice
2. **Error-first structure** - Show the problem before the solution
3. **Quantify impact** - Include specific metrics (10x faster, 50% smaller)
4. **Self-contained examples** - Complete, runnable SQL
5. **Semantic naming** - Use meaningful names (users, email), not (table1, col1)
## Impact Levels
| Level | Improvement | Examples |
|-------|-------------|----------|
| CRITICAL | 10-100x | Missing indexes, connection exhaustion |
| HIGH | 5-20x | Wrong index types, poor partitioning |
| MEDIUM-HIGH | 2-5x | N+1 queries, RLS optimization |
| MEDIUM | 1.5-3x | Redundant indexes, stale statistics |
| LOW-MEDIUM | 1.2-2x | VACUUM tuning, config tweaks |
| LOW | Incremental | Advanced patterns, edge cases |
## Supabase-Specific Content
Keep ~90% of content as universal PostgreSQL patterns. Add Supabase notes for:
- Supavisor connection pooling
- Dashboard features (index monitoring, query stats)
- RLS best practices with Supabase auth
- PostgREST considerations

View File

@@ -0,0 +1,57 @@
---
name: supabase-postgresql-best-practices
description: PostgreSQL performance optimization and best practices from Supabase. Use this skill when writing, reviewing, or optimizing PostgreSQL queries, schema designs, or database configurations.
license: MIT
metadata:
author: supabase
version: "0.1.0"
---
# Supabase PostgreSQL Best Practices
Comprehensive performance optimization guide for PostgreSQL, maintained by Supabase. Contains rules across 8 categories, prioritized by impact to guide automated query optimization and schema design.
## When to Apply
Reference these guidelines when:
- Writing SQL queries or designing schemas
- Implementing indexes or query optimization
- Reviewing database performance issues
- Configuring connection pooling or scaling
- Optimizing for PostgreSQL-specific features
- Working with Row-Level Security (RLS)
## Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|----------|----------|--------|--------|
| 1 | Query Performance | CRITICAL | `query-` |
| 2 | Connection Management | CRITICAL | `conn-` |
| 3 | Schema Design | HIGH | `schema-` |
| 4 | Concurrency & Locking | MEDIUM-HIGH | `lock-` |
| 5 | Security & RLS | MEDIUM-HIGH | `security-` |
| 6 | Data Access Patterns | MEDIUM | `data-` |
| 7 | Monitoring & Diagnostics | LOW-MEDIUM | `monitor-` |
| 8 | Advanced Features | LOW | `advanced-` |
## How to Use
Read individual rule files for detailed explanations and SQL examples:
```
rules/query-missing-indexes.md
rules/schema-partial-indexes.md
rules/_sections.md
```
Each rule file contains:
- Brief explanation of why it matters
- Incorrect SQL example with explanation
- Correct SQL example with explanation
- Optional EXPLAIN output or metrics
- Additional context and references
- Supabase-specific notes (when applicable)
## Full Compiled Document
For the complete guide with all rules expanded: `AGENTS.md`

View File

@@ -0,0 +1,16 @@
{
"version": "0.1.0",
"organization": "Supabase",
"date": "January 2026",
"abstract": "Comprehensive PostgreSQL performance optimization guide for developers using Supabase and PostgreSQL. Contains performance rules across 8 categories, prioritized by impact from critical (query performance, connection management) to incremental (advanced features). Each rule includes detailed explanations, incorrect vs. correct SQL examples, query plan analysis, and specific performance metrics to guide automated optimization and code generation.",
"references": [
"https://www.postgresql.org/docs/current/",
"https://supabase.com/docs",
"https://wiki.postgresql.org/wiki/Performance_Optimization",
"https://supabase.com/docs/guides/database/overview",
"https://supabase.com/docs/guides/auth/row-level-security"
],
"maintainers": [
"Supabase PostgreSQL Team"
]
}

View File

@@ -0,0 +1,179 @@
# Writing Guidelines for PostgreSQL Rules
This document provides guidelines for creating effective PostgreSQL best practice rules that work well with AI agents and LLMs.
## Key Principles
### 1. Concrete Transformation Patterns
Show exact SQL rewrites. Avoid philosophical advice.
**Good:** "Use `WHERE id = ANY(ARRAY[...])` instead of `WHERE id IN (SELECT ...)`"
**Bad:** "Design good schemas"
### 2. Error-First Structure
Always show the problematic pattern first, then the solution. This trains agents to recognize anti-patterns.
```markdown
**Incorrect (sequential queries):**
[bad example]
**Correct (batched query):**
[good example]
```
### 3. Quantified Impact
Include specific metrics. Helps agents prioritize fixes.
**Good:** "10x faster queries", "50% smaller index", "Eliminates N+1"
**Bad:** "Faster", "Better", "More efficient"
### 4. Self-Contained Examples
Examples should be complete and runnable (or close to it). Include CREATE TABLE if context is needed.
```sql
-- Include table definition when needed for clarity
CREATE TABLE users (
id bigint PRIMARY KEY,
email text NOT NULL,
deleted_at timestamptz
);
-- Now show the index
CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
```
### 5. Semantic Naming
Use meaningful table/column names. Names carry intent for LLMs.
**Good:** `users`, `email`, `created_at`, `is_active`
**Bad:** `table1`, `col1`, `field`, `flag`
---
## Code Example Standards
### SQL Formatting
```sql
-- Use lowercase keywords, clear formatting
CREATE INDEX CONCURRENTLY users_email_idx
ON users(email)
WHERE deleted_at IS NULL;
-- Not cramped or ALL CAPS
CREATE INDEX CONCURRENTLY USERS_EMAIL_IDX ON USERS(EMAIL) WHERE DELETED_AT IS NULL;
```
### Comments
- Explain *why*, not *what*
- Highlight performance implications
- Point out common pitfalls
### Language Tags
- `sql` - Standard SQL queries
- `plpgsql` - Stored procedures/functions
- `typescript` - Application code (when needed)
- `python` - Application code (when needed)
---
## When to Include Application Code
**Default: SQL Only**
Most rules should focus on pure SQL patterns. This keeps examples portable.
**Include Application Code When:**
- Connection pooling configuration
- Transaction management in application context
- ORM anti-patterns (N+1 in Prisma/TypeORM)
- Prepared statement usage
**Format for Mixed Examples:**
```markdown
**Incorrect (N+1 in application):**
```typescript
for (const user of users) {
const posts = await db.query('SELECT * FROM posts WHERE user_id = $1', [user.id])
}
```
**Correct (batch query):**
```typescript
const posts = await db.query('SELECT * FROM posts WHERE user_id = ANY($1)', [userIds])
```
```
---
## Impact Level Guidelines
| Level | Improvement | Use When |
|-------|-------------|----------|
| **CRITICAL** | 10-100x | Missing indexes, connection exhaustion, sequential scans on large tables |
| **HIGH** | 5-20x | Wrong index types, poor partitioning, missing covering indexes |
| **MEDIUM-HIGH** | 2-5x | N+1 queries, inefficient pagination, RLS optimization |
| **MEDIUM** | 1.5-3x | Redundant indexes, query plan instability |
| **LOW-MEDIUM** | 1.2-2x | VACUUM tuning, configuration tweaks |
| **LOW** | Incremental | Advanced patterns, edge cases |
---
## Supabase-Specific Notes
**When to Add:**
- Supavisor pooling configuration
- Dashboard features (index monitoring, query stats)
- RLS patterns specific to Supabase auth
- PostgREST implications
**Format:**
```markdown
**Supabase Note:** The Dashboard > Database > Indexes page shows index usage statistics.
```
**Balance:** ~10% of content should be Supabase-specific. Core rules should work on any PostgreSQL.
---
## Reference Standards
**Primary Sources:**
- Official PostgreSQL documentation
- Supabase documentation
- PostgreSQL wiki
- Established blogs (2ndQuadrant, Crunchy Data)
**Format:**
```markdown
Reference: [PostgreSQL Indexes](https://www.postgresql.org/docs/current/indexes.html)
```
---
## Review Checklist
Before submitting a rule:
- [ ] Title is clear and action-oriented
- [ ] Impact level matches the performance gain
- [ ] impactDescription includes quantification
- [ ] Explanation is concise (1-2 sentences)
- [ ] Has at least 1 **Incorrect** SQL example
- [ ] Has at least 1 **Correct** SQL example
- [ ] SQL uses semantic naming
- [ ] Comments explain *why*, not *what*
- [ ] Trade-offs mentioned if applicable
- [ ] Reference links included
- [ ] `npm run validate` passes
- [ ] `npm run build` generates correct output

View File

@@ -0,0 +1,37 @@
# Section Definitions
This file defines the 8 rule categories for PostgreSQL best practices. Rules are automatically assigned to sections based on their filename prefix.
---
## 1. Query Performance (query)
**Impact:** CRITICAL
**Description:** Slow queries, missing indexes, inefficient query plans. The most common source of PostgreSQL performance issues.
## 2. Connection Management (conn)
**Impact:** CRITICAL
**Description:** Connection pooling, limits, and serverless strategies. Critical for applications with high concurrency or serverless deployments.
## 3. Schema Design (schema)
**Impact:** HIGH
**Description:** Table design, index strategies, partitioning, and data type selection. Foundation for long-term performance.
## 4. Concurrency & Locking (lock)
**Impact:** MEDIUM-HIGH
**Description:** Transaction management, isolation levels, deadlock prevention, and lock contention patterns.
## 5. Security & RLS (security)
**Impact:** MEDIUM-HIGH
**Description:** Row-Level Security policies, privilege management, and authentication patterns.
## 6. Data Access Patterns (data)
**Impact:** MEDIUM
**Description:** N+1 query elimination, batch operations, cursor-based pagination, and efficient data fetching.
## 7. Monitoring & Diagnostics (monitor)
**Impact:** LOW-MEDIUM
**Description:** Using pg_stat_statements, EXPLAIN ANALYZE, metrics collection, and performance diagnostics.
## 8. Advanced Features (advanced)
**Impact:** LOW
**Description:** Full-text search, JSONB optimization, PostGIS, extensions, and advanced PostgreSQL features.

View File

@@ -0,0 +1,36 @@
---
title: Clear, Action-Oriented Title (e.g., "Use Partial Indexes for Filtered Queries")
impact: MEDIUM
impactDescription: 5-20x query speedup for filtered queries
tags: indexes, query-optimization, performance
---
## [Rule Title]
[1-2 sentence explanation of the problem and why it matters. Focus on performance impact.]
**Incorrect (describe the problem):**
```sql
-- Comment explaining what makes this slow/problematic
CREATE INDEX users_email_idx ON users(email);
SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
-- This scans deleted records unnecessarily
```
**Correct (describe the solution):**
```sql
-- Comment explaining why this is better
CREATE INDEX users_active_email_idx ON users(email) WHERE deleted_at IS NULL;
SELECT * FROM users WHERE email = 'user@example.com' AND deleted_at IS NULL;
-- Only indexes active users, 10x smaller index, faster queries
```
[Optional: Additional context, edge cases, or trade-offs]
**Supabase Note:** [Optional platform-specific guidance, e.g., "Use Dashboard > Database > Indexes to monitor index usage"]
Reference: [PostgreSQL Docs](https://www.postgresql.org/docs/current/)