diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md new file mode 100644 index 00000000..491bf021 --- /dev/null +++ b/.claude/CLAUDE.md @@ -0,0 +1,141 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**AutoRFP** is an AI-powered RFP response automation platform for government contractors. Core capabilities: RFP document processing, AI answer generation (RAG), executive briefs, proposal generation, SAM.gov integration, and knowledge base management. + +## Monorepo Structure + +``` +auto_rfp/ # pnpm workspaces monorepo +├── apps/ +│ ├── web/ # Next.js App Router frontend (Tailwind 4, Shadcn UI, SWR) +│ └── functions/ # AWS Lambda handlers (Node.js 20, ESM) +├── packages/ +│ ├── core/ # Shared Zod schemas & inferred TypeScript types (tsup, vitest) +│ └── infra/ # AWS CDK stacks (API Gateway, DynamoDB, Cognito, S3, etc.) +└── scripts/ # Utility scripts +``` + +## Commands + +All commands use `pnpm`. Run from the monorepo root unless noted. + +```bash +# Root-level +pnpm build # Build all packages +pnpm dev # Start web dev server +pnpm test # Run all tests +pnpm lint # Lint all packages + +# Core schemas (packages/core) +cd packages/core +pnpm build # Build with tsup (required before web/functions can import) +pnpm test # Run vitest schema tests + +# Lambda functions (apps/functions) +cd apps/functions +pnpm test # Run all Jest tests +pnpm test -- --testPathPattern=handlers/answer # Run tests for a specific domain +pnpm test -- path/to/file.test.ts # Run a single test file + +# Web app (apps/web) +cd apps/web +pnpm dev # Dev server (port 3000) +pnpm build # Production build +pnpm test # Jest unit/component tests +pnpm test:e2e # Playwright e2e tests +pnpm test:e2e:ui # Playwright with UI +pnpm lint # ESLint + +# Infrastructure (packages/infra) +cd packages/infra +pnpm test # CDK/Jest tests + +# Deployments (from root) +pnpm deploy:dev # Deploy all CDK stacks to dev +pnpm deploy:dev:hotswap # Hotswap deploy (faster for Lambda changes) +pnpm deploy:dev:api # Deploy only API stack to dev +``` + +### Build Order + +`packages/core` must be built first — both `apps/web` and `apps/functions` depend on it: +```bash +cd packages/core && pnpm build # Always rebuild after changing schemas +``` + +### Type Checking + +```bash +cd apps/functions && pnpm build # tsc (checks types) +cd apps/web && npx tsc --noEmit # Type-check without emitting +cd packages/infra && pnpm build # tsc +``` + +## Architecture + +### Backend (apps/functions) + +Lambda handlers are organized by domain in `src/handlers//`. Each handler follows a thin pattern: +1. Parse event → 2. Validate with Zod (destructure `safeParse`) → 3. Call helper → 4. Return `apiResponse()` + +Key directories: +- `src/handlers/` — Thin Lambda handlers grouped by domain (~30 domains) +- `src/helpers/` — Business logic, DynamoDB operations, AI integrations +- `src/constants/` — PK constants, config values +- `src/middleware/` — Middy middleware (auth, RBAC, error handling) +- `src/types/` — DynamoDB item types extending core schemas with PK/SK + +DynamoDB uses single-table design. All operations go through `src/helpers/db.ts` (`createItem`, `getItem`, `queryBySkPrefix`, etc.). PK constants are in `src/constants/`. SK strings are built via helper functions — never manually. + +### Frontend (apps/web) + +Next.js App Router with Feature-Sliced Design: +- `app/` — Pages and layouts (route groups: `(auth)`, `(dashboard)`) +- `features/` — Domain modules with `components/`, `hooks/`, `lib/`, `index.ts` barrel exports +- `components/ui/` — Shadcn UI primitives +- `lib/hooks/` — Shared SWR data-fetching hooks +- `context/` — Auth, organization providers + +State: SWR for server state, AWS Amplify for auth, `nuqs` for URL state. + +### Infrastructure (packages/infra) + +CDK stacks in `lib/`. API routes defined in `api/routes/.routes.ts` and registered in `api/api-orchestrator-stack.ts`. + +### Shared Types (packages/core) + +All domain types are Zod schemas in `src/schemas/`. Types are always inferred via `z.infer<>` — never defined manually. Built with tsup to ESM + CJS. + +## Git Workflow + +- **develop** — Main development branch. PRs target here. Deploys to staging. +- **production** — Customer-facing. Updated only via Release workflow. +- Create feature branches from `develop`, open PRs to `develop`. + +## Key Conventions + +Detailed rules are in `.claude/rules/`. The most critical ones: + +- **No `any` type.** Use `unknown` with type guards, or specific type assertions. +- **All types from Zod.** `type Foo = z.infer` — never manual interfaces for domain types. +- **`const` arrow functions** for all function definitions (except Next.js page/layout defaults). +- **Destructure `safeParse`** immediately: `const { success, data, error } = Schema.safeParse(raw)`. +- **`orgId` from request body/query/path** — never from JWT token or `event.auth`. +- **Use `apiResponse()`** from `@/helpers/api` for all REST responses. +- **No raw DynamoDB SDK in handlers** — use helpers from `@/helpers/db` or domain helpers. +- **Skeleton loading states** — never spinners or "Loading..." text. +- **Shadcn UI components** — never raw HTML elements for buttons, inputs, etc. +- **Tests are co-located** with source files (e.g., `create-foo.ts` → `create-foo.test.ts`). +- **Test the exported business function directly**, not the middy-wrapped handler. +- **Mock middy and AWS SDK before imports** in test files. + +## Lessons Learned + +- When modifying handler parameters, update ALL corresponding test files. +- All React hooks must be called before any conditional returns. +- Mock function names must exactly match the imported function names. +- After changing core schemas, rebuild `packages/core` before running dependent tests. \ No newline at end of file diff --git a/.claude/README.md b/.claude/README.md new file mode 100644 index 00000000..fbc93a69 --- /dev/null +++ b/.claude/README.md @@ -0,0 +1,144 @@ +# Claude Code Configuration + +This directory contains configuration and rules for [Claude Code](https://claude.ai/code). + +## Directory Structure + +``` +.claude/ +├── README.md # This file +├── settings.json # Team settings (tracked in git) +├── settings.local.json # Personal settings (gitignored) +├── agents/ # Custom agents for specialized workflows +│ ├── feature-implementer.md # End-to-end feature implementation +│ ├── code-reviewer.md # Convention compliance & security audit +│ └── test-generator.md # Comprehensive test suite generation +├── skills/ # Reusable skills (10 skills) +│ ├── audit-logging/SKILL.md # Audit trail logging for handlers & services +│ ├── backend-test/SKILL.md # Jest tests with AWS SDK & middy mocking +│ ├── cdk-route/SKILL.md # API Gateway routes with Lambda integration +│ ├── dynamodb-helper/SKILL.md # DynamoDB helpers with SK builders & CRUD +│ ├── e2e-test/SKILL.md # Playwright E2E tests with auth fixtures +│ ├── frontend-feature/SKILL.md # Feature modules (hooks, components, pages) +│ ├── frontend-form/SKILL.md # Forms with react-hook-form & Zod validation +│ ├── lambda/SKILL.md # Lambda handlers with middy & Sentry +│ ├── step-function/SKILL.md # Step Functions pipelines with CDK +│ └── zod-schema/SKILL.md # Zod schemas with types & DTOs +└── rules/ # Project rules (tracked in git) + ├── 01-project-structure.md + ├── 02-typescript-best-practices.md + ├── 03-entity-definitions.md + ├── 04-backend-architecture.md + ├── 05-dynamodb-design.md + ├── 06-frontend-architecture.md + ├── 07-infrastructure.md + ├── 08-cicd.md + ├── 09-testing.md + ├── RULES.md + ├── README.md + ├── cost-optimization.md + ├── next-js.md + ├── web-development.md + └── workflows/ + ├── architecture.md + └── implementation.md +``` + +## 🤖 Agents + +Agents are specialized personas that can be invoked in Claude Code to handle specific workflows. Use them with `/agent ` in Claude Code. + +### 1. Feature Implementer (`feature-implementer`) + +**When to use**: Building a new feature end-to-end across the monorepo. + +Implements features in the correct dependency order: +``` +Core Schemas → Constants → Helpers → Lambda Handlers → CDK Routes → CDK Infra → Frontend Hooks → Components → Tests +``` + +**Example prompts**: +- `"Implement the FOIA request feature from docs/FOIA-IMPLEMENTATION.md"` +- `"Build a new notifications CRUD with REST API and React UI"` +- `"Add a deadline extraction feature with DynamoDB storage and frontend display"` + +--- + +### 2. Code Reviewer (`code-reviewer`) + +**When to use**: Auditing code for correctness, security, and convention compliance before merging. + +Checks 30+ rules across TypeScript, backend, frontend, DynamoDB, testing, and audit trail categories. Produces a structured report at `docs/reviews/`. + +**Example prompts**: +- `"Review the answer feature"` +- `"Review apps/functions/src/handlers/clustering/"` +- `"Security review the auth handlers"` +- `"Review apps/web/components/brief/helpers.ts"` + +**Output**: Structured markdown report with severity levels (🔴 Critical, 🟡 Warning, 🔵 Info) and a compliance summary table. + +--- + +### 3. Test Generator (`test-generator`) + +**When to use**: Writing comprehensive tests for handlers, helpers, schemas, or components. + +Generates tests with proper AWS SDK mocking, covers all code paths (happy path, validation, not-found, guards, errors, edge cases), and follows project conventions. + +**Example prompts**: +- `"Write tests for apps/functions/src/handlers/document/download-document.ts"` +- `"Write tests for the brief feature"` +- `"Write schema tests for packages/core/src/schemas/project.ts"` +- `"Write tests for apps/web/components/brief/"` + +--- + +## 🛠️ Skills + +Skills are reusable instruction sets that Claude Code can activate for specific tasks. Each skill provides step-by-step templates and hard rules for a particular type of work. + +| # | Skill | Description | Trigger Example | +|---|---|---|---| +| 1 | **`zod-schema`** | Create Zod schemas with types, Create/Update DTOs, barrel exports | `"Create a schema for notifications"` | +| 2 | **`lambda`** | Lambda handler with middy, Zod validation, audit, Sentry | `"Create a handler to list notifications"` | +| 3 | **`cdk-route`** | API Gateway route with Lambda integration in CDK | `"Add API routes for the notification domain"` | +| 4 | **`dynamodb-helper`** | DynamoDB helpers with SK builders and CRUD operations | `"Create DynamoDB helpers for notifications"` | +| 5 | **`frontend-feature`** | Feature module with hooks, components, pages (FSD) | `"Create the notifications frontend feature"` | +| 6 | **`frontend-form`** | Form page with react-hook-form, Zod, Shadcn UI | `"Create a notification create/edit form"` | +| 7 | **`backend-test`** | Jest tests with AWS SDK mocking, middy mocking | `"Write tests for the create-notification handler"` | +| 8 | **`e2e-test`** | Playwright E2E tests with auth fixtures, page objects | `"Write E2E tests for the notifications feature"` | +| 9 | **`audit-logging`** | Audit trail logging with proper actions and patterns | `"Add audit logging to the notification handlers"` | +| 10 | **`step-function`** | Step Functions pipelines with CDK for async workflows | `"Create a notification delivery pipeline"` | + +--- + +## Rules Directory + +The `rules/` directory contains markdown files that Claude Code automatically reads when working on this project. These rules define: + +- **Project conventions** and coding standards +- **Architecture patterns** for backend and frontend +- **Database design** patterns +- **Testing requirements** +- **CI/CD workflows** + +These files are synced from `.clinerules/` to ensure consistency across different AI coding assistants. + +## Settings + +- `settings.json` - Team-wide Claude Code settings (tracked in git) +- `settings.local.json` - Personal Claude Code settings (gitignored) + +## Syncing Rules + +To update Claude Code rules from clinerules: + +```bash +cp -r .clinerules/* .claude/rules/ +``` + +## Learn More + +- [Claude Code Documentation](https://docs.claude.ai/code) +- [Project Rules in .clinerules/](../.clinerules/README.md) diff --git a/.claude/agents/code-reviewer.md b/.claude/agents/code-reviewer.md new file mode 100644 index 00000000..7939158c --- /dev/null +++ b/.claude/agents/code-reviewer.md @@ -0,0 +1,167 @@ +# Code Reviewer Agent + +You are a meticulous senior code reviewer for the AutoRFP monorepo. You audit code for correctness, security, convention compliance, and completeness. You produce structured review reports with actionable findings. + +--- + +## How to Use + +Invoke with a target: +- `"Review the answer feature"` — full feature review +- `"Review apps/functions/src/handlers/clustering/"` — directory review +- `"Review apps/web/components/brief/helpers.ts"` — single file review +- `"Security review the auth handlers"` — security-focused review + +--- + +## Review Process + +### Phase 1 — Scope Discovery + +1. Identify all files in scope (handler + helpers + constants + schemas + tests + frontend components) +2. Read every file in scope — never review from memory alone +3. Map the dependency chain: schema → constants → helpers → handlers → routes → frontend + +### Phase 2 — Convention Compliance Audit + +Check every file against these project rules. For each violation, record the file, line, rule, and fix. + +#### TypeScript Rules +| # | Rule | What to look for | +|---|---|---| +| T1 | No `any` type | Search for `: any`, `as any`, `` | +| T2 | No manual type definitions | Types must use `z.infer` — flag any `interface` or `type` that should be Zod-inferred | +| T3 | `const` arrow functions only | Flag any `function` keyword (except Next.js `export default function`) | +| T4 | No `.js` extensions in imports | Flag any `from './foo.js'` | +| T5 | Strict mode compliance | No `@ts-ignore`, no `@ts-expect-error` without justification | + +#### Backend Rules +| # | Rule | What to look for | +|---|---|---| +| B1 | Thin Lambda handlers | Business logic must be in helpers, not handlers | +| B2 | `safeParse` destructured | Flag `const parsed = Schema.safeParse(...)` — must be `const { success, data, error } = ...` | +| B3 | `orgId` from request | Flag any `event.auth?.orgId`, `event.auth?.claims`, token-based orgId reads | +| B4 | `apiResponse` for REST | Flag any inline `{ statusCode, headers, body }` in REST handlers | +| B5 | No raw DynamoDB SDK in handlers | Flag `DynamoDBClient`, `PutCommand`, `QueryCommand` imports in handler files | +| B6 | Middy middleware stack | Verify: `authContextMiddleware → orgMembershipMiddleware → requirePermission → auditMiddleware → httpErrorMiddleware` | +| B7 | Sentry wrapper | Every exported `handler` must use `withSentryLambda(middy(baseHandler)...)` | +| B8 | Audit logging | Every mutation (create/update/delete) must have `setAuditContext` or `writeAuditLog` | +| B9 | Error handling | Handlers must handle errors gracefully, not throw unhandled exceptions | + +#### Frontend Rules +| # | Rule | What to look for | +|---|---|---| +| F1 | Shadcn UI components | Flag raw ` + } + /> + <List items={s} isLoading={isLoading} /> + + ); +}; + +export default Page; +``` + +## 7. Loading Skeleton + +Create `apps/web/app/(dashboard)//loading.tsx`: + +```typescript +import { PageLoadingSkeleton } from '@/components/layout/page-loading-skeleton'; + +const Loading = () => ; + +export default Loading; +``` + +## 8. Hard Rules + +- **Components are pure presentation** — no API calls, no business logic, no routing +- **Logic lives in hooks** — data fetching, mutations, state management +- **Use Shadcn UI components** — never raw HTML elements for buttons, inputs, cards +- **Use design tokens for dark mode** — `bg-card`, `text-foreground`, `text-muted-foreground`, `hover:bg-accent` +- **Never use hardcoded colors** — no `bg-white`, `text-gray-500`, `border-gray-200` +- **Loading states use ``** — never spinners or "Loading..." text +- **Types from `@auto-rfp/core`** — only define local types for UI-specific concerns +- **Forms use react-hook-form + zodResolver** — no manual `useState` for form fields +- **Create/Edit are separate pages** — never inline forms in list pages +- **Barrel exports** — pages import from `@/features/`, never internal paths +- **Use `const` arrow functions** — never `function` keyword (except `export default function` for Next.js pages if required) diff --git a/.claude/skills/frontend-form/SKILL.md b/.claude/skills/frontend-form/SKILL.md new file mode 100644 index 00000000..a354c038 --- /dev/null +++ b/.claude/skills/frontend-form/SKILL.md @@ -0,0 +1,185 @@ +--- +name: frontend-form +description: Create a form page with react-hook-form, Zod validation, Shadcn UI inputs, and proper create/edit patterns +--- + +# Frontend Form Creation + +When creating a form (create or edit page) in this project, follow these exact steps: + +## 1. Create Page + +Create `apps/web/app/(dashboard)//create/page.tsx`: + +```typescript +'use client'; + +import { useRouter } from 'next/navigation'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { CreateSchema } from '@auto-rfp/core'; +import type { z } from 'zod'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { PageHeader } from '@/components/ui/page-header'; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from '@/components/ui/breadcrumb'; +import { useCreate } from '@/features/'; +import { useCurrentOrganization } from '@/context/organization-context'; +import { toast } from 'sonner'; + +type FormValues = z.inputSchema>; + +const CreatePage = () => { + const router = useRouter(); + const { organization } = useCurrentOrganization(); + const { create, isSubmitting } = useCreate(); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm({ + resolver: zodResolver(CreateSchema), + defaultValues: { + orgId: organization?.id ?? '', + }, + }); + + const onSubmit = async (data: FormValues) => { + try { + await create(data); + toast.success(' created successfully'); + router.push('/'); + } catch { + toast.error('Failed to create '); + } + }; + + return ( +
+ + + + + + + Create + + + + + + + + Details + + +
+
+ + + {errors.name && ( +

{errors.name.message}

+ )} +
+ +
+ + + {errors.description && ( +

{errors.description.message}

+ )} +
+ +
+ + +
+
+
+
+
+ ); +}; + +export default CreatePage; +``` + +## 2. Edit Page Pattern + +Create `apps/web/app/(dashboard)//[id]/edit/page.tsx`: + +- Same form structure but pre-populate with existing data via SWR +- Use `useForm({ defaultValues })` with fetched data +- Use `reset()` when data loads: `useEffect(() => { if (data) reset(data); }, [data, reset]);` +- Submit calls update endpoint instead of create + +## 3. Form with Select/Dropdown + +```typescript +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Controller } from 'react-hook-form'; + + ( + + )} +/> +``` + +## 4. Hard Rules + +- **Use `z.input`** as form type — handles `.default()` fields correctly +- **Use `zodResolver(Schema)`** for validation — never manual validation +- **No manual `useState` for form fields** — use `register()` from react-hook-form +- **Use `Controller` for non-native inputs** (Select, Switch, DatePicker) +- **Create/Edit MUST be separate pages** — never inline forms or use modals +- **Use Shadcn UI components** — `Input`, `Select`, `Button`, `Card` +- **Error messages use `text-destructive`** — never hardcoded red colors +- **Labels use `text-foreground`** — never hardcoded text colors +- **Toast notifications** via `sonner` for success/error feedback +- **Breadcrumb navigation** on all create/edit pages +- **Use `const` arrow functions** — never `function` keyword diff --git a/.claude/skills/lambda/SKILL.md b/.claude/skills/lambda/SKILL.md new file mode 100644 index 00000000..520c59f7 --- /dev/null +++ b/.claude/skills/lambda/SKILL.md @@ -0,0 +1,104 @@ +--- +name: lambda +description: Create a new Lambda handler with middy middleware, Zod validation, audit logging, and Sentry wrapping +--- + +# Lambda Handler Creation + +When creating a new Lambda handler in this project, follow these exact steps: + +## 1. File Location + +Create `apps/functions/src/handlers//.ts` + +## 2. Handler Template + +```typescript +import type { APIGatewayProxyResultV2 } from 'aws-lambda'; +import { apiResponse, getOrgId } from '@/helpers/api'; +import { withSentryLambda } from '@/sentry-lambda'; +import { nowIso } from '@/helpers/date'; +import { + authContextMiddleware, + type AuthedEvent, + httpErrorMiddleware, + orgMembershipMiddleware, + requirePermission, +} from '@/middleware/rbac-middleware'; +import { auditMiddleware, setAuditContext } from '@/middleware/audit-middleware'; +import middy from '@middy/core'; +import { } from '@auto-rfp/core'; + +export const = async ( + event: AuthedEvent, +): Promise => { + // 1. Parse input + const bodyJson = event.body ? JSON.parse(event.body) : {}; + + // 2. Validate — ALWAYS destructure safeParse immediately + const { success, data, error } = .safeParse(bodyJson); + if (!success) { + return apiResponse(400, { message: 'Validation failed', issues: error.issues }); + } + + // 3. Get orgId from request — NEVER from event.auth or JWT + const orgId = getOrgId(event); + + // 4. Call helper — NO business logic in handler + const result = await someHelper(data, orgId); + + // 5. Set audit context + setAuditContext(event, { + action: '_CREATED', + resource: '', + resourceId: result.id, + }); + + // 6. Return with apiResponse — NEVER raw { statusCode, body } + return apiResponse(200, { ok: true, data: result }); +}; + +export const handler = withSentryLambda( + middy() + .use(authContextMiddleware()) + .use(orgMembershipMiddleware()) + .use(requirePermission(':')) + .use(auditMiddleware()) + .use(httpErrorMiddleware()), +); +``` + +## 3. Hard Rules + +- **Lambdas MUST be thin** — only parse, validate, call helper, return response +- **NO business logic** in handlers — all logic goes in `apps/functions/src/helpers/` +- **`safeParse` MUST be destructured immediately**: `const { success, data, error } = ...` +- **`orgId` from request** (body/query/path) — NEVER from `event.auth` or JWT claims +- **Always use `apiResponse`** from `@/helpers/api` — never construct raw response objects +- **Always use `withSentryLambda`** wrapper for error tracking +- **Always use `const` arrow functions** — never `function` keyword +- **Always set audit context** via `setAuditContext` for mutations +- **Middleware order**: `authContextMiddleware → orgMembershipMiddleware → requirePermission → auditMiddleware → httpErrorMiddleware` + +## 4. GET Handler Pattern + +For GET handlers, extract params from query string: +```typescript +const { orgId, entityId } = event.queryStringParameters ?? {}; +if (!orgId) return apiResponse(400, { message: 'orgId is required' }); +``` + +## 5. Register CDK Route + +Add to `packages/infra/api/routes/.routes.ts`: +```typescript +{ method: 'POST', path: '', entry: lambdaEntry('/.ts') }, +``` + +## 6. Create Tests + +Create `apps/functions/src/handlers//.test.ts`: +- Mock middy, AWS SDK, and env vars before imports +- Test the exported function directly (not the middy-wrapped handler) +- Cover: happy path, validation errors, not found, guard clauses, edge cases +- Reset mocks in `beforeEach` with `jest.clearAllMocks()` diff --git a/.claude/skills/step-function/SKILL.md b/.claude/skills/step-function/SKILL.md new file mode 100644 index 00000000..f4f0638e --- /dev/null +++ b/.claude/skills/step-function/SKILL.md @@ -0,0 +1,121 @@ +--- +name: step-function +description: Create AWS Step Functions pipelines with CDK for document processing, answer generation, and async workflows +--- + +# Step Function Pipeline Creation + +When creating a new Step Functions pipeline in this project, follow these exact steps: + +## 1. Pipeline Architecture + +Step Functions are used for multi-step async workflows: +- **Document Processing**: Upload → Textract → Chunking → Embedding → Index +- **Answer Generation**: Prepare Questions → Batch Process → Generate Answers +- **Question Extraction**: Upload → Textract → AI Analysis → Store Questions + +## 2. CDK Definition + +Create `packages/infra/-step-function.ts`: + +```typescript +import * as cdk from 'aws-cdk-lib'; +import * as sfn from 'aws-cdk-lib/aws-stepfunctions'; +import * as tasks from 'aws-cdk-lib/aws-stepfunctions-tasks'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { Construct } from 'constructs'; + +export interface StepFunctionProps { + stage: string; + table: cdk.aws_dynamodb.ITable; + bucket: cdk.aws_s3.IBucket; + // ... other shared resources +} + +export const createStepFunction = ( + scope: Construct, + props: StepFunctionProps, +): sfn.StateMachine => { + const { stage, table, bucket } = props; + + // Step 1: Invoke Lambda + const step1 = new tasks.LambdaInvoke(scope, 'Step1Name', { + lambdaFunction: step1Lambda, + outputPath: '$.Payload', + retryOnServiceExceptions: true, + }); + + // Step 2: Choice state + const isComplete = new sfn.Choice(scope, 'IsComplete?') + .when(sfn.Condition.stringEquals('$.status', 'COMPLETE'), successState) + .otherwise(step3); + + // Build chain + const definition = step1 + .next(isComplete); + + return new sfn.StateMachine(scope, `${stage}-Pipeline`, { + definitionBody: sfn.DefinitionBody.fromChainable(definition), + timeout: cdk.Duration.minutes(30), + tracingEnabled: false, // Cost optimization: no X-Ray + }); +}; +``` + +## 3. Lambda Step Handlers + +Each step in the pipeline is a Lambda handler in `apps/functions/src/handlers//`: + +```typescript +import type { Context } from 'aws-lambda'; + +interface StepInput { + orgId: string; + projectId: string; + // ... step-specific fields +} + +interface StepOutput { + status: 'CONTINUE' | 'COMPLETE' | 'FAILED'; + // ... output fields +} + +export const handler = async (event: StepInput, _context: Context): Promise => { + // Step Functions handlers receive direct JSON input (not API Gateway events) + // No middy middleware needed — these are internal invocations + + try { + const result = await processStep(event); + return { status: 'COMPLETE', ...result }; + } catch (error) { + console.error('Step failed:', error); + return { status: 'FAILED', error: (error as Error).message }; + } +}; +``` + +## 4. Error Handling + +```typescript +// Add retry and catch to steps +const step1 = new tasks.LambdaInvoke(scope, 'Step1', { + lambdaFunction: fn, + retryOnServiceExceptions: true, +}).addRetry({ + maxAttempts: 2, + interval: cdk.Duration.seconds(5), + backoffRate: 2, +}).addCatch(failureState, { + resultPath: '$.error', +}); +``` + +## 5. Hard Rules + +- **Step Function handlers do NOT use middy** — they receive direct JSON, not API Gateway events +- **Use audit logging** in each step — `writeAuditLog` with `userId: 'system'` +- **Set reasonable timeouts** — 30 min max for pipelines, 5 min per step +- **No X-Ray tracing** — cost optimization +- **Error states must update DynamoDB** — mark entity status as FAILED +- **Use `outputPath: '$.Payload'`** — extract Lambda response from wrapper +- **Add retries** — at least 2 retries with exponential backoff for transient failures diff --git a/.claude/skills/zod-schema/SKILL.md b/.claude/skills/zod-schema/SKILL.md new file mode 100644 index 00000000..c70ddd9f --- /dev/null +++ b/.claude/skills/zod-schema/SKILL.md @@ -0,0 +1,79 @@ +--- +name: zod-schema +description: Create a new Zod schema in packages/core with proper types, Create/Update DTOs, and barrel exports +--- + +# Zod Schema Creation + +When creating a new entity schema in this project, follow these exact steps: + +## 1. Create the Schema File + +Create `packages/core/src/schemas/.ts`: + +```typescript +import { z } from 'zod'; + +// --- Item Schema (full DynamoDB record) --- +export const ItemSchema = z.object({ + partition_key: z.string().optional(), + sort_key: z.string().optional(), + id: z.string().uuid(), + orgId: z.string().uuid().optional(), + // ... entity-specific fields ... + createdAt: z.string().datetime().optional(), + updatedAt: z.string().datetime().optional(), +}); + +export type Item = z.inferItemSchema>; + +// --- Create DTO (omit id + timestamps) --- +export const CreateSchema = ItemSchema.omit({ + partition_key: true, + sort_key: true, + id: true, + createdAt: true, + updatedAt: true, +}); + +export type CreateDTO = z.inferSchema>; + +// --- Update DTO (partial of Create) --- +export const UpdateSchema = CreateSchema.partial(); + +export type UpdateDTO = z.inferSchema>; +``` + +## 2. Export from Barrel + +Add to `packages/core/src/schemas/index.ts`: +```typescript +export * from './'; +``` + +## 3. Hard Rules + +- **ALL types MUST be inferred from Zod** using `z.infer` — never define types manually +- Use `z.string().uuid()` for all ID fields +- Use `z.string().datetime()` for timestamp fields +- Use `z.enum([...])` for status fields — never string unions +- Use `.optional()` for nullable fields — never `z.nullable()` +- Use `.default()` for fields with default values +- Use `.trim()` on string fields that accept user input +- Add `.min()` / `.max()` validators with descriptive error messages +- Use `const` arrow functions — never `function` keyword + +## 4. Verify + +```bash +cd packages/core && pnpm tsc --noEmit +``` + +## 5. Create Tests + +Create `packages/core/src/schemas/.test.ts` using Vitest: +- Valid data passes `safeParse` +- Invalid data fails with correct errors +- Default values applied correctly +- Optional fields can be omitted +- Enum values validated diff --git a/.clinerules/01-project-structure.md b/.clinerules/01-project-structure.md new file mode 100644 index 00000000..20e65cae --- /dev/null +++ b/.clinerules/01-project-structure.md @@ -0,0 +1,27 @@ +# Project Structure + +> Defines the monorepo organization and directory conventions. + +--- + +## 📁 Directory Layout + +- **`apps/`** — Deployable applications (follows Turborepo convention) + - `apps/web/` — Next.js App Router frontend (`@auto-rfp/web`) + - `apps/functions/` — AWS Lambda handlers (`@auto-rfp/functions`) +- **`packages/`** — Shared libraries & tooling + - `packages/core/` — Shared Zod schemas & TypeScript types (`@auto-rfp/core`) + - `packages/infra/` — AWS CDK infrastructure stacks (`@auto-rfp/infra`) +- **`scripts/`** — Utility scripts for maintenance and migrations + +--- + +## 🔧 General Conventions + +- Use ESM (`"type": "module"`) everywhere. +- Target Node.js 20+ for Lambda runtime. +- Use `pnpm` as the package manager with workspaces. +- Prefer `const` over `let`; never use `var`. +- Use TypeScript strict mode in all packages. +- Destructure where possible for cleaner code. +- **Never use `.js` extensions in import paths.** Use `moduleResolution: "bundler"` in tsconfig. diff --git a/.clinerules/02-typescript-best-practices.md b/.clinerules/02-typescript-best-practices.md new file mode 100644 index 00000000..abb1ffbc --- /dev/null +++ b/.clinerules/02-typescript-best-practices.md @@ -0,0 +1,44 @@ +# TypeScript Best Practices + +> Strict TypeScript guidelines to ensure type safety and code quality. + +--- + +## 🎯 Core Principles + +- **NEVER use `any` type.** Always use proper types, `unknown`, or type assertions when absolutely necessary. + - If you need to cast, use specific type assertions (e.g., `as DocumentDBItem`) instead of `as any`. + - Use `unknown` for truly unknown types and narrow them with type guards. + +- **AVOID using `as Record` or similar loose type assertions.** + - Define proper types or interfaces for objects instead of using generic Record types. + - If the structure is truly dynamic, use Zod schemas to validate and infer the type. + - Exception: When working with third-party libraries that don't provide proper types. + +- **NEVER define types manually without Zod schemas.** + - All types MUST be inferred from Zod schemas using `z.infer`. + - Exception: Infrastructure-specific types like `DocumentDBItem` that extend core types with DynamoDB keys. + - This ensures runtime validation matches compile-time types. + +- **Use type guards** for runtime type checking instead of type assertions when possible. + +- **Prefer interfaces over types** for object shapes (except when inferring from Zod). + +- **Use discriminated unions** for complex type scenarios instead of `any` or loose types. + +## 🔧 Function Definitions + +- **ALWAYS use `const` arrow functions** instead of the `function` keyword for all function definitions. + - This applies to React components, hooks, helpers, and all other functions. + - Exception: `export default function` for Next.js page/layout files (required by the framework). + ```typescript + // ✅ correct + const MyComponent = () => { ... }; + const handleClick = (e: React.MouseEvent) => { ... }; + const formatDate = (date: string): string => { ... }; + + // ❌ wrong + function MyComponent() { ... } + function handleClick(e: React.MouseEvent) { ... } + function formatDate(date: string): string { ... } + ``` diff --git a/.clinerules/03-entity-definitions.md b/.clinerules/03-entity-definitions.md new file mode 100644 index 00000000..ee542e2d --- /dev/null +++ b/.clinerules/03-entity-definitions.md @@ -0,0 +1,31 @@ +# Entity Definitions + +> Rules for defining domain entities using Zod schemas. + +--- + +## 🧩 Schema Conventions + +- **Every entity MUST be defined in `packages/core/` using Zod schemas.** +- TypeScript types are always inferred from Zod schemas using `z.infer<>` — never define types manually. +- Each entity gets its own file in `packages/core/src/schemas/`. +- Schemas must be re-exported from `packages/core/src/index.ts`. +- Use `CreateXxxSchema` (omit id + timestamps) and `UpdateXxxSchema` (partial) patterns for CRUD. + +--- + +## 🗄️ DynamoDB Item Types + +If an entity schema does not include `partition_key` and `sort_key` properties, define a separate `EntityNameDBItem` type in `apps/functions/src/types/` that extends the base entity type with DynamoDB keys: + +```typescript +import { PK_NAME, SK_NAME } from '@/constants/common'; +import { EntityItem } from '@auto-rfp/core'; + +export type EntityDBItem = EntityItem & { + [PK_NAME]: string; + [SK_NAME]: string; +}; +``` + +This allows type-safe access to DynamoDB keys without polluting the core schema with infrastructure concerns. diff --git a/.clinerules/04-backend-architecture.md b/.clinerules/04-backend-architecture.md new file mode 100644 index 00000000..292bc143 --- /dev/null +++ b/.clinerules/04-backend-architecture.md @@ -0,0 +1,80 @@ +# Backend Architecture + +> Guidelines for Lambda handlers, services, and business logic organization. + +--- + +## ⚡ Lambda Handlers + +- **Lambdas MUST be slim/thin.** They are responsible only for: + 1. Parsing the incoming event (extracting path params, query params, body) + 2. Calling the appropriate service/helper function + 3. Returning the formatted HTTP response + +- **NO business logic in Lambda handlers.** All business logic lives in `apps/functions/helpers/` and domain-specific service files. + +- **Zod `safeParse` results MUST always be destructured immediately** — never access `.success`, `.data`, or `.error` via the intermediate variable: + ```typescript + // ✅ correct + const { success, data, error } = MySchema.safeParse(raw); + if (!success) return apiResponse(400, { message: 'Invalid payload', issues: error.issues }); + + // ❌ wrong — do not keep a named intermediate + const parsed = MySchema.safeParse(raw); + if (!parsed.success) { ... } + const value = parsed.data; + ``` + When you need to rename `data` for clarity, use an alias: `const { success, data: dto, error } = ...`. + +- **`orgId` is NEVER read from the JWT token.** It must come from the request itself: + - **Body** (preferred for POST/PUT/PATCH — include `orgId` in the request payload) + - **Query string** (`?orgId=...` for GET/DELETE) + - **Path parameter** (`/{orgId}/...` when scoped by org in the URL) + Never read it from `event.auth?.claims` or any token field. + ```typescript + // ✅ correct — from body (POST/PUT) + const orgId = data.orgId ?? event.queryStringParameters?.orgId; + if (!orgId) return apiResponse(400, { message: 'orgId is required' }); + + // ✅ correct — from query param (GET) + const { orgId, projectId } = event.queryStringParameters ?? {}; + if (!orgId) return apiResponse(400, { message: 'orgId is required' }); + + // ❌ wrong — from token/auth context + const orgId = event.auth?.orgId; + const orgId = event.auth?.claims?.['custom:orgId']; + ``` + +- **Always use `apiResponse` from `@/helpers/api`** for all HTTP responses in REST Lambda handlers. Never construct raw response objects (`{ statusCode, headers, body }`) inline. + ```typescript + // ✅ correct + return apiResponse(200, { items }); + return apiResponse(400, { message: 'Invalid payload', issues: error.issues }); + + // ❌ wrong + return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items }) }; + ``` + Note: WebSocket handlers (`$connect`, `$disconnect`, `$default`) return plain `{ statusCode, body }` objects directly — `apiResponse` is for REST handlers only. + +- Each handler is organized by domain under `apps/functions//`. + +- **Every Lambda MUST have an explicit CloudWatch Log Group** defined in CDK with controlled retention (2 weeks for non-prod, retained for prod). + +--- + +## 🧠 Business Logic & Services + +- All business logic lives in **`apps/functions/helpers/`** and domain-specific files. +- Services are organized by domain within the functions directory structure. +- Services receive validated, typed data — they never parse raw events. +- Services interact with DynamoDB, Cognito, and other AWS services. + +--- + +## 👤 User Management + +- **Users MUST be created in both DynamoDB AND Cognito.** +- When creating a user: + 1. Create the user in Cognito (via `@aws-sdk/client-cognito-identity-provider`) + 2. Store the user record in DynamoDB with the Cognito `sub` as the user ID +- User deletion should clean up both Cognito and DynamoDB. diff --git a/.clinerules/05-dynamodb-design.md b/.clinerules/05-dynamodb-design.md new file mode 100644 index 00000000..fde70063 --- /dev/null +++ b/.clinerules/05-dynamodb-design.md @@ -0,0 +1,29 @@ +# DynamoDB Design + +> Single-table design patterns and access patterns. + +--- + +## 🗄️ Single-Table Design + +- We use a **single-table design** with a shared DynamoDB table. + +- **PK (Partition Key)**: Use constants from `PK` object — **no magic strings**. + - `PK.USER`, `PK.ORGANIZATION`, `PK.PROJECT`, etc. (defined in `apps/functions/constants/`) + +- **SK (Sort Key)**: Composite key with `#` separator, built via helper functions. + - Pattern: `{orgId}#{projectId}#{entityId}` (empty segments are omitted) + - Use helper functions — never construct SK strings manually. + +- **Multitenancy**: All entities support optional `orgId` as the first SK segment. + - `orgId` scopes data to an organization. When empty, data is global. + - Example: `PK = PK.USER`, `SK = "org123#proj456#user789"` + - Query by org: `skPrefix = "org123"`, by org+project: `skPrefix = "org123#proj456"` + +- Each entity has key builder functions in their respective function handlers. + +- GSI1 can be used for access patterns that reverse PK/SK. + +- All DynamoDB operations go through helper functions in `apps/functions/helpers/`. + +- All services accept `orgId` as a parameter (can be undefined for global scope). diff --git a/.clinerules/06-frontend-architecture.md b/.clinerules/06-frontend-architecture.md new file mode 100644 index 00000000..752e0778 --- /dev/null +++ b/.clinerules/06-frontend-architecture.md @@ -0,0 +1,115 @@ +# Frontend Architecture + +> Next.js App Router + Domain-Driven Design patterns. + +--- + +## Framework & Structure + +- **Framework**: Next.js 15+ with App Router +- **Path aliases**: Use `@/*` for all imports (e.g., `import { UserList } from '@/components/users/UserList'`) +- **Route groups**: `(auth)` and `(dashboard)` use different layouts without affecting URL paths +- **Auth guard**: Dashboard layout redirects to `/login` if not authenticated + +--- + +## Component Architecture + +### Server vs Client Components + +- Root `layout.tsx` is a Server Component (defines metadata, wraps with Providers) +- All interactive components use `'use client'` directive +- `Providers.tsx` wraps the app with SWR config and Amplify initialization + +### Feature Modules (Feature-Sliced Design) + +Each domain has its own directory with clear subdirectories: + +``` +features/ +├── users/ +│ ├── components/ # Presentation-only components +│ │ └── UserList.tsx +│ ├── hooks/ # Feature-specific logic hooks +│ │ ├── useCreateUser.ts +│ │ └── useEditUser.ts +│ ├── lib/ # Helper functions and utilities +│ │ ├── validation.ts +│ │ └── formatting.ts +│ ├── types.ts # Local types (only if not in @auto-rfp/core) +│ └── index.ts # Barrel export +``` + +- **Components must be pure presentation** — no business logic, API calls, or routing +- **Keep components small and simple** — if a component exceeds 200 lines, split it into smaller components +- **Logic lives in feature hooks** in the `hooks/` subdirectory +- **Helper functions** (validation, formatting, calculations) go in the `lib/` subdirectory +- **Types should be imported from `@auto-rfp/core`** — only define local types if they're UI-specific and not part of the domain model +- **Barrel exports** (`index.ts`) — pages import from `@/features/users`, never from internal paths + +--- + +## Pages & Routing + +- **Create/Edit pages MUST be separate pages** — never inline forms in list pages or use dialogs/modals + - Create: `/users/create` → `app/(dashboard)/users/create/page.tsx` + - Edit: `/users/[id]/edit` → `app/(dashboard)/users/[id]/edit/page.tsx` + - List pages link to create/edit pages via `` with breadcrumb navigation + +--- + +## Data Fetching & State + +- **Data fetching**: Use **SWR** with `authenticatedFetcher` for all client-side API calls + - `useApi(path)` — Generic hook for GET requests with caching + - `apiMutate(path, options)` — Helper for POST/PUT/DELETE + +- **Authentication**: Use **AWS Amplify** (`aws-amplify`) to authenticate with Cognito + - `useAuth()` hook provides `signIn`, `signOut`, `isAuthenticated`, `username` + - JWT tokens are automatically attached to API requests via `authenticatedFetcher` + +- **Health check**: `useHealth()` hook polls `/health` every 30s + - `HealthBanner` component shows an error banner when the API is unreachable + +- **API Response Types**: All response types (`UsersResponse`, `UserResponse`, etc.) are defined in `@auto-rfp/core` — never define inline interfaces in components + +--- + +## Forms + +- Use **react-hook-form** with `@hookform/resolvers/zod` and Zod schemas from `@auto-rfp/core` +- Use `z.input` as the form type (handles `.default()` fields correctly) +- Use `zodResolver(Schema)` for validation +- No manual `useState` for form fields — use `register()` from react-hook-form + +--- + +## UI & Styling + +- **Styling**: Use **Tailwind CSS v4** — no raw CSS files. All styling via utility classes + - Custom theme tokens defined in `globals.css` via `@theme` directive + - Indigo (`indigo-500`) as primary color, Slate for neutrals, Emerald for success + +- **UI Components**: Use **Shadcn UI** components from `@/components/ui/` + - Components: `Button`, `Input`, `Select`, `Card`, `Badge`, `PageHeader`, `Breadcrumb`, etc. + - **Never use raw HTML elements** for buttons, inputs, cards, etc. — always use the UI components + - To swap the underlying component library, only change the `components/ui/` implementations + +--- + +## Loading States + +- **ALWAYS use skeleton components for loading states** — never use spinners or "Loading..." text + +- **Page-level loading**: Use `PageLoadingSkeleton` from `@/components/layout/page-loading-skeleton` + - Create `loading.tsx` files in route directories that render appropriate skeleton components + - Skeleton variants: `list`, `grid`, `detail` — choose based on the content being loaded + - Example: `` for detail pages + +- **Component-level loading**: Use `Skeleton` from `@/components/ui/skeleton` for inline loading states + +--- + +## Environment Variables + +- Use `NEXT_PUBLIC_` prefix for client-side env vars diff --git a/.clinerules/07-infrastructure.md b/.clinerules/07-infrastructure.md new file mode 100644 index 00000000..7c87aa93 --- /dev/null +++ b/.clinerules/07-infrastructure.md @@ -0,0 +1,31 @@ +# Infrastructure (AWS CDK) + +> AWS infrastructure definitions and deployment patterns. + +--- + +## 🏗️ CDK Organization + +- All infrastructure is defined in `packages/infra/lib/`. + +- Stacks are organized by concern: + - `api/` — API Gateway + Lambda function definitions + - `database-stack.ts` — DynamoDB table + GSIs + - `auth-stack.ts` — Cognito User Pool + Client + - `amplify-fe-stack.ts` — Amplify Hosting for frontend + - `storage-stack.ts` — S3 buckets for file storage + - `network-stack.ts` — VPC and networking resources + +- Stack outputs are used to pass values between stacks (e.g., table name, user pool ID). + +- Environment variables are passed to Lambda functions for resource references. + +- Multi-stage support via environment-specific configurations. + +--- + +## 🌐 Frontend Deployment + +- **Frontend is deployed via AWS Amplify Hosting** (not S3 + CloudFront). +- The CDK stack uses `@aws-cdk/aws-amplify-alpha` to define the Amplify app. +- The built `apps/web/dist` is deployed as an S3 asset to an Amplify branch. diff --git a/.clinerules/08-cicd.md b/.clinerules/08-cicd.md new file mode 100644 index 00000000..8f764698 --- /dev/null +++ b/.clinerules/08-cicd.md @@ -0,0 +1,42 @@ +# CI/CD (GitHub Actions) + +> Continuous integration and deployment workflows. + +--- + +## 🚀 Branching Strategy + +- `develop` — Development branch (deploys to **dev** environment) +- `main` — Test branch (deploys to **test** environment) +- Feature branches → PR to `develop` +- `develop` → PR to `main` for promotion to test + +--- + +## 🔄 Workflows + +Located in `.github/workflows/`: + +- **`ci.yml`** — Runs on every push/PR to `develop` and `main` + - Steps: install → build → test → upload artifacts + +- **`deploy-dev.yml`** — Triggered on push to `develop` + - Builds and deploys all CDK stacks with `-c stage=dev` + +- **`deploy-test.yml`** — Triggered on push to `main` + - Builds and deploys all CDK stacks with `-c stage=test` + +--- + +## 🔐 AWS Authentication + +- Uses OIDC (`id-token: write`) with `aws-actions/configure-aws-credentials@v4`. +- Requires `AWS_ROLE_ARN` secret and optional `AWS_REGION` variable per GitHub environment. +- **GitHub Environments**: `dev` and `test` environments should be configured in repo settings with appropriate secrets. + +--- + +## ⚙️ Configuration + +- **Concurrency**: CI jobs cancel in-progress runs; deploy jobs do NOT cancel (to avoid partial deployments). +- **Caching**: pnpm store is cached between runs for faster installs. diff --git a/.clinerules/09-testing.md b/.clinerules/09-testing.md new file mode 100644 index 00000000..99a6ece7 --- /dev/null +++ b/.clinerules/09-testing.md @@ -0,0 +1,124 @@ +# Testing + +> Rules for writing and maintaining tests across the project. + +--- + +## 🧪 Core Principle + +- **Every new handler, helper, or component MUST have corresponding tests.** Never generate code without also generating or updating its test file. +- Tests are co-located with the source file they test (e.g., `create-foia-request.ts` → `create-foia-request.test.ts`). + +--- + +## ⚡ Backend Tests (`apps/functions/`) + +- **Framework**: Jest with TypeScript +- **Test file naming**: `.test.ts` in the same directory as the handler +- **Mock pattern**: Mock AWS SDK and middy at the top of every test file before imports: + ```typescript + // ✅ correct — mock middy before importing handlers + jest.mock('@middy/core', () => { + const middy = (handler: unknown) => ({ + use: jest.fn().mockReturnThis(), + handler, + }); + return { __esModule: true, default: middy }; + }); + + // Mock AWS SDK + const mockSend = jest.fn(); + jest.mock('@aws-sdk/client-dynamodb', () => ({ + DynamoDBClient: jest.fn(() => ({})), + })); + + jest.mock('@aws-sdk/lib-dynamodb', () => ({ + DynamoDBDocumentClient: { + from: jest.fn(() => ({ send: mockSend })), + }, + PutCommand: jest.fn((params) => ({ type: 'Put', params })), + GetCommand: jest.fn((params) => ({ type: 'Get', params })), + // ... other commands as needed + })); + + // Set required environment variables + process.env.DB_TABLE_NAME = 'test-table'; + process.env.REGION = 'us-east-1'; + ``` + +- **Test the exported function, not the handler wrapper.** Import and test `createFOIARequest`, `updateFOIARequest`, `generateFOIALetter`, etc. — not the middy-wrapped `handler`. + ```typescript + // ✅ correct — test the business function directly + import { createFOIARequest } from './create-foia-request'; + const result = await createFOIARequest(dto, 'user-789'); + + // ❌ wrong — testing the middy-wrapped handler requires full event simulation + import { handler } from './create-foia-request'; + ``` + +- **Reset mocks in `beforeEach`**: + ```typescript + beforeEach(() => { + jest.clearAllMocks(); + mockSend.mockReset(); + }); + ``` + +- **What to test for every handler:** + 1. Happy path — correct input produces expected output + 2. Validation — invalid input returns 400 with error details + 3. Not found — missing resources return 404 + 4. Guard clauses — business rules enforced (e.g., LOST status check) + 5. DynamoDB calls — correct table name, keys, and expressions + 6. Edge cases — optional fields missing, empty arrays, etc. + +--- + +## 🧩 Schema Tests (`packages/core/`) + +- **Framework**: Vitest +- **Test file naming**: `.test.ts` in the same directory +- **What to test:** + 1. Valid data passes `safeParse` + 2. Invalid data fails `safeParse` with correct error + 3. Default values are applied correctly + 4. Optional fields can be omitted + 5. Enum values are validated + 6. Helper functions produce correct results + +--- + +## 🌐 Frontend Tests (`apps/web/`) + +- **Framework**: Jest with React Testing Library +- **Test file location**: `__tests__/` subdirectory next to the component +- **What to test:** + 1. Component renders without crashing + 2. Loading states show skeletons (not spinners) + 3. Empty states display correct messaging + 4. User interactions trigger correct callbacks + 5. Permission guards hide/show elements correctly + 6. Form validation displays error messages + +--- + +## 📋 When to Update Tests + +- **New handler created** → Create `.test.ts` with all test categories above +- **Handler logic changed** → Update existing tests to cover new behavior +- **Schema changed** → Update schema tests for new fields/validations +- **Component refactored** → Update component tests for new behavior +- **Bug fixed** → Add a regression test that would have caught the bug + +--- + +## ❌ Common Testing Mistakes + +| Mistake | Correct approach | +|---|---| +| No tests for new code | Always create tests alongside new handlers/components | +| Testing only happy path | Include validation, not-found, guard clause, and edge case tests | +| Not mocking AWS SDK | Mock all AWS SDK clients before imports | +| Testing middy-wrapped handler | Test the exported business function directly | +| Hardcoded dates in assertions | Use `expect.any(String)` for timestamps | +| Not resetting mocks | Always `jest.clearAllMocks()` in `beforeEach` | diff --git a/.clinerules/10-audit-trail.md b/.clinerules/10-audit-trail.md new file mode 100644 index 00000000..0d200ecb --- /dev/null +++ b/.clinerules/10-audit-trail.md @@ -0,0 +1,202 @@ +# Audit Trail + +> Every new feature MUST emit audit log events. This is a non-negotiable requirement. + +--- + +## 🔒 Core Principle + +**Every new handler, service, or AI feature MUST write audit log entries for all significant actions.** +Never ship a new feature without audit coverage. Audit logs are required for security compliance (FedRAMP, ISO 27001), debugging, and data access tracking. + +--- + +## 📋 When to Write Audit Logs + +Write an audit log entry for **every** action that: + +- Creates, updates, or deletes a resource (CRUD) +- Triggers an AI generation (document, brief, answer) +- Invokes an AI tool (DynamoDB query, semantic search, etc.) +- Changes permissions or configuration +- Accesses sensitive data (org details, user info, contact data) +- Starts or completes a pipeline or background job +- Results in a failure or error that affects a user + +--- + +## 🎯 Audit Actions to Use + +Use existing actions from `AuditActionSchema` in `packages/core/src/schemas/audit.ts`. + +**For new features, add new actions to the schema** — never use a generic action when a specific one is more accurate. + +Common patterns: + +| Feature Type | Actions to Emit | +|---|---| +| New CRUD handler | `{ENTITY}_CREATED`, `{ENTITY}_UPDATED`, `{ENTITY}_DELETED` | +| AI generation | `AI_GENERATION_STARTED`, `AI_GENERATION_COMPLETED`, `AI_GENERATION_FAILED` | +| AI tool call | `AI_TOOL_CALLED`, `AI_TOOL_FAILED` | +| Pipeline | `PIPELINE_STARTED`, `PIPELINE_COMPLETED`, `PIPELINE_FAILED` | +| Config change | `CONFIG_CHANGED` | +| Data export | `DATA_EXPORTED` | + +--- + +## ✅ How to Write Audit Logs + +Use `writeAuditLog()` from `apps/functions/src/helpers/audit-log.ts`: + +```typescript +import { writeAuditLog } from '@/helpers/audit-log'; +import { getHmacSecret } from '@/helpers/secret'; +import { v4 as uuidv4 } from 'uuid'; +import { nowIso } from '@/helpers/date'; + +// In a handler or service: +await writeAuditLog( + { + logId: uuidv4(), + timestamp: nowIso(), + userId: event.auth?.userId ?? 'system', + userName: event.auth?.userName ?? 'system', + organizationId: orgId, + action: 'DOCUMENT_CREATED', + resource: 'document', + resourceId: documentId, + changes: { + before: undefined, + after: { documentType, title }, + }, + ipAddress: event.requestContext?.http?.sourceIp ?? '0.0.0.0', + userAgent: event.headers?.['user-agent'] ?? 'system', + result: 'success', + }, + await getHmacSecret(), +); +``` + +### For background workers (SQS, Step Functions) + +Background workers have no HTTP context. Use `'system'` for `userId`, `userName`, `ipAddress`, and `userAgent`: + +```typescript +await writeAuditLog( + { + logId: uuidv4(), + timestamp: nowIso(), + userId: 'system', + userName: 'system', + organizationId: orgId, + action: 'AI_GENERATION_COMPLETED', + resource: 'document', + resourceId: documentId, + changes: { + after: { documentType, tokensUsed, toolRounds }, + }, + ipAddress: '0.0.0.0', + userAgent: 'system', + result: 'success', + }, + await getHmacSecret(), +); +``` + +### Non-blocking audit logs + +For high-frequency events (e.g., AI tool calls per generation), write audit logs **non-blocking** to avoid adding latency to the critical path: + +```typescript +// ✅ correct — non-blocking, errors are swallowed gracefully +writeAuditLog(payload, hmacSecret).catch(err => + console.warn('Failed to write audit log (non-blocking):', err.message), +); + +// ❌ wrong — blocks the critical path for a non-critical operation +await writeAuditLog(payload, hmacSecret); +``` + +Use `await` only when the audit log is itself a critical operation (e.g., compliance-required write before a destructive action). + +--- + +## 🏗️ Adding New Audit Actions + +When a new feature requires an action not in `AuditActionSchema`: + +1. **Add the action** to `AuditActionSchema` in `packages/core/src/schemas/audit.ts`: + ```typescript + export const AuditActionSchema = z.enum([ + // ... existing actions ... + 'MY_NEW_ACTION', // add here + ]); + ``` + +2. **Add the resource type** to `AuditResourceSchema` if needed: + ```typescript + export const AuditResourceSchema = z.enum([ + // ... existing resources ... + 'my_new_resource', // add here + ]); + ``` + +3. **Export the updated types** — `AuditAction` and `AuditResource` are inferred from the schemas, so no manual type updates needed. + +4. **Update schema tests** in `packages/core/src/schemas/audit.test.ts` to cover the new action. + +--- + +## 📊 What to Include in `changes` + +The `changes` field captures the before/after state of the affected resource: + +```typescript +// For CREATE operations: +changes: { after: { ...newEntityFields } } + +// For UPDATE operations: +changes: { before: { ...oldFields }, after: { ...newFields } } + +// For DELETE operations: +changes: { before: { ...deletedEntityFields } } + +// For AI operations (no entity state change): +changes: { + after: { + toolName, // which tool was called + resultLength, // chars returned + resultEmpty, // whether result was empty + durationMs, // execution time + } +} +``` + +**Do NOT include PII or secrets** in `changes`. Sanitize inputs before logging: +- Omit passwords, tokens, API keys +- Truncate large text fields (max 500 chars) +- Omit full document HTML content + +--- + +## 🚫 Common Mistakes + +| Mistake | Correct approach | +|---|---| +| No audit log for new handler | Always add audit logging before shipping | +| Using `await` for non-critical audit writes | Use non-blocking `.catch()` pattern for high-frequency events | +| Logging PII or secrets in `changes` | Sanitize inputs — omit passwords, tokens, truncate large text | +| Using a generic action like `CONFIG_CHANGED` for a specific event | Add a specific action to the schema | +| Forgetting to add new actions to the schema | Update `AuditActionSchema` and `AuditResourceSchema` in `packages/core` | +| Not logging failures | Always emit `*_FAILED` action on errors, not just success | +| Skipping audit logs in background workers | Background workers MUST log — use `userId: 'system'` | + +--- + +## 🔗 Related Files + +- `packages/core/src/schemas/audit.ts` — Action and resource type definitions +- `apps/functions/src/helpers/audit-log.ts` — `writeAuditLog()` implementation +- `apps/functions/src/middleware/audit-middleware.ts` — Automatic audit logging for REST handlers +- `apps/functions/src/constants/audit.ts` — `AUDIT_LOG_PK`, TTL constants +- `docs/AUDIT-LOGGING-IMPLEMENTATION.md` — Full audit system design diff --git a/.clinerules/11-dark-mode.md b/.clinerules/11-dark-mode.md new file mode 100644 index 00000000..40f4046e --- /dev/null +++ b/.clinerules/11-dark-mode.md @@ -0,0 +1,130 @@ +# Dark Mode Best Practices + +> Guidelines for ensuring UI components work in both light and dark themes. + +--- + +## 🎨 Core Principle + +**NEVER use hardcoded colors like `bg-white`, `bg-gray-200`, `text-slate-500`, etc.** + +Always use semantic design tokens that automatically adapt to the current theme. + +--- + +## ✅ Use Semantic Design Tokens + +Use these Tailwind CSS classes that reference CSS variables defined in `globals.css`: + +| Purpose | Light-mode hardcoded ❌ | Dark-mode compatible ✅ | +|---------|------------------------|------------------------| +| Background | `bg-white` | `bg-background` or `bg-card` | +| Surface/Card | `bg-gray-50`, `bg-slate-100` | `bg-muted` or `bg-card` | +| Text (primary) | `text-black`, `text-slate-900` | `text-foreground` | +| Text (secondary) | `text-gray-500`, `text-slate-600` | `text-muted-foreground` | +| Border | `border-gray-200` | `border` (uses `--border` variable) | +| Hover state | `hover:bg-gray-100` | `hover:bg-accent` | +| Primary accent | `bg-indigo-500`, `text-indigo-600` | `bg-primary`, `text-primary` | +| Primary on bg | `bg-indigo-100` | `bg-primary/10` | +| Divider | `divide-slate-100` | `divide-border` | + +--- + +## 🎯 Available Design Tokens + +These tokens are defined in `apps/web/app/globals.css` with both light and dark values: + +### Backgrounds & Surfaces +- `bg-background` — Main page background +- `bg-card` — Card/elevated surfaces +- `bg-popover` — Popover/dropdown backgrounds +- `bg-muted` — Muted/subtle backgrounds +- `bg-accent` — Interactive highlight backgrounds +- `bg-sidebar` — Sidebar-specific background + +### Text Colors +- `text-foreground` — Primary text +- `text-card-foreground` — Text on cards +- `text-muted-foreground` — Secondary/helper text +- `text-accent-foreground` — Text on accent backgrounds +- `text-primary` — Primary brand color text +- `text-primary-foreground` — Text on primary backgrounds +- `text-destructive` — Error/danger text + +### Interactive States +- `hover:bg-accent` — Hover backgrounds +- `hover:text-accent-foreground` — Hover text +- `focus:ring-ring` — Focus ring color + +### Borders +- `border` — Default border (uses `border-border` internally) +- `border-input` — Form input borders +- `divide-border` — List dividers + +--- + +## 🖌️ Status Colors with Dark Mode + +For status-specific colors (success, warning, error, info), use the `dark:` prefix: + +```tsx +// ✅ correct — adapts to dark mode +className="bg-emerald-100 dark:bg-emerald-900/50 text-emerald-600 dark:text-emerald-400" + +// ❌ wrong — invisible in dark mode +className="bg-emerald-100 text-emerald-600" +``` + +Common pattern for status badges/icons: +```tsx +const STATUS_CONFIG = { + success: { bg: 'bg-emerald-100 dark:bg-emerald-900/50', text: 'text-emerald-600 dark:text-emerald-400' }, + warning: { bg: 'bg-amber-100 dark:bg-amber-900/50', text: 'text-amber-600 dark:text-amber-400' }, + error: { bg: 'bg-red-100 dark:bg-red-900/50', text: 'text-red-500 dark:text-red-400' }, + info: { bg: 'bg-blue-100 dark:bg-blue-900/50', text: 'text-blue-600 dark:text-blue-400' }, +}; +``` + +--- + +## 🚫 Common Mistakes + +| Mistake | Fix | +|---------|-----| +| `bg-white` | `bg-background` or `bg-card` | +| `bg-gray-50`, `bg-slate-50` | `bg-muted` | +| `text-gray-900`, `text-slate-900` | `text-foreground` | +| `text-gray-500`, `text-slate-500` | `text-muted-foreground` | +| `hover:bg-gray-100` | `hover:bg-accent` | +| `border-gray-200` | Remove color (just `border`) | +| `divide-gray-100` | `divide-border` | +| `bg-indigo-50` for highlights | `bg-primary/5` or `bg-primary/10` | + +--- + +## 🔍 Testing Dark Mode + +1. **System preference**: Set your OS to dark mode +2. **Dev tools**: In Chrome DevTools, use "Rendering" > "Emulate CSS media feature prefers-color-scheme" +3. **Manual toggle**: If a theme toggle is added, test both states + +--- + +## 📋 Checklist for New Components + +Before submitting a PR, verify: + +- [ ] No hardcoded white/gray backgrounds (`bg-white`, `bg-gray-*`, `bg-slate-*`) +- [ ] No hardcoded text colors (`text-black`, `text-gray-*`, `text-slate-*`) +- [ ] Status colors have `dark:` variants +- [ ] Borders use semantic `border` class (not `border-gray-*`) +- [ ] Dividers use `divide-border` +- [ ] Hover states use `hover:bg-accent` +- [ ] Component is readable in both light and dark themes + +--- + +## 🔗 Related Files + +- `apps/web/app/globals.css` — CSS variable definitions for light/dark themes +- `apps/web/tailwind.config.ts` — Tailwind configuration with `darkMode: ["class", "media"]` diff --git a/.clinerules/README.md b/.clinerules/README.md new file mode 100644 index 00000000..0c701de2 --- /dev/null +++ b/.clinerules/README.md @@ -0,0 +1,70 @@ +# Project Rules & Conventions + +> This directory contains the single source of truth for project conventions. +> Update these files every time a new rule or pattern is established. + +--- + +## 📚 Documentation Structure + +1. **[01-project-structure.md](01-project-structure.md)** — Monorepo organization and directory conventions +2. **[02-typescript-best-practices.md](02-typescript-best-practices.md)** — TypeScript guidelines and type safety rules +3. **[03-entity-definitions.md](03-entity-definitions.md)** — Domain entity and Zod schema conventions +4. **[04-backend-architecture.md](04-backend-architecture.md)** — Lambda handlers, services, and business logic +5. **[05-dynamodb-design.md](05-dynamodb-design.md)** — Single-table design patterns and access patterns +6. **[06-frontend-architecture.md](06-frontend-architecture.md)** — Next.js App Router and component patterns +7. **[07-infrastructure.md](07-infrastructure.md)** — AWS CDK infrastructure definitions +8. **[08-cicd.md](08-cicd.md)** — CI/CD workflows and deployment strategies +9. **[09-testing.md](09-testing.md)** — Testing rules and conventions +10. **[10-audit-trail.md](10-audit-trail.md)** — Audit trail requirements for every new feature +11. **[11-dark-mode.md](11-dark-mode.md)** — Dark mode best practices and design tokens + +--- + +## 🔄 Workflows + +Workflows are step-by-step guides for Cline/Claude to follow when performing specific tasks: + +| Workflow | Trigger | Description | +|---|---|---| +| **[Architecture](workflows/architecture.md)** | "Design [feature]" | Produce implementation-ready architecture docs for new features | +| **[Implementation](workflows/implementation.md)** | "Implement [feature]" | Build a feature from an existing architecture doc | +| **[Code Review](workflows/code-review.md)** | "Review [feature/file/dir]" | AI-powered code audit with structured findings report | +| **[Fix Review](workflows/fix-review.md)** | "Fix [review report]" | Systematically resolve issues found by code review | + +### Code Review Quick Start + +Ask Cline/Claude to review code using any of these patterns: +- `"Review the answer feature"` — Full feature review (schemas, handlers, tests, frontend) +- `"Review apps/functions/src/handlers/clustering/"` — Directory review +- `"Review apps/web/components/brief/helpers.ts"` — Single file review +- `"Security review the auth handlers"` — Security-focused review + +Reports are generated at `docs/reviews/-review-YYYY-MM-DD.md`. + +--- + +## 🎯 Quick Reference + +### For Backend Development +- Start with [04-backend-architecture.md](04-backend-architecture.md) for Lambda patterns +- Reference [03-entity-definitions.md](03-entity-definitions.md) for schema creation +- Check [05-dynamodb-design.md](05-dynamodb-design.md) for data access patterns + +### For Frontend Development +- Start with [06-frontend-architecture.md](06-frontend-architecture.md) for component patterns +- Reference [02-typescript-best-practices.md](02-typescript-best-practices.md) for type safety + +### For Infrastructure +- See [07-infrastructure.md](07-infrastructure.md) for CDK stack organization +- Check [08-cicd.md](08-cicd.md) for deployment workflows + +--- + +## 📝 Maintenance + +When adding new conventions: +1. Update the appropriate file based on the topic +2. If the topic doesn't fit existing files, create a new numbered file +3. Update this README with the new file reference +4. Keep examples concise and actionable diff --git a/.clinerules/RULES.md b/.clinerules/RULES.md new file mode 100644 index 00000000..2a7d792b --- /dev/null +++ b/.clinerules/RULES.md @@ -0,0 +1,294 @@ +# Project Rules & Conventions + +> This file is the single source of truth for project conventions. +> Update it every time a new rule or pattern is established. + +--- + +## 📁 Project Structure + +- **`apps/`** — Deployable applications (follows Turborepo convention) + - `apps/web/` — Next.js App Router frontend (`@auto-rfp/web`) + - `apps/functions/` — AWS Lambda handlers (`@auto-rfp/functions`) +- **`packages/`** — Shared libraries & tooling + - `packages/core/` — Shared Zod schemas & TypeScript types (`@auto-rfp/core`) + - `packages/infra/` — AWS CDK infrastructure stacks (`@auto-rfp/infra`) +- **`scripts/`** — Utility scripts for maintenance and migrations + +--- + +## 🧩 Entity Definitions + +- **Every entity MUST be defined in `packages/core/` using Zod schemas.** +- TypeScript types are always inferred from Zod schemas using `z.infer<>` — never define types manually. +- Each entity gets its own file in `packages/core/src/schemas/`. +- Schemas must be re-exported from `packages/core/src/index.ts`. +- Use `CreateXxxSchema` (omit id + timestamps) and `UpdateXxxSchema` (partial) patterns for CRUD. +- **DynamoDB Item Types**: If an entity schema does not include `partition_key` and `sort_key` properties, define a separate `EntityNameDBItem` type in `apps/functions/src/types/` that extends the base entity type with DynamoDB keys: + ```typescript + import { PK_NAME, SK_NAME } from '@/constants/common'; + import { EntityItem } from '@auto-rfp/core'; + + export type EntityDBItem = EntityItem & { + [PK_NAME]: string; + [SK_NAME]: string; + }; + ``` + This allows type-safe access to DynamoDB keys without polluting the core schema with infrastructure concerns. + +--- + +## ⚡ Lambda Handlers + +- **Lambdas MUST be slim/thin.** They are responsible only for: + 1. Parsing the incoming event (extracting path params, query params, body) + 2. Calling the appropriate service/helper function + 3. Returning the formatted HTTP response +- **NO business logic in Lambda handlers.** All business logic lives in `apps/functions/helpers/` and domain-specific service files. +- Validation results should be destructured: `const { success, data, errors } = validateInput(...)`. +- Each handler is organized by domain under `apps/functions//`. +- **Every Lambda MUST have an explicit CloudWatch Log Group** defined in CDK with controlled retention (2 weeks for non-prod, retained for prod). + +--- + +## 🧠 Business Logic & Services + +- All business logic lives in **`apps/functions/helpers/`** and domain-specific files. +- Services are organized by domain within the functions directory structure. +- Services receive validated, typed data — they never parse raw events. +- Services interact with DynamoDB, Cognito, and other AWS services. + +--- + +## 🗄️ DynamoDB Design (Single-Table) + +- We use a **single-table design** with a shared DynamoDB table. +- **PK (Partition Key)**: Use constants from `PK` object — **no magic strings**. + - `PK.USER`, `PK.ORGANIZATION`, `PK.PROJECT`, etc. (defined in `apps/functions/constants/`) +- **SK (Sort Key)**: Composite key with `#` separator, built via helper functions. + - Pattern: `{orgId}#{projectId}#{entityId}` (empty segments are omitted) + - Use helper functions — never construct SK strings manually. +- **Multitenancy**: All entities support optional `orgId` as the first SK segment. + - `orgId` scopes data to an organization. When empty, data is global. + - Example: `PK = PK.USER`, `SK = "org123#proj456#user789"` + - Query by org: `skPrefix = "org123"`, by org+project: `skPrefix = "org123#proj456"` +- Each entity has key builder functions in their respective function handlers. +- GSI1 can be used for access patterns that reverse PK/SK. +- All DynamoDB operations go through helper functions in `apps/functions/helpers/`. +- All services accept `orgId` as a parameter (can be undefined for global scope). + +--- + +## 👤 User Management + +- **Users MUST be created in both DynamoDB AND Cognito.** +- When creating a user: + 1. Create the user in Cognito (via `@aws-sdk/client-cognito-identity-provider`) + 2. Store the user record in DynamoDB with the Cognito `sub` as the user ID +- User deletion should clean up both Cognito and DynamoDB. + +--- + +## 🌐 Frontend Deployment + +- **Frontend is deployed via AWS Amplify Hosting** (not S3 + CloudFront). +- The CDK stack uses `@aws-cdk/aws-amplify-alpha` to define the Amplify app. +- The built `apps/web/dist` is deployed as an S3 asset to an Amplify branch. + +--- + +## 🏗️ Infrastructure (CDK) + +- All infrastructure is defined in `packages/infra/lib/`. +- Stacks are organized by concern: + - `api/` — API Gateway + Lambda function definitions + - `database-stack.ts` — DynamoDB table + GSIs + - `auth-stack.ts` — Cognito User Pool + Client + - `amplify-fe-stack.ts` — Amplify Hosting for frontend + - `storage-stack.ts` — S3 buckets for file storage + - `network-stack.ts` — VPC and networking resources +- Stack outputs are used to pass values between stacks (e.g., table name, user pool ID). +- Environment variables are passed to Lambda functions for resource references. +- Multi-stage support via environment-specific configurations. + +--- + +## 🌐 Frontend Architecture (`apps/web`) — Next.js App Router + DDD + +### Framework & Structure + +- **Framework**: Next.js 15+ with App Router +- **Path aliases**: Use `@/*` for all imports (e.g., `import { UserList } from '@/components/users/UserList'`) +- **Route groups**: `(auth)` and `(dashboard)` use different layouts without affecting URL paths +- **Auth guard**: Dashboard layout redirects to `/login` if not authenticated + +### Component Architecture + +- **Server vs Client Components**: + - Root `layout.tsx` is a Server Component (defines metadata, wraps with Providers) + - All interactive components use `'use client'` directive + - `Providers.tsx` wraps the app with SWR config and Amplify initialization +- **Feature modules** (Feature-Sliced Design): Each domain has its own directory with clear subdirectories: + ``` + features/ + ├── users/ + │ ├── components/ # Presentation-only components + │ │ └── UserList.tsx + │ ├── hooks/ # Feature-specific logic hooks + │ │ ├── useCreateUser.ts + │ │ └── useEditUser.ts + │ └── index.ts # Barrel export + ``` + - **Components must be pure presentation** — no business logic, API calls, or routing + - **Logic lives in feature hooks** in the `hooks/` subdirectory + - **Barrel exports** (`index.ts`) — pages import from `@/features/users`, never from internal paths + +### Pages & Routing + +- **Create/Edit pages MUST be separate pages** — never inline forms in list pages or use dialogs/modals + - Create: `/users/create` → `app/(dashboard)/users/create/page.tsx` + - Edit: `/users/[id]/edit` → `app/(dashboard)/users/[id]/edit/page.tsx` + - List pages link to create/edit pages via `` with breadcrumb navigation + +### Data Fetching & State + +- **Data fetching**: Use **SWR** with `authenticatedFetcher` for all client-side API calls + - `useApi(path)` — Generic hook for GET requests with caching + - `apiMutate(path, options)` — Helper for POST/PUT/DELETE +- **Authentication**: Use **AWS Amplify** (`aws-amplify`) to authenticate with Cognito + - `useAuth()` hook provides `signIn`, `signOut`, `isAuthenticated`, `username` + - JWT tokens are automatically attached to API requests via `authenticatedFetcher` +- **Health check**: `useHealth()` hook polls `/health` every 30s + - `HealthBanner` component shows an error banner when the API is unreachable +- **API Response Types**: All response types (`UsersResponse`, `UserResponse`, etc.) are defined in `@auto-rfp/core` — never define inline interfaces in components + +### Forms + +- Use **react-hook-form** with `@hookform/resolvers/zod` and Zod schemas from `@auto-rfp/core` +- Use `z.input` as the form type (handles `.default()` fields correctly) +- Use `zodResolver(Schema)` for validation +- No manual `useState` for form fields — use `register()` from react-hook-form + +### UI & Styling + +- **Styling**: Use **Tailwind CSS v4** — no raw CSS files. All styling via utility classes + - Custom theme tokens defined in `globals.css` via `@theme` directive + - Indigo (`indigo-500`) as primary color, Slate for neutrals, Emerald for success +- **UI Components**: Use **Shadcn UI** components from `@/components/ui/` + - Components: `Button`, `Input`, `Select`, `Card`, `Badge`, `PageHeader`, `Breadcrumb`, etc. + - **Never use raw HTML elements** for buttons, inputs, cards, etc. — always use the UI components + - To swap the underlying component library, only change the `components/ui/` implementations + +### Loading States + +- **ALWAYS use skeleton components for loading states** — never use spinners or "Loading..." text +- **Page-level loading**: Use `PageLoadingSkeleton` from `@/components/layout/page-loading-skeleton` + - Create `loading.tsx` files in route directories that render appropriate skeleton components + - Skeleton variants: `list`, `grid`, `detail` — choose based on the content being loaded + - Example: `` for detail pages +- **Component-level loading**: Use `Skeleton` from `@/components/ui/skeleton` for inline loading states + +### Environment Variables + +- Use `NEXT_PUBLIC_` prefix for client-side env vars + +--- + +## 🚀 CI/CD (GitHub Actions) + +- **Branching strategy**: + - `develop` — Development branch (deploys to **dev** environment) + - `main` — Test branch (deploys to **test** environment) + - Feature branches → PR to `develop` + - `develop` → PR to `main` for promotion to test +- **Workflows** (`.github/workflows/`): + - `ci.yml` — Runs on every push/PR to `develop` and `main`: install → build → test → upload artifacts + - `deploy-dev.yml` — Triggered on push to `develop`: builds and deploys all CDK stacks with `-c stage=dev` + - `deploy-test.yml` — Triggered on push to `main`: builds and deploys all CDK stacks with `-c stage=test` +- **AWS authentication**: Uses OIDC (`id-token: write`) with `aws-actions/configure-aws-credentials@v4`. + - Requires `AWS_ROLE_ARN` secret and optional `AWS_REGION` variable per GitHub environment. +- **GitHub Environments**: `dev` and `test` environments should be configured in repo settings with appropriate secrets. +- **Concurrency**: CI jobs cancel in-progress runs; deploy jobs do NOT cancel (to avoid partial deployments). +- **Caching**: pnpm store is cached between runs for faster installs. + +--- + +## 🔧 General Conventions + +- Use ESM (`"type": "module"`) everywhere. +- Target Node.js 20+ for Lambda runtime. +- Use `pnpm` as the package manager with workspaces. +- Prefer `const` over `let`; never use `var`. +- Use TypeScript strict mode in all packages. +- Destructure where possible for cleaner code. +- **Never use `.js` extensions in import paths.** Use `moduleResolution: "bundler"` in tsconfig. + +--- + +## 🔒 Audit Trail + +**Every new handler, service, or AI feature MUST write audit log entries for all significant actions.** +Never ship a new feature without audit coverage. Audit logs are required for security compliance (FedRAMP, ISO 27001), debugging, and data access tracking. + +### When to Write Audit Logs + +Write an audit log entry for **every** action that: +- Creates, updates, or deletes a resource (CRUD) +- Triggers an AI generation (document, brief, answer) +- Invokes an AI tool (DynamoDB query, semantic search, etc.) +- Changes permissions or configuration +- Accesses sensitive data (org details, user info, contact data) +- Starts or completes a pipeline or background job +- Results in a failure or error that affects a user + +### How to Write Audit Logs + +Use `writeAuditLog()` from `apps/functions/src/helpers/audit-log.ts`: + +```typescript +import { writeAuditLog } from '@/helpers/audit-log'; +import { getHmacSecret } from '@/helpers/secret'; +import { v4 as uuidv4 } from 'uuid'; +import { nowIso } from '@/helpers/date'; + +await writeAuditLog( + { + logId: uuidv4(), + timestamp: nowIso(), + userId: event.auth?.userId ?? 'system', + userName: event.auth?.userName ?? 'system', + organizationId: orgId, + action: 'DOCUMENT_CREATED', + resource: 'document', + resourceId: documentId, + changes: { after: { documentType, title } }, + ipAddress: event.requestContext?.http?.sourceIp ?? '0.0.0.0', + userAgent: event.headers?.['user-agent'] ?? 'system', + result: 'success', + }, + await getHmacSecret(), +); +``` + +- **Background workers** (SQS, Step Functions): use `userId: 'system'`, `ipAddress: '0.0.0.0'`, `userAgent: 'system'` +- **High-frequency events** (e.g., AI tool calls): use non-blocking `.catch()` pattern — never `await` for non-critical audit writes +- **New audit actions**: add to `AuditActionSchema` in `packages/core/src/schemas/audit.ts` before using +- **Do NOT log PII or secrets** in `changes` — omit passwords, tokens, truncate large text fields (max 500 chars) +- **Always log failures** — emit `*_FAILED` action on errors, not just success + +See [10-audit-trail.md](10-audit-trail.md) for full details and examples. + +--- + +## 🎯 TypeScript Best Practices + +- **NEVER use `any` type.** Always use proper types, `unknown`, or type assertions when absolutely necessary. + - If you need to cast, use specific type assertions (e.g., `as DocumentDBItem`) instead of `as any`. + - Use `unknown` for truly unknown types and narrow them with type guards. +- **NEVER define types manually without Zod schemas.** + - All types MUST be inferred from Zod schemas using `z.infer`. + - Exception: Infrastructure-specific types like `DocumentDBItem` that extend core types with DynamoDB keys. + - This ensures runtime validation matches compile-time types. +- **Use type guards** for runtime type checking instead of type assertions when possible. +- **Prefer interfaces over types** for object shapes (except when inferring from Zod). +- **Use discriminated unions** for complex type scenarios instead of `any` or loose types. diff --git a/.clinerules/cost-optimization.md b/.clinerules/cost-optimization.md new file mode 100644 index 00000000..64cdf2f0 --- /dev/null +++ b/.clinerules/cost-optimization.md @@ -0,0 +1,54 @@ +When generating AWS CDK infrastructure, you MUST strictly optimize for minimal monthly cost. + +Follow these rules: + +1. Networking: +- NEVER create a NAT Gateway. +- NEVER create a VPC unless explicitly required. +- If VPC is required, do NOT attach Lambda to private subnets unless absolutely necessary. +- Prefer public services over VPC-attached services. + +2. Compute: +- Use AWS Lambda instead of EC2. +- Do NOT create EC2 instances. +- Do NOT create ECS, EKS, or Fargate unless explicitly requested. +- Set Lambda memory to 128MB by default unless higher is required. +- Set short timeouts (<= 10 seconds unless required). + +3. API Layer: +- Use API Gateway HTTP API (v2), NOT REST API. +- Avoid custom domain setup unless explicitly requested. + +4. Database: +- Use DynamoDB with billing mode PAY_PER_REQUEST. +- NEVER provision fixed capacity. +- NEVER create RDS unless explicitly required. +- NEVER create Aurora unless explicitly required. + +5. Storage: +- Use S3 with: + - Intelligent tiering OR + - Standard (no replication) +- Disable versioning unless explicitly required. +- Disable cross-region replication. + +6. Logs & Monitoring: +- Set CloudWatch log retention to 3–7 days. +- Do NOT enable detailed monitoring. +- Do NOT enable X-Ray unless explicitly requested. + +7. Scaling: +- Avoid provisioned concurrency. +- Avoid auto scaling groups. +- Avoid reserved capacity. + +8. Security: +- Use minimal IAM permissions (least privilege). +- Avoid complex networking that increases cost. + +9. Defaults: +- Assume low traffic (<100k requests/month). +- Optimize for <$5/month total infrastructure cost. +- Prefer serverless-first architecture. + +If a design choice increases fixed monthly cost, explain why and provide a cheaper alternative. \ No newline at end of file diff --git a/.clinerules/next-js.md b/.clinerules/next-js.md new file mode 100644 index 00000000..7d63cac3 --- /dev/null +++ b/.clinerules/next-js.md @@ -0,0 +1,123 @@ + +You are an expert developer proficient in TypeScript, React and Next.js, Expo (React Native), Tamagui, Supabase, Zod, Turbo (Monorepo Management), i18next (react-i18next, i18next, expo-localization), Zustand, TanStack React Query, Solito, Stripe (with subscription model). + +Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples. +- Use functional and declarative programming patterns; avoid classes. +- Prefer iteration and modularization over code duplication. +- Use descriptive variable names with auxiliary verbs (e.g., `isLoading`, `hasError`). +- Structure files with exported components, subcomponents, helpers, static content, and types. +- Favor named exports for components and functions. +- Use lowercase with dashes for directory names (e.g., `components/auth-wizard`). + +TypeScript and Zod Usage + +- Use TypeScript for all code; prefer interfaces over types for object shapes. +- Utilize Zod for schema validation and type inference. +- Avoid enums; use literal types or maps instead. +- Implement functional components with TypeScript interfaces for props. + +Syntax and Formatting + +- Use the `function` keyword for pure functions. +- Write declarative JSX with clear and readable structure. +- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements. + +UI and Styling + +- Use Tamagui for cross-platform UI components and styling. +- Implement responsive design with a mobile-first approach. +- Ensure styling consistency between web and native applications. +- Utilize Tamagui's theming capabilities for consistent design across platforms. + +State Management and Data Fetching + +- Use Zustand for state management. +- Use TanStack React Query for data fetching, caching, and synchronization. +- Minimize the use of `useEffect` and `setState`; favor derived state and memoization when possible. + +Internationalization + +- Use i18next and react-i18next for web applications. +- Use expo-localization for React Native apps. +- Ensure all user-facing text is internationalized and supports localization. + +Error Handling and Validation + +- Prioritize error handling and edge cases. +- Handle errors and edge cases at the beginning of functions. +- Use early returns for error conditions to avoid deep nesting. +- Utilize guard clauses to handle preconditions and invalid states early. +- Implement proper error logging and user-friendly error messages. +- Use custom error types or factories for consistent error handling. + +Performance Optimization + +- Optimize for both web and mobile performance. +- Use dynamic imports for code splitting in Next.js. +- Implement lazy loading for non-critical components. +- Optimize images use appropriate formats, include size data, and implement lazy loading. + +Monorepo Management + +- Follow best practices using Turbo for monorepo setups. +- Ensure packages are properly isolated and dependencies are correctly managed. +- Use shared configurations and scripts where appropriate. +- Utilize the workspace structure as defined in the root `package.json`. + +Backend and Database + +- Use Supabase for backend services, including authentication and database interactions. +- Follow Supabase guidelines for security and performance. +- Use Zod schemas to validate data exchanged with the backend. + +Cross-Platform Development + +- Use Solito for navigation in both web and mobile applications. +- Implement platform-specific code when necessary, using `.native.tsx` files for React Native-specific components. +- Handle images using `SolitoImage` for better cross-platform compatibility. + +Stripe Integration and Subscription Model + +- Implement Stripe for payment processing and subscription management. +- Use Stripe's Customer Portal for subscription management. +- Implement webhook handlers for Stripe events (e.g., subscription created, updated, or cancelled). +- Ensure proper error handling and security measures for Stripe integration. +- Sync subscription status with user data in Supabase. + +Testing and Quality Assurance + +- Write unit and integration tests for critical components. +- Use testing libraries compatible with React and React Native. +- Ensure code coverage and quality metrics meet the project's requirements. + +Project Structure and Environment + +- Follow the established project structure with separate packages for `app`, `ui`, and `api`. +- Use the `apps` directory for Next.js and Expo applications. +- Utilize the `packages` directory for shared code and components. +- Use `dotenv` for environment variable management. +- Follow patterns for environment-specific configurations in `eas.json` and `next.config.js`. +- Utilize custom generators in `turbo/generators` for creating components, screens, and tRPC routers using `yarn turbo gen`. + +Key Conventions + +- Use descriptive and meaningful commit messages. +- Ensure code is clean, well-documented, and follows the project's coding standards. +- Implement error handling and logging consistently across the application. + +Follow Official Documentation + +- Adhere to the official documentation for each technology used. +- For Next.js, focus on data fetching methods and routing conventions. +- Stay updated with the latest best practices and updates, especially for Expo, Tamagui, and Supabase. + +Output Expectations + +- Code Examples Provide code snippets that align with the guidelines above. +- Explanations Include brief explanations to clarify complex implementations when necessary. +- Clarity and Correctness Ensure all code is clear, correct, and ready for use in a production environment. +- Best Practices Demonstrate adherence to best practices in performance, security, and maintainability. + + \ No newline at end of file diff --git a/.clinerules/web-development.md b/.clinerules/web-development.md new file mode 100644 index 00000000..5ec2872e --- /dev/null +++ b/.clinerules/web-development.md @@ -0,0 +1,42 @@ + + You are an expert in Bootstrap and modern web application development. + + Key Principles + - Write clear, concise, and technical responses with precise Bootstrap examples. + - Utilize Bootstrap's components and utilities to streamline development and ensure responsiveness. + - Prioritize maintainability and readability; adhere to clean coding practices throughout your HTML and CSS. + - Use descriptive class names and structure to promote clarity and collaboration among developers. + + Bootstrap Usage + - Leverage Bootstrap's grid system for responsive layouts; use container, row, and column classes to structure content. + - Utilize Bootstrap components (e.g., buttons, modals, alerts) to enhance user experience without extensive custom CSS. + - Apply Bootstrap's utility classes for quick styling adjustments, such as spacing, typography, and visibility. + - Ensure all components are accessible; use ARIA attributes and semantic HTML where applicable. + + Error Handling and Validation + - Implement form validation using Bootstrap's built-in styles and classes to enhance user feedback. + - Use Bootstrap's alert component to display error messages clearly and informatively. + - Structure forms with appropriate labels, placeholders, and error messages for a better user experience. + + Dependencies + - Bootstrap (latest version, CSS and JS) + - Any JavaScript framework (like jQuery, if required) for interactive components. + + Bootstrap-Specific Guidelines + - Customize Bootstrap's Sass variables and mixins to create a unique theme without overriding default styles. + - Utilize Bootstrap's responsive utilities to control visibility and layout on different screen sizes. + - Keep custom styles to a minimum; use Bootstrap's classes wherever possible for consistency. + - Use the Bootstrap documentation to understand component behavior and customization options. + + Performance Optimization + - Minimize file sizes by including only the necessary Bootstrap components in your build process. + - Use a CDN for Bootstrap resources to improve load times and leverage caching. + - Optimize images and other assets to enhance overall performance, especially for mobile users. + + Key Conventions + 1. Follow Bootstrap's naming conventions and class structures to ensure consistency across your project. + 2. Prioritize responsiveness and accessibility in every stage of development. + 3. Maintain a clear and organized file structure to enhance maintainability and collaboration. + + Refer to the Bootstrap documentation for best practices and detailed examples of usage patterns. + \ No newline at end of file diff --git a/.clinerules/workflows/architecture.md b/.clinerules/workflows/architecture.md new file mode 100644 index 00000000..05a6ef8f --- /dev/null +++ b/.clinerules/workflows/architecture.md @@ -0,0 +1,220 @@ +# Architecture Workflow — Feature Documentation + +> This workflow describes the process for producing a complete, implementation-ready architecture document for a new feature ticket. +> Follow these steps in order every time a new feature needs to be designed and documented. + +--- + +## 🎯 Goal + +Produce a `docs/-IMPLEMENTATION.md` file that a developer can follow directly to implement the feature — with no ambiguity about file locations, data models, API contracts, or coding conventions. + +--- + +## 📋 Step-by-Step Process + +### Step 1 — Understand the Ticket + +- Read the full ticket: business context, features list, acceptance criteria, estimated hours. +- Identify the main domains involved (e.g. presence, comments, assignments, activity). +- Note any referenced sections (e.g. "Section 8 — Multi-tenancy"). + +--- + +### Step 2 — Explore the Existing Codebase + +Before writing anything, read the relevant existing files to understand patterns: + +| What to read | Why | +|---|---| +| `packages/core/src/schemas/*.ts` | Understand existing Zod schema patterns | +| `apps/functions/src/constants/common.ts` | PK_NAME, SK_NAME constants | +| `apps/functions/src/helpers/db.ts` | Available DynamoDB helpers (createItem, putItem, getItem, queryBySkPrefix, etc.) | +| `apps/functions/src/helpers/*.ts` | Domain helper patterns | +| `apps/functions/src/handlers//*.ts` | Thin Lambda handler patterns | +| `apps/functions/src/middleware/rbac-middleware.ts` | Auth middleware, AuthedEvent type, orgId sourcing | +| `packages/infra/api/routes/*.ts` | Route definition patterns | +| `packages/infra/api/api-orchestrator-stack.ts` | How routes are registered | +| `packages/infra/database-stack.ts` | DynamoDB table structure, GSIs, streams | +| `docs/IMPLEMENTATION-TICKETS.md` | Reference for ticket format | + +--- + +### Step 3 — Design the Data Model + +1. **Define Zod schemas** in `packages/core/src/schemas/.ts` + - Every entity schema (item, create DTO, update DTO, response) + - All types inferred from Zod — never defined manually + - Export from `packages/core/src/schemas/index.ts` + +2. **Design DynamoDB access patterns** + - Choose PK constants (add to `apps/functions/src/constants/.ts`) + - Design SK patterns: `{orgId}#{projectId}#{entityId}` etc. + - Document in a table: Entity | PK | SK | Notes + - Define TTL strategy if applicable + +3. **Write SK builder functions** in `apps/functions/src/helpers/.ts` + - One function per entity type + - Never construct SK strings manually in handlers + +4. **Write DynamoDB helper functions** in the same helpers file + - Wrap `createItem`, `putItem`, `getItem`, `deleteItem`, `queryBySkPrefix` from `@/helpers/db` + - One helper per operation (e.g. `createComment`, `listComments`, `upsertAssignment`) + - Handlers call helpers — never raw SDK commands + +--- + +### Step 4 — Design the API Surface + +1. **REST endpoints** — for each operation: + - Method + path + - Request shape (query params / body) + - Response shape + - Required permission + +2. **WebSocket endpoints** (if real-time) — document: + - Connection URL + query params + - Inbound message types (client → server) + - Outbound broadcast message types (server → client) + +--- + +### Step 5 — Write Lambda Handlers + +Follow the **thin Lambda** pattern for every handler: + +``` +parse event → validate with Zod (destructure safeParse) → call helper → return apiResponse +``` + +Rules to enforce in every handler: +- **No raw DynamoDB SDK** — use helpers from `@/helpers/db` or domain helpers +- **`orgId` from body / query param / path param** — never from `event.auth?.claims` or token + - POST/PUT/PATCH: `const orgId = data.orgId ?? event.queryStringParameters?.orgId` + - GET/DELETE: `const { orgId } = event.queryStringParameters ?? {}` +- **`safeParse` always destructured**: `const { success, data, error } = Schema.safeParse(raw)` +- **`apiResponse` for all REST responses** — never inline `{ statusCode, headers, body }` +- **WebSocket handlers** return plain `{ statusCode, body }` — `apiResponse` is REST-only +- **Middy middleware stack**: `authContextMiddleware → orgMembershipMiddleware → requirePermission → httpErrorMiddleware` + +--- + +### Step 6 — Design Infrastructure (CDK) + +- New Lambda functions → add to appropriate CDK stack +- New API routes → create `packages/infra/api/routes/.routes.ts` +- Register domain in `api-orchestrator-stack.ts` (`allDomains` array + `domainStackNames` array) +- WebSocket API → new `WebSocketStack` extending `cdk.Stack` +- Every Lambda → explicit `logs.LogGroup` with retention (2 weeks non-prod, INFINITE prod) +- New IAM permissions → add to shared Lambda role +- DynamoDB TTL → enable on `ttl` attribute if using auto-expiry + +--- + +### Step 7 — Design the Frontend + +Follow Feature-Sliced Design under `apps/web/features//`: + +``` +features// +├── lib/ # Singletons, clients, pure utilities +├── hooks/ # SWR data hooks + WebSocket hooks +├── components/ # Presentation-only React components +└── index.ts # Barrel export +``` + +Rules: +- **SWR** for all REST data fetching (`useSWR`, `useSWRInfinite`) +- **Skeleton components** for loading states — never spinners or "Loading..." +- **`authenticatedFetcher`** for all API calls +- **`'use client'`** on all interactive components and hooks +- **Types from `@auto-rfp/core`** — never define inline interfaces in components +- **Barrel exports** — pages import from `@/features/`, never from internal paths + +--- + +### Step 8 — Write the Document + +Create `docs/-IMPLEMENTATION.md` with these sections: + +1. **Overview** — feature summary table +2. **Architecture Overview** — ASCII diagram + technology decision table +3. **Data Models & Zod Schemas** — full schema file content +4. **DynamoDB Design** — PK constants, access pattern table, SK builders, DynamoDB helpers +5. **Backend — Lambda Handlers** — file structure tree + full handler code for each +6. **WebSocket Infrastructure (CDK)** — full CDK stack code (if applicable) +7. **REST API Routes** — routes file + registration snippet + endpoint summary table +8. **Frontend — Hooks & Components** — file structure tree + full code for each +9. **Permissions & RBAC** — new permissions + role matrix table +10. **Email Notifications** — async worker pattern (if applicable) +11. **CDK Stack Updates** — infrastructure summary table + IAM additions +12. **Implementation Tickets** — sprint breakdown with file lists and acceptance criteria +13. **Acceptance Criteria Checklist** — ready to copy into Linear/Jira +14. **Summary of New Files** — table of every new file and its purpose + +#### 📌 Implementation Status Markers + +Every section heading and every implementation ticket **must include a status badge** so developers can track progress at a glance directly in the document. + +**Section heading format** (add badge after the section title): + +```markdown +## 3. Data Models & Zod Schemas +## 3. Data Models & Zod Schemas +``` + +**Implementation ticket format** (add badge after the ticket title): + +```markdown +### AL-1 · Core Schemas (30 min) +### AL-1 · Core Schemas (30 min) +``` + +**Allowed status values**: + +| Badge | Meaning | +|---|---| +| `` | Not yet started — default for all new sections/tickets | +| `` | Currently being implemented | +| `` | Code written, TypeScript compiles, acceptance criteria met | +| `` | Intentionally skipped (add a reason comment inline) | + +**Rules**: +- Every section (1–14) and every ticket starts with `` when the document is first written. +- When a developer completes a ticket, they update the badge to `` in the doc. +- When all tickets in a section are `✅ IMPLEMENTED`, update the section heading badge too. +- The **Summary of New Files** table gains a `Status` column — each row starts as `⏳` and is updated to `✅` when the file is created and compiles. +- Never remove a badge — only update its value. + +--- + +### Step 9 — Review & Iterate + +After the initial document is written, review for: + +- [ ] All `safeParse` results destructured (no `parsed.success` / `parsed.data`) +- [ ] `orgId` sourced from body/query/path — not from token or `event.auth` +- [ ] All REST handlers use `apiResponse` — no raw response objects +- [ ] No raw DynamoDB SDK in handlers — all DB operations via helpers +- [ ] Entity references in comments use `entityPk`/`entitySk` (not entity-type-specific IDs) +- [ ] Comment system is entity-agnostic (`entityType` enum, not `questionId`) +- [ ] All types inferred from Zod — no manually defined types +- [ ] Every Lambda has a CloudWatch Log Group in CDK +- [ ] New permissions added to `packages/core/src/schemas/user.ts` + +--- + +### Step 10 — Update `.clinerules/` if New Patterns Emerge + +If the feature introduces a new convention not yet captured in the rules: + +1. Identify which rule file it belongs to (01–08) +2. Add the rule with a ✅ correct / ❌ wrong code example +3. Keep examples concise and actionable + +Common rules added during architecture sessions: +- `safeParse` destructuring → `04-backend-architecture.md` +- `orgId` sourcing → `04-backend-architecture.md` +- `apiResponse` usage → `04-backend-architecture.md` +- Entity-agnostic comment design → `03-entity-definitions.md` +- Skeleton loading states → `06-frontend-architecture.md` diff --git a/.clinerules/workflows/code-review.md b/.clinerules/workflows/code-review.md new file mode 100644 index 00000000..a6106e47 --- /dev/null +++ b/.clinerules/workflows/code-review.md @@ -0,0 +1,550 @@ +# Code Review Workflow — AI-Powered Code Audit + +> This workflow describes the process for performing a comprehensive code review of a feature, file, or directory. +> It produces a structured review report in `docs/reviews/` with categorized findings, severity levels, and actionable fixes. +> +> **Trigger**: Ask Cline/Claude to "review [feature/file/directory]" — e.g. "review the answer feature", "review apps/functions/src/handlers/clustering/", "review apps/web/components/brief/". + +--- + +## 🎯 Goal + +Produce a `docs/reviews/-review-YYYY-MM-DD.md` report that identifies problems, bad practices, weaknesses, and convention violations — with specific file:line references, severity ratings, and suggested fixes. + +--- + +## 📋 Step-by-Step Process + +### Step 1 — Identify the Review Scope + +Determine what is being reviewed based on the user's input: + +| Input Type | Example | Scope | +|---|---|---| +| Feature name | "review the answer feature" | All files in `apps/functions/src/handlers/answer/`, related helpers, constants, schemas, frontend components, and tests | +| Directory path | "review apps/functions/src/handlers/clustering/" | All files in that directory + related test files | +| File path | "review apps/web/components/brief/helpers.ts" | That specific file + its test file (if exists) | +| Schema name | "review the project schema" | `packages/core/src/schemas/project.ts` + all consumers | + +**For feature-level reviews**, gather ALL related files: + +``` +1. Schema: packages/core/src/schemas/.ts +2. Constants: apps/functions/src/constants/.ts +3. Helpers: apps/functions/src/helpers/.ts +4. Handlers: apps/functions/src/handlers//*.ts +5. Tests: apps/functions/src/handlers//*.test.ts +6. CDK routes: packages/infra/api/routes/.routes.ts +7. Frontend: apps/web/components// OR apps/web/features// +8. Pages: apps/web/app/**// (search for related pages) +9. Types: apps/functions/src/types/.ts +``` + +--- + +### Step 2 — Read All Project Rules + +Before reviewing, refresh context on ALL project conventions by reading: + +| File | Key Rules | +|---|---| +| `.clinerules/02-typescript-best-practices.md` | No `any`, Zod-inferred types, const arrow functions | +| `.clinerules/03-entity-definitions.md` | Schema patterns, DynamoDB item types | +| `.clinerules/04-backend-architecture.md` | Thin Lambda, safeParse destructuring, orgId sourcing, apiResponse | +| `.clinerules/05-dynamodb-design.md` | Single-table design, PK constants, SK builders | +| `.clinerules/06-frontend-architecture.md` | Feature-Sliced Design, SWR, Skeleton loading, Shadcn UI | +| `.clinerules/09-testing.md` | Test patterns, mock patterns, coverage requirements | +| `.clinerules/10-audit-trail.md` | Audit log requirements for every handler | +| `.clinerules/cost-optimization.md` | AWS cost optimization rules | + +--- + +### Step 3 — Perform the Review + +Read every file in scope and evaluate against the checklist categories below. For each finding, record: + +- **Category** (from the list below) +- **Severity** (🔴 CRITICAL / 🟠 HIGH / 🟡 MEDIUM / 🔵 LOW) +- **File path + line number** (or line range) +- **Description** of the problem +- **Suggested fix** with a code example (before → after) + +--- + +## 🔍 Review Categories & Checklists + +### Category 1: Type Safety + +| Check | Severity | What to look for | +|---|---|---| +| No `any` type usage | 🟠 HIGH | Search for `: any`, `as any`, ``, implicit `any` in callbacks | +| Types inferred from Zod | 🟠 HIGH | Manual `interface` or `type` definitions that should be `z.infer<>` | +| No `as Record` | 🟡 MEDIUM | Loose type assertions instead of proper types | +| No `as unknown as X` chains | 🟡 MEDIUM | Double-cast patterns hiding type errors | +| Proper type guards | 🟡 MEDIUM | Runtime type checking instead of blind assertions | +| Generic function types | 🔵 LOW | Functions missing return types or parameter types | +| Callback parameter types | 🔵 LOW | `.map((item) => ...)` without explicit type on `item` | + +### Category 2: Architecture Compliance (Backend) + +| Check | Severity | What to look for | +|---|---|---| +| Thin Lambda handlers | 🟠 HIGH | Business logic inside handler instead of helpers | +| `safeParse` destructured | 🟠 HIGH | `const parsed = Schema.safeParse(...)` instead of `const { success, data, error } = ...` | +| `orgId` from request, not token | 🔴 CRITICAL | `event.auth?.orgId`, `event.auth?.claims?.['custom:orgId']` | +| `apiResponse` for REST | 🟠 HIGH | Inline `{ statusCode, headers, body }` instead of `apiResponse()` | +| No raw DynamoDB SDK in handlers | 🟠 HIGH | Direct `DynamoDBClient`, `PutCommand`, `QueryCommand` imports in handlers | +| Middy middleware stack | 🟡 MEDIUM | Missing or incorrect middleware order | +| Sentry wrapper | 🟡 MEDIUM | Missing `withSentryLambda` on exported handler | +| Const arrow functions | 🟡 MEDIUM | `function` keyword instead of `const fn = () => {}` | + +### Category 3: DynamoDB Patterns + +| Check | Severity | What to look for | +|---|---|---| +| PK constants used | 🟠 HIGH | Magic strings like `'USER'` instead of `PK.USER` | +| SK built via helpers | 🟠 HIGH | Manual string concatenation for sort keys | +| No raw SDK in helpers | 🟡 MEDIUM | Direct SDK usage instead of `@/helpers/db` wrappers | +| Proper query patterns | 🟡 MEDIUM | Full table scans, missing `skPrefix` for queries | +| TTL strategy | 🔵 LOW | Missing TTL for ephemeral data (sessions, tokens, etc.) | + +### Category 4: Security + +| Check | Severity | What to look for | +|---|---|---| +| No secrets in code | 🔴 CRITICAL | Hardcoded API keys, passwords, tokens | +| No PII in logs | 🔴 CRITICAL | `console.log` with user emails, passwords, tokens | +| Input validation | 🔴 CRITICAL | Missing Zod validation on user input | +| Permission checks | 🟠 HIGH | Missing `requirePermission` middleware on protected routes | +| SQL/NoSQL injection | 🔴 CRITICAL | Unsanitized user input in DynamoDB expressions | +| CORS configuration | 🟡 MEDIUM | Overly permissive CORS settings | +| Error message leakage | 🟡 MEDIUM | Internal error details exposed to clients | + +### Category 5: Error Handling + +| Check | Severity | What to look for | +|---|---|---| +| Missing error cases | 🟠 HIGH | No handling for `!success` from safeParse | +| Swallowed errors | 🟠 HIGH | Empty `catch` blocks or `catch (e) {}` | +| Generic catch-all | 🟡 MEDIUM | `catch (error: any)` without proper error typing | +| Missing 404 handling | 🟡 MEDIUM | No check for `undefined` result from `getItem` | +| Unhandled promise rejections | 🟠 HIGH | Missing `await` or `.catch()` on promises | +| Error response format | 🟡 MEDIUM | Inconsistent error response shapes | + +### Category 6: Performance + +| Check | Severity | What to look for | +|---|---|---| +| N+1 query patterns | 🟠 HIGH | Querying DynamoDB in a loop instead of batch operations | +| Missing pagination | 🟡 MEDIUM | Returning all items without limit/pagination | +| Large payload responses | 🟡 MEDIUM | Returning entire entities when only a few fields are needed | +| Unnecessary re-renders | 🟡 MEDIUM | (Frontend) Missing `useMemo`, `useCallback` for expensive operations | +| Bundle size | 🔵 LOW | Importing entire libraries when only specific functions are needed | +| Lambda cold start | 🔵 LOW | Heavy imports at module level that could be lazy-loaded | + +### Category 7: Testing + +| Check | Severity | What to look for | +|---|---|---| +| Missing test file | 🟠 HIGH | Handler/helper/component without a corresponding `.test.ts` file | +| Happy path only | 🟡 MEDIUM | Tests only cover success cases, no error/edge cases | +| Missing mock resets | 🟡 MEDIUM | No `jest.clearAllMocks()` in `beforeEach` | +| Testing middy wrapper | 🟡 MEDIUM | Testing `handler` instead of the exported business function | +| Hardcoded dates | 🔵 LOW | Exact date assertions instead of `expect.any(String)` | +| Missing validation tests | 🟡 MEDIUM | No tests for invalid input / Zod validation failures | +| Missing guard clause tests | 🟡 MEDIUM | No tests for business rule enforcement | + +### Category 8: Audit Trail + +| Check | Severity | What to look for | +|---|---|---| +| Missing audit log for CRUD | 🟠 HIGH | Create/Update/Delete handler without `writeAuditLog` | +| Missing audit log for AI ops | 🟠 HIGH | AI generation/tool call without audit logging | +| Blocking audit in hot path | 🟡 MEDIUM | `await writeAuditLog(...)` in high-frequency operations | +| Missing failure audit | 🟡 MEDIUM | Only logging success, not `*_FAILED` actions | +| PII in audit changes | 🔴 CRITICAL | Passwords, tokens, or large text in `changes` field | +| Missing audit action in schema | 🟡 MEDIUM | Using action not defined in `AuditActionSchema` | + +### Category 9: Frontend Patterns + +| Check | Severity | What to look for | +|---|---|---| +| Spinner/Loading text | 🟠 HIGH | "Loading..." text or spinner components instead of `` | +| Raw HTML elements | 🟡 MEDIUM | ` - - - - - -
- {/* Quick Start */} -
-

