Add Biome formatter/linter and restore CI workflow (#6)

- Install Biome as the project formatter and linter
- Configure Biome with recommended settings
- Add format, lint, and check scripts to package.json
- Restore CI workflow from git history (commit 0a543e1)
- Extend CI with new Biome job for format and lint checks
- Apply Biome formatting to all TypeScript files
- Fix linting issues (use node: protocol, template literals, forEach pattern)

CI now runs on:
- All pushes to main branch
- All pull requests

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Pedro Rodrigues
2026-01-22 08:28:49 +00:00
committed by GitHub
parent 0ffac720f0
commit f323d3b601
13 changed files with 980 additions and 609 deletions

View File

@@ -1,22 +1,20 @@
{
"name": "supabase-agent-skills",
"owner": {
"name": "Supabase",
"email": "support@supabase.com"
},
"metadata": {
"description": "Official Supabase agent skills for Claude Code",
"version": "1.0.0"
},
"plugins": [
{
"name": "postgres-best-practices",
"description": "Postgres performance optimization and best practices. Use when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.",
"source": "./",
"strict": false,
"skills": [
"./skills/postgres-best-practices"
]
}
]
"name": "supabase-agent-skills",
"owner": {
"name": "Supabase",
"email": "support@supabase.com"
},
"metadata": {
"description": "Official Supabase agent skills for Claude Code",
"version": "1.0.0"
},
"plugins": [
{
"name": "postgres-best-practices",
"description": "Postgres performance optimization and best practices. Use when writing, reviewing, or optimizing Postgres queries, schema designs, or database configurations.",
"source": "./",
"strict": false,
"skills": ["./skills/postgres-best-practices"]
}
]
}

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

@@ -0,0 +1,54 @@
name: Postgres Best Practices CI
on:
push:
branches: [main]
pull_request:
jobs:
biome:
name: Format and Lint (Biome)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Run Biome CI
run: npm run ci:check
validate-and-build:
name: 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/postgres-best-practices-build
run: npm install
- name: Validate rule files
working-directory: packages/postgres-best-practices-build
run: npm run validate
- name: Build AGENTS.md
working-directory: packages/postgres-best-practices-build
run: npm run build
- name: Check for uncommitted changes
run: |
if [[ -n $(git status --porcelain skills/postgres-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/postgres-best-practices/AGENTS.md
exit 1
fi

34
biome.json Normal file
View File

@@ -0,0 +1,34 @@
{
"$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
"vcs": {
"enabled": true,
"clientKind": "git",
"useIgnoreFile": true
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true,
"indentStyle": "tab"
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"quoteStyle": "double"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

179
package-lock.json generated Normal file
View File

@@ -0,0 +1,179 @@
{
"name": "supabase-agent-skills",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "supabase-agent-skills",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"@biomejs/biome": "2.3.11"
}
},
"node_modules/@biomejs/biome": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.11.tgz",
"integrity": "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==",
"dev": true,
"license": "MIT OR Apache-2.0",
"bin": {
"biome": "bin/biome"
},
"engines": {
"node": ">=14.21.3"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/biome"
},
"optionalDependencies": {
"@biomejs/cli-darwin-arm64": "2.3.11",
"@biomejs/cli-darwin-x64": "2.3.11",
"@biomejs/cli-linux-arm64": "2.3.11",
"@biomejs/cli-linux-arm64-musl": "2.3.11",
"@biomejs/cli-linux-x64": "2.3.11",
"@biomejs/cli-linux-x64-musl": "2.3.11",
"@biomejs/cli-win32-arm64": "2.3.11",
"@biomejs/cli-win32-x64": "2.3.11"
}
},
"node_modules/@biomejs/cli-darwin-arm64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.11.tgz",
"integrity": "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-darwin-x64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.11.tgz",
"integrity": "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.11.tgz",
"integrity": "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-arm64-musl": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.11.tgz",
"integrity": "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.11.tgz",
"integrity": "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-linux-x64-musl": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.11.tgz",
"integrity": "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-arm64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.11.tgz",
"integrity": "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
},
"node_modules/@biomejs/cli-win32-x64": {
"version": "2.3.11",
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.11.tgz",
"integrity": "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT OR Apache-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=14.21.3"
}
}
}
}

View File

@@ -1,11 +1,20 @@
{
"name": "supabase-agent-skills",
"version": "1.0.0",
"author": "Supabase",
"license": "MIT",
"description": "Official Supabase agent skills",
"scripts": {
"build": "npm --prefix packages/postgres-best-practices-build run build",
"validate": "npm --prefix packages/postgres-best-practices-build run validate"
}
"name": "supabase-agent-skills",
"version": "1.0.0",
"author": "Supabase",
"license": "MIT",
"description": "Official Supabase agent skills",
"scripts": {
"build": "npm --prefix packages/postgres-best-practices-build run build",
"validate": "npm --prefix packages/postgres-best-practices-build run validate",
"format": "biome format --write .",
"format:check": "biome format .",
"lint": "biome lint --write .",
"lint:check": "biome lint .",
"check": "biome check --write .",
"ci:check": "biome ci ."
},
"devDependencies": {
"@biomejs/biome": "2.3.11"
}
}

