From 10dd0e2af2c514666868c0ffa1a526df1a4a2191 Mon Sep 17 00:00:00 2001 From: Eunjae Lee Date: Tue, 3 Feb 2026 16:40:04 +0100 Subject: [PATCH] feat: add GitHub workflows to sync agents/ to Devin Knowledge (#26994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add GitHub workflows to sync agents/ to Devin Knowledge - Add parse-to-devin-knowledge.ts to convert agents/ markdown to Devin Knowledge JSON - Add validate-format.ts to validate rules have frontmatter and knowledge-base sections start with 'When...' - Add sync-to-devin.ts to sync knowledge entries to Devin API - Add export-devin-knowledge.sh to backup existing Devin knowledge - Add validate-agents-format.yml workflow to validate format on PRs - Add sync-agents-to-devin.yml workflow to sync on merge to main - Add devin-knowledge.json to .gitignore (generated file) Co-Authored-By: eunjae@cal.com * fix: use tsx instead of ts-node for better ESM support in CI Co-Authored-By: eunjae@cal.com * docs: add missing knowledge entries from Devin backup Co-Authored-By: eunjae@cal.com * refactor: move agents scripts to scripts/ folder Co-Authored-By: eunjae@cal.com * refactor: rename scripts to devin-knowledge-* for clarity Co-Authored-By: eunjae@cal.com * refactor: move scripts to scripts/devin/ with clearer names Co-Authored-By: eunjae@cal.com * rename DEVIN_API_TOKEN to DEVIN_API_KEY * docs: fix usage comment script path Co-Authored-By: eunjae@cal.com * fix: remove folder creation from sync script (API doesn't support it) Co-Authored-By: eunjae@cal.com * fix: add -S flag to shebang for proper env execution Co-Authored-By: eunjae@cal.com * use pull_request_target * fix: add -S flag to shebang in sync-knowledge-to-devin.ts for proper env execution Co-Authored-By: unknown <> * restructure workflows * refactor: consolidate agent docs and split knowledge-base into modular rules - Delete knowledge-base.md, migrate content to 17 new rule files - Delete coding-standards.md (content duplicated in AGENTS.md and rules) - Add ci- and reference- prefixes to rules/_sections.md - Update AGENTS.md to reference new rule files - Update agents/README.md as rules index - Clean up parse-local-knowledge.ts (remove deleted file references) New rule files: - testing-playwright, testing-mocking, testing-timezone - ci-check-failures, ci-type-check-first, ci-git-workflow - data-prisma-migrations, data-prisma-feature-flags - quality-error-handling, quality-imports, quality-pr-creation, quality-code-comments - architecture-features-modules - patterns-workflow-triggers, patterns-app-store - reference-file-locations, reference-local-dev Co-Authored-By: Claude Opus 4.5 * Revert "refactor: consolidate agent docs and split knowledge-base into modular rules" This reverts commit 8251b6b214c7c01a3bfe2137c6aa9292dd72427e. * refactor: reorganize agent docs - extract coding rules, keep domain knowledge - Slim down knowledge-base.md (356 → 96 lines) to domain knowledge only - Add Business rules section (managed events, orgs/teams, OAuth clients) - Delete coding-standards.md (content moved to rules) - Create 19 new rule files for coding guidelines: - quality-*: PR creation, error handling, imports, comments, code review - testing-*: playwright, mocking, timezone, incremental - ci-*: check failures, type-check-first, git workflow - data-prisma-*: migrations, feature flags - patterns-*: workflow triggers, app store - architecture-features-modules, reference-file-locations, reference-local-dev - Update agents/README.md as rules index (43 total rules) - Update _sections.md with CI/CD and Reference sections - Clean up parse-local-knowledge.ts Co-Authored-By: Claude Opus 4.5 * fix: rename knowledge-base sections to start with "When..." Update section headers to pass validation rules: - "Business Rules" → "When working with managed events, organizations, or OAuth clients" - "Product & Codebase Knowledge" → "When you need product or codebase context" Co-Authored-By: Claude Opus 4.5 * refactor: split knowledge-base business rules into separate sections Split the combined "When working with managed events, organizations, or OAuth clients" section into three distinct ## sections for better Devin triggering: - When working with managed event types - When working with organizations and teams - When working with OAuth clients Co-Authored-By: Claude Opus 4.5 * refactor: simplify trigger description logic in parse-local-knowledge.ts Since validate-local-knowledge.ts enforces that all section titles must start with "When...", we can remove the manual fallback logic and just use the title directly as the trigger description. Co-Authored-By: Claude Opus 4.5 * refactor: simplify knowledge-base section validation Simplify the validation to only check that section titles start with "When..." since we've standardized on that pattern. Remove the special cases for error, file naming, PR, and repo note sections. Co-Authored-By: Claude Opus 4.5 * feat: add script to delete all Devin knowledge entries Add delete-all-devin-knowledge.ts script that: - Lists all knowledge entries before deletion - Requires interactive confirmation (Y) to proceed - Blocks execution in non-TTY environments (CI, piped input) - Shows progress while deleting entries Co-Authored-By: Claude Opus 4.5 * docs: add API reference links to delete-all-devin-knowledge script Co-Authored-By: Claude Opus 4.5 * fix: replace NOW() with CURRENT_TIMESTAMP in feature flag migration example Co-Authored-By: unknown <> * add -S * docs: consolidate agent documentation and add AI setup guide - Remove duplicated Project Structure & Tech Stack from agents/README.md - Condense Commands section in AGENTS.md, link to agents/commands.md - Add AI-Assisted Development section to root README.md explaining the agents/ folder structure and symlink configuration Co-Authored-By: Claude Opus 4.5 * docs: remove incorrect CLAUDE.md symlink claim from README Co-Authored-By: unknown <> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 --- .github/workflows/pr.yml | 15 + .github/workflows/sync-agents-to-devin.yml | 30 ++ .github/workflows/validate-agents-format.yml | 20 + .gitignore | 1 + AGENTS.md | 79 +--- README.md | 44 +++ agents/README.md | 120 +++--- agents/coding-standards.md | 62 ---- agents/knowledge-base.md | 349 +++--------------- agents/rules/_sections.md | 10 + agents/rules/architecture-features-modules.md | 54 +++ agents/rules/ci-check-failures.md | 33 ++ agents/rules/ci-git-workflow.md | 37 ++ agents/rules/ci-type-check-first.md | 43 +++ agents/rules/data-prisma-feature-flags.md | 40 ++ agents/rules/data-prisma-migrations.md | 52 +++ agents/rules/patterns-app-store.md | 31 ++ agents/rules/patterns-workflow-triggers.md | 39 ++ agents/rules/quality-code-comments.md | 48 +++ agents/rules/quality-code-review.md | 36 ++ agents/rules/quality-error-handling.md | 52 +++ agents/rules/quality-imports.md | 49 +++ agents/rules/quality-pr-creation.md | 36 ++ agents/rules/reference-file-locations.md | 50 +++ agents/rules/reference-local-dev.md | 73 ++++ agents/rules/testing-incremental.md | 27 ++ agents/rules/testing-mocking.md | 26 ++ agents/rules/testing-playwright.md | 32 ++ agents/rules/testing-timezone.md | 37 ++ scripts/devin/delete-all-devin-knowledge.ts | 131 +++++++ scripts/devin/export-devin-knowledge.ts | 82 ++++ scripts/devin/parse-local-knowledge.ts | 203 ++++++++++ scripts/devin/sync-knowledge-to-devin.ts | 202 ++++++++++ scripts/devin/validate-local-knowledge.ts | 165 +++++++++ 34 files changed, 1823 insertions(+), 485 deletions(-) create mode 100644 .github/workflows/sync-agents-to-devin.yml create mode 100644 .github/workflows/validate-agents-format.yml delete mode 100644 agents/coding-standards.md create mode 100644 agents/rules/architecture-features-modules.md create mode 100644 agents/rules/ci-check-failures.md create mode 100644 agents/rules/ci-git-workflow.md create mode 100644 agents/rules/ci-type-check-first.md create mode 100644 agents/rules/data-prisma-feature-flags.md create mode 100644 agents/rules/data-prisma-migrations.md create mode 100644 agents/rules/patterns-app-store.md create mode 100644 agents/rules/patterns-workflow-triggers.md create mode 100644 agents/rules/quality-code-comments.md create mode 100644 agents/rules/quality-code-review.md create mode 100644 agents/rules/quality-error-handling.md create mode 100644 agents/rules/quality-imports.md create mode 100644 agents/rules/quality-pr-creation.md create mode 100644 agents/rules/reference-file-locations.md create mode 100644 agents/rules/reference-local-dev.md create mode 100644 agents/rules/testing-incremental.md create mode 100644 agents/rules/testing-mocking.md create mode 100644 agents/rules/testing-playwright.md create mode 100644 agents/rules/testing-timezone.md create mode 100644 scripts/devin/delete-all-devin-knowledge.ts create mode 100644 scripts/devin/export-devin-knowledge.ts create mode 100644 scripts/devin/parse-local-knowledge.ts create mode 100644 scripts/devin/sync-knowledge-to-devin.ts create mode 100644 scripts/devin/validate-local-knowledge.ts diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e6713a91ca..e6ddda105e 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -170,6 +170,7 @@ jobs: has-companion: ${{ steps.filter-inclusions.outputs.has-companion }} has-api-v2-changes: ${{ steps.filter-inclusions.outputs.has-api-v2-changes }} has-prisma-changes: ${{ steps.filter-inclusions.outputs.has-prisma-changes }} + has-agents-changes: ${{ steps.filter-inclusions.outputs.has-agents-changes }} commit-sha: ${{ steps.get_sha.outputs.commit-sha }} run-e2e: ${{ steps.check-if-pr-has-label.outputs.run-e2e == 'true' }} db-cache-hit: ${{ steps.cache-db-check.outputs.cache-hit }} @@ -210,6 +211,8 @@ jobs: id: filter-inclusions with: filters: | + has-agents-changes: + - "agents/**" has-companion: - "companion/**" has-api-v2-changes: @@ -298,6 +301,13 @@ jobs: with: skip-install-if-cache-hit: "true" + validate-agents-format: + name: Validate agents/ format + needs: [trust-check, prepare] + if: needs.trust-check.outputs.is-trusted == 'true' && needs.prepare.outputs.has-agents-changes == 'true' && github.event.pull_request + uses: ./.github/workflows/validate-agents-format.yml + secrets: inherit + type-check: name: Type Checks needs: [prepare] @@ -465,6 +475,7 @@ jobs: [ trust-check, prepare, + validate-agents-format, lint, type-check, unit-test, @@ -536,4 +547,8 @@ jobs: needs.typecheck-companion.result != 'success' || needs.lint-companion.result != 'success' ) + ) || + ( + needs.prepare.outputs.has-agents-changes == 'true' && + needs.validate-agents-format.result != 'success' ) diff --git a/.github/workflows/sync-agents-to-devin.yml b/.github/workflows/sync-agents-to-devin.yml new file mode 100644 index 0000000000..45d264a91c --- /dev/null +++ b/.github/workflows/sync-agents-to-devin.yml @@ -0,0 +1,30 @@ +name: Sync Agents to Devin Knowledge + +on: + push: + paths: + - "agents/**" + branches: + - main + +permissions: + contents: read + +jobs: + sync: + name: Sync to Devin Knowledge + runs-on: blacksmith-2vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: .github + - uses: ./.github/actions/cache-checkout + - uses: ./.github/actions/yarn-install + + - name: Generate knowledge JSON + run: npx tsx scripts/devin/parse-local-knowledge.ts + + - name: Sync to Devin + env: + DEVIN_API_KEY: ${{ secrets.DEVIN_API_KEY }} + run: npx tsx scripts/devin/sync-knowledge-to-devin.ts diff --git a/.github/workflows/validate-agents-format.yml b/.github/workflows/validate-agents-format.yml new file mode 100644 index 0000000000..90e7f82b4d --- /dev/null +++ b/.github/workflows/validate-agents-format.yml @@ -0,0 +1,20 @@ +name: Validate Agents Format + +on: + workflow_call: + +permissions: + contents: read + +jobs: + validate-agents-format: + runs-on: blacksmith-2vcpu-ubuntu-2404 + steps: + - uses: actions/checkout@v4 + with: + sparse-checkout: .github + - uses: ./.github/actions/cache-checkout + - uses: ./.github/actions/yarn-install + + - name: Validate format + run: npx tsx scripts/devin/validate-local-knowledge.ts diff --git a/.gitignore b/.gitignore index 93c93563ae..0be912ac41 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,4 @@ packages/**/.yarn/ci-cache/ # trigger.dev .trigger +agents/devin-knowledge.json diff --git a/AGENTS.md b/AGENTS.md index 66f91934f1..85597b192e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -75,60 +75,13 @@ When a task requires extensive changes, break it into multiple PRs: ## Commands -### File-scoped (preferred for speed) +See [agents/commands.md](agents/commands.md) for full reference. Key commands: ```bash -# Type check - always run on changed files -yarn type-check:ci --force - -# Lint and format single file -yarn biome check --write path/to/file.tsx - -# Unit test specific file -yarn vitest run path/to/file.test.ts - -# Unit test specific file + specific test -yarn vitest run path/to/file.test.ts --testNamePattern="specific test name" - -# Integration test specific file -VITEST_MODE=integration yarn test path/to/file.integration-test.ts - -# Integration test specific file + specific test -VITEST_MODE=integration yarn test path/to/file.integration-test.ts --testNamePattern="specific test name" - -# E2E test specific file -PLAYWRIGHT_HEADLESS=1 yarn e2e path/to/file.e2e.ts - -# E2E test specific file + specific test -PLAYWRIGHT_HEADLESS=1 yarn e2e path/to/file.e2e.ts --grep "specific test name" -``` - -### Project-wide (use sparingly) - -```bash -# Development -yarn dev # Start dev server -yarn dx # Dev with database setup - -# Build & check -yarn build # Build all packages -yarn biome check --write . # Lint and format all -yarn type-check # Type check all - -# Tests (use TZ=UTC for consistency) -TZ=UTC yarn test # All unit tests -yarn e2e # All E2E tests - -# Database -yarn prisma generate # Regenerate types after schema changes -yarn workspace @calcom/prisma db-migrate # Run migrations -``` - -### Biome focused workflow -+ -```bash -yarn biome check --write . -yarn type-check:ci --force +yarn type-check:ci --force # Type check (always run before pushing) +yarn biome check --write . # Lint and format +TZ=UTC yarn test # Run unit tests +yarn prisma generate # Regenerate types after schema changes ``` @@ -197,7 +150,7 @@ throw new Error(`Unable to create booking: User ${userId} has no available time throw new Error("Booking failed"); ``` -For which error class to use (`ErrorWithCode` vs `TRPCError`) and concrete examples, see [Error Types in knowledge-base.md](agents/knowledge-base.md#error-types). +For which error class to use (`ErrorWithCode` vs `TRPCError`) and concrete examples, see [quality-error-handling](agents/rules/quality-error-handling.md). ### Good Prisma query @@ -271,25 +224,11 @@ import { ProfileRepository } from "@calcom/features/profile/repositories/Profile - Fix type errors before test failures - they're often the root cause - Run `yarn prisma generate` if you see missing enum/type errors -## Business rules -1. Managed event types -- When a managed event type is created we create a managed event type for team (parent managed event type) and for each user that has been assigned to it (child managed event type). Parent managed event type will have "teamId" set in the EventType table row and child one "userId". If we create managed event type and assign Alice and Bob then three rows will be inserted in the EventType table. -- It is possible to book only child managed event type. - -2. Organizations and teams both are stored in the "Team" table. Organizations have "isOrganization" set to true, and if the entry has -"parentId" set then it means it is a team within an organization. - -3. There are two types of OAuth clients you have to distinguish between: -- "OAuth client" which resides in the "OAuthClient" table. This OAuth client allows 3rd party apps users to connect their cal.com accounts. -- "Platform OAuth client" which resides in the "PlatformOAuthClient" table. This OAuth client is used only by platform customers integrating cal.com scheduling directly in their platforms. -If someone says "platform OAuth client" then they mean the one in the "PlatformOAuthClient" table. - ## Extended Documentation For detailed information, see the `agents/` directory: -- **[agents/README.md](agents/README.md)** - Architecture overview and patterns -- **[agents/rules/](agents/rules/)** - Modular engineering rules (performance, architecture, data layer, etc.) +- **[agents/README.md](agents/README.md)** - Rules index and architecture overview +- **[agents/rules/](agents/rules/)** - Modular engineering rules - **[agents/commands.md](agents/commands.md)** - Complete command reference -- **[agents/knowledge-base.md](agents/knowledge-base.md)** - Domain knowledge and best practices -- **[agents/coding-standards.md](agents/coding-standards.md)** - Coding standards with examples +- **[agents/knowledge-base.md](agents/knowledge-base.md)** - Domain knowledge and business rules diff --git a/README.md b/README.md index 30058f4264..28949ff67a 100644 --- a/README.md +++ b/README.md @@ -384,6 +384,50 @@ Executable doesn't exist at /Users/alice/Library/Caches/ms-playwright/chromium-1 ``` 1. Enjoy the new version. + +## AI-Assisted Development + +This repository includes configuration for AI coding assistants. All AI configuration lives in the `agents/` directory as a single source of truth. + +### Structure + +``` +agents/ +├── rules/ # Modular engineering rules +├── skills/ # Reusable skills/prompts +├── commands.md # Command reference +└── knowledge-base.md # Domain knowledge + +AGENTS.md # Main agent instructions +``` + +### Tool Configuration + +We use symlinks to share configuration across tools: + +``` +.claude/ +├── rules -> ../agents/rules +└── skills -> ../agents/skills + +.cursor/ +├── rules -> ../agents/rules +└── skills -> ../agents/skills +``` + +### Using Other Tools + +If you prefer other AI tools (Windsurf, Goose, OpenCode, etc.), you can create your own dot folders and exclude them from git: + +```bash +# Add to .git/info/exclude (local only, not committed) +.windsurf/ +.goose/ +.opencode/ +``` + +This keeps the repository clean while allowing personal tool preferences. + ## Deployment diff --git a/agents/README.md b/agents/README.md index 7fa624bbc3..fdf1ff6a2c 100644 --- a/agents/README.md +++ b/agents/README.md @@ -1,83 +1,77 @@ -# Cal.com Development Guide for AI Agents +# Cal.com Agent Documentation Index -This directory contains comprehensive documentation for AI agents working on the Cal.com codebase. +- **[../AGENTS.md](../AGENTS.md)** - Main guide (structure, tech stack, commands, examples) +- **[commands.md](commands.md)** - Command reference +- **[knowledge-base.md](knowledge-base.md)** - Domain knowledge and business rules -## Quick Navigation +## Rules Index -- **[Rules](rules/)** - Modular engineering rules derived from our 2026 standards -- **[Commands](commands.md)** - Build, test, and development commands -- **[Knowledge Base](knowledge-base.md)** - Knowledge base & best practices -- **[Architecture Overview](#architecture-overview)** - System structure and patterns +### Architecture -## Getting Started +- [architecture-vertical-slices](rules/architecture-vertical-slices.md) - Vertical slice architecture +- [architecture-feature-boundaries](rules/architecture-feature-boundaries.md) - Feature boundaries +- [architecture-page-level-auth](rules/architecture-page-level-auth.md) - Auth in page.tsx, not layout.tsx +- [architecture-features-modules](rules/architecture-features-modules.md) - packages/features vs apps/web/modules -Cal.com is a monorepo using Yarn workspaces and Turbo for build orchestration. The main application is in `apps/web/` with shared packages in `packages/`. +### Quality -### Key Directories +- [quality-avoid-barrel-imports](rules/quality-avoid-barrel-imports.md) - Avoid index.ts barrel imports +- [quality-simplicity](rules/quality-simplicity.md) - Keep code simple +- [quality-no-followup-prs](rules/quality-no-followup-prs.md) - Complete work in PR +- [quality-thorough-code-review](rules/quality-thorough-code-review.md) - Code review standards +- [quality-error-handling](rules/quality-error-handling.md) - ErrorWithCode vs TRPCError +- [quality-imports](rules/quality-imports.md) - Import patterns and named exports +- [quality-pr-creation](rules/quality-pr-creation.md) - PR best practices +- [quality-code-comments](rules/quality-code-comments.md) - Comment guidelines +- [quality-code-review](rules/quality-code-review.md) - Code review focus -- `apps/web/` - Main Next.js application -- `packages/prisma/` - Database schema and migrations -- `packages/trpc/` - API layer using tRPC -- `packages/ui/` - Shared UI components -- `packages/features/` - Feature-specific code -- `packages/app-store/` - Third-party app integrations +### Data Layer -## Architecture Overview +- [data-prefer-select-over-include](rules/data-prefer-select-over-include.md) - Use select in Prisma queries +- [data-repository-pattern](rules/data-repository-pattern.md) - Repository pattern +- [data-repository-methods](rules/data-repository-methods.md) - Repository method standards +- [data-dto-boundaries](rules/data-dto-boundaries.md) - DTO boundaries +- [data-prisma-migrations](rules/data-prisma-migrations.md) - Schema changes and migrations +- [data-prisma-feature-flags](rules/data-prisma-feature-flags.md) - Feature flag seeding -### Database Layer +### API -- **Prisma ORM** with PostgreSQL -- Schema in `packages/prisma/schema.prisma` -- Always use `select` instead of `include` for better performance -- Never expose `credential.key` field in API responses - -### API Layer - -- **tRPC** for type-safe APIs -- Routers in `packages/trpc/server/routers/` -- Authentication handled via NextAuth.js - -### Frontend - -- **Next.js 13+** with App Router in some areas -- **React 18** with TypeScript -- **Tailwind CSS** for styling -- Internationalization with `next-i18next` - -## Common Patterns - -### Error Handling - -- Use early returns to reduce nesting -- Throw descriptive errors with proper error codes -- Prefer composition over prop drilling +- [api-no-breaking-changes](rules/api-no-breaking-changes.md) - API stability +- [api-thin-controllers](rules/api-thin-controllers.md) - Thin controller pattern ### Performance -- Avoid O(n²) logic in backend code -- Minimize Day.js usage in performance-critical paths -- Use `select` queries to only fetch needed data -- Consider using `.utc()` for Day.js operations +- [performance-avoid-quadratic](rules/performance-avoid-quadratic.md) - Avoid O(n²) algorithms +- [performance-dayjs-usage](rules/performance-dayjs-usage.md) - Day.js optimization +- [performance-scheduling-complexity](rules/performance-scheduling-complexity.md) - Scheduling performance -### Security +### Testing -- Never commit secrets or API keys -- Always validate input data -- Use proper authentication checks -- Never expose sensitive credential fields +- [testing-coverage-requirements](rules/testing-coverage-requirements.md) - Test coverage standards +- [testing-playwright](rules/testing-playwright.md) - Playwright test execution +- [testing-mocking](rules/testing-mocking.md) - Mock services and integrations +- [testing-timezone](rules/testing-timezone.md) - Timezone handling (TZ=UTC) +- [testing-incremental](rules/testing-incremental.md) - Incremental test fixing -## Testing Strategy +### CI/CD -- **Unit tests** with Vitest -- **Integration tests** for complex workflows -- **E2E tests** with Playwright -- Test files use `.test.ts` or `.spec.ts` extensions +- [ci-check-failures](rules/ci-check-failures.md) - Handling CI failures +- [ci-type-check-first](rules/ci-type-check-first.md) - Type-check before tests +- [ci-git-workflow](rules/ci-git-workflow.md) - Git and CI workflow -## Pull Request Guidelines +### Patterns -For large PRs (>500 lines or >10 files): +- [patterns-dependency-injection](rules/patterns-dependency-injection.md) - DI patterns +- [patterns-factory-pattern](rules/patterns-factory-pattern.md) - Factory pattern +- [patterns-workflow-triggers](rules/patterns-workflow-triggers.md) - Workflow implementation +- [patterns-app-store](rules/patterns-app-store.md) - App store integration patterns -- Split by feature boundaries -- Separate database migrations, backend logic, frontend components -- Create dependency chains that can be merged sequentially -- Pattern: Database → Backend → Frontend → Tests +### Culture + +- [culture-accountability](rules/culture-accountability.md) - Engineering accountability +- [culture-leverage-ai](rules/culture-leverage-ai.md) - AI tooling practices + +### Reference + +- [reference-file-locations](rules/reference-file-locations.md) - Key file paths +- [reference-local-dev](rules/reference-local-dev.md) - Local development setup diff --git a/agents/coding-standards.md b/agents/coding-standards.md deleted file mode 100644 index 6e1039906a..0000000000 --- a/agents/coding-standards.md +++ /dev/null @@ -1,62 +0,0 @@ -# Coding Standards & Best Practices - - - -## Import Guidelines - -### Type Imports - -```typescript -// ✅ Good - Use type imports for TypeScript types -import type { User } from "@prisma/client"; -import type { NextApiRequest, NextApiResponse } from "next"; - -// ❌ Bad - Regular import for types -import { User } from "@prisma/client"; -``` - - - -## Code Structure - -### Early Returns - -- Prefer early returns to reduce nesting: `if (!booking) return null;` - -### Composition Over Prop Drilling - -- Use React children and context instead of passing props through multiple components - -### ORM and Types - -- Never import `@calcom/prisma/client` in features, services, UI, or handlers. -- Use repository DTOs or domain types instead. -- See "Repository + DTO Pattern and Method Conventions" in the knowledge base for details and examples. - -### Security Rules - -```typescript -// ❌ NEVER expose credential keys -const user = await prisma.user.findFirst({ - select: { - credentials: { - select: { - key: true, // ❌ NEVER do this - } - } - } -}); - -// ✅ Good - Never select credential.key field -const user = await prisma.user.findFirst({ - select: { - credentials: { - select: { - id: true, - type: true, - // key field is excluded for security - } - } - } -}); -``` diff --git a/agents/knowledge-base.md b/agents/knowledge-base.md index d46243d047..ac6b851ce4 100644 --- a/agents/knowledge-base.md +++ b/agents/knowledge-base.md @@ -1,325 +1,94 @@ -# Knowledge Base - Product & Domain-Specific Information +# Knowledge Base - Domain & Product-Specific Information -## Repo note for calcom/cal.com +This file contains domain knowledge about the Cal.com product and codebase. For coding guidelines and rules, see [`rules/`](rules/). -The whole thing is a monorepo. You need to be working in the apps/web folder. +## When working with managed event types -Linting and Formatting -- Run lint with report generation: `yarn lint:report` -- Run type checking: `yarn type-check:ci --force` -- Run auto-fix: `yarn lint -- --fix` +When a managed event type is created, we create: +- A **parent managed event type** for the team (has `teamId` set in EventType table) +- A **child managed event type** for each assigned user (has `userId` set in EventType table) -Development -- Install dependencies: `yarn` -- Set up environment: - - Copy `.env.example` to `.env` - - Generate NEXTAUTH_SECRET with `openssl rand -base64 32` - - Generate CALENDSO_ENCRYPTION_KEY with `openssl rand -base64 24` (must be 32 characters for AES256) - - Configure Postgres database URL in `.env` - - Set DATABASE_DIRECT_URL to the same value as DATABASE_URL +Example: If we create a managed event type and assign Alice and Bob, three rows will be inserted in the EventType table (1 parent + 2 children). -Database Setup -- Development: `yarn workspace @calcom/prisma db-migrate` -- Production: `yarn workspace @calcom/prisma db-deploy` +**Important**: Only child managed event types can be booked. -When setting up local development database, it'll create a bunch of users for you. The passwords are the same as the username. e.g. 'free:free' and 'pro:pro' +## When working with organizations and teams -PR Requirements -- PR title must follow Conventional Commits specification -- For most PRs, you only need to run linting and type checking -- E2E tests will only run if PR has "ready-for-e2e" label +Both organizations and teams are stored in the `Team` table: +- **Organizations**: Have `isOrganization` set to `true` +- **Teams within an organization**: Have `parentId` set (pointing to the organization) -Logging -- Control logging verbosity by setting `NEXT_PUBLIC_LOGGER_LEVEL` in .env: - - 0: silly - - 1: trace - - 2: debug - - 3: info - - 4: warn - - 5: error - - 6: fatal +## When working with OAuth clients -## When addressing issues in the Cal.com repository +There are two types of OAuth clients: -When working on the Cal.com repository, prioritize fixing type issues before addressing failing tests. Running `yarn type-check:ci --force` to identify and fix TypeScript errors should be done first, as these errors are often the root cause of test failures. Only after resolving type issues should you move on to fixing failing tests with `TZ=UTC yarn test`. +| Type | Table | Purpose | +|------|-------|---------| +| OAuth client | `OAuthClient` | Allows 3rd party apps to connect users' cal.com accounts | +| Platform OAuth client | `PlatformOAuthClient` | Used by platform customers integrating cal.com scheduling directly in their platforms | -## When creating pull requests +If someone says "platform OAuth client" they mean the one in the `PlatformOAuthClient` table. -Create pull requests in draft mode by default, so that actual human can mark it as ready for review only when it is. +## When you need product or codebase context -## When developing Playwright tests in the Cal.com repository +### Monorepo Structure -Always ensure Playwright tests pass locally before pushing code. The user requires fast local e2e feedback loops instead of relying on CI, which is too slow for development iteration. Never push test code until those tests are passing locally first. +The whole repository is a monorepo. The main web app is in `apps/web` folder. -## When fixing failing tests in the Cal.com repository +### Local Development Database -When fixing failing tests in the Cal.com repository, take an incremental approach by addressing one file at a time rather than attempting to fix all issues simultaneously. This methodical approach makes it easier to identify and resolve specific issues without getting overwhelmed by the complexity of multiple failing tests across different files. Focus on getting each file's tests passing completely before moving on to the next file. +When setting up local development database, it creates test users. The passwords are the same as the username: +- `free:free` +- `pro:pro` -To identify and fix issues in the Cal.com codebase: +### Logging Levels -1. Run `yarn type-check:ci --force` to identify TypeScript type errors and get fresh results always, bypassing any caching issues -2. Run `yarn test` to identify failing unit tests -3. Address both type errors and failing tests before considering the task complete -4. Type errors often need to be fixed first as they may be causing the test failures +Control logging verbosity by setting `NEXT_PUBLIC_LOGGER_LEVEL` in .env: +- 0: silly +- 1: trace +- 2: debug +- 3: info +- 4: warn +- 5: error +- 6: fatal -## When implementing mock services in the Cal.com test files +### Cal.com Event Identification -When mocking calendar services in Cal.com test files, implement the Calendar interface rather than adding individual properties from each specific calendar service type (like FeishuCalendarService). Since all calendar services implement the Calendar interface and are stored in a map, the mock service should also implement this interface to ensure type compatibility. This approach is more maintainable than trying to add all properties from each specific calendar service implementation. For complex mocks that cause type compatibility issues with deep mocks, consider using simpler fake implementations that directly implement the required interfaces instead of trying to match all properties of the original service classes. When needed, you can modify other mock files to support your implementation rather than trying to force compatibility with existing mocks. +Cal.com events in Google Calendar can be identified by checking if the iCalUID ends with `@Cal.com` (e.g., `2GBXSdEixretciJfKVmYN8@Cal.com`). This identifier is used to distinguish Cal.com bookings from other calendar events for data storage and privacy purposes. -## When implementing mocks for Cal.com app-store integrations +### UI Component Locations -When mocking app-store resources in Cal.com tests, prefer implementing simpler mock designs that directly implement the required interfaces rather than trying to match complex deep mock structures created with mockDeep. This approach is more maintainable and helps resolve type compatibility issues. The user encourages creative solutions and refactoring to better designs when the standard mocking approach causes persistent type errors. +- Event types page: `apps/web/modules/event-types/views/event-types-listing-view.tsx` +- Bookings page: `apps/web/modules/bookings/views/bookings-view.tsx` +- Shared elements (tabs, search bars, filter buttons) should maintain consistent alignment across views -## When implementing workflow triggers in the Cal.com repository +### DataTable -To trigger workflows in Cal.com, use the `scheduleWorkflowReminders` function. This is the standard approach used throughout the codebase for triggering any workflow. Before implementing new workflow triggers, examine existing implementations in the codebase to understand the pattern. The function filters workflows by trigger type and processes each workflow step. Key locations where this is used include booking handlers, confirmation processes, and other booking-related events. +Refer to the DataTable guide at `packages/features/data-table/GUIDE.md` for implementation patterns and best practices. -## When making changes to Prisma schema in Cal.com +### Round-Robin Scheduling -After making changes to the Prisma schema in Cal.com and creating migrations, you need to run `yarn prisma generate` to update the TypeScript types. This is especially important when switching Node.js versions, after adding new fields to models, or after pulling changes that include Prisma schema updates, as it ensures the TypeScript compiler recognizes the updated schema structure. If you encounter errors related to missing enum values (like CreationSource.WEBAPP), running `yarn prisma generate` will typically resolve these issues by regenerating the TypeScript types from the schema. +Reuse existing code in `packages/features/bookings/lib/getLuckyUser.ts` which handles: +- Weight-based selection +- Priority ranking +- Round-robin fairness algorithms -If you encounter enum generator errors during the Prisma generate step (like "Cannot find module './enum-generator.ts'"), run `yarn install` first before trying to generate. This ensures all dependencies are properly installed before the generation process. +Check if existing functions can be extended before creating new implementations. -Whenever you change the schema.prisma file, remember to always consolidate migrations by squashing them as declared in the Prisma docs: https://www.prisma.io/docs/orm/prisma-migrate/workflows/squashing-migrations. This helps maintain a clean migration history and prevents accumulation of multiple migration files. +### Calendar Cache System -## When reviewing PRs in the Cal.com repository +The calendar cache system follows specific patterns in `packages/features/calendar-cache-sql`. When implementing provider-specific calendar cache services (like for Outlook/Office365), place the provider-specific code in the corresponding provider directory (e.g., `packages/app-store/office365calendar`). -When asked to review a PR, focus on providing a clear summary of what the PR is doing and its core functionality. Avoid getting sidetracked by CI failures, testing issues, or technical implementation details unless specifically requested. The user prefers concise, focused reviews that prioritize understanding the main purpose and changes of the PR. +### API Documentation -## When running Playwright tests in the Cal.com repository +The OpenAPI specification at `docs/api-reference/v2/openapi.json` is auto-generated from NestJS controllers. Manual edits will be wiped out. -Use the command format `PLAYWRIGHT_HEADLESS=1 yarn e2e [test-file.e2e.ts]` to run Playwright tests instead of the standard `yarn playwright test` command. This format includes the proper timezone setting, virtual display server, and uses the repository's e2e runner. +To make persistent changes to API documentation, use NestJS decorators (`@ApiQuery`, `@ApiOperation`, etc.) in the controller files at `apps/api/v2/src/modules/*/controllers/*.controller.ts`. -## When running tests in the Cal.com repository +### Workflows vs Webhooks -When running tests in the Cal.com repository, use the TZ=UTC environment variable (e.g., `TZ=UTC yarn test`) to ensure consistent timezone handling. This prevents timezone-related test failures that might occur when tests are run in different environments or by different developers with varying local timezone settings. +Workflows and webhooks are two completely separate features in Cal.com with different implementations and file structures: +- Workflow constants: `packages/features/ee/workflows/lib/constants.ts` +- NOT in the webhooks directory -## When working on calendar cache features in the Cal.com codebase - -The calendar cache system follows specific patterns in `packages/features/calendar-cache-sql`. When implementing provider-specific calendar cache services (like for Outlook/Office365), the provider-specific code should be placed in the corresponding provider directory (e.g., `packages/app-store/office365calendar`). - -## When working on PRs in the Cal.com repository - -When making changes to the Cal.com codebase, always run type checks locally using `yarn type-check:ci` before concluding that CI failures are unrelated to your changes. Even if errors appear in files you haven't directly modified, your changes might still be causing type issues through dependencies or type inference. Compare type check results between the main branch and your feature branch to confirm whether you've introduced new type errors. - -## When working on type issues in the Cal.com repository - -Type casting with "as any" is strictly forbidden in the Cal.com codebase. When encountering Prisma type incompatibilities or other TypeScript type issues, proper type-safe solutions must be used instead, such as Prisma extensions system, type parameter constraints, repository pattern isolation, explicit type definitions, and extension composition patterns that are already established in the codebase. - -## When working on UI layout changes in the Cal.com web application - -The event types page UI components are located in `apps/web/modules/event-types/views/event-types-listing-view.tsx`. This file contains the layout implementation for the search bar and team tabs components on the event types listing page. - -Changes to shared UI patterns (like tab layouts and button alignments) need to be checked across multiple views to maintain consistency: -- Event types page layout: `apps/web/modules/event-types/views/event-types-listing-view.tsx` -- Bookings page layout: `apps/web/modules/bookings/views/bookings-view.tsx` -- Common elements like tabs, search bars, and filter buttons should maintain consistent alignment across views - -## When working on workflow triggers or similar enum-based features in the Cal.com codebase - -To add new workflow triggers: - -1. Check packages/prisma/schema.prisma for existing webhooks and workflow trigger enums as reference -2. Add the same enums to workflows (only when asked by user specifically, or else focus on users requirement) -3. Add enums to packages/features/ee/workflows/lib/constants.ts for UI display -4. Add translations to en/locale.json using the format {enum}_trigger (all lowercase). Webhook triggers serve as the reference implementation pattern for workflow triggers. - -## When working with app-store integrations in Cal.com - -The Cal.com repository uses generated files (*.generated.ts) for app-store integrations. These files are created by the app-store-cli tool. When making structural changes to how integrations are imported or used, you need to update CLI code that generates these files. Typically manual changes to the *.generated.ts files are not made. That was only for PR 19771 for a proof-of-concept. Recent changes have moved from dynamic imports to static map-based imports for better performance. When working with browser components in the app-store, static imports should be used rather than dynamic imports (using Next.js' dynamic function) to maintain consistency with the performance improvements. - -When modifying the app-store-cli build.ts file, you must ensure it correctly handles all types of generated files: -1. Regular service files (calendar.services.generated.ts, crm.services.generated.ts, etc.) need default imports -2. Browser component files (apps.browser-addon.generated.tsx, etc.) may require dynamic imports with Next.js -3. After making changes to build.ts, always verify all generated files have the correct imports by checking each file type - -The lazyImport parameter in getExportedObject() determines whether to use dynamic imports (for browser components) or static imports (for server-side services). - -## When working with branches in the Cal.com repository - -When asked to move changes to a different branch in the Cal.com repository, use git commands to commit existing changes to the specified branch rather than redoing the work. This is more efficient and prevents duplication of effort. The user prefers direct branch operations over reimplementing the same changes multiple times. - -## When working with calendar events in the Cal.com codebase - -Cal.com events in Google Calendar can be identified by checking if the iCalUID ends with "@Cal.com" (e.g., "2GBXSdEixretciJfKVmYN8@Cal.com"). This identifier is used to distinguish Cal.com bookings from other calendar events for data storage and privacy purposes. - -## When working with CI/CD in the Cal.com repository - -When reviewing CI check failures in Cal.com: -1. E2E tests can be flaky and may fail intermittently -2. Focus only on CI failures that are directly related to your code changes -3. Infrastructure-related failures (like dependency installation issues) can be disregarded if all code-specific checks (type checking, linting, unit tests) are passing - -## When working with database models in Cal.com - -Database models in Cal.com are defined in `packages/prisma/schema.prisma`. When adding new fields to models: - -1. For timestamp fields like `createdAt` and `updatedAt`: - - Do not set default values if you want existing records to have null values - - Only new records should get timestamps automatically - - For `updatedAt` fields, ensure they're updated when records are modified - -2. Create a migration using `npx prisma migrate dev --name migration_name` to update the database schema - -3. When implementing cache-related features that require timestamp tracking, always update the database schema first before modifying application code that references those fields. The schema changes must be completed and migrated before the application can successfully query or use the new fields. - -## When working with generated files in the Cal.com repository - -When fixing imports in Cal.com's generated files (like packages/app-store/apps.browser-*.generated.tsx), always check the actual exports in the source files first. For EventTypeAppCardInterface components, they likely use named exports rather than default exports, requiring imports like `import * as ComponentName from "./path"` instead of `import ComponentName from "./path"`. This verification step is crucial when working with browser-addon, browser-appsettings, browser-eventtypesettings, and browser-install generated files. For these files, if you're seeing import errors, check whether the components are exported as default exports in their source files and adjust your import statements accordingly. - -## When working with git and CI systems - -Always push committed changes to the remote repository before waiting for or checking CI status. Waiting for CI checks on unpushed local commits is backwards - the CI runs on the remote repository state, not local commits. The proper sequence is: commit locally, run local checks, push to remote, then monitor CI status. - -## When working with imports/exports in the Cal.com codebase - -When working with imports in the Cal.com codebase, particularly in app-store integrations, pay attention to whether modules use named exports or default exports. Many services like VideoApiAdapter, CalendarService, and PaymentService are exported as named exports, but the actual export name may differ from the generic service type (e.g., `export class AppleCalendarService` instead of `export class CalendarService`). When importing these services, verify the actual export name in the source file and use the appropriate named import syntax (e.g., `import { AppleCalendarService } from "./applecalendar/lib/CalendarService"` or with renaming `import { AppleCalendarService as ApplecalendarCalendarService } from "./applecalendar/lib/CalendarService"`). Always check the actual export pattern and name in the source file to determine the correct import syntax. - -## When working with tRPC components in the Cal.com repository - -When making changes that affect tRPC components or after pulling updates that modify tRPC-related files, you need to follow a specific build order: -1. First run `yarn prisma generate` to ensure all database types are up-to-date -2. Then run `cd packages/trpc && yarn build` to rebuild the tRPC package -This sequence ensures that type definitions are properly generated before building the tRPC components that depend on them, preventing type errors during compilation. - -## When working with workflows or webhooks in the Cal.com repository - -Workflows and webhooks are two completely separate features in Cal.com with different implementations and file structures. The workflow constants file is located at `packages/features/ee/workflows/lib/constants.ts`, not in the webhooks directory. When working on workflow triggers, do not reference or use webhook trigger implementations - they are distinct systems that should not be confused or mixed. - -## When adding new feature flags to the Cal.com repository - -To seed new feature flags in Cal.com, create a Prisma migration using the command `yarn prisma migrate dev --create-only --name seed_[feature_name]_feature`. The migration file should be placed in `packages/prisma/migrations/` with a timestamp prefix format like `20250724210733_seed_calendar_cache_sql_features/migration.sql`. Follow the pattern from existing feature seeding migrations like `packages/prisma/migrations/20241216000000_add_calendar_cache_serve/migration.sql` for the SQL structure. The migration should INSERT the new features into the `Feature` table with appropriate type (like `OPERATIONAL`) and default `enabled` status for manual team enablement. - -## When adding new UI elements or text strings to Cal.com - -All UI strings in Cal.com must be properly translated using the i18n system. This includes: -- Labels for new UI elements (like dropdown labels, settings headers) -- Option values that are displayed to users -- Any text that appears in the interface - -Even if some related strings are already translated (like "Bookings" and "Insights"), new strings must be explicitly added to the translation system. - -## When creating a pull request - -### Title - -- Use conventional commits: `feat:`, `fix:`, `refactor:` -- Be specific: `fix: handle timezone edge case in booking creation` -- Not generic: `fix: booking bug` - -### Size Limits - -- **Large PRs** (>500 lines or >10 files) are not recommended. -- Guide the user how to split large PRs into smaller ones. - -## When handling errors - -### Descriptive Errors - -```typescript -// ✅ Good - Descriptive error with context -throw new Error(`Unable to create booking: User ${userId} has no available time slots for ${date}`); - -// ❌ Bad - Generic error -throw new Error("Booking failed"); -``` - -### Error Types - -Use `ErrorWithCode` for files that are not directly coupled to tRPC. The tRPC package has a middleware called `errorConversionMiddleware` that automatically converts `ErrorWithCode` instances into `TRPCError` instances. - -```typescript -// ✅ Good - Use ErrorWithCode in non-tRPC files (services, repositories, utilities) -import { ErrorCode } from "@calcom/lib/errorCodes"; -import { ErrorWithCode } from "@calcom/lib/errors"; - -// Option 1: Using constructor with ErrorCode enum -throw new ErrorWithCode(ErrorCode.BookingNotFound, "Booking not found"); - -// Option 2: Using the Factory pattern for common HTTP errors -throw ErrorWithCode.Factory.Forbidden("You don't have permission to view this"); -throw ErrorWithCode.Factory.NotFound("Resource not found"); -throw ErrorWithCode.Factory.BadRequest("Invalid input"); - -// ✅ Good - Use TRPCError only in tRPC routers/procedures -import { TRPCError } from "@trpc/server"; - -throw new TRPCError({ - code: "BAD_REQUEST", - message: "Invalid booking time slot", -}); - -// ❌ Bad - Using TRPCError in non-tRPC files -import { TRPCError } from "@trpc/server"; -// Don't use TRPCError in services, repositories, or utility files -``` - -### packages/features Import Restrictions - -Files in `packages/features/**` should NOT import from `@calcom/trpc`. This keeps the features package decoupled from the tRPC layer, making the code more reusable and testable. Use `ErrorWithCode` for error handling in these files, and let the tRPC middleware handle the conversion. - -**Architecture: packages/features vs apps/web/modules** - -The `packages/features` package should contain only framework-agnostic code: -- Repositories (data access layer) -- Services (business logic) -- Core utilities and helpers -- Types and interfaces - -Web-specific code, particularly anything that uses tRPC, should live in `apps/web/modules/...`. This includes: -- React hooks that use tRPC queries/mutations -- tRPC-specific utilities -- Web-only UI components that depend on tRPC - -**Example:** - -If you have a feature called `feature-opt-in`: - -``` -packages/features/feature-opt-in/ -├── repository/ -│ └── FeatureOptInRepository.ts # Data access - OK here -├── service/ -│ └── FeatureOptInService.ts # Business logic - OK here -└── types.ts # Types - OK here - -apps/web/modules/feature-opt-in/ -└── hooks/ - └── useFeatureOptIn.ts # tRPC hook - MUST be here, not in packages/features -``` - -```typescript -// ❌ Bad - tRPC hook in packages/features -// packages/features/feature-opt-in/hooks/useFeatureOptIn.ts -import { trpc } from "@calcom/trpc/react"; -export function useFeatureOptIn() { - return trpc.viewer.featureOptIn.useQuery(); -} - -// ✅ Good - tRPC hook in apps/web/modules -// apps/web/modules/feature-opt-in/hooks/useFeatureOptIn.ts -import { trpc } from "@calcom/trpc/react"; -export function useFeatureOptIn() { - return trpc.viewer.featureOptIn.useQuery(); -} -``` - -This separation ensures that `packages/features` remains portable and can be used by other apps (like `apps/api/v2`) without pulling in web-specific dependencies like tRPC React hooks. - -## File Naming Conventions - -### Repository Files - -- **Must** include `Repository` suffix, PascalCase matching class: `PrismaBookingRepository.ts` - -### Service Files - -- **Must** include `Service` suffix, PascalCase matching class, avoid generic names: `MembershipService.ts` - -### General Files - -- **Components**: PascalCase (e.g., `BookingForm.tsx`) -- **Utilities**: kebab-case (e.g., `date-utils.ts`) -- **Types**: PascalCase with `.types.ts` suffix (e.g., `Booking.types.ts`) -- **Tests**: Same as source file + `.test.ts` or `.spec.ts` -- **Avoid**: Dot-suffixes like `.service.ts`, `.repository.ts` (except for tests, types, specs) +When working on workflow triggers, do not reference or use webhook trigger implementations - they are distinct systems. diff --git a/agents/rules/_sections.md b/agents/rules/_sections.md index 92beccfee3..fbc319cb24 100644 --- a/agents/rules/_sections.md +++ b/agents/rules/_sections.md @@ -44,3 +44,13 @@ The section ID (in parentheses) is the filename prefix used to group rules. **Impact:** MEDIUM **Description:** Engineering culture, accountability, and collaboration standards. + +## 9. CI/CD (ci) + +**Impact:** HIGH +**Description:** Continuous integration practices, type checking priorities, and git workflow standards. + +## 10. Reference (reference) + +**Impact:** LOW +**Description:** Informational lookups, file locations, and local development setup guides. diff --git a/agents/rules/architecture-features-modules.md b/agents/rules/architecture-features-modules.md new file mode 100644 index 0000000000..3bb1c9d709 --- /dev/null +++ b/agents/rules/architecture-features-modules.md @@ -0,0 +1,54 @@ +--- +title: packages/features vs apps/web/modules +impact: HIGH +impactDescription: Wrong placement causes tight coupling and import issues +tags: architecture, features, modules, trpc +--- + +# packages/features vs apps/web/modules + +## packages/features + +The `packages/features` package should contain only framework-agnostic code: +- Repositories (data access layer) +- Services (business logic) +- Core utilities and helpers +- Types and interfaces + +**Files in `packages/features/**` should NOT import from `@calcom/trpc`.** + +## apps/web/modules + +Web-specific code, particularly anything that uses tRPC, should live in `apps/web/modules/...`: +- React hooks that use tRPC queries/mutations +- tRPC-specific utilities +- Web-only UI components that depend on tRPC + +## Example Structure + +``` +packages/features/feature-opt-in/ +├── repository/ +│ └── FeatureOptInRepository.ts # Data access - OK here +├── service/ +│ └── FeatureOptInService.ts # Business logic - OK here +└── types.ts # Types - OK here + +apps/web/modules/feature-opt-in/ +└── hooks/ + └── useFeatureOptIn.ts # tRPC hook - MUST be here +``` + +## Why This Matters + +```typescript +// ❌ Bad - tRPC hook in packages/features +// packages/features/feature-opt-in/hooks/useFeatureOptIn.ts +import { trpc } from "@calcom/trpc/react"; + +// ✅ Good - tRPC hook in apps/web/modules +// apps/web/modules/feature-opt-in/hooks/useFeatureOptIn.ts +import { trpc } from "@calcom/trpc/react"; +``` + +This separation ensures that `packages/features` remains portable and can be used by other apps (like `apps/api/v2`) without pulling in web-specific dependencies. diff --git a/agents/rules/ci-check-failures.md b/agents/rules/ci-check-failures.md new file mode 100644 index 0000000000..5858b1bd65 --- /dev/null +++ b/agents/rules/ci-check-failures.md @@ -0,0 +1,33 @@ +--- +title: CI Check Failure Handling +impact: HIGH +impactDescription: Misinterpreting CI failures wastes debugging time +tags: ci, debugging, workflow +--- + +# CI Check Failure Handling + +## What to Focus On + +When reviewing CI check failures in Cal.com: + +1. **E2E tests can be flaky** and may fail intermittently +2. **Focus only on CI failures that are directly related to your code changes** +3. Infrastructure-related failures (like dependency installation issues) can be disregarded if all code-specific checks pass + +## Known CI Issues to Ignore + +These errors are related to SAML database misconfiguration on CI and should be ignored: +- "password authentication failed for user postgres" +- "Invalid URL" + +## E2E Tests Skipping + +**E2E tests skipping is expected behavior:** +- When E2E tests are skipped, it's because the `ready-for-e2e` label has not been added to the PR +- The "required" check intentionally fails when E2E tests are skipped to prevent merging without E2E +- Do not try to fix anything related to skipped E2E tests - this is completely expected and normal + +## Before Blaming CI + +Always run type checks locally using `yarn type-check:ci --force` before concluding that CI failures are unrelated to your changes. Even if errors appear in files you haven't directly modified, your changes might still be causing type issues through dependencies or type inference. diff --git a/agents/rules/ci-git-workflow.md b/agents/rules/ci-git-workflow.md new file mode 100644 index 0000000000..83b6414c0b --- /dev/null +++ b/agents/rules/ci-git-workflow.md @@ -0,0 +1,37 @@ +--- +title: Git and CI Workflow +impact: HIGH +impactDescription: Incorrect workflow causes wasted CI cycles and confusion +tags: git, ci, workflow +--- + +# Git and CI Workflow + +## Push Before Checking CI + +Always push committed changes to the remote repository before waiting for or checking CI status. + +Waiting for CI checks on unpushed local commits is backwards - the CI runs on the remote repository state, not local commits. + +**Proper sequence:** +1. Commit locally +2. Run local checks (`yarn type-check:ci --force`, `yarn biome check --write .`) +3. Push to remote +4. Monitor CI status + +## Branch Operations + +When asked to move changes to a different branch, use git commands to commit existing changes to the specified branch rather than redoing the work. This is more efficient and prevents duplication of effort. + +## Never Force Push + +**Never force push to main or production branches** - under any circumstances. + +## Working with tRPC Changes + +When making changes that affect tRPC components or after pulling updates that modify tRPC-related files: + +1. First run `yarn prisma generate` to ensure all database types are up-to-date +2. Then run `cd packages/trpc && yarn build` to rebuild the tRPC package + +This sequence ensures that type definitions are properly generated before building. diff --git a/agents/rules/ci-type-check-first.md b/agents/rules/ci-type-check-first.md new file mode 100644 index 0000000000..68e0dc0c78 --- /dev/null +++ b/agents/rules/ci-type-check-first.md @@ -0,0 +1,43 @@ +--- +title: Type Check Before Tests +impact: HIGH +impactDescription: Type errors are often the root cause of test failures +tags: ci, typescript, type-check, workflow +--- + +# Type Check Before Tests + +## Priority Order + +When working on the Cal.com repository, prioritize fixing type issues before addressing failing tests. + +1. Run `yarn type-check:ci --force` first +2. Fix all TypeScript errors +3. Then run tests with `TZ=UTC yarn test` + +## Why Type Check First + +Type errors are often the root cause of test failures. Fixing types first: +- Eliminates cascading failures +- Ensures code compiles correctly +- Catches issues that tests might miss + +## Comparing Branches + +Compare type check results between the main branch and your feature branch to confirm whether you've introduced new type errors: + +```bash +# On your branch +yarn type-check:ci --force 2>&1 | tee /tmp/feature-types.log + +# On main +git checkout main +yarn type-check:ci --force 2>&1 | tee /tmp/main-types.log + +# Compare +diff /tmp/main-types.log /tmp/feature-types.log +``` + +## Missing Enum Errors + +If you encounter errors related to missing enum values (like `CreationSource.WEBAPP`), running `yarn prisma generate` will typically resolve these issues by regenerating the TypeScript types from the schema. diff --git a/agents/rules/data-prisma-feature-flags.md b/agents/rules/data-prisma-feature-flags.md new file mode 100644 index 0000000000..bd35517946 --- /dev/null +++ b/agents/rules/data-prisma-feature-flags.md @@ -0,0 +1,40 @@ +--- +title: Feature Flag Seeding +impact: MEDIUM +impactDescription: Proper feature flag setup enables controlled rollouts +tags: prisma, feature-flags, migrations +--- + +# Feature Flag Seeding + +## Creating Feature Flag Migrations + +To seed new feature flags in Cal.com, create a Prisma migration: + +```bash +yarn prisma migrate dev --create-only --name seed_[feature_name]_feature +``` + +## Migration File Location + +The migration file should be placed in `packages/prisma/migrations/` with a timestamp prefix format: + +``` +20250724210733_seed_calendar_cache_sql_features/migration.sql +``` + +## SQL Structure + +Follow the pattern from existing feature seeding migrations like: +`packages/prisma/migrations/20241216000000_add_calendar_cache_serve/migration.sql` + +```sql +INSERT INTO "Feature" ("slug", "enabled", "type", "description", "createdAt", "updatedAt") +VALUES + ('your-feature-slug', false, 'OPERATIONAL', 'Description of feature', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP) +ON CONFLICT ("slug") DO NOTHING; +``` + +The migration should INSERT the new features into the `Feature` table with: +- Appropriate type (like `OPERATIONAL`) +- Default `enabled` status for manual team enablement diff --git a/agents/rules/data-prisma-migrations.md b/agents/rules/data-prisma-migrations.md new file mode 100644 index 0000000000..b2d15236fd --- /dev/null +++ b/agents/rules/data-prisma-migrations.md @@ -0,0 +1,52 @@ +--- +title: Prisma Schema and Migrations +impact: HIGH +impactDescription: Schema changes affect all downstream code and deployments +tags: prisma, database, migrations, schema +--- + +# Prisma Schema and Migrations + +## After Schema Changes + +After making changes to the Prisma schema in Cal.com and creating migrations, you need to run: + +```bash +yarn prisma generate +``` + +This updates the TypeScript types. This is especially important: +- When switching Node.js versions +- After adding new fields to models +- After pulling changes that include Prisma schema updates + +## Creating Migrations + +```bash +# Development migration +npx prisma migrate dev --name migration_name + +# Production deployment +yarn workspace @calcom/prisma db-deploy +``` + +## Timestamp Fields + +When adding timestamp fields like `createdAt` and `updatedAt`: +- Do not set default values if you want existing records to have null values +- Only new records should get timestamps automatically +- For `updatedAt` fields, ensure they're updated when records are modified + +## Squash Migrations + +Whenever you change the schema.prisma file, remember to always consolidate migrations by squashing them as declared in the [Prisma docs](https://www.prisma.io/docs/orm/prisma-migrate/workflows/squashing-migrations). + +This helps maintain a clean migration history and prevents accumulation of multiple migration files. + +## Enum Generator Errors + +If you encounter enum generator errors during the Prisma generate step (like "Cannot find module './enum-generator.ts'"), run `yarn install` first before trying to generate. + +## Cache-Related Features + +When implementing cache-related features that require timestamp tracking, always update the database schema first before modifying application code that references those fields. diff --git a/agents/rules/patterns-app-store.md b/agents/rules/patterns-app-store.md new file mode 100644 index 0000000000..e0b4a6c81d --- /dev/null +++ b/agents/rules/patterns-app-store.md @@ -0,0 +1,31 @@ +--- +title: App Store Integration Patterns +impact: MEDIUM +impactDescription: App store integrations require specific patterns for generated files +tags: app-store, integrations, generated-files +--- + +# App Store Integration Patterns + +## Generated Files + +The Cal.com repository uses generated files (`*.generated.ts`) for app-store integrations. These files are created by the app-store-cli tool. + +**Do not manually modify** `*.generated.ts` files. If you need structural changes to how integrations are imported or used, update the CLI code that generates these files. + +## Import Patterns + +Recent changes have moved from dynamic imports to static map-based imports for better performance. When working with browser components in the app-store, static imports should be used rather than dynamic imports. + +### Generated File Types + +When modifying the app-store-cli `build.ts` file, ensure it correctly handles all types: + +1. **Regular service files** (`calendar.services.generated.ts`, `crm.services.generated.ts`, etc.) - need default imports +2. **Browser component files** (`apps.browser-addon.generated.tsx`, etc.) - may require dynamic imports with Next.js + +The `lazyImport` parameter in `getExportedObject()` determines whether to use dynamic imports (for browser components) or static imports (for server-side services). + +## Calendar Cache Features + +The calendar cache system follows specific patterns in `packages/features/calendar-cache-sql`. When implementing provider-specific calendar cache services (like for Outlook/Office365), the provider-specific code should be placed in the corresponding provider directory (e.g., `packages/app-store/office365calendar`). diff --git a/agents/rules/patterns-workflow-triggers.md b/agents/rules/patterns-workflow-triggers.md new file mode 100644 index 0000000000..38a68c53e9 --- /dev/null +++ b/agents/rules/patterns-workflow-triggers.md @@ -0,0 +1,39 @@ +--- +title: Workflow Trigger Implementation +impact: MEDIUM +impactDescription: Consistent workflow patterns ensure reliable automation +tags: workflows, triggers, automation +--- + +# Workflow Trigger Implementation + +## Using scheduleWorkflowReminders + +To trigger workflows in Cal.com, use the `scheduleWorkflowReminders` function. This is the standard approach used throughout the codebase. + +Before implementing new workflow triggers, examine existing implementations in the codebase to understand the pattern. The function: +- Filters workflows by trigger type +- Processes each workflow step + +Key locations where this is used: +- Booking handlers +- Confirmation processes +- Other booking-related events + +## Adding New Workflow Triggers + +1. Check `packages/prisma/schema.prisma` for existing webhooks and workflow trigger enums as reference +2. Add the same enums to workflows (only when asked specifically) +3. Add enums to `packages/features/ee/workflows/lib/constants.ts` for UI display +4. Add translations to `en/locale.json` using the format `{enum}_trigger` (all lowercase) + +Webhook triggers serve as the reference implementation pattern for workflow triggers. + +## Workflows vs Webhooks + +Workflows and webhooks are two completely separate features in Cal.com with different implementations and file structures: + +- Workflow constants: `packages/features/ee/workflows/lib/constants.ts` +- NOT in the webhooks directory + +When working on workflow triggers, do not reference or use webhook trigger implementations - they are distinct systems. diff --git a/agents/rules/quality-code-comments.md b/agents/rules/quality-code-comments.md new file mode 100644 index 0000000000..f8332827a9 --- /dev/null +++ b/agents/rules/quality-code-comments.md @@ -0,0 +1,48 @@ +--- +title: Code Comment Guidelines +impact: MEDIUM +impactDescription: Excessive comments add noise; missing comments hurt maintainability +tags: comments, documentation, readability +--- + +# Code Comment Guidelines + +## General Principle + +Keep comments limited and avoid obvious ones. Comments should explain "why" not "what" - the code itself should be clear enough to explain what it does. + +## When to Comment + +- Complex business logic that isn't obvious from the code +- Workarounds or hacks with explanation of why they're needed +- Non-obvious performance optimizations +- Important security considerations + +## When NOT to Comment + +```typescript +// ❌ Bad - Obvious comment +// Get the user +const user = await getUser(userId); + +// ❌ Bad - Restating the code +// Loop through bookings +for (const booking of bookings) { + // Process booking + processBooking(booking); +} +``` + +## Good Examples + +```typescript +// ✅ Good - Explains why, not what +// We need to fetch availability before slots because the timezone +// conversion depends on the user's configured availability rules +const availability = await getAvailability(userId); +const slots = convertToSlots(availability, timezone); + +// ✅ Good - Documents a non-obvious constraint +// Google Calendar API has a 2500 event limit per sync request +const BATCH_SIZE = 2500; +``` diff --git a/agents/rules/quality-code-review.md b/agents/rules/quality-code-review.md new file mode 100644 index 0000000000..62bf70e1d6 --- /dev/null +++ b/agents/rules/quality-code-review.md @@ -0,0 +1,36 @@ +--- +title: Code Review Focus +impact: MEDIUM +impactDescription: Focused reviews are more useful than scattered feedback +tags: code-review, workflow +--- + +# Code Review Focus + +## When Asked to Review a PR + +Focus on providing a clear summary of what the PR is doing and its core functionality. + +**Avoid getting sidetracked by:** +- CI failures +- Testing issues +- Technical implementation details (unless specifically requested) + +## Good Review Structure + +1. **Summary**: What does this PR do? +2. **Core changes**: What are the main code changes? +3. **Impact**: What parts of the system does this affect? + +## What to Look For + +- Does the code do what it claims to do? +- Are there any obvious bugs or edge cases? +- Does it follow Cal.com coding standards? +- Is the change appropriately scoped? + +## What to Skip (Unless Asked) + +- Nitpicks about style (Biome handles this) +- Suggestions for refactoring unrelated code +- Deep dives into implementation details diff --git a/agents/rules/quality-error-handling.md b/agents/rules/quality-error-handling.md new file mode 100644 index 0000000000..5634faef11 --- /dev/null +++ b/agents/rules/quality-error-handling.md @@ -0,0 +1,52 @@ +--- +title: Error Handling Patterns +impact: HIGH +impactDescription: Proper error handling ensures debuggable and secure code +tags: errors, trpc, services, repositories +--- + +# Error Handling Patterns + +## Descriptive Errors + +```typescript +// ✅ Good - Descriptive error with context +throw new Error(`Unable to create booking: User ${userId} has no available time slots for ${date}`); + +// ❌ Bad - Generic error +throw new Error("Booking failed"); +``` + +## ErrorWithCode vs TRPCError + +Use `ErrorWithCode` for files that are not directly coupled to tRPC. The tRPC package has a middleware called `errorConversionMiddleware` that automatically converts `ErrorWithCode` instances into `TRPCError` instances. + +### In Non-tRPC Files (services, repositories, utilities) + +```typescript +import { ErrorCode } from "@calcom/lib/errorCodes"; +import { ErrorWithCode } from "@calcom/lib/errors"; + +// Option 1: Using constructor with ErrorCode enum +throw new ErrorWithCode(ErrorCode.BookingNotFound, "Booking not found"); + +// Option 2: Using the Factory pattern for common HTTP errors +throw ErrorWithCode.Factory.Forbidden("You don't have permission to view this"); +throw ErrorWithCode.Factory.NotFound("Resource not found"); +throw ErrorWithCode.Factory.BadRequest("Invalid input"); +``` + +### In tRPC Routers Only + +```typescript +import { TRPCError } from "@trpc/server"; + +throw new TRPCError({ + code: "BAD_REQUEST", + message: "Invalid booking time slot", +}); +``` + +## packages/features Import Restrictions + +Files in `packages/features/**` should NOT import from `@calcom/trpc`. This keeps the features package decoupled from the tRPC layer, making the code more reusable and testable. Use `ErrorWithCode` for error handling in these files. diff --git a/agents/rules/quality-imports.md b/agents/rules/quality-imports.md new file mode 100644 index 0000000000..df55be8f10 --- /dev/null +++ b/agents/rules/quality-imports.md @@ -0,0 +1,49 @@ +--- +title: Import and Export Patterns +impact: MEDIUM +impactDescription: Incorrect imports cause build failures and bundle bloat +tags: imports, exports, modules, app-store +--- + +# Import and Export Patterns + +## Named vs Default Exports + +When working with imports in the Cal.com codebase, particularly in app-store integrations, pay attention to whether modules use named exports or default exports. + +Many services like VideoApiAdapter, CalendarService, and PaymentService are exported as named exports, but the actual export name may differ from the generic service type. + +```typescript +// ✅ Good - Verify actual export name and use named import +import { AppleCalendarService } from "./applecalendar/lib/CalendarService"; + +// With renaming if needed +import { AppleCalendarService as ApplecalendarCalendarService } from "./applecalendar/lib/CalendarService"; + +// ❌ Bad - Assuming default export without checking +import CalendarService from "./applecalendar/lib/CalendarService"; +``` + +## Generated Files + +When fixing imports in Cal.com's generated files (like `packages/app-store/apps.browser-*.generated.tsx`), always check the actual exports in the source files first. + +For EventTypeAppCardInterface components, they likely use named exports rather than default exports, requiring: + +```typescript +import * as ComponentName from "./path"; +// instead of +import ComponentName from "./path"; +``` + +## Factory Function Naming + +When creating factory functions that replace class exports, use the naming convention `Build[ServiceName]` instead of just `[ServiceName]`: + +```typescript +// ✅ Good - Clear factory function naming +export function BuildPaymentService() { ... } + +// ❌ Bad - Confusing with class export +export function PaymentService() { ... } +``` diff --git a/agents/rules/quality-pr-creation.md b/agents/rules/quality-pr-creation.md new file mode 100644 index 0000000000..fbc759dc89 --- /dev/null +++ b/agents/rules/quality-pr-creation.md @@ -0,0 +1,36 @@ +--- +title: PR Creation Best Practices +impact: HIGH +impactDescription: PRs that don't follow guidelines slow down review cycles +tags: pull-request, code-review, workflow +--- + +# PR Creation Best Practices + +## Draft Mode + +Create pull requests in draft mode by default, so that a human reviewer can mark it as ready for review only when it is. + +## PR Title + +- Use conventional commits: `feat:`, `fix:`, `refactor:` +- Be specific: `fix: handle timezone edge case in booking creation` +- Not generic: `fix: booking bug` + +## Size Limits + +- **Large PRs** (>500 lines or >10 files) are not recommended +- Split large changes by layer (database, backend, frontend) +- Split by feature component (API, UI, integration) + +## PR Requirements + +- PR title must follow Conventional Commits specification +- For most PRs, you only need to run linting and type checking +- E2E tests will only run if PR has "ready-for-e2e" label + +## Before Pushing + +1. Run `yarn type-check:ci --force` to check types +2. Run `yarn biome check --write .` to lint and format +3. Run relevant tests locally diff --git a/agents/rules/reference-file-locations.md b/agents/rules/reference-file-locations.md new file mode 100644 index 0000000000..3d5f06194f --- /dev/null +++ b/agents/rules/reference-file-locations.md @@ -0,0 +1,50 @@ +--- +title: Key File Locations +impact: LOW +impactDescription: Quick reference for finding important files +tags: reference, navigation, file-locations +--- + +# Key File Locations + +## UI Components + +- Event types page: `apps/web/modules/event-types/views/event-types-listing-view.tsx` +- Bookings page: `apps/web/modules/bookings/views/bookings-view.tsx` +- Shared UI patterns (tabs, search bars, filter buttons) should maintain consistent alignment across views + +## Database + +- Schema: `packages/prisma/schema.prisma` +- Migrations: `packages/prisma/migrations/` + +## API + +- tRPC routers: `packages/trpc/server/routers/` +- API v2 controllers: `apps/api/v2/src/modules/*/controllers/*.controller.ts` +- OpenAPI spec: `docs/api-reference/v2/openapi.json` (auto-generated, don't edit manually) + +## Features + +- Workflow constants: `packages/features/ee/workflows/lib/constants.ts` +- Round-robin/host prioritization: `packages/features/bookings/lib/getLuckyUser.ts` +- Calendar cache: `packages/features/calendar-cache-sql` +- DataTable guide: `packages/features/data-table/GUIDE.md` + +## Translations + +- English: `apps/web/public/static/locales/en/common.json` + +## App Store + +- Generated files: `packages/app-store/*.generated.ts` +- CLI tool: `packages/app-store-cli/` + +## File Naming Conventions + +- **Repository files**: `PrismaBookingRepository.ts` (PascalCase with Repository suffix) +- **Service files**: `MembershipService.ts` (PascalCase with Service suffix) +- **Components**: `BookingForm.tsx` (PascalCase) +- **Utilities**: `date-utils.ts` (kebab-case) +- **Types**: `Booking.types.ts` (PascalCase with .types.ts suffix) +- **Tests**: Same as source file + `.test.ts` or `.spec.ts` diff --git a/agents/rules/reference-local-dev.md b/agents/rules/reference-local-dev.md new file mode 100644 index 0000000000..f7f8a871ca --- /dev/null +++ b/agents/rules/reference-local-dev.md @@ -0,0 +1,73 @@ +--- +title: Local Development Setup +impact: LOW +impactDescription: Reference guide for local development environment +tags: reference, development, setup +--- + +# Local Development Setup + +## Initial Setup + +```bash +# Install dependencies +yarn + +# Set up environment +cp .env.example .env +``` + +## Environment Variables + +Generate required secrets: + +```bash +# NEXTAUTH_SECRET +openssl rand -base64 32 + +# CALENDSO_ENCRYPTION_KEY (must be 32 characters for AES256) +openssl rand -base64 24 +``` + +Configure in `.env`: +- `DATABASE_URL` - PostgreSQL connection string +- `DATABASE_DIRECT_URL` - Same as DATABASE_URL + +## Database Setup + +```bash +# Development +yarn workspace @calcom/prisma db-migrate + +# Production +yarn workspace @calcom/prisma db-deploy +``` + +## Test Users + +When setting up local development database, it creates test users. The passwords are the same as the username: +- `free:free` +- `pro:pro` + +## Logging + +Control logging verbosity by setting `NEXT_PUBLIC_LOGGER_LEVEL` in .env: +- 0: silly +- 1: trace +- 2: debug +- 3: info +- 4: warn +- 5: error +- 6: fatal + +## API v2 Imports + +If you need to import from `@calcom/features` or `@calcom/trpc` into `apps/api/v2`, use the platform-libraries package instead: + +```typescript +// ✅ Good +import { SomeService } from "@calcom/platform-libraries"; + +// ❌ Bad - Will cause module resolution errors +import { SomeService } from "@calcom/features/..."; +``` diff --git a/agents/rules/testing-incremental.md b/agents/rules/testing-incremental.md new file mode 100644 index 0000000000..7d9f5937b7 --- /dev/null +++ b/agents/rules/testing-incremental.md @@ -0,0 +1,27 @@ +--- +title: Incremental Test Fixing +impact: MEDIUM +impactDescription: Methodical approach prevents getting overwhelmed by test failures +tags: testing, debugging, workflow +--- + +# Incremental Test Fixing + +## One File at a Time + +When fixing failing tests in the Cal.com repository, take an incremental approach by addressing one file at a time rather than attempting to fix all issues simultaneously. + +This methodical approach makes it easier to identify and resolve specific issues without getting overwhelmed by the complexity of multiple failing tests across different files. + +## Recommended Order + +1. Run `yarn type-check:ci --force` to identify TypeScript type errors +2. Run `yarn test` to identify failing unit tests +3. Address both type errors and failing tests before considering the task complete +4. Type errors often need to be fixed first as they may be causing the test failures + +## Focus Strategy + +- Focus on getting each file's tests passing completely before moving on to the next file +- Fix type errors before test failures - they're often the root cause +- Run `yarn prisma generate` if you see missing enum/type errors diff --git a/agents/rules/testing-mocking.md b/agents/rules/testing-mocking.md new file mode 100644 index 0000000000..67b40a02f9 --- /dev/null +++ b/agents/rules/testing-mocking.md @@ -0,0 +1,26 @@ +--- +title: Mock Implementation Patterns +impact: MEDIUM +impactDescription: Poor mocks cause flaky tests and false positives +tags: testing, mocking, calendar, app-store +--- + +# Mock Implementation Patterns + +## Calendar Service Mocks + +When mocking calendar services in Cal.com test files, implement the `Calendar` interface rather than adding individual properties from each specific calendar service type (like `FeishuCalendarService`). + +Since all calendar services implement the `Calendar` interface and are stored in a map, the mock service should also implement this interface to ensure type compatibility. + +## App-Store Integration Mocks + +When mocking app-store resources in Cal.com tests, prefer implementing simpler mock designs that directly implement the required interfaces rather than trying to match complex deep mock structures created with `mockDeep`. + +This approach is more maintainable and helps resolve type compatibility issues. + +## General Guidance + +- For complex mocks that cause type compatibility issues with deep mocks, consider using simpler fake implementations +- When needed, you can modify other mock files to support your implementation +- Creative solutions and refactoring to better designs are encouraged when standard mocking causes persistent type errors diff --git a/agents/rules/testing-playwright.md b/agents/rules/testing-playwright.md new file mode 100644 index 0000000000..400fccde15 --- /dev/null +++ b/agents/rules/testing-playwright.md @@ -0,0 +1,32 @@ +--- +title: Playwright E2E Testing +impact: HIGH +impactDescription: E2E tests catch integration issues before production +tags: testing, playwright, e2e +--- + +# Playwright E2E Testing + +## Running Tests + +Use the command format: + +```bash +PLAYWRIGHT_HEADLESS=1 yarn e2e [test-file.e2e.ts] +``` + +This format includes the proper timezone setting, virtual display server, and uses the repository's e2e runner. + +**Do not use** the standard `yarn playwright test` command. + +## Local Testing First + +Always ensure Playwright tests pass locally before pushing code. The user requires fast local e2e feedback loops instead of relying on CI, which is too slow for development iteration. + +**Never push test code until those tests are passing locally first.** + +## CI Behavior + +- E2E tests will only run if PR has "ready-for-e2e" label +- When E2E tests are skipped, the "required" check intentionally fails to prevent merging without E2E +- Do not try to fix anything related to skipped E2E tests - this is expected behavior diff --git a/agents/rules/testing-timezone.md b/agents/rules/testing-timezone.md new file mode 100644 index 0000000000..33074d4d61 --- /dev/null +++ b/agents/rules/testing-timezone.md @@ -0,0 +1,37 @@ +--- +title: Timezone Handling in Tests +impact: HIGH +impactDescription: Timezone bugs are hard to reproduce without consistent test environments +tags: testing, timezone, consistency +--- + +# Timezone Handling in Tests + +## Always Use TZ=UTC + +When running tests in the Cal.com repository, use the `TZ=UTC` environment variable: + +```bash +TZ=UTC yarn test +``` + +This ensures consistent timezone handling and prevents timezone-related test failures that might occur when tests are run in different environments or by different developers with varying local timezone settings. + +## Why This Matters + +- Tests may pass locally but fail in CI (or vice versa) +- Date/time assertions become unpredictable +- Debugging timezone issues is time-consuming + +## Test Commands + +```bash +# Unit tests +TZ=UTC yarn test + +# Specific test file +TZ=UTC yarn vitest run path/to/file.test.ts + +# E2E tests (already configured in yarn e2e) +PLAYWRIGHT_HEADLESS=1 yarn e2e +``` diff --git a/scripts/devin/delete-all-devin-knowledge.ts b/scripts/devin/delete-all-devin-knowledge.ts new file mode 100644 index 0000000000..11d86ba25b --- /dev/null +++ b/scripts/devin/delete-all-devin-knowledge.ts @@ -0,0 +1,131 @@ +#!/usr/bin/env -S npx tsx + +/** + * Deletes ALL knowledge entries from Devin's Knowledge API. + * + * WARNING: This is a destructive operation! It will delete all knowledge entries. + * The script requires interactive confirmation to prevent accidental execution. + * + * API Reference: + * - List: https://docs.devin.ai/api-reference/knowledge/list-knowledge + * - Delete: https://docs.devin.ai/api-reference/knowledge/delete-knowledge + * + * Usage: DEVIN_API_KEY=your_token npx tsx scripts/devin/delete-all-devin-knowledge.ts + */ + +import process from "node:process"; +import * as readline from "readline"; + +interface ApiKnowledgeEntry { + id: string; + name: string; +} + +interface ApiListResponse { + folders: { id: string; name: string }[]; + knowledge: ApiKnowledgeEntry[]; +} + +const API_BASE = "https://api.devin.ai/v1"; + +async function apiRequest(method: string, endpoint: string): Promise { + const token = process.env.DEVIN_API_KEY; + if (!token) { + throw new Error("DEVIN_API_KEY environment variable is not set"); + } + + const url = `${API_BASE}${endpoint}`; + const options: RequestInit = { + method, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }; + + const response = await fetch(url, options); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`); + } + + if (response.status === 204) { + return {} as T; + } + + return response.json() as Promise; +} + +async function listKnowledge(): Promise { + return apiRequest("GET", "/knowledge"); +} + +async function deleteKnowledge(noteId: string): Promise { + await apiRequest("DELETE", `/knowledge/${noteId}`); +} + +function askConfirmation(question: string): Promise { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + + return new Promise((resolve) => { + rl.question(question, (answer) => { + rl.close(); + resolve(answer.trim().toUpperCase() === "Y"); + }); + }); +} + +async function main() { + // Check if running in a TTY (interactive terminal) + if (!process.stdin.isTTY) { + console.error("Error: This script must be run interactively (not in CI or piped input)."); + console.error("This is a safety measure to prevent accidental deletion."); + process.exit(1); + } + + console.log("Fetching existing knowledge from Devin API...\n"); + const remoteData = await listKnowledge(); + + const entryCount = remoteData.knowledge.length; + + if (entryCount === 0) { + console.log("No knowledge entries found. Nothing to delete."); + process.exit(0); + } + + console.log(`Found ${entryCount} knowledge entries:\n`); + for (const entry of remoteData.knowledge) { + console.log(` - ${entry.name}`); + } + + console.log("\n⚠️ WARNING: This will permanently delete ALL knowledge entries listed above!"); + console.log("This action cannot be undone.\n"); + + const confirmed = await askConfirmation("Are you sure you want to delete all entries? (Y/n): "); + + if (!confirmed) { + console.log("\nAborted. No entries were deleted."); + process.exit(0); + } + + console.log("\nDeleting all knowledge entries...\n"); + + let deleted = 0; + for (const entry of remoteData.knowledge) { + process.stdout.write(` Deleting: ${entry.name}...`); + await deleteKnowledge(entry.id); + console.log(" ✓"); + deleted++; + } + + console.log(`\nSuccessfully deleted ${deleted} knowledge entries.`); +} + +main().catch((error) => { + console.error("Error:", error.message); + process.exit(1); +}); diff --git a/scripts/devin/export-devin-knowledge.ts b/scripts/devin/export-devin-knowledge.ts new file mode 100644 index 0000000000..209a252189 --- /dev/null +++ b/scripts/devin/export-devin-knowledge.ts @@ -0,0 +1,82 @@ +#!/usr/bin/env -S npx tsx + +/** + * Export all Devin Knowledge to a backup JSON file + * Usage: DEVIN_API_KEY=your_token npx tsx scripts/devin/export-devin-knowledge.ts + */ + +import process from "node:process"; +import * as fs from "fs"; +import * as path from "path"; + +const API_BASE = "https://api.devin.ai/v1"; + +interface ApiFolder { + id: string; + name: string; + description: string; + created_at: string; +} + +interface ApiKnowledgeEntry { + id: string; + name: string; + body: string; + trigger_description: string; + created_at: string; + created_by?: { + full_name: string; + id: string; + }; + parent_folder_id?: string; +} + +interface ApiListResponse { + folders: ApiFolder[]; + knowledge: ApiKnowledgeEntry[]; +} + +async function main() { + const token = process.env.DEVIN_API_KEY; + if (!token) { + console.error("Error: DEVIN_API_KEY environment variable is not set"); + console.error("Usage: DEVIN_API_KEY=your_token npx tsx scripts/devin/export-devin-knowledge.ts"); + console.error(""); + console.error("Get your API token from: https://app.devin.ai/settings/api-keys"); + process.exit(1); + } + + console.log("Exporting Devin Knowledge..."); + + const response = await fetch(`${API_BASE}/knowledge`, { + method: "GET", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + console.error(`Error: API request failed with status ${response.status}`); + console.error(`Response: ${errorText}`); + process.exit(1); + } + + const data: ApiListResponse = await response.json(); + + const timestamp = new Date().toISOString().replace(/[-:]/g, "").replace("T", "_").slice(0, 15); + const backupFile = `devin-knowledge-backup-${timestamp}.json`; + const outputPath = path.join(process.cwd(), backupFile); + + fs.writeFileSync(outputPath, JSON.stringify(data, null, 2)); + + console.log(`Success! Backup saved to: ${backupFile}`); + console.log(` - Folders: ${data.folders.length}`); + console.log(` - Knowledge entries: ${data.knowledge.length}`); +} + +main().catch((error) => { + console.error("Error:", error.message); + process.exit(1); +}); diff --git a/scripts/devin/parse-local-knowledge.ts b/scripts/devin/parse-local-knowledge.ts new file mode 100644 index 0000000000..2b492344c7 --- /dev/null +++ b/scripts/devin/parse-local-knowledge.ts @@ -0,0 +1,203 @@ +#!/usr/bin/env -S npx ts-node + +import * as fs from "fs"; +import * as path from "path"; + +interface DevinKnowledgeEntry { + name: string; + body: string; + trigger_description: string; + folder?: string; +} + +interface DevinKnowledgeFolder { + name: string; + description: string; +} + +interface DevinKnowledgeOutput { + folders: DevinKnowledgeFolder[]; + knowledge: DevinKnowledgeEntry[]; +} + +interface RuleFrontmatter { + title: string; + impact: string; + impactDescription: string; + tags: string; +} + +function parseFrontmatter(content: string): { + frontmatter: RuleFrontmatter | null; + body: string; +} { + const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; + const match = content.match(frontmatterRegex); + + if (!match) { + return { frontmatter: null, body: content }; + } + + const frontmatterStr = match[1]; + const body = match[2].trim(); + + const frontmatter: Partial = {}; + const lines = frontmatterStr.split("\n"); + + for (const line of lines) { + const colonIndex = line.indexOf(":"); + if (colonIndex > 0) { + const key = line.substring(0, colonIndex).trim(); + const value = line.substring(colonIndex + 1).trim(); + (frontmatter as Record)[key] = value; + } + } + + return { + frontmatter: frontmatter as RuleFrontmatter, + body, + }; +} + +function parseRuleFile(filePath: string, fileName: string): DevinKnowledgeEntry { + const content = fs.readFileSync(filePath, "utf-8"); + const { frontmatter, body } = parseFrontmatter(content); + + const name = frontmatter?.title || fileName.replace(".md", "").replace(/-/g, " "); + const tags = frontmatter?.tags || ""; + const impact = frontmatter?.impact || ""; + const impactDesc = frontmatter?.impactDescription || ""; + + let triggerDescription = `Use this rule when working on Cal.com codebase`; + if (tags) { + triggerDescription += ` and the task involves: ${tags}`; + } + if (impact) { + triggerDescription += `. Impact: ${impact}`; + } + + return { + name: `[Rule] ${name}`, + body, + trigger_description: triggerDescription, + folder: "Rules", + }; +} + +function parseKnowledgeBaseSections(filePath: string): DevinKnowledgeEntry[] { + const content = fs.readFileSync(filePath, "utf-8"); + const entries: DevinKnowledgeEntry[] = []; + + const sectionRegex = /^## (.+)$/gm; + const sections: { title: string; startIndex: number }[] = []; + + let match; + while ((match = sectionRegex.exec(content)) !== null) { + sections.push({ + title: match[1], + startIndex: match.index, + }); + } + + for (let i = 0; i < sections.length; i++) { + const section = sections[i]; + const nextSection = sections[i + 1]; + const endIndex = nextSection ? nextSection.startIndex : content.length; + const sectionContent = content.substring(section.startIndex, endIndex).trim(); + + const title = section.title; + // Section titles in knowledge-base.md must start with "When..." (enforced by validate-local-knowledge.ts) + // so we use the title directly as the trigger description + entries.push({ + name: title, + body: sectionContent, + trigger_description: title, + folder: "Domain Knowledge", + }); + } + + return entries; +} + +function parseCommandsFile(filePath: string): DevinKnowledgeEntry { + const content = fs.readFileSync(filePath, "utf-8"); + + return { + name: "Cal.com Build, Test & Development Commands", + body: content, + trigger_description: + "When you need to run commands in the Cal.com repository such as build, test, lint, type-check, database operations, or development server", + folder: "Commands", + }; +} + +function main() { + const agentsDir = path.join(__dirname, "..", "..", "agents"); + const rulesDir = path.join(agentsDir, "rules"); + + const output: DevinKnowledgeOutput = { + folders: [ + { + name: "Rules", + description: "Engineering rules and standards for Cal.com development", + }, + { + name: "Domain Knowledge", + description: "Product and domain-specific knowledge for Cal.com", + }, + { + name: "Commands", + description: "Build, test, and development commands for Cal.com", + }, + ], + knowledge: [], + }; + + // Parse rules directory + if (fs.existsSync(rulesDir)) { + const ruleFiles = fs + .readdirSync(rulesDir) + .filter((f) => f.endsWith(".md") && f !== "README.md" && f !== "_template.md" && f !== "_sections.md"); + + for (const ruleFile of ruleFiles) { + const filePath = path.join(rulesDir, ruleFile); + const entry = parseRuleFile(filePath, ruleFile); + output.knowledge.push(entry); + } + } + + // Parse knowledge-base.md + const knowledgeBasePath = path.join(agentsDir, "knowledge-base.md"); + if (fs.existsSync(knowledgeBasePath)) { + const sections = parseKnowledgeBaseSections(knowledgeBasePath); + output.knowledge.push(...sections); + } + + // Parse commands.md + const commandsPath = path.join(agentsDir, "commands.md"); + if (fs.existsSync(commandsPath)) { + const entry = parseCommandsFile(commandsPath); + output.knowledge.push(entry); + } + + // Write output + const outputPath = path.join(agentsDir, "devin-knowledge.json"); + fs.writeFileSync(outputPath, JSON.stringify(output, null, 2)); + + console.log(`Generated ${output.knowledge.length} knowledge entries in ${output.folders.length} folders`); + console.log(`Output written to: ${outputPath}`); + + // Print summary + const folderCounts: Record = {}; + for (const entry of output.knowledge) { + const folder = entry.folder || "Uncategorized"; + folderCounts[folder] = (folderCounts[folder] || 0) + 1; + } + + console.log("\nSummary by folder:"); + for (const [folder, count] of Object.entries(folderCounts)) { + console.log(` ${folder}: ${count} entries`); + } +} + +main(); diff --git a/scripts/devin/sync-knowledge-to-devin.ts b/scripts/devin/sync-knowledge-to-devin.ts new file mode 100644 index 0000000000..cbad26d194 --- /dev/null +++ b/scripts/devin/sync-knowledge-to-devin.ts @@ -0,0 +1,202 @@ +#!/usr/bin/env -S npx tsx + +/** + * Syncs knowledge from the generated JSON file to Devin's Knowledge API. + * - Creates new knowledge entries + * - Updates existing entries (matched by name) + * - Optionally deletes entries that no longer exist in the source + * + * Note: The Devin API v1 does not support creating folders via API. + * Entries will be created without folder assignment. You can organize + * them into folders manually in the Devin UI if needed. + * + * Usage: DEVIN_API_KEY=your_token npx tsx scripts/devin/sync-knowledge-to-devin.ts [--delete-removed] + */ + +import process from "node:process"; +import * as fs from "fs"; +import * as path from "path"; + +interface DevinKnowledgeEntry { + name: string; + body: string; + trigger_description: string; +} + +interface DevinKnowledgeOutput { + knowledge: DevinKnowledgeEntry[]; +} + +interface ApiKnowledgeEntry { + id: string; + name: string; + body: string; + trigger_description: string; + created_at: string; + created_by?: { + full_name: string; + id: string; + }; + parent_folder_id?: string; +} + +interface ApiListResponse { + folders: { id: string; name: string }[]; + knowledge: ApiKnowledgeEntry[]; +} + +const API_BASE = "https://api.devin.ai/v1"; + +async function apiRequest(method: string, endpoint: string, body?: Record): Promise { + const token = process.env.DEVIN_API_KEY; + if (!token) { + throw new Error("DEVIN_API_KEY environment variable is not set"); + } + + const url = `${API_BASE}${endpoint}`; + const options: RequestInit = { + method, + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + }; + + if (body) { + options.body = JSON.stringify(body); + } + + const response = await fetch(url, options); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API request failed: ${response.status} ${response.statusText} - ${errorText}`); + } + + if (response.status === 204) { + return {} as T; + } + + return response.json() as Promise; +} + +async function listKnowledge(): Promise { + return apiRequest("GET", "/knowledge"); +} + +async function createKnowledge( + name: string, + body: string, + triggerDescription: string +): Promise { + return apiRequest("POST", "/knowledge", { + name, + body, + trigger_description: triggerDescription, + }); +} + +async function updateKnowledge( + noteId: string, + name: string, + body: string, + triggerDescription: string +): Promise { + return apiRequest("PUT", `/knowledge/${noteId}`, { + name, + body, + trigger_description: triggerDescription, + }); +} + +async function deleteKnowledge(noteId: string): Promise { + await apiRequest("DELETE", `/knowledge/${noteId}`); +} + +async function main() { + const args = process.argv.slice(2); + const deleteRemoved = args.includes("--delete-removed"); + + const agentsDir = path.join(path.dirname(__filename), "..", "..", "agents"); + const jsonPath = path.join(agentsDir, "devin-knowledge.json"); + + if (!fs.existsSync(jsonPath)) { + console.error("Error: devin-knowledge.json not found. Run parse-to-devin-knowledge.ts first."); + process.exit(1); + } + + const localData: DevinKnowledgeOutput = JSON.parse(fs.readFileSync(jsonPath, "utf-8")); + + console.log("Fetching existing knowledge from Devin API..."); + const remoteData = await listKnowledge(); + + console.log( + `Found ${remoteData.folders.length} folders and ${remoteData.knowledge.length} entries in Devin\n` + ); + + // Build knowledge name -> entry map for remote + const remoteKnowledgeMap = new Map(); + for (const entry of remoteData.knowledge) { + remoteKnowledgeMap.set(entry.name, entry); + } + + // Track which remote entries we've seen + const seenRemoteIds = new Set(); + + // Sync knowledge entries + console.log("\nSyncing knowledge entries..."); + let created = 0; + let updated = 0; + let unchanged = 0; + + for (const entry of localData.knowledge) { + const existing = remoteKnowledgeMap.get(entry.name); + + if (existing) { + seenRemoteIds.add(existing.id); + + const bodyChanged = existing.body !== entry.body; + const triggerChanged = existing.trigger_description !== entry.trigger_description; + + if (bodyChanged || triggerChanged) { + console.log(` Updating: ${entry.name}`); + await updateKnowledge(existing.id, entry.name, entry.body, entry.trigger_description); + updated++; + } else { + unchanged++; + } + } else { + console.log(` Creating: ${entry.name}`); + await createKnowledge(entry.name, entry.body, entry.trigger_description); + created++; + } + } + + // Delete removed entries if flag is set + let deleted = 0; + if (deleteRemoved) { + console.log("\nChecking for entries to delete..."); + for (const entry of remoteData.knowledge) { + if (!seenRemoteIds.has(entry.id)) { + console.log(` Deleting: ${entry.name}`); + await deleteKnowledge(entry.id); + deleted++; + } + } + } + + // Summary + console.log("\n--- Sync Summary ---"); + console.log(` Created: ${created}`); + console.log(` Updated: ${updated}`); + console.log(` Unchanged: ${unchanged}`); + if (deleteRemoved) { + console.log(` Deleted: ${deleted}`); + } + console.log("Sync complete!"); +} + +main().catch((error) => { + console.error("Error:", error.message); + process.exit(1); +}); diff --git a/scripts/devin/validate-local-knowledge.ts b/scripts/devin/validate-local-knowledge.ts new file mode 100644 index 0000000000..2ecaf916c7 --- /dev/null +++ b/scripts/devin/validate-local-knowledge.ts @@ -0,0 +1,165 @@ +#!/usr/bin/env -S npx ts-node + +/** + * Validates the format of files in the agents/ directory. + * - Rules files must have proper frontmatter (title, tags) + * - Knowledge-base sections must start with "When..." + * + * Exit code 0 = valid, Exit code 1 = invalid + */ + +import process from "node:process"; +import * as fs from "fs"; +import * as path from "path"; + +interface ValidationError { + file: string; + message: string; +} + +interface RuleFrontmatter { + title?: string; + impact?: string; + impactDescription?: string; + tags?: string; +} + +function parseFrontmatter(content: string): { + frontmatter: RuleFrontmatter | null; + body: string; +} { + const frontmatterRegex = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/; + const match = content.match(frontmatterRegex); + + if (!match) { + return { frontmatter: null, body: content }; + } + + const frontmatterStr = match[1]; + const body = match[2].trim(); + + const frontmatter: RuleFrontmatter = {}; + const lines = frontmatterStr.split("\n"); + + for (const line of lines) { + const colonIndex = line.indexOf(":"); + if (colonIndex > 0) { + const key = line.substring(0, colonIndex).trim(); + const value = line.substring(colonIndex + 1).trim(); + (frontmatter as Record)[key] = value; + } + } + + return { frontmatter, body }; +} + +function validateRuleFile(filePath: string, fileName: string): ValidationError[] { + const errors: ValidationError[] = []; + const content = fs.readFileSync(filePath, "utf-8"); + const { frontmatter } = parseFrontmatter(content); + + if (!frontmatter) { + errors.push({ + file: `rules/${fileName}`, + message: "Missing YAML frontmatter. Rules must have frontmatter with title and tags.", + }); + return errors; + } + + if (!frontmatter.title || frontmatter.title.trim() === "") { + errors.push({ + file: `rules/${fileName}`, + message: "Missing 'title' in frontmatter. Add a descriptive title for this rule.", + }); + } + + if (!frontmatter.tags || frontmatter.tags.trim() === "") { + errors.push({ + file: `rules/${fileName}`, + message: + "Missing 'tags' in frontmatter. Add comma-separated tags to help Devin know when to apply this rule.", + }); + } + + return errors; +} + +function validateKnowledgeBase(filePath: string): ValidationError[] { + const errors: ValidationError[] = []; + const content = fs.readFileSync(filePath, "utf-8"); + + // Find all ## headers + const sectionRegex = /^## (.+)$/gm; + let match; + const invalidSections: string[] = []; + + while ((match = sectionRegex.exec(content)) !== null) { + const title = match[1].trim(); + + // Section titles must start with "When..." for clear trigger descriptions + if (!title.toLowerCase().startsWith("when ")) { + invalidSections.push(title); + } + } + + if (invalidSections.length > 0) { + errors.push({ + file: "knowledge-base.md", + message: `The following sections don't have clear trigger descriptions. Consider renaming them to start with "When..." to help Devin know when to use this knowledge:\n${invalidSections.map((s) => ` - "${s}"`).join("\n")}`, + }); + } + + return errors; +} + +function main() { + const agentsDir = path.join(path.dirname(__filename), "..", "..", "agents"); + const rulesDir = path.join(agentsDir, "rules"); + const knowledgeBasePath = path.join(agentsDir, "knowledge-base.md"); + + const allErrors: ValidationError[] = []; + + console.log("Validating agents/ directory format...\n"); + + // Validate rules files + if (fs.existsSync(rulesDir)) { + const ruleFiles = fs + .readdirSync(rulesDir) + .filter((f) => f.endsWith(".md") && f !== "README.md" && f !== "_template.md" && f !== "_sections.md"); + + console.log(`Checking ${ruleFiles.length} rule files...`); + + for (const ruleFile of ruleFiles) { + const filePath = path.join(rulesDir, ruleFile); + const errors = validateRuleFile(filePath, ruleFile); + allErrors.push(...errors); + } + } + + // Validate knowledge-base.md + if (fs.existsSync(knowledgeBasePath)) { + console.log("Checking knowledge-base.md..."); + const errors = validateKnowledgeBase(knowledgeBasePath); + allErrors.push(...errors); + } + + // Report results + console.log(""); + + if (allErrors.length === 0) { + console.log("All files are valid!"); + process.exit(0); + } else { + console.log(`Found ${allErrors.length} validation error(s):\n`); + + for (const error of allErrors) { + console.log(`[ERROR] ${error.file}`); + console.log(` ${error.message}\n`); + } + + console.log("Please fix the above errors before merging."); + process.exit(1); + } +} + +main();