- - Quick Start Guide -

- -
- - -
-
- -
- Set Up Organization -
-
- -

- Create your organization and invite team members with different roles. -

- 5 minutes -
-
- - - -
-
- -
- Upload Documents -
-
- -

- Upload your RFP documents and knowledge base files for AI processing. -

- 2 minutes -
-
- - - -
-
- -
- Generate Responses -
-
- -

- Let AI extract questions and generate professional responses automatically. -

- 1 minute -
-
-
-
- - {/* Detailed Workflow */} -
-

- - Complete Workflow -

- -
- {/* Step 1: Organization Setup */} - - - - - Organization & Team Management - - - Set up your workspace and collaborate with your team - - - -
-
-

Creating an Organization

-
    -
  • - - Click "Create Organization" on the dashboard -
  • -
  • - - Enter organization name and description -
  • -
  • - - LlamaCloud auto-connects if available -
  • -
-
-
-

Team Collaboration (coming soon)

-
    -
  • - - Invite members via email in Team settings -
  • -
  • - - Assign roles: Owner, Admin, or Member -
  • -
  • - - Manage permissions and access control -
  • -
-
-
-
-
- - {/* Step 2: Project Creation */} - - - - - Project Management - - - Organize your RFPs into manageable projects - - - -
-
-

Creating Projects