View File

@@ -1,18 +1,18 @@
{
"name": "postgres-best-practices-build",
"version": "1.0.0",
"type": "module",
"author": "Supabase",
"license": "MIT",
"description": "Build system for Supabase agent skills",
"scripts": {
"build": "tsx src/build.ts",
"validate": "tsx src/validate.ts",
"dev": "npm run validate && npm run build"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
"name": "postgres-best-practices-build",
"version": "1.0.0",
"type": "module",
"author": "Supabase",
"license": "MIT",
"description": "Build system for Supabase agent skills",
"scripts": {
"build": "tsx src/build.ts",
"validate": "tsx src/validate.ts",
"dev": "npm run validate && npm run build"
},
"devDependencies": {
"@types/node": "^20.10.0",
"tsx": "^4.7.0",
"typescript": "^5.3.0"
}
}

View File

@@ -1,243 +1,306 @@
import { readdirSync, readFileSync, writeFileSync, existsSync } from "fs";
import { join, basename } from "path";
import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
import { basename, join } from "node:path";
import { AGENTS_OUTPUT, METADATA_FILE, RULES_DIR } from "./config.js";
import { parseRuleFile } from "./parser.js";
import type { Metadata, Rule, Section } from "./types.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 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 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
);
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(),
});
}
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();
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" },
];
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: "1.0.0",
organization: "Supabase",
date: new Date().toLocaleDateString("en-US", { month: "long", year: "numeric" }),
abstract: "Postgres performance optimization guide for developers.",
references: [],
};
}
if (!existsSync(METADATA_FILE)) {
return {
version: "1.0.0",
organization: "Supabase",
date: new Date().toLocaleDateString("en-US", {
month: "long",
year: "numeric",
}),
abstract: "Postgres performance optimization guide for developers.",
references: [],
};
}
return JSON.parse(readFileSync(METADATA_FILE, "utf-8"));
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, "-");
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");
console.log("Building AGENTS.md...\n");
// Load metadata and sections
const metadata = loadMetadata();
const sections = parseSections();
// 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));
// 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.");
}
if (ruleFiles.length === 0) {
console.log("No rule files found. Generating empty AGENTS.md template.");
}
// Parse and validate all rules
const rules: Rule[] = [];
// 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;
}
for (const file of ruleFiles) {
const validation = validateRuleFile(file);
if (!validation.valid) {
console.error(`Skipping invalid file ${basename(file)}:`);
for (const e of validation.errors) {
console.error(` - ${e}`);
}
continue;
}
const result = parseRuleFile(file);
if (result.success && result.rule) {
rules.push(result.rule);
}
}
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[]>();
// 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);
}
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}`;
});
}
// 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[] = [];
// Generate markdown output
const output: string[] = [];
// Header
output.push("# Postgres 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");
// Header
output.push("# Postgres 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");
// Abstract
output.push("## Abstract\n");
output.push(`${metadata.abstract}\n`);
output.push("---\n");
// Table of Contents
output.push("## Table of Contents\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 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)})`);
}
for (const rule of sectionRules) {
output.push(
` - ${rule.id} [${rule.title}](#${toAnchor(`${rule.id}-${rule.title}`)})`,
);
}
output.push("");
}
output.push("");
}
output.push("---\n");
output.push("---\n");
// Sections and Rules
for (const section of sections) {
const sectionRules = rulesBySection.get(section.number) || [];
// 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`);
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");
}
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`);
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`);
}
if (rule.impactDescription) {
output.push(`**Impact: ${rule.impact} (${rule.impactDescription})**\n`);
} else {
output.push(`**Impact: ${rule.impact}**\n`);
}
output.push(`${rule.explanation}\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`);
}
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");
output.push(`\`\`\`${example.language || "sql"}`);
output.push(example.code);
output.push("```\n");
if (example.additionalText) {
output.push(`${example.additionalText}\n`);
}
}
if (example.additionalText) {
output.push(`${example.additionalText}\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("");
}
}
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");
}
}
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("");
}
// 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}`);
// 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");
const isMainModule =
process.argv[1]?.endsWith("build.ts") ||
process.argv[1]?.endsWith("build.js");
if (isMainModule) {
buildAgents();
buildAgents();
}
export { buildAgents };

View File

@@ -1,5 +1,5 @@
import { fileURLToPath } from "url";
import { dirname, join } from "path";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -8,7 +8,10 @@ const __dirname = dirname(__filename);
export const BUILD_DIR = join(__dirname, "..");
// Skill directory (relative to build package)
export const SKILL_DIR = join(BUILD_DIR, "../../skills/postgres-best-practices");
export const SKILL_DIR = join(
BUILD_DIR,
"../../skills/postgres-best-practices",
);
// Rules directory
export const RULES_DIR = join(SKILL_DIR, "rules");
@@ -19,23 +22,23 @@ export const METADATA_FILE = join(SKILL_DIR, "metadata.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,
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",
"CRITICAL",
"HIGH",
"MEDIUM-HIGH",
"MEDIUM",
"LOW-MEDIUM",
"LOW",
] as const;

View File

@@ -1,261 +1,275 @@
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";
import { readFileSync } from "node:fs";
import { basename } from "node:path";
import { IMPACT_LEVELS, SECTION_MAP } from "./config.js";
import type { CodeExample, ImpactLevel, ParseResult, Rule } from "./types.js";
/**
* Parse YAML-style frontmatter from markdown content
*/
function parseFrontmatter(content: string): {
frontmatter: Record<string, string>;
body: string;
frontmatter: Record<string, string>;
body: string;
} {
const frontmatter: Record<string, string> = {};
const frontmatter: Record<string, string> = {};
if (!content.startsWith("---")) {
return { frontmatter, body: content };
}
if (!content.startsWith("---")) {
return { frontmatter, body: content };
}
const endIndex = content.indexOf("---", 3);
if (endIndex === -1) {
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();
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;
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();
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);
}
// Strip quotes
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
frontmatter[key] = value;
}
frontmatter[key] = value;
}
return { frontmatter, body };
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;
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");
const examples: CodeExample[] = [];
const lines = body.split("\n");
let currentLabel = "";
let currentDescription = "";
let inCodeBlock = false;
let codeBlockLang = "";
let codeBlockContent: string[] = [];
let additionalText: string[] = [];
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];
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,
});
}
// 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;
}
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 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;
}
// Check for code block end
if (line.startsWith("```") && inCodeBlock) {
inCodeBlock = false;
continue;
}
// Collect code block content
if (inCodeBlock) {
codeBlockContent.push(line);
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);
}
}
// 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,
});
}
// 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;
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;
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;
const lines = body.split("\n");
const explanationLines: string[] = [];
let foundTitle = false;
for (const line of lines) {
if (line.startsWith("## ")) {
foundTitle = true;
continue;
}
for (const line of lines) {
if (line.startsWith("## ")) {
foundTitle = true;
continue;
}
if (!foundTitle) continue;
if (!foundTitle) continue;
// Stop at first example label or code block
if (line.match(/^\*\*[^*]+:\*\*/) || line.startsWith("```")) {
break;
}
// Stop at first example label or code block
if (line.match(/^\*\*[^*]+:\*\*/) || line.startsWith("```")) {
break;
}
explanationLines.push(line);
}
explanationLines.push(line);
}
return explanationLines.join("\n").trim();
return explanationLines.join("\n").trim();
}
/**
* Extract references from body
*/
function extractReferences(body: string): string[] {
const references: string[] = [];
const lines = body.split("\n");
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;
}
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]);
}
}
// Match list items under References section
const listMatch = line.match(/^-\s*\[([^\]]+)\]\(([^)]+)\)/);
if (listMatch) {
references.push(listMatch[2]);
}
}
return references;
return references;
}
/**
* Parse a rule file and return structured data
*/
export function parseRuleFile(filePath: string): ParseResult {
const errors: string[] = [];
const warnings: string[] = [];
const errors: string[] = [];
const warnings: string[] = [];
try {
const content = readFileSync(filePath, "utf-8");
const { frontmatter, body } = parseFrontmatter(content);
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 };
}
// 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 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 };
}
// 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);
// Extract other fields
const explanation = extractExplanation(body);
const examples = extractExamples(body);
const tags = frontmatter.tags?.split(",").map((t) => t.trim()) || [];
const tags = frontmatter.tags?.split(",").map((t) => t.trim()) || [];
// Validation warnings
if (!explanation || explanation.length < 20) {
warnings.push("Explanation is very short or missing");
}
// 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");
}
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: extractReferences(body),
tags: tags.length > 0 ? tags : undefined,
};
const rule: Rule = {
id: "", // Will be assigned during build
title,
section,
impact,
impactDescription: frontmatter.impactDescription,
explanation,
examples,
references: extractReferences(body),
tags: tags.length > 0 ? tags : undefined,
};
return { success: true, rule, errors, warnings };
} catch (error) {
errors.push(`Failed to parse file: ${error}`);
return { success: false, errors, warnings };
}
return { success: true, rule, errors, warnings };
} catch (error) {
errors.push(`Failed to parse file: ${error}`);
return { success: false, errors, warnings };
}
}

