Skip to content

Latest commit

 

History

History
278 lines (221 loc) · 13 KB

File metadata and controls

278 lines (221 loc) · 13 KB

Contributing Guidelines

Welcome! This document describes how to contribute to this repository. Following these guidelines helps us maintain a clean history, consistent quality, and smooth review process. All pull requests are subject to automated and manual review against these guidelines.


Commit Messages and PR Titles

All commits and PR titles must follow the Conventional Commits format. Both the type (feat, fix, chore, etc.) and the scope (the component in parentheses) are required. To indicate a breaking change, append ! after the scope (e.g., feat!: ...).

We use this format to determine version bumps and to generate changelogs. It applies to both commit messages and PR titles, since PRs are merged using squash and the PR title becomes the commit message.

Examples of Good Messages

feat: Add new tool for fetching actor details
feat!: Migrate to new MCP SDK version [internal]
fix: Handle connection errors gracefully
refactor: Improve type definitions [ignore]
chore: Update dependencies

Pull Request Descriptions

Your PR description should extend your commit message:

  • Explain what, why, and how.
  • Mention potential risks.
  • Provide instructions for reviewers.
  • Link related issues or resources.

Pull Request Comments

Use comments to guide reviewers:

  • Flag code that was just moved, so they don't re-review it.
  • Explain reasoning behind non-obvious changes.
  • Highlight extra cleanup or unrelated fixes.

Pull Request Size

  • Aim for ≤ 300 lines changed.
  • Large PRs should be split into smaller, focused changes.

Coding Standards & Pitfalls

Key reminders

  • Keep logic flat: avoid deep nesting and unnecessary else; return early instead.
  • Readability first: small, focused functions and consistent naming.
  • Error handling: always handle and propagate errors clearly.
  • Minimal parameters: functions should only accept what they actually use.
  • Reuse utilities: prefer existing helpers instead of re-implementing logic.
  • Consistency reinforcement: rely on the shared ESLint config (@apify/eslint-config) and EditorConfig settings to enforce standards automatically.