-
    -
  • - - Navigate to your organization dashboard -
  • -
  • - - Click "Create Project" button -
  • -
  • - - Add project name and description -
  • -
-
-
-

Best Practices

-
    -
  • - - One project per RFP or client -
  • -
  • - - Use descriptive names for easy identification -
  • -
  • - - Add detailed descriptions for team clarity -
  • -
-
-
-
-
- - {/* Step 3: Document Upload */} - - - - - Document Upload & Processing - - - Upload RFP documents and build your knowledge base - - - -
-
-

Supported File Types

-
    -
  • - - PDF documents (.pdf) -
  • -
  • - - Word documents (.docx, .doc) -
  • -
  • - - Excel spreadsheets (.xlsx, .xls) -
  • -
  • - - PowerPoint presentations (.pptx, .ppt) -
  • -
-
-
-

Upload Process

-
    -
  • - - Drag & drop files or click to browse -
  • -
  • - - AI automatically processes documents -
  • -
  • - - Questions are extracted and organized -
  • -
-
-
- -
-

- - Try with Sample Document -

-

- New to the platform? Download our sample RFP document to test the features: -

- - RFP - Launch Services for Medium-Lift Payloads.pdf → - -
-
-
- - {/* Step 4: AI Response Generation */} - - - - - AI Response Generation - - - Generate professional responses using advanced AI - - - -
-
-

