From ceb24909a254098f1bb3e226add686378d6494d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 09:39:56 +0200 Subject: [PATCH 1/9] feat: setup rules table --- .../20250820130012_create_rules_table.sql | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 supabase/migrations/20250820130012_create_rules_table.sql diff --git a/supabase/migrations/20250820130012_create_rules_table.sql b/supabase/migrations/20250820130012_create_rules_table.sql new file mode 100644 index 0000000..8794e55 --- /dev/null +++ b/supabase/migrations/20250820130012_create_rules_table.sql @@ -0,0 +1,31 @@ +-- Create rules table for storing AI rules +CREATE TABLE rules ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + library_id VARCHAR(100) NOT NULL, -- Maps to Library enum + rule_content TEXT NOT NULL, + sort_order INTEGER DEFAULT 0, + is_active BOOLEAN DEFAULT true, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW(), + UNIQUE(library_id, rule_content) +); + +-- Create indexes for performance +CREATE INDEX idx_rules_library ON rules(library_id); +CREATE INDEX idx_rules_active ON rules(is_active); +CREATE INDEX idx_rules_library_active ON rules(library_id, is_active); + +-- Add RLS (Row Level Security) - allow read access to all users +ALTER TABLE rules ENABLE ROW LEVEL SECURITY; + +-- Policy to allow read access to all authenticated and anonymous users +CREATE POLICY "Allow read access to rules" ON rules + FOR SELECT + TO public + USING (is_active = true); + +-- Policy to allow insert/update/delete only to authenticated users (for future admin functionality) +CREATE POLICY "Allow admin access to rules" ON rules + FOR ALL + TO authenticated + USING (true); \ No newline at end of file From 9ad247e03ff3fb35e802cd7f147e69b9291dc9af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 09:42:04 +0200 Subject: [PATCH 2/9] docs: create CLAUDE.md file for CC --- CLAUDE.md | 71 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c6b8f0e..8bbb573 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,7 +20,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Testing - `npm run test` - Run unit tests with Vitest -- `npm run test:watch` - Run tests in watch mode +- `npm run test:watch` - Run tests in watch mode - `npm run test:ui` - Run tests with UI interface - `npm run test:coverage` - Generate test coverage report - `npm run test:e2e` - Run end-to-end tests with Playwright @@ -28,17 +28,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `npm run test:e2e:codegen` - Generate test code with Playwright ### Special Scripts -- `npm run generate-rules` - Generate rules JSON from TypeScript definitions +- `npm run generate-rules` - Generate rules JSON from TypeScript definitions (required before running MCP server locally) ## Architecture Overview ### Technology Stack - **Framework**: Astro 5 with React 18.3 integration -- **Styling**: Tailwind CSS 4 -- **State Management**: Zustand for client-side state -- **Database**: Supabase (PostgreSQL with real-time features) -- **Testing**: Vitest for unit tests, Playwright for E2E tests -- **Authentication**: Supabase Auth with email/password and password reset +- **Styling**: Tailwind CSS 4 with Typography plugin +- **State Management**: Zustand for client-side state with URL persistence +- **Database**: Supabase (PostgreSQL with auth and real-time features) +- **Testing**: Vitest (unit tests with JSDOM), Playwright (E2E with Page Object Model) +- **Authentication**: Supabase Auth with email/password and password reset flow +- **Validation**: Zod for schema validation, React Hook Form for forms +- **Build Tools**: TypeScript 5, TSX for scripts ### Project Structure @@ -49,51 +51,60 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `services/` - Business logic services, notably `RulesBuilderService` - `store/` - Zustand stores for state management - `hooks/` - Custom React hooks +- `features/` - Feature flags system for environment-based functionality control +- `i18n/` - Internationalization with translations +- `db/` - Database types and utilities #### Key Components Architecture -- **Rules System**: Rules are organized by technology stacks (frontend, backend, database, etc.) and stored in `src/data/rules/` -- **Rules Builder Service**: Core service in `src/services/rules-builder/` that generates markdown content using strategy pattern (single-file vs multi-file output) -- **Collections System**: User can save and manage rule collections via `collectionsStore` -- **Feature Flags**: Environment-based feature toggling system in `src/features/featureFlags.ts` +- **Rules System**: Rules are organized by technology stacks (frontend, backend, database, etc.) and stored in `src/data/rules/`. Each rule category exports typed rule arrays. +- **Rules Builder Service**: Core service in `src/services/rules-builder/` that generates markdown content using strategy pattern (single-file vs multi-file output strategies) +- **Collections System**: User can save and manage rule collections via `collectionsStore` with dirty state tracking and Supabase persistence +- **Feature Flags**: Environment-based feature toggling system in `src/features/featureFlags.ts` controlling API endpoints, pages, and UI components #### MCP Server (`mcp-server/`) Standalone Cloudflare Worker implementing Model Context Protocol for programmatic access to AI rules. Provides tools: -- `listAvailableRules` - Get available rule categories -- `getRuleContent` - Fetch specific rule content +- `listAvailableRules` - Get available rule categories with identifiers and stack hierarchy +- `getRuleContent` - Fetch specific rule content by library identifier + +Deployed at: `https://10x-rules-mcp-server.przeprogramowani.workers.dev/sse` ### State Management Pattern The application uses Zustand with multiple specialized stores: -- `techStackStore` - Manages selected libraries and tech stack +- `techStackStore` - Manages selected libraries and tech stack with URL sync - `collectionsStore` - Handles saved rule collections with dirty state tracking -- `authStore` - Authentication state management -- `projectStore` - Project metadata (name, description) +- `authStore` - Authentication state management with Supabase session handling +- `projectStore` - Project metadata (name, description) with URL persistence +- `navigationStore` - UI navigation state ### Environment Configuration - Uses Astro's environment schema for type-safe environment variables -- Supports three environments: `local`, `integration`, `prod` -- Feature flags control functionality per environment +- Supports three environments: `local`, `integration`, `prod` (via `PUBLIC_ENV_NAME`) +- Feature flags control functionality per environment (.ai/feature-flags.md) - Requires `.env.local` with Supabase credentials and Cloudflare Turnstile keys ### Database Integration - Supabase integration with TypeScript types in `src/db/database.types.ts` - Collections are stored in Supabase with user association -- Real-time capabilities available but not currently utilized +- Authentication flow includes email verification and password reset +- Rate limiting implemented for API endpoints ### Testing Strategy -- Unit tests use Vitest with React Testing Library and JSDOM +- Unit tests use Vitest with React Testing Library and JSDOM environment - E2E tests use Playwright with Page Object Model pattern -- Test files located in `tests/` for unit tests and `e2e/` for E2E tests -- All tests run in CI/CD pipeline +- Test setup includes MSW for API mocking +- Tests run in CI/CD pipeline via GitHub Actions +- E2E tests use `.env.integration` for isolated testing environment ### Rules Content System Rules are defined as TypeScript objects and exported from category-specific files in `src/data/rules/`. The system supports: -- Categorization by technology layers (frontend, backend, database, etc.) -- Library-specific rules with placeholder replacement -- Multi-file vs single-file output strategies -- Markdown generation with project context +- Categorization by technology layers (frontend, backend, database, testing, infrastructure, accessibility, coding standards) +- Library-specific rules with placeholder replacement ({{project_name}}, {{library}}) +- Multi-file vs single-file output strategies based on editor type +- Markdown generation with project context and library-specific content ### Development Workflow -1. Rules contributions go in `src/data/rules/` with corresponding translations in `src/i18n/translations.ts` -2. Use feature flags to control new functionality rollout -3. Collections allow users to save and share rule combinations -4. The MCP server enables programmatic access for AI assistants \ No newline at end of file +1. Rules contributions go in `src/data/rules/` with corresponding translations in `src/i18n/translations.ts` (unit tests will fail without translations) +2. Use feature flags to control new functionality rollout across environments +3. Collections allow users to save and share rule combinations with authentication +4. The MCP server enables programmatic access for AI assistants (Cursor, Claude, etc.) +5. Run `npm run generate-rules` after modifying rules to update `preparedRules.json` for MCP server \ No newline at end of file From f0a01dca27c046e343fcc3f2fdad538938169670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 09:43:14 +0200 Subject: [PATCH 3/9] chore: gen types from supabase table --- database.types.ts | 250 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 database.types.ts diff --git a/database.types.ts b/database.types.ts new file mode 100644 index 0000000..d378807 --- /dev/null +++ b/database.types.ts @@ -0,0 +1,250 @@ +export type Json = + | string + | number + | boolean + | null + | { [key: string]: Json | undefined } + | Json[] + +export type Database = { + graphql_public: { + Tables: { + [_ in never]: never + } + Views: { + [_ in never]: never + } + Functions: { + graphql: { + Args: { + operationName?: string + query?: string + variables?: Json + extensions?: Json + } + Returns: Json + } + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } + public: { + Tables: { + collections: { + Row: { + created_at: string + description: string | null + id: string + libraries: string[] + name: string + updated_at: string + user_id: string + } + Insert: { + created_at?: string + description?: string | null + id?: string + libraries?: string[] + name: string + updated_at?: string + user_id: string + } + Update: { + created_at?: string + description?: string | null + id?: string + libraries?: string[] + name?: string + updated_at?: string + user_id?: string + } + Relationships: [] + } + rules: { + Row: { + created_at: string | null + id: string + is_active: boolean | null + library_id: string + rule_content: string + sort_order: number | null + updated_at: string | null + } + Insert: { + created_at?: string | null + id?: string + is_active?: boolean | null + library_id: string + rule_content: string + sort_order?: number | null + updated_at?: string | null + } + Update: { + created_at?: string | null + id?: string + is_active?: boolean | null + library_id?: string + rule_content?: string + sort_order?: number | null + updated_at?: string | null + } + Relationships: [] + } + user_consents: { + Row: { + consented_at: string + created_at: string + id: string + privacy_policy_version: string + user_id: string + } + Insert: { + consented_at?: string + created_at?: string + id?: string + privacy_policy_version: string + user_id: string + } + Update: { + consented_at?: string + created_at?: string + id?: string + privacy_policy_version?: string + user_id?: string + } + Relationships: [] + } + } + Views: { + [_ in never]: never + } + Functions: { + [_ in never]: never + } + Enums: { + [_ in never]: never + } + CompositeTypes: { + [_ in never]: never + } + } +} + +type DefaultSchema = Database[Extract] + +export type Tables< + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema["Tables"] & DefaultSchema["Views"]) + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"]) + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] & + Database[DefaultSchemaTableNameOrOptions["schema"]]["Views"])[TableName] extends { + Row: infer R + } + ? R + : never + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema["Tables"] & + DefaultSchema["Views"]) + ? (DefaultSchema["Tables"] & + DefaultSchema["Views"])[DefaultSchemaTableNameOrOptions] extends { + Row: infer R + } + ? R + : never + : never + +export type TablesInsert< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Insert: infer I + } + ? I + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Insert: infer I + } + ? I + : never + : never + +export type TablesUpdate< + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema["Tables"] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"] + : never = never, +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions["schema"]]["Tables"][TableName] extends { + Update: infer U + } + ? U + : never + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema["Tables"] + ? DefaultSchema["Tables"][DefaultSchemaTableNameOrOptions] extends { + Update: infer U + } + ? U + : never + : never + +export type Enums< + DefaultSchemaEnumNameOrOptions extends + | keyof DefaultSchema["Enums"] + | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"] + : never = never, +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions["schema"]]["Enums"][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema["Enums"] + ? DefaultSchema["Enums"][DefaultSchemaEnumNameOrOptions] + : never + +export type CompositeTypes< + PublicCompositeTypeNameOrOptions extends + | keyof DefaultSchema["CompositeTypes"] + | { schema: keyof Database }, + CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { + schema: keyof Database + } + ? keyof Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"] + : never = never, +> = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } + ? Database[PublicCompositeTypeNameOrOptions["schema"]]["CompositeTypes"][CompositeTypeName] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema["CompositeTypes"] + ? DefaultSchema["CompositeTypes"][PublicCompositeTypeNameOrOptions] + : never + +export const Constants = { + graphql_public: { + Enums: {}, + }, + public: { + Enums: {}, + }, +} as const + From fd99a11e082df6801be1e5ec3822e43b272e008b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 09:43:41 +0200 Subject: [PATCH 4/9] feat: use rules loaded from DB --- package.json | 1 + src/components/TwoPane.tsx | 12 +- src/components/rule-builder/RuleBuilder.tsx | 6 +- .../rule-builder/hooks/useRuleBuilder.ts | 14 +- src/components/rule-preview/RulePreview.tsx | 10 +- src/db/database.types.ts | 170 ++++++++++++++---- src/pages/index.astro | 60 ++++++- .../rules-builder/RulesBuilderService.ts | 7 +- .../MultiFileRulesStrategy.ts | 20 ++- .../SingleFileRulesStrategy.ts | 24 ++- 10 files changed, 270 insertions(+), 54 deletions(-) diff --git a/package.json b/package.json index 307facd..540d806 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "generate-rules": "tsx ./scripts/generate-rules-json.mts", + "migrate-rules": "tsx ./scripts/migrate-rules-to-supabase.ts", "dev": "astro dev -- --mode local", "dev:e2e": "npm run astro dev -- --mode integration", "build": "astro build", diff --git a/src/components/TwoPane.tsx b/src/components/TwoPane.tsx index 070f720..2767470 100644 --- a/src/components/TwoPane.tsx +++ b/src/components/TwoPane.tsx @@ -6,8 +6,9 @@ import { MobileNavigation } from './MobileNavigation'; import { useNavigationStore } from '../store/navigationStore'; import { isFeatureEnabled } from '../features/featureFlags'; import { useTechStackStore } from '../store/techStackStore'; +import type { LibraryRulesMap } from '../data/rules/types'; -function RulesPane() { +function RulesPane({ libraryRules }: { libraryRules: LibraryRulesMap }) { const { activePanel, isSidebarOpen, toggleSidebar, setSidebarOpen } = useNavigationStore(); const isCollectionsEnabled = isFeatureEnabled('authOnUI'); @@ -34,7 +35,7 @@ function RulesPane() { w-full md:w-1/3 lg:w-2/5 md:p-4 border-b md:border-b-0 md:border-r border-gray-800 min-h-full overflow-y-auto `} > - +
- +
@@ -55,9 +56,10 @@ function RulesPane() { type TwoPaneProps = { initialUrl: URL; + libraryRules: LibraryRulesMap; }; -export default function TwoPane({ initialUrl }: TwoPaneProps) { +export default function TwoPane({ initialUrl, libraryRules }: TwoPaneProps) { const { anyLibrariesToLoad } = useTechStackStore(); const [isHydrated, setIsHydrated] = useState(false); @@ -71,5 +73,5 @@ export default function TwoPane({ initialUrl }: TwoPaneProps) { return
Loading...
; } - return ; + return ; } diff --git a/src/components/rule-builder/RuleBuilder.tsx b/src/components/rule-builder/RuleBuilder.tsx index e2e53ed..8929e83 100644 --- a/src/components/rule-builder/RuleBuilder.tsx +++ b/src/components/rule-builder/RuleBuilder.tsx @@ -8,12 +8,14 @@ import { LayerItem } from './LayerItem'; import { MCPDialog } from './modals/MCPDialog'; import { SearchInput } from './SearchInput'; import { SelectedRules } from './SelectedRules'; +import type { LibraryRulesMap } from '../../data/rules/types'; interface RuleBuilderProps { className?: string; + libraryRules: LibraryRulesMap; } -export const RuleBuilder: React.FC = ({ className = '' }) => { +export const RuleBuilder: React.FC = ({ className = '', libraryRules }) => { const { layers, selectedLibraries, @@ -41,7 +43,7 @@ export const RuleBuilder: React.FC = ({ className = '' }) => { getFilteredLibrariesByStack, getLibraryCounts, isSearchActive, - } = useRuleBuilder(); + } = useRuleBuilder(libraryRules); const { totalCount, matchedCount } = getLibraryCounts; const accordionRef = useRef(null); diff --git a/src/components/rule-builder/hooks/useRuleBuilder.ts b/src/components/rule-builder/hooks/useRuleBuilder.ts index a224ef9..acbb00b 100644 --- a/src/components/rule-builder/hooks/useRuleBuilder.ts +++ b/src/components/rule-builder/hooks/useRuleBuilder.ts @@ -9,8 +9,9 @@ import { import { useTechStackStore } from '../../../store/techStackStore'; import type { LayerType } from '../../../styles/theme'; import { layerToType } from '../../../styles/theme'; +import type { LibraryRulesMap } from '../../../data/rules/types'; -export const useRuleBuilder = () => { +export const useRuleBuilder = (libraryRules: LibraryRulesMap) => { const { selectedLibraries, selectLayer, @@ -59,6 +60,14 @@ export const useRuleBuilder = () => { // Get all available layers const layers = useMemo(() => Object.values(Layer), []); + // Helper function to get rules for a library using passed libraryRules + const getRulesForLibrary = useCallback( + (library: Library): string[] => { + return libraryRules[library] || []; + }, + [libraryRules], + ); + // Function to get layer type for a layer const getLayerType = useCallback((layer: Layer): LayerType => { return layerToType(layer); @@ -386,5 +395,8 @@ export const useRuleBuilder = () => { getLayerType, getStackLayerType, getLibraryLayerType, + + // Rules access + getRulesForLibrary, }; }; diff --git a/src/components/rule-preview/RulePreview.tsx b/src/components/rule-preview/RulePreview.tsx index 13c9017..d8941b1 100644 --- a/src/components/rule-preview/RulePreview.tsx +++ b/src/components/rule-preview/RulePreview.tsx @@ -7,8 +7,13 @@ import { RulePreviewTopbar } from './RulePreviewTopbar'; import { DependencyUpload } from './DependencyUpload.tsx'; import { MarkdownContentRenderer } from './MarkdownContentRenderer.tsx'; import type { RulesContent } from '../../services/rules-builder/RulesBuilderTypes.ts'; +import type { LibraryRulesMap } from '../../data/rules/types'; -export const RulePreview: React.FC = () => { +interface RulePreviewProps { + libraryRules: LibraryRulesMap; +} + +export const RulePreview: React.FC = ({ libraryRules }) => { const { selectedLibraries } = useTechStackStore(); const { projectName, projectDescription, isMultiFileEnvironment } = useProjectStore(); const [markdownContent, setMarkdownContent] = useState([]); @@ -21,9 +26,10 @@ export const RulePreview: React.FC = () => { projectDescription, selectedLibraries, isMultiFileEnvironment, + libraryRules, ); setMarkdownContent(markdowns); - }, [selectedLibraries, projectName, projectDescription, isMultiFileEnvironment]); + }, [selectedLibraries, projectName, projectDescription, isMultiFileEnvironment, libraryRules]); // Handle drag events const handleDragOver = useCallback((e: React.DragEvent) => { diff --git a/src/db/database.types.ts b/src/db/database.types.ts index 5c86ec1..7af8da7 100644 --- a/src/db/database.types.ts +++ b/src/db/database.types.ts @@ -1,6 +1,31 @@ export type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[]; export type Database = { + graphql_public: { + Tables: { + [_ in never]: never; + }; + Views: { + [_ in never]: never; + }; + Functions: { + graphql: { + Args: { + operationName?: string; + query?: string; + variables?: Json; + extensions?: Json; + }; + Returns: Json; + }; + }; + Enums: { + [_ in never]: never; + }; + CompositeTypes: { + [_ in never]: never; + }; + }; public: { Tables: { collections: { @@ -33,6 +58,60 @@ export type Database = { }; Relationships: []; }; + rules: { + Row: { + created_at: string | null; + id: string; + is_active: boolean | null; + library_id: string; + rule_content: string; + sort_order: number | null; + updated_at: string | null; + }; + Insert: { + created_at?: string | null; + id?: string; + is_active?: boolean | null; + library_id: string; + rule_content: string; + sort_order?: number | null; + updated_at?: string | null; + }; + Update: { + created_at?: string | null; + id?: string; + is_active?: boolean | null; + library_id?: string; + rule_content?: string; + sort_order?: number | null; + updated_at?: string | null; + }; + Relationships: []; + }; + user_consents: { + Row: { + consented_at: string; + created_at: string; + id: string; + privacy_policy_version: string; + user_id: string; + }; + Insert: { + consented_at?: string; + created_at?: string; + id?: string; + privacy_policy_version: string; + user_id: string; + }; + Update: { + consented_at?: string; + created_at?: string; + id?: string; + privacy_policy_version?: string; + user_id?: string; + }; + Relationships: []; + }; }; Views: { [_ in never]: never; @@ -49,25 +128,27 @@ export type Database = { }; }; -type PublicSchema = Database[Extract]; +type DefaultSchema = Database[Extract]; export type Tables< - PublicTableNameOrOptions extends - | keyof (PublicSchema['Tables'] & PublicSchema['Views']) + DefaultSchemaTableNameOrOptions extends + | keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views']) + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database; + } + ? keyof (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views']) : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? (Database[PublicTableNameOrOptions['schema']]['Tables'] & - Database[PublicTableNameOrOptions['schema']]['Views'])[TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? (Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] & + Database[DefaultSchemaTableNameOrOptions['schema']]['Views'])[TableName] extends { Row: infer R; } ? R : never - : PublicTableNameOrOptions extends keyof (PublicSchema['Tables'] & PublicSchema['Views']) - ? (PublicSchema['Tables'] & PublicSchema['Views'])[PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & DefaultSchema['Views']) + ? (DefaultSchema['Tables'] & DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends { Row: infer R; } ? R @@ -75,18 +156,22 @@ export type Tables< : never; export type TablesInsert< - PublicTableNameOrOptions extends keyof PublicSchema['Tables'] | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { Insert: infer I; } ? I : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { Insert: infer I; } ? I @@ -94,18 +179,22 @@ export type TablesInsert< : never; export type TablesUpdate< - PublicTableNameOrOptions extends keyof PublicSchema['Tables'] | { schema: keyof Database }, - TableName extends PublicTableNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicTableNameOrOptions['schema']]['Tables'] + DefaultSchemaTableNameOrOptions extends + | keyof DefaultSchema['Tables'] + | { schema: keyof Database }, + TableName extends DefaultSchemaTableNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'] : never = never, -> = PublicTableNameOrOptions extends { schema: keyof Database } - ? Database[PublicTableNameOrOptions['schema']]['Tables'][TableName] extends { +> = DefaultSchemaTableNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaTableNameOrOptions['schema']]['Tables'][TableName] extends { Update: infer U; } ? U : never - : PublicTableNameOrOptions extends keyof PublicSchema['Tables'] - ? PublicSchema['Tables'][PublicTableNameOrOptions] extends { + : DefaultSchemaTableNameOrOptions extends keyof DefaultSchema['Tables'] + ? DefaultSchema['Tables'][DefaultSchemaTableNameOrOptions] extends { Update: infer U; } ? U @@ -113,19 +202,21 @@ export type TablesUpdate< : never; export type Enums< - PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] | { schema: keyof Database }, - EnumName extends PublicEnumNameOrOptions extends { schema: keyof Database } - ? keyof Database[PublicEnumNameOrOptions['schema']]['Enums'] + DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] | { schema: keyof Database }, + EnumName extends DefaultSchemaEnumNameOrOptions extends { + schema: keyof Database; + } + ? keyof Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'] : never = never, -> = PublicEnumNameOrOptions extends { schema: keyof Database } - ? Database[PublicEnumNameOrOptions['schema']]['Enums'][EnumName] - : PublicEnumNameOrOptions extends keyof PublicSchema['Enums'] - ? PublicSchema['Enums'][PublicEnumNameOrOptions] +> = DefaultSchemaEnumNameOrOptions extends { schema: keyof Database } + ? Database[DefaultSchemaEnumNameOrOptions['schema']]['Enums'][EnumName] + : DefaultSchemaEnumNameOrOptions extends keyof DefaultSchema['Enums'] + ? DefaultSchema['Enums'][DefaultSchemaEnumNameOrOptions] : never; export type CompositeTypes< PublicCompositeTypeNameOrOptions extends - | keyof PublicSchema['CompositeTypes'] + | keyof DefaultSchema['CompositeTypes'] | { schema: keyof Database }, CompositeTypeName extends PublicCompositeTypeNameOrOptions extends { schema: keyof Database; @@ -134,6 +225,15 @@ export type CompositeTypes< : never = never, > = PublicCompositeTypeNameOrOptions extends { schema: keyof Database } ? Database[PublicCompositeTypeNameOrOptions['schema']]['CompositeTypes'][CompositeTypeName] - : PublicCompositeTypeNameOrOptions extends keyof PublicSchema['CompositeTypes'] - ? PublicSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] + : PublicCompositeTypeNameOrOptions extends keyof DefaultSchema['CompositeTypes'] + ? DefaultSchema['CompositeTypes'][PublicCompositeTypeNameOrOptions] : never; + +export const Constants = { + graphql_public: { + Enums: {}, + }, + public: { + Enums: {}, + }, +} as const; diff --git a/src/pages/index.astro b/src/pages/index.astro index f3b32c0..e163d28 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -3,16 +3,74 @@ import Layout from '../layouts/Layout.astro'; import Topbar from '../components/Topbar'; import TwoPane from '../components/TwoPane'; import Footer from '../components/Footer'; +import { createSupabaseServerInstance } from '../db/supabase.client'; +import type { LibraryRulesMap } from '../data/rules/types'; +import type { Library } from '../data/dictionaries'; const user = Astro.locals.user; const initialUrl = Astro.url; + +// Fetch rules from Supabase at request time +let libraryRules: LibraryRulesMap = {}; + +const USE_DATABASE_RULES = import.meta.env.USE_DATABASE_RULES !== 'false'; + +if (USE_DATABASE_RULES) { + try { + const supabase = createSupabaseServerInstance({ + headers: Astro.request.headers, + cookies: Astro.cookies, + }); + + const { data: rulesData, error } = await supabase + .from('rules') + .select('library_id, rule_content, sort_order') + .eq('is_active', true) + .order('library_id', { ascending: true }) + .order('sort_order', { ascending: true }); + + if (!error && rulesData) { + // Transform flat array to LibraryRulesMap structure + rulesData.forEach((rule: { library_id: Library; rule_content: string }) => { + const libraryId = rule.library_id; + if (!libraryRules[libraryId]) { + libraryRules[libraryId] = []; + } + libraryRules[libraryId]?.push(rule.rule_content); + }); + console.log(`✅ Loaded ${rulesData.length} rules from database`); + } else if (error) { + console.warn('⚠️ Failed to load rules from database, using fallback:', error.message); + } + } catch (err) { + console.warn('⚠️ Error loading rules from database, using fallback:', err); + } +} + +// Fallback to file-based rules if database failed or disabled +if (Object.keys(libraryRules).length === 0) { + try { + const { libraryRules: fileRules } = await import('../data/rules'); + libraryRules = fileRules; + console.log(`📁 Using file-based rules (${Object.keys(libraryRules).length} libraries)`); + } catch (err) { + console.error('❌ Failed to load fallback rules:', err); + libraryRules = {}; + } +} + +// Add caching headers for edge performance +Astro.response.headers.set( + 'Cache-Control', + 'public, max-age=60, s-maxage=300, stale-while-revalidate=86400', +); ---
- +
diff --git a/src/services/rules-builder/RulesBuilderService.ts b/src/services/rules-builder/RulesBuilderService.ts index c2a8378..ec62e4d 100644 --- a/src/services/rules-builder/RulesBuilderService.ts +++ b/src/services/rules-builder/RulesBuilderService.ts @@ -9,6 +9,7 @@ import type { RulesContent } from './RulesBuilderTypes.ts'; import type { RulesGenerationStrategy } from './RulesGenerationStrategy.ts'; import { MultiFileRulesStrategy } from './rules-generation-strategies/MultiFileRulesStrategy.ts'; import { SingleFileRulesStrategy } from './rules-generation-strategies/SingleFileRulesStrategy.ts'; +import type { LibraryRulesMap } from '../../data/rules/types'; /** * Service for building AI rules based on selected libraries @@ -21,6 +22,7 @@ export class RulesBuilderService { * @param projectDescription - The description of the project * @param selectedLibraries - Array of selected libraries * @param multiFile - Whether to generate multiple files per each rule content + * @param libraryRules - Optional library rules map (fallback to file-based rules if not provided) * @returns The generated markdown content */ static generateRulesContent( @@ -28,14 +30,15 @@ export class RulesBuilderService { projectDescription: string, selectedLibraries: Library[], multiFile?: boolean, + libraryRules?: LibraryRulesMap, ): RulesContent[] { // Group libraries by stack and layer const librariesByStack = this.groupLibrariesByStack(selectedLibraries); const stacksByLayer = this.groupStacksByLayer(Object.keys(librariesByStack) as Stack[]); const strategy: RulesGenerationStrategy = multiFile - ? new MultiFileRulesStrategy() - : new SingleFileRulesStrategy(); + ? new MultiFileRulesStrategy(libraryRules) + : new SingleFileRulesStrategy(libraryRules); return strategy.generateRules( projectName, diff --git a/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts b/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts index 124b632..41ebc05 100644 --- a/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts +++ b/src/services/rules-builder/rules-generation-strategies/MultiFileRulesStrategy.ts @@ -1,13 +1,29 @@ import type { RulesGenerationStrategy } from '../RulesGenerationStrategy.ts'; import { Layer, type Library, Stack } from '../../../data/dictionaries.ts'; import type { RulesContent } from '../RulesBuilderTypes.ts'; -import { getRulesForLibrary } from '../../../data/rules'; +import { getRulesForLibrary as getFileBasedRulesForLibrary } from '../../../data/rules'; import { slugify } from '../../../utils/slugify.ts'; +import type { LibraryRulesMap } from '../../../data/rules/types'; /** * Strategy for multi-file rules generation */ export class MultiFileRulesStrategy implements RulesGenerationStrategy { + private libraryRules?: LibraryRulesMap; + + constructor(libraryRules?: LibraryRulesMap) { + this.libraryRules = libraryRules; + } + + /** + * Get rules for a library - uses passed rules or falls back to file-based rules + */ + private getRulesForLibrary(library: Library): string[] { + if (this.libraryRules) { + return this.libraryRules[library] || []; + } + return getFileBasedRulesForLibrary(library); + } generateRules( projectName: string, projectDescription: string, @@ -37,7 +53,7 @@ export class MultiFileRulesStrategy implements RulesGenerationStrategy { layer, stack, library, - libraryRules: getRulesForLibrary(library), + libraryRules: this.getRulesForLibrary(library), }), ); }); diff --git a/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts b/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts index 467c9e7..887ff58 100644 --- a/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts +++ b/src/services/rules-builder/rules-generation-strategies/SingleFileRulesStrategy.ts @@ -1,12 +1,28 @@ import type { RulesGenerationStrategy } from '../RulesGenerationStrategy.ts'; import { Layer, Library, Stack } from '../../../data/dictionaries.ts'; import type { RulesContent } from '../RulesBuilderTypes.ts'; -import { getRulesForLibrary } from '../../../data/rules.ts'; +import { getRulesForLibrary as getFileBasedRulesForLibrary } from '../../../data/rules.ts'; +import type { LibraryRulesMap } from '../../../data/rules/types'; /** * Strategy for single-file rules generation */ export class SingleFileRulesStrategy implements RulesGenerationStrategy { + private libraryRules?: LibraryRulesMap; + + constructor(libraryRules?: LibraryRulesMap) { + this.libraryRules = libraryRules; + } + + /** + * Get rules for a library - uses passed rules or falls back to file-based rules + */ + private getRulesForLibrary(library: Library): string[] { + if (this.libraryRules) { + return this.libraryRules[library] || []; + } + return getFileBasedRulesForLibrary(library); + } generateRules( projectName: string, projectDescription: string, @@ -49,9 +65,9 @@ export class SingleFileRulesStrategy implements RulesGenerationStrategy { markdown += `#### ${library}\n\n`; // Get specific rules for this library - const libraryRules = getRulesForLibrary(library); - if (libraryRules.length > 0) { - libraryRules.forEach((rule) => { + const librarySpecificRules = this.getRulesForLibrary(library); + if (librarySpecificRules.length > 0) { + librarySpecificRules.forEach((rule) => { markdown += `- ${rule}\n`; }); } else { From a6da6bbf6d589e178a139cacaa9364b54723abde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 11:03:35 +0200 Subject: [PATCH 5/9] feat: add migration script --- scripts/migrate-rules-to-supabase.ts | 124 +++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 scripts/migrate-rules-to-supabase.ts diff --git a/scripts/migrate-rules-to-supabase.ts b/scripts/migrate-rules-to-supabase.ts new file mode 100644 index 0000000..0a1dd3c --- /dev/null +++ b/scripts/migrate-rules-to-supabase.ts @@ -0,0 +1,124 @@ +#!/usr/bin/env tsx + +import { createClient } from '@supabase/supabase-js'; +import { libraryRules } from '../src/data/rules.ts'; +import dotenv from 'dotenv'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// Load environment variables +dotenv.config({ path: path.resolve(__dirname, '../.env.local') }); + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY; + +if (!supabaseUrl || !supabaseServiceKey) { + console.error('Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY in .env.local'); + process.exit(1); +} + +const supabase = createClient(supabaseUrl, supabaseServiceKey); + +interface RuleRecord { + library_id: string; + rule_content: string; + sort_order: number; + is_active: boolean; +} + +async function migrateRules() { + console.log('🚀 Starting rules migration to Supabase...'); + + try { + // Prepare rules data for insertion + const rulesData: RuleRecord[] = []; + let totalRules = 0; + + for (const [libraryId, rules] of Object.entries(libraryRules)) { + if (Array.isArray(rules)) { + rules.forEach((ruleContent, index) => { + rulesData.push({ + library_id: libraryId, + rule_content: ruleContent, + sort_order: (index + 1) * 10, // Allow space for future insertions + is_active: true + }); + totalRules++; + }); + } + } + + console.log(`📊 Found ${totalRules} rules across ${Object.keys(libraryRules).length} libraries`); + + // Clear existing rules (if any) + console.log('🗑️ Clearing existing rules...'); + const { error: deleteError } = await supabase + .from('rules') + .delete() + .neq('id', '00000000-0000-0000-0000-000000000000'); // Delete all records + + if (deleteError) { + console.warn('Warning: Could not clear existing rules:', deleteError.message); + } + + // Insert rules in batches (Supabase has a limit on batch size) + const batchSize = 100; + let insertedCount = 0; + + for (let i = 0; i < rulesData.length; i += batchSize) { + const batch = rulesData.slice(i, i + batchSize); + console.log(`📤 Inserting batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(rulesData.length / batchSize)} (${batch.length} rules)...`); + + const { data, error } = await supabase + .from('rules') + .insert(batch) + .select('id'); + + if (error) { + console.error('❌ Error inserting batch:', error); + throw error; + } + + insertedCount += data?.length || 0; + } + + console.log(`✅ Successfully migrated ${insertedCount} rules to Supabase!`); + + // Verify the migration + const { data: verifyData, error: verifyError } = await supabase + .from('rules') + .select('library_id, rule_content') + .eq('is_active', true); + + if (verifyError) { + console.error('❌ Error verifying migration:', verifyError); + } else { + console.log(`✅ Verification: Found ${verifyData?.length || 0} active rules in database`); + + // Group by library for verification + const libraryCount: Record = {}; + verifyData?.forEach(rule => { + libraryCount[rule.library_id] = (libraryCount[rule.library_id] || 0) + 1; + }); + + console.log('📋 Rules per library:'); + Object.entries(libraryCount) + .sort(([a], [b]) => a.localeCompare(b)) + .forEach(([library, count]) => { + console.log(` ${library}: ${count} rules`); + }); + } + + console.log('🎉 Migration completed successfully!'); + + } catch (error) { + console.error('💥 Migration failed:', error); + process.exit(1); + } +} + +// Run the migration +migrateRules(); \ No newline at end of file From a1ee1466262d99eb09872257219eb1cf8cb512ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 11:04:02 +0200 Subject: [PATCH 6/9] chore: loosen up env requirements for local dev --- astro.config.mjs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/astro.config.mjs b/astro.config.mjs index 5cb4132..723eded 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -20,10 +20,12 @@ export default defineConfig({ CF_CAPTCHA_SITE_KEY: envField.string({ context: 'server', access: 'secret', + optional: true, }), CF_CAPTCHA_SECRET_KEY: envField.string({ context: 'server', access: 'secret', + optional: true, }), }, }, From 06aa9f0d4956c13cb2c4f09737ded0d23d2acb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 11:04:36 +0200 Subject: [PATCH 7/9] docs: update README to reflect changes --- README.md | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e43feb7..483204b 100644 --- a/README.md +++ b/README.md @@ -80,13 +80,14 @@ CF_CAPTCHA_SECRET_KEY=1x0000000000000000000000000000000AA - React 18.3 - Tailwind 4 - Zustand +- Supabase (PostgreSQL) - Lucide React ## Project Components This repository contains multiple key components: -- **AI Rules Builder UI (Root):** The main Astro/React application providing the web interface for creating and managing AI rules. +- **AI Rules Builder UI (Root):** The main Astro/React application providing the web interface for creating and managing AI rules. Rules are dynamically loaded from Supabase database with automatic fallback to file-based rules for reliability. - **MCP Server (`./mcp-server`):** A Cloudflare Worker implementing the Model Context Protocol (MCP). This server allows AI assistants (like Cursor, Claude, etc.) to programmatically access the defined AI rules via specific tools (`listAvailableRules`, `getRuleContent`). This enables integration with editors for fetching context-aware coding guidelines. For detailed setup, usage, and planned features, see the [MCP Server README](./mcp-server/README.md). ### Feature Flags @@ -99,6 +100,27 @@ The project uses a feature flags system to separate deployments from releases. F For detailed documentation about feature flags implementation, see `.ai/feature-flags.md`. +### Rules Management + +The application supports both database-stored and file-based rules: + +- **Database Rules (Default):** Rules are stored in Supabase and can be updated without code deployments +- **File-Based Fallback:** Automatic fallback to TypeScript files in `src/data/rules/` if database is unavailable +- **Environment Control:** Use `USE_DATABASE_RULES=false` to disable database rules entirely + +#### Available Scripts + +```bash +# Migrate existing rules from files to database +npm run migrate-rules + +# Development server (uses database rules by default) +npm run dev + +# Generate rules JSON for MCP server +npm run generate-rules +``` + ### Testing This project uses a comprehensive testing stack including unit tests and end-to-end tests. @@ -154,12 +176,19 @@ Tests are automatically run in the CI/CD pipeline using GitHub Actions. See `.gi ## Contributions -Send updates to: +### Adding New Rules + +- Add rules to appropriate files in `src/data/rules/` +- Update `src/data/dictionaries.ts` for new libraries +- Add translations in `src/i18n/translations.ts` (required for tests to pass) +- Run `npm run migrate-rules` to sync with database -- `src/data/dictionaries.ts` -- `src/data/rules/...` +### Development Workflow -Important: Introduce translations for new rules in `src/i18n/translations.ts`, otherwise the unit test will fail. +1. Add new rules via database or update TypeScript files +2. If adding new libraries, update dictionaries and translations +3. Test locally with both database and fallback modes +4. Submit pull request with proper documentation ## How to Write Effective Rules From 40a6210f07396eb5b38babee0db6a13e7330363b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 11:16:05 +0200 Subject: [PATCH 8/9] docs: update CLAUDE.md with info about latest changes --- CLAUDE.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8bbb573..65b1ff1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,4 +107,23 @@ Rules are defined as TypeScript objects and exported from category-specific file 2. Use feature flags to control new functionality rollout across environments 3. Collections allow users to save and share rule combinations with authentication 4. The MCP server enables programmatic access for AI assistants (Cursor, Claude, etc.) -5. Run `npm run generate-rules` after modifying rules to update `preparedRules.json` for MCP server \ No newline at end of file +5. Run `npm run generate-rules` after modifying rules to update `preparedRules.json` for MCP server + +## Important Development Instructions + +### Key Development Principles +- Do what has been asked; nothing more, nothing less +- NEVER create files unless they're absolutely necessary for achieving your goal +- ALWAYS prefer editing existing files to creating new ones +- NEVER proactively create documentation files (*.md) or README files unless explicitly requested + +### Working with Rules +- When adding new rules, they must be added to both `src/data/rules/` TypeScript files AND corresponding translations in `src/i18n/translations.ts` +- Unit tests will fail if translations are missing for new rules +- Always run `npm run migrate-rules` after adding rules to sync them with the database +- Run `npm run generate-rules` after modifying rules to update `preparedRules.json` for the MCP server + +### Database vs File-Based Rules +- The system supports both database-stored rules (default) and file-based fallback +- Use `USE_DATABASE_RULES=false` environment variable to force file-based rules during development +- Database rules allow updates without code deployments \ No newline at end of file From db894d0094b6ab63a273322d847784185bdf8cd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cmkczarkowski=E2=80=9D?= Date: Thu, 21 Aug 2025 12:17:18 +0200 Subject: [PATCH 9/9] chore: add custom command to generate types from supabase autogen --- .../commands/generate-types-from-supabase.md | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .claude/commands/generate-types-from-supabase.md diff --git a/.claude/commands/generate-types-from-supabase.md b/.claude/commands/generate-types-from-supabase.md new file mode 100644 index 0000000..857de75 --- /dev/null +++ b/.claude/commands/generate-types-from-supabase.md @@ -0,0 +1,33 @@ +You are a skilled TypeScript developer tasked with creating a library of DTO (Data Transfer Object) types and a Command Model for the application. Your task is to analyse the database model definition, and then create appropriate DTO types that accurately represent the data structures required by the API, while maintaining the connection to the underlying database models. + +First, carefully review the following input data: + + +@database.types.ts + + +Your task is to create TypeScript type definitions for the DTO and Command Models, ensuring that they are derived from the database models. Follow these steps: + +1. Analyse the database models. +2. Create DTO and Command Model types, using the database entity definitions. +3. Ensure consistency between DTO and Command Models. +4. Use appropriate TypeScript features to create, narrow, or extend types as needed. +5. Perform a final check to ensure that all DTOs are included and correctly linked to entity definitions. + +Before creating the final output, work inside the tags in your thinking block to show your thought process and ensure that all requirements are met. In your analysis: +- List all DTOs and Command Models, numbering each one. +- For each DTO and Command Model: +- Identify the relevant database entities and any necessary type transformations. + - Describe the TypeScript functions or tools you plan to use. + - Create a brief outline of the DTO and Command Model structure. +- Explain how you will ensure that each DTO and Command Model is directly or indirectly linked to the entity type definitions. + +After completing your analysis, provide the final definitions for the DTO and Command Model types that will appear in the src/types.ts file. Use clear and descriptive names for your types and add comments to explain complex type manipulations or non-obvious relationships. + +Remember: +- Make sure that all DTOs and Command Models are included. +- Each DTO and Command Model should directly refer to one or more database entities. +- Use TypeScript features such as Pick, Omit, Partial, etc. as needed. +- Add comments to explain complex or non-obvious type manipulations. + +The final result should consist solely of the DTO and Command Model type definitions that you will save in the src/types - create new files and/or update existing files, without duplicating or re-doing any work done in the thinking block. \ No newline at end of file