View File

@@ -1,60 +1,59 @@
export type ImpactLevel =
| "CRITICAL"
| "HIGH"
| "MEDIUM-HIGH"
| "MEDIUM"
| "LOW-MEDIUM"
| "LOW";
| "CRITICAL"
| "HIGH"
| "MEDIUM-HIGH"
| "MEDIUM"
| "LOW-MEDIUM"
| "LOW";
export interface CodeExample {
label: string;
description?: string;
code: string;
language?: string;
additionalText?: string;
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;
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;
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[];
version: string;
organization: string;
date: string;
abstract: string;
references: string[];
maintainers?: string[];
}
export interface ParseResult {
success: boolean;
rule?: Rule;
errors: string[];
warnings: string[];
success: boolean;
rule?: Rule;
errors: string[];
warnings: string[];
}
export interface ValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
valid: boolean;
errors: string[];
warnings: string[];
}

View File

@@ -1,186 +1,204 @@
import { readdirSync } from "fs";
import { join, basename } from "path";
import { readdirSync } from "node:fs";
import { basename, join } from "node:path";
import { IMPACT_LEVELS, RULES_DIR } from "./config.js";
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");
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")
);
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 errors: string[] = [];
const warnings: string[] = [];
const result = parseRuleFile(filePath);
const result = parseRuleFile(filePath);
// Add parser errors and warnings
errors.push(...result.errors);
warnings.push(...result.warnings);
// Add parser errors and warnings
errors.push(...result.errors);
warnings.push(...result.warnings);
if (!result.success || !result.rule) {
return { valid: false, errors, warnings };
}
if (!result.success || !result.rule) {
return { valid: false, errors, warnings };
}
const rule = result.rule;
const rule = result.rule;
// Validate title
if (!rule.title || rule.title.trim().length === 0) {
errors.push("Missing or empty title");
}
// 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 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));
// 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");
}
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 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`);
}
}
}
// 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(", ")}`);
}
// 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)");
}
// Warning for missing impact description
if (!rule.impactDescription) {
warnings.push(
"Missing impactDescription (recommended for quantifying benefit)",
);
}
return {
valid: errors.length === 0,
errors,
warnings,
};
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>;
totalFiles: number;
validFiles: number;
invalidFiles: number;
results: Map<string, ValidationResult>;
} {
const results = new Map<string, ValidationResult>();
let validFiles = 0;
let invalidFiles = 0;
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));
// 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);
for (const file of files) {
const result = validateRuleFile(file);
results.set(basename(file), result);
if (result.valid) {
validFiles++;
} else {
invalidFiles++;
}
}
if (result.valid) {
validFiles++;
} else {
invalidFiles++;
}
}
return {
totalFiles: files.length,
validFiles,
invalidFiles,
results,
};
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");
const isMainModule =
process.argv[1]?.endsWith("validate.ts") ||
process.argv[1]?.endsWith("validate.js");
if (isMainModule) {
console.log("Validating Postgres best practices rules...\n");
console.log("Validating Postgres best practices rules...\n");
const { totalFiles, validFiles, invalidFiles, results } = validateAllRules();
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/postgres-best-practices/rules/");
console.log("Use the _template.md as a starting point.\n");
process.exit(0);
}
if (totalFiles === 0) {
console.log("No rule files found (this is expected for initial setup).");
console.log("Create rule files in: skills/postgres-best-practices/rules/");
console.log("Use the _template.md as a starting point.\n");
process.exit(0);
}
let hasErrors = false;
let hasErrors = false;
for (const [filename, result] of results) {
if (!result.valid || result.warnings.length > 0) {
console.log(`\n${filename}:`);
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 error of result.errors) {
console.log(` ERROR: ${error}`);
hasErrors = true;
}
for (const warning of result.warnings) {
console.log(` WARNING: ${warning}`);
}
}
}
for (const warning of result.warnings) {
console.log(` WARNING: ${warning}`);
}
}
}
console.log(`\n${"=".repeat(50)}`);
console.log(`Total: ${totalFiles} files | Valid: ${validFiles} | Invalid: ${invalidFiles}`);
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);
}
if (hasErrors) {
console.log("\nValidation failed. Please fix the errors above.");
process.exit(1);
} else {
console.log("\nValidation passed!");
process.exit(0);
}
}

View File

@@ -1,16 +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"]
"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

@@ -1,13 +1,13 @@
{
"version": "1.0.0",
"organization": "Supabase",
"date": "January 2026",
"abstract": "Comprehensive Postgres performance optimization guide for developers using Supabase and Postgres. 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"
]
"version": "1.0.0",
"organization": "Supabase",
"date": "January 2026",
"abstract": "Comprehensive Postgres performance optimization guide for developers using Supabase and Postgres. 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"
]
}