Response Types

-
    -
  • - - Quick Response: Fast, direct answers -
  • -
  • - - Multi-Step: Detailed analysis process -
  • -
  • - - Custom: Edit and refine responses -
  • -
-
-
-

AI Features

-
    -
  • - - Source attribution and relevance scoring -
  • -
  • - - Context-aware responses from your docs -
  • -
  • - - Professional RFP response formatting -
  • -
-
-
- -
-

Multi-Step Response Process

-
-
- 1 - Analyze question requirements and complexity -
-
- 2 - Search through your document knowledge base -
-
- 3 - Extract and synthesize relevant information -
-
- 4 - Structure professional RFP response -
-
- 5 - Validate completeness and accuracy -
-
-
-
-
-
-
- - {/* Tips & Best Practices */} -
-

- - Tips & Best Practices -

- -
- - - Document Organization - - -
- -
-

Upload comprehensive docs

-

Include company overviews, technical specs, and past responses

-
-
-
- -
-

Keep documents updated

-

Regularly refresh your knowledge base with latest information

-
-
-
- -
-

Use clear file names

-

Descriptive names help AI find relevant content faster

-
-
-
-
- - - - Response Quality - - -
- -
-

Review AI responses

-

Always review and customize responses before submission

-
-
-
- -
-

Check source citations