Standards

  • Avoid else: Return early to reduce indentation and keep logic flat.

  • Naming: Use full, descriptive names. Avoid single-letter variables except for common loop indices (e.g., i for index).

    • Constants: When a constant is global (defined at the module's top level), immutable, and optionally exported, use uppercase SNAKE_CASE format.
      • If a different applicable naming rule is defined below, that rule takes precedence (e.g., for classes, components, functions, Zod validators, schemas, ...).
    • Functions & Variables: Use camelCase format.
    • Classes, Types, Schemas, Components: Use capitalized PascalCase format.
    • Files & Folders: Use lowercase snake_case format.
    • Endpoint Paths: Use lowercase kebab-case format.
    • Booleans: Prefix with is, has, or should (e.g., isValid, hasFinished, shouldRetry).
    • Units: Suffix with the unit of measure (e.g., externalCostUsd, intervalMillis).
    • Date/Time: Suffix with At (e.g., updateStartedAt, paidAt).
    • Zod Validators: Suffix with Validator.
    • Text/Copy: Use the branded term Actor (capitalized) instead of actor in user-facing texts, labels, notifications, error messages, etc.
  • Comments:

    • Use proper English (spelling, grammar, punctuation, capitalization).
    • Use JSDoc /** for documentation, // for generic comments, and avoid /* (single asterix multiline comments).
  • Parameters

    • Minimal Parameters: Pass only the parameters that the function actually uses.
      • When the parameter is an object (e.g., User), include only the fields used by the function instead of passing full objects with unnecessary data.
      • Use TypeScript generics or utility types to preserve correct typing while narrowing the shape.
        export const getTransformedUser = <TUser extends FieldsRequiredForGetTransformedUser>(
            user: TUser,
        ): TUser & TransformedUserFields => { /* ... */ };
    • Function Parameters:
      • You may define a function with a comma-separated list of parameters only if it has up to 3 parameters.
        public async getActorBasicInfo(
            actorId: string,
            impersonatedUserId: string,
            token: string
        ): Promise<ActorBasicInfo | null> { /* ... */ };
      • If the function has more than 3 parameters, define it with a single object parameter that contains them.
        private ensureActorAccess = async ({ actorId, userId, token, req }: {
            actorId: string;
            userId: string;
            token: string;
            req: AuthenticatedRequest;
        }) => { /* ... */ };
      • Optional parameters must be at the end of the list.
    • Optional Parameters (? vs | undefined):
      • Use ? if calling the function without the parameter makes sense.
      • Use | undefined if the parameter should be passed but might be undefined for a specific reason (e.g., not found).
  • Async functions

    • await vs void:
      • Use await when you care about the Promise result or exceptions.
      • Use void when you don't need to wait for the Promise (e.g., fire-and-forget operations).
    • Use return await when returning Promises:
      • Ensures that exceptions are thrown within the current function, preserving accurate stack traces and making debugging easier.
  • Enumerations:

    • Define as as const object instead of TypeScript enum.
    • Name both the enumeration object and its keys in singular uppercase SNAKE_CASE format.
    • Ensure that each key and its value are identical.
    • Create a TypeScript type with the same name as the enumeration object.
      export const ACTOR_STATUS = {
          READY: 'READY',
          RUNNING: 'RUNNING',
          SUCCEEDED: 'SUCCEEDED',
      } as const;
      export type ACTOR_STATUS = ValueOf<typeof ACTOR_STATUS>;
  • type vs interface:

    • Prefer type for flexibility.
    • Use interface only when it's required for class implementations (implements).
  • Types organization:

    • Centralize shared/public types in src/types.ts.
    • Co-locate module-specific types next to their usage (same file or a local types.ts).
    • Use a folder-level types.ts when multiple files in a folder share types, instead of inflating the root src/types.ts.
  • Code Structure:

    • Keep functions short, focused, and cohesive.
    • Declare variables close to their first use.
    • Extract reusable or complex logic into named helper functions.
    • Avoid intermediate variables for single-use expressions — don't create a variable if it's only used once; inline it directly.
      • ❌ Don't: const docSourceEnum = z.enum([...]); const schema = z.object({ docSource: docSourceEnum })
      • ✅ Do: const schema = z.object({ docSource: z.enum([...]) })
      • Exception: Only create intermediate variables if they improve readability for complex expressions.
  • Immutability:

    • Avoid mutating function parameters.
    • If mutation is absolutely necessary (e.g., for performance), clearly document and explain it with a comment.
  • Imports:

    • Imports are ordered and grouped automatically by ESLint: builtin → external → parent/sibling → index → object, alphabetized within groups, with newlines between groups.
    • Use import type for type-only imports.
    • Do not duplicate imports — reuse existing ones.
    • Do not use dynamic imports unless explicitly required.
  • Readability vs. Performance:

    • Prioritize readability over micro-optimizations.
    • For performance-critical code, optimize only when justified and document the reasoning.
  • Assertions & Validations:

    • Use Zod for schema-based validation of complex shapes or intricate checks.
    • Use custom validators and assertions for lightweight, in-code checks - e.g. validating primitive values, simple shapes, or decision logic inside functions.
    • No double validation: When using Zod schemas with AJV (ajvValidate in tool definitions), do NOT add redundant manual validation — let the schema handle it. Use the parsed data directly.
  • Error Handling:

    • User Errors:
      • Use appropriate error codes (4xx for client errors).
      • Log as softFail or appropriate level.
    • Internal Errors:
      • Use appropriate error codes (5xx for server errors).
      • Log with log.exception, log.error, or appropriate error logging.
    • Don't log then throw: Do NOT call log.error() immediately before throwing — the error will be logged by the caller. This creates duplicate logs and violates separation of concerns.
      • ❌ Don't: log.error('Something failed'); throw new Error('Something failed');
      • ✅ Do: throw new Error('Something failed');
  • Logging:

    • Log meaningful information for debugging, especially errors in critical system parts.
    • Use appropriate log levels:
      • softFail - client errors
      • exception / error - server errors
      • warn - suspicious but non-critical behavior
      • info - progress or important state changes
      • debug - local development
  • Sensitive Data:

    • Never send sensitive information without proper permission checks.
    • Sanitize data before sending or logging.
    • Use appropriate data structures to limit exposed fields.
  • Common patterns:

    • Tool implementation: Tools are defined in src/tools/ using Zod schemas for validation.
    • Actor interaction: Use src/utils/apify_client.ts for Apify API calls — never call the Apify API directly.
    • Error responses: Return user-friendly error messages with suggestions.
    • Input validation: Always validate tool inputs with Zod before processing.
    • Caching: Use TTL-based caching for Actor schemas and details (see src/utils/ttl_lru.ts).
    • Constants and tool names: Always use constants and never hardcoded values. When referring to tools, ALWAYS use the HelperTools enum.
      • Exception: Integration tests (tests/integration/) must use hardcoded strings for tool names. This ensures tests fail if a tool is renamed, preventing accidental breaking changes.
  • Anti-patterns:

    • Don't call Apify API directly — always use the Apify client utilities.
    • Don't mutate function parameters without clear documentation.
    • Don't skip input validation — all tool inputs must be validated with Zod.
    • Don't use Promise.then() — prefer async/await.
    • Don't create tools without proper error handling and user-friendly messages.
    • Don't add features not in the requirements.
    • Don't refactor working code unless asked.
    • Don't add error handling for impossible scenarios.
    • Don't create abstractions for one-time operations.
    • Don't optimize prematurely.

Design System Compliance (MANDATORY)

READ FIRST: DESIGN_SYSTEM_AGENT_INSTRUCTIONS.md — Complete design system rules.

Quick Rules (Zero tolerance):

  • ✅ ALWAYS use theme.* tokens (colors, spacing)
  • ❌ NEVER hardcode: #hex, rgb(), Npx values, font sizes
  • ✅ Import from @apify/ui-library only
  • ✅ Check mcp__storybook__* and mcp__figma__* availability before UI work
  • ✅ Call mcp__storybook__get-ui-building-instructions first
  • ✅ Read 1-3 similar components for patterns (max 3 files)
  • ✅ Verify zero hardcoded values before submitting

Figma Integration: Call mcp__figma__get_design_context when working from designs.


Development Setup

For local development setup, scripts, and manual testing, see DEVELOPMENT.md.


Code Review Guidelines

  • Make two passes on substantial PRs:
    • First: understand changes at a high level.
    • Second: focus on details.
  • If the PR is too complex, suggest refactoring.
  • Use the important, suggestion, and nit keywords to indicate how crucial the comment is.

Testing

  • Write tests for new features and bug fixes.
  • Ensure all tests pass before submitting a PR.
  • Aim for good test coverage, especially for critical paths.
  • Use descriptive test names that explain what is being tested.

Documentation

  • Update README.md if adding new features or changing behavior.
  • Add JSDoc comments for public APIs.
  • Keep code comments clear and concise.

Questions?

If you have questions or need help, please:

  • Open an issue for discussion
  • Check existing issues and PRs
  • Review the codebase for examples