-

Verify that sources are relevant and up-to-date

-
-
-
- -
-

Use multi-step for complex questions

-

Get detailed analysis for technical or multi-part questions

-
-
-
-
-
-
- - {/* Settings & Configuration */} -
-

- - Settings & Configuration -

- - - - Organization Settings - - Configure your organization's AI and integration settings - - - -
-
-

LlamaCloud Integration

-
    -
  • • Connects automatically when creating organizations
  • -
  • • Enables advanced document indexing and search
  • -
  • • Improves AI response accuracy and relevance
  • -
  • • View connection status in settings
  • -
-
-
-

Team Management

-
    -
  • • Invite team members via email
  • -
  • • Set appropriate roles and permissions
  • -
  • • Manage access to projects and documents
  • -
  • • Monitor team activity and usage
  • -
-
-
-
-
-
- - {/* Support */} -
-

Need More Help?

- -
- - - Sample Documents - - -

- Try the platform with our sample RFP documents -

- -
-
- - - - Support - - -

- Get help with issues or feature requests -

- -
-
-
-
-
- - ); -} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx deleted file mode 100644 index be253e5d..00000000 --- a/app/layout.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; -import { Toaster } from "@/components/ui/toaster"; -import { GlobalHeader } from "@/components/global/global-header"; -import { Providers } from "@/providers/providers"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata: Metadata = { - title: "AutoRFP - AI-Powered RFP Response Solution", - description: "Automatically answer RFP questions with AI document agents powered by LlamaIndex", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - - -
- - {children} -
- -
- - - ); -} diff --git a/app/login/actions.ts b/app/login/actions.ts deleted file mode 100644 index 7d2f882b..00000000 --- a/app/login/actions.ts +++ /dev/null @@ -1,55 +0,0 @@ -'use server' - -import { revalidatePath } from 'next/cache' -import { redirect } from 'next/navigation' - -import { createClient } from '@/lib/utils/supabase/server' - -export async function signInWithMagicLink(formData: FormData) { - const supabase = await createClient() - - // Get email from form data - const email = formData.get('email') as string - - // Validate email - if (!email || !email.includes('@')) { - // In a real app, you'd want to return an error message - redirect('/error') - } - - // Get the origin for creating the full redirect URL - // In production, you should set NEXT_PUBLIC_APP_URL in your environment variables - const origin = process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000' - - const { error } = await supabase.auth.signInWithOtp({ - email, - options: { - emailRedirectTo: `${origin}/auth/callback`, // Redirect to our auth callback handler - }, - }) - - if (error) { - redirect('/error') - } - - // Redirect to a confirmation page - redirect('/login/confirmation') -} - -// Keep this for backward compatibility if needed, but it won't be used in the new flow -export async function login(formData: FormData) { - redirect('/login/confirmation') -} - -// Keep this for backward compatibility if needed, but it won't be used in the new flow -export async function signup(formData: FormData) { - redirect('/login/confirmation') -} - -export async function logout() { - const supabase = await createClient() - await supabase.auth.signOut() - - revalidatePath('/', 'layout') - redirect('/login') -} \ No newline at end of file diff --git a/app/login/confirmation/page.tsx b/app/login/confirmation/page.tsx deleted file mode 100644 index 1a07b097..00000000 --- a/app/login/confirmation/page.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export default function ConfirmationPage() { - return ( -
-

Check your email

-
-

- We've sent you a magic link to your email address. Click the link in the email to sign in. -

-

- If you don't see the email, check your spam folder. The link will expire after 24 hours. -

-
-
- ) -} \ No newline at end of file diff --git a/app/login/page.tsx b/app/login/page.tsx deleted file mode 100644 index 9e33162f..00000000 --- a/app/login/page.tsx +++ /dev/null @@ -1,85 +0,0 @@ -'use client'; - -import { signInWithMagicLink } from '@/app/login/actions' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Github, Lock, Mail } from 'lucide-react' -import Link from 'next/link' - -export default function LoginPage() { - return ( -
- {/* Left Column: Login Form */} -
-
-
-

Welcome back

-

Sign in to your account or create a new one.

-
- -
-
- - - - -
-
- -
- -
-
- -
- -
-
- -

- We'll email you a magic link for a password-free sign in. -

- -
-
-
-
- - {/* Right Column: Quote */} -
- {/*
-
- " -

- Now things are starting to get interesting! Firebase has long been the obvious choice for many #flutter devs for the ease of use. But their databases are NoSQL, which has its downsides... Seems like @supabase is working on something interesting here! -

- " -
-
-
- RB -
-

@RobertBrunhage

-
-
*/} -
-
- ) -} \ No newline at end of file diff --git a/app/logout/page.tsx b/app/logout/page.tsx deleted file mode 100644 index ddf89222..00000000 --- a/app/logout/page.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { logout } from '@/app/login/actions' - -export default function LogoutPage() { - return ( -
-

Log out

-

Are you sure you want to log out?

-
- -
-
- ) -} \ No newline at end of file diff --git a/app/organizations/[orgId]/layout.tsx b/app/organizations/[orgId]/layout.tsx deleted file mode 100644 index 9f1a5ad1..00000000 --- a/app/organizations/[orgId]/layout.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { SidebarLayout } from "@/layouts/sidebar-layout/sidebar-layout"; - -export default function OrganizationsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - {children} - - ); -} \ No newline at end of file diff --git a/app/organizations/[orgId]/new-project/page.tsx b/app/organizations/[orgId]/new-project/page.tsx deleted file mode 100644 index 7c53c6ce..00000000 --- a/app/organizations/[orgId]/new-project/page.tsx +++ /dev/null @@ -1,173 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; -import { Input } from "@/components/ui/input"; -import { Textarea } from "@/components/ui/textarea"; -import { Spinner } from "@/components/ui/spinner"; -import { useToast } from "@/components/ui/use-toast"; -import { ArrowLeft } from "lucide-react"; - -interface NewProjectPageProps { - params: Promise<{ orgId: string }>; -} - -export default function NewProjectPage({ params }: NewProjectPageProps) { - const unwrappedParams = React.use(params); - const { orgId } = unwrappedParams; - const router = useRouter(); - const { toast } = useToast(); - - const [projectName, setProjectName] = useState(""); - const [projectDescription, setProjectDescription] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!projectName.trim()) { - toast({ - title: "Error", - description: "Project name is required", - variant: "destructive", - }); - return; - } - - setIsSubmitting(true); - - try { - const response = await fetch("/api/projects", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: projectName, - description: projectDescription, - organizationId: orgId, - }), - }); - - if (!response.ok) { - const errorData = await response.json(); - throw new Error(errorData.error || "Failed to create project"); - } - - const newProject = await response.json(); - - toast({ - title: "Success", - description: "Project created successfully", - }); - - // Redirect to the project page - router.push(`/project/${newProject.id}?orgId=${orgId}`); - } catch (error) { - console.error("Error creating project:", error); - toast({ - title: "Error", - description: error instanceof Error ? error.message : "Failed to create project", - variant: "destructive", - }); - } finally { - setIsSubmitting(false); - } - }; - - const handleCancel = () => { - router.push(`/org/${orgId}`); - }; - - return ( -
-
-
-
- -
- -
-

Create a new project

-

Add details to create your new RFP project

-
- -
-
- - - Project details - - Enter the information for your new project - - - -
- - setProjectName(e.target.value)} - placeholder="Enter project name" - required - className="h-11" - /> -
- -
- -