1. Главная
  2. Docs
  3. Документация по инструментам и технологиям
  4. Cursor
  5. Организация командной работы с Cursor | Глава 12

Организация командной работы с Cursor | Глава 12

18 октября 2025
136

Глава 12. Работа в команде

12.1. Коллаборативная разработка

Организация командной работы с Cursor

Cursor создан для индивидуальной работы, но его можно эффективно использовать в команде через общие практики и конфигурации.

Командная структура проекта:


project/
├── .cursor/
│   ├── index.mdc              # Общие правила команды
│   ├── team-settings.json     # Настройки команды
│   └── workflows/             # Рабочие процессы
│       ├── feature.mdc
│       ├── bugfix.mdc
│       └── refactor.mdc
├── docs/
│   ├── cursor-setup.md        # Инструкция по настройке
│   ├── ai-guidelines.md       # Гайдлайны по работе с AI
│   └── team-conventions.md    # Соглашения команды
└── .cursorignore              # Общие исключения

Командный конфигурационный файл:


// .cursor/team-settings.json

{
"team": {
"name": "Product Development Team",
"timezone": "UTC",
"workingHours": {
"start": "09:00",
"end": "18:00"
}
},
"workflow": {
"branchNaming": {
"feature": "feature/{ticket-number}-{description}",
"bugfix": "bugfix/{ticket-number}-{description}",
"hotfix": "hotfix/{ticket-number}-{description}"
},
"commitConvention": "conventional-commits",
"prTemplate": ".github/pull_request_template.md"
},
"codeReview": {
"required": true,
"minReviewers": 2,
"autoAssign": true,
"labels": ["needs-review", "ai-assisted"]
},
"ai": {
"sharedRules": true,
"rulesVersion": "1.2.0",
"customInstructions": [
"Follow team code style guide",
"Add JSDoc comments for all public APIs",
"Write tests for new features",
"Update documentation when changing APIs"
]
}
}

Командный Rules файл:


# .cursor/index.mdc

---
title: "Team Development Guidelines"
version: "1.2.0"
team: "Product Development"
lastUpdated: "2024-01-15"
---

# Team Context

## Our Team
We are a distributed team of 12 developers working on a SaaS product.
We value code quality, collaboration, and continuous learning.

## Tech Stack
- **Frontend**: React 18, TypeScript, Tailwind CSS
- **Backend**: Node.js, Express, PostgreSQL
- **Infrastructure**: AWS, Docker, Kubernetes
- **Testing**: Jest, Playwright, Cypress

## Development Workflow

### Branch Strategy
We use Git Flow:
- `main` - production code
- `develop` - integration branch
- `feature/*` - new features
- `bugfix/*` - bug fixes
- `hotfix/*` - urgent production fixes

### Commit Convention
We follow Conventional Commits:

type(scope): subject

body

footer



Types:
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation
- `style`: Formatting
- `refactor`: Code restructuring
- `test`: Adding tests
- `chore`: Maintenance

Example:

feat(auth): add two-factor authentication

Implement 2FA using TOTP tokens.
Users can enable it in settings.

Closes #123



### Pull Request Process

1. **Create PR from feature branch to develop**
2. **Fill PR template** (description, testing, screenshots)
3. **Request reviews** (auto-assigned to 2 reviewers)
4. **AI pre-review** (automated checks)
5. **Address feedback**
6. **Merge when approved**

## Code Standards

### Naming Conventions

**Files:**
- Components: `PascalCase.tsx` (UserProfile.tsx)
- Utilities: `camelCase.ts` (formatDate.ts)
- Constants: `UPPER_SNAKE_CASE.ts`
- Tests: `*.test.ts` or `*.spec.ts`

**Code:**
- Variables/Functions: `camelCase`
- Classes/Components: `PascalCase`
- Constants: `UPPER_SNAKE_CASE`
- Private methods: `_camelCase`

**Database:**
- Tables: `snake_case` (user_profiles)
- Columns: `snake_case`
- Indexes: `idx_{table}_{column}`

### File Structure

React Component:

components/UserProfile/
├── index.ts # Export
├── UserProfile.tsx # Component
├── UserProfile.test.tsx # Tests
├── UserProfile.module.css # Styles
├── types.ts # TypeScript types
└── README.md # Component docs



API Module:

api/users/
├── index.ts # Export
├── users.routes.ts # Routes
├── users.controller.ts # Controllers
├── users.service.ts # Business logic
├── users.repository.ts # Data access
├── users.validator.ts # Validation
├── users.test.ts # Tests
└── types.ts # Types



### Code Quality Requirements

**Every Pull Request Must:**
-  Pass all tests (100% of existing tests)
-  Have 80%+ code coverage for new code
-  Pass ESLint with no warnings
-  Pass TypeScript checks
-  Include tests for new features
-  Update documentation if APIs changed
-  Be approved by 2+ reviewers

**Documentation:**
- All exported functions need JSDoc
- Complex logic needs inline comments
- README for each major module
- API changes need changelog entry

Example:
```typescript
/**
 * Calculates the discounted price for a product
 * 
 * @param price - Original price in cents
 * @param discountCode - Discount code to apply
 * @returns Final price in cents after discount and tax
 * 
 * @throws {InvalidDiscountCodeError} If discount code is invalid
 * @throws {DiscountExpiredError} If discount code has expired
 * 
 * @example
 * const finalPrice = calculateDiscountedPrice(10000, 'SAVE20');
 * // Returns: 8640 (10000 * 0.8 * 1.08 for 8% tax)
 */
export function calculateDiscountedPrice(
  price: number,
  discountCode?: string
): number {
  // Implementation
}

Testing Requirements

Unit Tests:


// Good test structure

describe('UserService', () => {
describe('createUser', () => {
it('should create user with valid data', async () => {
// Arrange
const userData = { email: 'test@example.com', name: 'Test User' };

// Act
const user = await userService.createUser(userData);

// Assert
expect(user).toMatchObject(userData);
expect(user.id).toBeDefined();
});

it('should throw error for duplicate email', async () => {
// Test error case
await expect(
userService.createUser({ email: 'existing@example.com' })
).rejects.toThrow('Email already exists');
});
});
});

Integration Tests:

  • Test API endpoints end-to-end
  • Use test database
  • Clean up after each test
  • Mock external services

E2E Tests:

  • Cover critical user flows
  • Run before deployment
  • Use realistic test data

Communication

When to Use AI vs Ask Team

Use AI for:

  • Generating boilerplate code
  • Writing tests
  • Refactoring
  • Finding bugs
  • Documentation
  • Code explanations

Ask Team for:

  • Architecture decisions
  • Business logic questions
  • Security concerns
  • Performance trade-offs
  • API design
  • Breaking changes

Code Review Comments

When reviewing AI-generated code, always verify:

  •  Logic correctness
  •  Security implications
  •  Performance impact
  •  Test coverage
  •  Documentation quality
  •  Alignment with team standards

AI Usage Guidelines

Best Practices

Do:

  •  Use AI for repetitive tasks
  •  Review all AI-generated code
  •  Share useful prompts with team
  •  Document AI usage in PR description
  •  Use AI to learn new patterns

Don't:

  •  Blindly accept AI suggestions
  •  Use AI for critical security code without review
  •  Share sensitive data with AI
  •  Replace code review with AI
  •  Skip testing AI-generated code

Prompt Templates

Store common prompts in team wiki:

Feature Implementation:


Implement [feature name] following our team conventions:
- Use TypeScript with strict mode
- Add comprehensive tests
- Include JSDoc documentation
- Follow our error handling pattern
- Add to existing [module name]

Requirements:
- [Requirement 1]
- [Requirement 2]

Context:
@[relevant files]

Bug Fix:


Fix bug: [bug description]

Expected behavior:
[What should happen]

Current behavior:
[What actually happens]

Steps to reproduce:
1. [Step 1]
2. [Step 2]

Related files:
@[files with bug]

Our debugging approach:
- Add logging first
- Write failing test
- Fix implementation
- Verify test passes

Onboarding

New team members should:

  1. Read this document
  2. Setup Cursor with team settings
  3. Review team conventions
  4. Pair with senior developer for first week
  5. Complete onboarding tasks using AI

Version Control

This rules file is versioned. When updating:

  1. Create PR with changes
  2. Get team approval
  3. Update version number
  4. Notify team in Slack
  5. Update docs/cursor-setup.md

Questions?

  • Slack: #dev-cursor-help
  • Wiki: wiki.company.com/cursor
  • Team Lead: @tech-lead


**Скрипт синхронизации настроек команды:**

```typescript
// scripts/sync-team-settings.ts
import * as fs from 'fs';
import * as path from 'path';
import fetch from 'node-fetch';

interface TeamSettings {
  version: string;
  rules: string;
  settings: any;
  lastUpdated: string;
}

class TeamSettingsSync {
  private remoteUrl: string;
  private localPath: string;

  constructor(remoteUrl: string) {
    this.remoteUrl = remoteUrl;
    this.localPath = path.join(process.cwd(), '.cursor');
  }

  /**
   * Проверить наличие обновлений
   */
  async checkForUpdates(): Promise<boolean> {
    try {
      const remote = await this.fetchRemoteSettings();
      const local = this.loadLocalSettings();

      if (!local) return true;

      return remote.version !== local.version;
    } catch (error) {
      console.error('Error checking for updates:', error);
      return false;
    }
  }

  /**
   * Синхронизировать настройки
   */
  async sync(force: boolean = false): Promise<void> {
    console.log('Syncing team settings...\n');

    const hasUpdates = await this.checkForUpdates();

    if (!hasUpdates && !force) {
      console.log('✓ Settings are up to date');
      return;
    }

    const remote = await this.fetchRemoteSettings();
    const local = this.loadLocalSettings();

    if (local && !force) {
      // Show diff
      console.log('Updates available:');
      console.log(`  Current version: ${local.version}`);
      console.log(`  New version: ${remote.version}`);
      console.log(`  Updated: ${remote.lastUpdated}`);
      console.log('\nChanges:');
      this.showDiff(local, remote);

      // Confirm
      const readline = require('readline').createInterface({
        input: process.stdin,
        output: process.stdout
      });

      const answer = await new Promise<string>((resolve) => {
        readline.question('\nApply updates? (y/n): ', resolve);
      });

      readline.close();

      if (answer.toLowerCase() !== 'y') {
        console.log('Sync cancelled');
        return;
      }
    }

    // Apply settings
    this.applySettings(remote);

    console.log('\n✓ Team settings synchronized');
  }

  private async fetchRemoteSettings(): Promise<TeamSettings> {
    const response = await fetch(`${this.remoteUrl}/team-settings.json`);
    
    if (!response.ok) {
      throw new Error(`Failed to fetch settings: ${response.statusText}`);
    }

    return await response.json();
  }

  private loadLocalSettings(): TeamSettings | null {
    const settingsPath = path.join(this.localPath, 'team-settings.json');
    
    if (!fs.existsSync(settingsPath)) {
      return null;
    }

    return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
  }

  private applySettings(settings: TeamSettings): void {
    // Ensure .cursor directory exists
    fs.mkdirSync(this.localPath, { recursive: true });

    // Save settings
    fs.writeFileSync(
      path.join(this.localPath, 'team-settings.json'),
      JSON.stringify(settings, null, 2)
    );

    // Save rules
    fs.writeFileSync(
      path.join(this.localPath, 'index.mdc'),
      settings.rules
    );

    // Apply additional settings if any
    if (settings.settings) {
      const cursorSettings = path.join(this.localPath, 'settings.json');
      fs.writeFileSync(
        cursorSettings,
        JSON.stringify(settings.settings, null, 2)
      );
    }
  }

  private showDiff(local: TeamSettings, remote: TeamSettings): void {
    // Simplified diff showing
    const localRules = local.rules.split('\n');
    const remoteRules = remote.rules.split('\n');

    let changes = 0;
    for (let i = 0; i < Math.max(localRules.length, remoteRules.length); i++) {
      if (localRules[i] !== remoteRules[i]) {
        changes++;
        if (changes <= 5) {
          console.log(`  Line ${i + 1}:`);
          console.log(`    - ${localRules[i] || '(deleted)'}`);
          console.log(`    + ${remoteRules[i] || '(added)'}`);
        }
      }
    }

    if (changes > 5) {
      console.log(`  ... and ${changes - 5} more changes`);
    }
  }
}

// CLI
async function main() {
  const remoteUrl = process.env.TEAM_SETTINGS_URL || 
                    'https://github.com/company/cursor-config/raw/main';

  const sync = new TeamSettingsSync(remoteUrl);

  const command = process.argv[2];

  switch (command) {
    case 'check':
      const hasUpdates = await sync.checkForUpdates();
      console.log(hasUpdates ? 'Updates available' : 'Up to date');
      break;

    case 'sync':
      await sync.sync();
      break;

    case 'force':
      await sync.sync(true);
      break;

    default:
      console.log('Usage:');
      console.log('  npm run team-sync check  - Check for updates');
      console.log('  npm run team-sync sync   - Sync settings');
      console.log('  npm run team-sync force  - Force sync');
  }
}

main().catch(console.error);

12.2. Обмен настройками и правилами

Git-based конфигурация для команды


# Создание репозитория для командных настроек

mkdir cursor-team-config
cd cursor-team-config
git init

# Структура
cursor-team-config/
├── README.md
├── rules/
│ ├── index.mdc # Базовые правила
│ ├── frontend.mdc # Frontend-специфичные
│ ├── backend.mdc # Backend-специфичные
│ └── security.mdc # Security guidelines
├── templates/
│ ├── component.template.tsx
│ ├── api-endpoint.template.ts
│ └── test.template.ts
├── snippets/
│ ├── react.json
│ ├── typescript.json
│ └── jest.json
└── scripts/
├── install.sh
└── update.sh

README для командной конфигурации:


# Cursor Team Configuration


Shared Cursor IDE configuration for [Team Name].

## Quick Start

### Installation

```bash
# Clone this repository
git clone https://github.com/company/cursor-team-config.git

# Run installation script
cd cursor-team-config
./scripts/install.sh

Manual Installation

  1. Copy rules to your project:


    cp -r rules/* /path/to/your/project/.cursor/

  2. Copy snippets to Cursor:


    cp snippets/* ~/.cursor/snippets/

  3. Restart Cursor

What's Included

Rules

  • index.mdc - Core team conventions and code standards
  • frontend.mdc - React/TypeScript specific guidelines
  • backend.mdc - Node.js/API development patterns
  • security.mdc - Security best practices

Templates

Pre-configured templates for common file types:

  • React components
  • API endpoints
  • Test files
  • Documentation

Snippets

Code snippets for frequently used patterns:

  • React hooks
  • Express routes
  • Database queries
  • Test cases

Updating Configuration

Configuration is versioned and updated regularly.

Check for updates:


cd cursor-team-config

git pull origin main
./scripts/update.sh

Contributing:

  1. Create branch: git checkout -b improve/description
  2. Make changes
  3. Create PR
  4. Get team approval
  5. Merge to main

Version History

v1.2.0 (2024-01-15)

  • Added security rules
  • Updated TypeScript conventions
  • New testing guidelines

v1.1.0 (2023-12-01)

  • Enhanced frontend rules
  • Added code review checklist
  • New snippets for common patterns

Support

  • Questions: Slack #dev-cursor
  • Issues: GitHub Issues
  • Documentation: wiki.company.com/cursor


**Installation Script:**

```bash
#!/bin/bash
# scripts/install.sh

set -e

echo "Installing Cursor Team Configuration"
echo "======================================"

# Detect OS
OS="$(uname -s)"
case "${OS}" in
    Linux*)     CURSOR_DIR="$HOME/.config/Cursor";;
    Darwin*)    CURSOR_DIR="$HOME/Library/Application Support/Cursor";;
    MINGW*|MSYS*|CYGWIN*) CURSOR_DIR="$APPDATA/Cursor";;
    *)          echo "Unsupported OS: ${OS}"; exit 1;;
esac

echo "Cursor directory: $CURSOR_DIR"

# Check if Cursor is installed
if [ ! -d "$CURSOR_DIR" ]; then
    echo "Error: Cursor not found. Please install Cursor first."
    exit 1
fi

# Backup existing settings
BACKUP_DIR="$CURSOR_DIR/backup-$(date +%Y%m%d-%H%M%S)"
if [ -d "$CURSOR_DIR/User" ]; then
    echo "Backing up existing settings to $BACKUP_DIR"
    mkdir -p "$BACKUP_DIR"
    cp -r "$CURSOR_DIR/User" "$BACKUP_DIR/"
fi

# Install rules
echo "Installing rules..."
mkdir -p "$CURSOR_DIR/User/cursor-rules"
cp -r rules/* "$CURSOR_DIR/User/cursor-rules/"

# Install snippets
echo "Installing snippets..."
mkdir -p "$CURSOR_DIR/User/snippets"
cp snippets/* "$CURSOR_DIR/User/snippets/"

# Install templates
echo "Installing templates..."
mkdir -p "$CURSOR_DIR/User/templates"
cp templates/* "$CURSOR_DIR/User/templates/"

# Create project-specific config
read -p "Do you want to setup project-specific config? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    read -p "Enter project path: " PROJECT_PATH
    
    if [ -d "$PROJECT_PATH" ]; then
        mkdir -p "$PROJECT_PATH/.cursor"
        
        # Create symlinks to shared rules
        ln -sf "$CURSOR_DIR/User/cursor-rules/index.mdc" "$PROJECT_PATH/.cursor/index.mdc"
        
        echo "✓ Project configured: $PROJECT_PATH"
    else
        echo "  Project path not found: $PROJECT_PATH"
    fi
fi

echo ""
echo "======================================"
echo "✓ Installation complete!"
echo "======================================"
echo ""
echo "Next steps:"
echo "1. Restart Cursor"
echo "2. Open your project"
echo "3. Verify rules are loaded (check status bar)"
echo ""
echo "Backup location: $BACKUP_DIR"

Update Script:


#!/bin/bash

# scripts/update.sh

set -e

echo "Updating Cursor Team Configuration"
echo "===================================="

# Pull latest changes
git pull origin main

# Detect OS
OS="$(uname -s)"
case "${OS}" in
Linux*) CURSOR_DIR="$HOME/.config/Cursor";;
Darwin*) CURSOR_DIR="$HOME/Library/Application Support/Cursor";;
MINGW*|MSYS*|CYGWIN*) CURSOR_DIR="$APPDATA/Cursor";;
esac

# Check version
CURRENT_VERSION=$(grep "version:" rules/index.mdc | head -1 | cut -d'"' -f2)
INSTALLED_VERSION=""

if [ -f "$CURSOR_DIR/User/cursor-rules/index.mdc" ]; then
INSTALLED_VERSION=$(grep "version:" "$CURSOR_DIR/User/cursor-rules/index.mdc" | head -1 | cut -d'"' -f2)
fi

echo "Current version: $CURRENT_VERSION"
echo "Installed version: ${INSTALLED_VERSION:-none}"

if [ "$CURRENT_VERSION" == "$INSTALLED_VERSION" ]; then
echo "✓ Already up to date"
exit 0
fi

# Update files
echo "Updating rules..."
cp -r rules/* "$CURSOR_DIR/User/cursor-rules/"

echo "Updating snippets..."
cp snippets/* "$CURSOR_DIR/User/snippets/"

echo "Updating templates..."
cp templates/* "$CURSOR_DIR/User/templates/"

echo ""
echo "✓ Update complete!"
echo "Please restart Cursor to apply changes"

12.3. Code review с помощью AI

AI-assisted code review процесс


# docs/ai-code-review.md


# AI-Assisted Code Review Guide

## Overview

We use AI to augment (not replace) human code review.
AI helps catch common issues, but human judgment is essential.

## Review Process

### 1. Automated AI Pre-Review

Before requesting human review:

```bash
# Run AI pre-review
npm run ai-review

This checks:

  • Code style compliance
  • Common bugs and anti-patterns
  • Security vulnerabilities
  • Performance issues
  • Missing tests
  • Documentation completeness

2. Human Review

After AI pre-review passes, request human reviewers.

Reviewers should focus on:

  • Business logic correctness
  • Architecture decisions
  • API design
  • User experience
  • Edge cases
  • Integration with existing code

3. AI Review Assistance

During review, use AI to:

Understand unfamiliar code:


You: @file.ts Explain what this module does and how it works

Check for issues:


You: Review this code for potential bugs and security issues
[Paste code or reference files]

Suggest improvements:


You: How can this code be improved for better performance and readability?

Generate test cases:


You: What edge cases should be tested for this function?
@function-name

AI Review Checklist

Use this prompt for comprehensive review:


Review the following changes for our code review:

@git-diff

Focus on:
1. **Correctness**: Does the code do what it's supposed to do?
2. **Security**: Any potential security vulnerabilities?
3. **Performance**: Any performance concerns?
4. **Testing**: Are tests adequate?
5. **Documentation**: Is code well-documented?
6. **Standards**: Follows team conventions?
7. **Edge Cases**: What edge cases might be missed?

Our standards:
@.cursor/index.mdc

Provide:
- Issues found (categorized by severity)
- Specific line numbers
- Suggested fixes
- Additional test cases needed

Example AI Review Session

Reviewer:


You: Review this PR for issues

@components/UserProfile.tsx
@components/UserProfile.test.tsx

AI Response:


CODE REVIEW FINDINGS

CRITICAL ISSUES:

1. Missing error handling (Line 45)
   const user = await api.getUser(userId); // No try-catch
   
   Recommendation:
   try {
     const user = await api.getUser(userId);
   } catch (error) {
     handleError(error);
     return <ErrorState />;
   }

2. Potential memory leak (Line 67)
   useEffect(() => {
     const interval = setInterval(fetchData, 5000);
     // Missing cleanup!
   }, []);
   
   Fix:
   useEffect(() => {
     const interval = setInterval(fetchData, 5000);
     return () => clearInterval(interval); // Add cleanup
   }, []);

WARNINGS:

1. Performance concern (Line 89)
   {data.map(item => <Component key={item.id} {...item} />)}
   
   Consider: React.memo for Component if re-renders are expensive

2. Accessibility issue (Line 102)
   <div onClick={handleClick}>Click me</div>
   
   Should be:
   <button onClick={handleClick}>Click me</button>
   Or add role="button" and keyboard handlers

SUGGESTIONS:

1. Test coverage (UserProfile.test.tsx)
   Missing tests for:
   - Error states
   - Loading states
   - User interaction edge cases
   
   Suggested test:
   it('should handle API error gracefully', async () => {
     // Mock API error
     // Verify error message shown
   })

2. PropTypes documentation (Line 15)
   Add JSDoc for complex props:
   
   interface UserProfileProps {
     /** User object with profile information */
     user: User;
     /** Callback when profile is updated */
     onUpdate: (user: User) => void;
   }

GOOD PRACTICES OBSERVED:

- TypeScript types properly defined
- Component structure is clear
- Separation of concerns
- Meaningful variable names

Reviewer continues:


You: Generate test cases for the error scenarios you identified

AI generates tests:


// Additional tests for UserProfile.test.tsx


describe('UserProfile - Error Handling', () => {
it('should display error message when API fails', async () => {
// Mock API to throw error
jest.spyOn(api, 'getUser').mockRejectedValue(
new Error('Network error')
);

render(<UserProfile userId="123" />);

// Wait for error state
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});

// Verify error message
expect(screen.getByText(/network error/i)).toBeInTheDocument();
});

it('should retry on error when retry button clicked', async () => {
const mockGetUser = jest.spyOn(api, 'getUser')
.mockRejectedValueOnce(new Error('Error'))
.mockResolvedValueOnce(mockUser);

render(<UserProfile userId="123" />);

// Error state shown
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});

// Click retry
fireEvent.click(screen.getByText(/retry/i));

// Should show user data
await waitFor(() => {
expect(screen.getByText(mockUser.name)).toBeInTheDocument();
});

expect(mockGetUser).toHaveBeenCalledTimes(2);
});

it('should cleanup interval on unmount', () => {
const clearIntervalSpy = jest.spyOn(global, 'clearInterval');

const { unmount } = render(<UserProfile userId="123" />);

unmount();

expect(clearIntervalSpy).toHaveBeenCalled();
});
});

describe('UserProfile - Accessibility', () => {
it('should be keyboard navigable', async () => {
render(<UserProfile userId="123" />);

const editButton = screen.getByRole('button', { name: /edit/i });

// Focus button
editButton.focus();
expect(editButton).toHaveFocus();

// Trigger with Enter
fireEvent.keyDown(editButton, { key: 'Enter' });

// Edit mode should be active
await waitFor(() => {
expect(screen.getByRole('textbox')).toBeInTheDocument();
});
});

it('should have proper ARIA labels', () => {
render(<UserProfile userId="123" />);

expect(screen.getByLabelText(/user profile/i)).toBeInTheDocument();
expect(screen.getByRole('region')).toHaveAttribute('aria-label');
});
});

12.4. Стандартизация кода

Автоматическая стандартизация с AI


You: Создай систему для обеспечения единообразия кода в команде

AI:


// scripts/standardize-code.ts

import * as fs from 'fs';
import * as path from 'path';
import { ESLint } from 'eslint';
import * as prettier from 'prettier';
import { glob } from 'glob';

interface StandardizationReport {
filesProcessed: number;
filesModified: number;
issues: Array<{
file: string;
type: string;
description: string;
fixed: boolean;
}>;
}

class CodeStandardizer {
private report: StandardizationReport = {
filesProcessed: 0,
filesModified: 0,
issues: []
};

/**
* Стандартизировать весь проект
*/
async standardizeProject(
projectPath: string,
options: {
fix: boolean;
dryRun: boolean;
} = { fix: true, dryRun: false }
): Promise<StandardizationReport> {
console.log('Starting code standardization...\n');

// Find all source files
const files = await glob('**/*.{ts,tsx,js,jsx}', {
cwd: projectPath,
ignore: [
'**/node_modules/**',
'**/dist/**',
'**/build/**',
'**/coverage/**'
],
absolute: true
});

console.log(`Found ${files.length} files to process\n`);

// Process each file
for (const file of files) {
await this.standardizeFile(file, options);
}

return this.report;
}

/**
* Стандартизировать отдельный файл
*/
private async standardizeFile(
filePath: string,
options: { fix: boolean; dryRun: boolean }
): Promise<void> {
this.report.filesProcessed++;

let content = fs.readFileSync(filePath, 'utf-8');
let modified = false;

// 1. Format with Prettier
try {
const formatted = await prettier.format(content, {
filepath: filePath,
...await prettier.resolveConfig(filePath)
});

if (formatted !== content) {
if (options.fix && !options.dryRun) {
content = formatted;
modified = true;
}

this.report.issues.push({
file: path.relative(process.cwd(), filePath),
type: 'formatting',
description: 'Code formatted with Prettier',
fixed: options.fix && !options.dryRun
});
}
} catch (error: any) {
this.report.issues.push({
file: path.relative(process.cwd(), filePath),
type: 'error',
description: `Prettier error: ${error.message}`,
fixed: false
});
}

// 2. Fix with ESLint
try {
const eslint = new ESLint({ fix: options.fix && !options.dryRun });
const results = await eslint.lintFiles([filePath]);

for (const result of results) {
if (result.errorCount > 0 || result.warningCount > 0) {
for (const message of result.messages) {
this.report.issues.push({
file: path.relative(process.cwd(), filePath),
type: message.severity === 2 ? 'error' : 'warning',
description: `Line ${message.line}: ${message.message}`,
fixed: message.fix !== undefined && options.fix && !options.dryRun
});
}
}

if (result.output && result.output !== content) {
content = result.output;
modified = true;
}
}
} catch (error: any) {
this.report.issues.push({
file: path.relative(process.cwd(), filePath),
type: 'error',
description: `ESLint error: ${error.message}`,
fixed: false
});
}

// 3. Apply team-specific transformations
const transformed = await this.applyTeamStandards(content, filePath);
if (transformed !== content) {
content = transformed;
modified = true;
}

// 4. Save if modified
if (modified && !options.dryRun) {
fs.writeFileSync(filePath, content);
this.report.filesModified++;
}
}

/**
* Применить командные стандарты
*/
private async applyTeamStandards(
content: string,
filePath: string
): Promise<string> {
let result = content;

// Example transformations based on team standards

// 1. Ensure consistent imports order
result = this.sortImports(result);

// 2. Add missing JSDoc for exports
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
result = this.ensureJSDoc(result);
}

// 3. Convert var to const/let
result = result.replace(/\bvar\s+(\w+)/g, 'const $1');

// 4. Ensure semicolons (if team standard)
// This is better handled by Prettier/ESLint

return result;
}

private sortImports(content: string): string {
// Simple import sorting (production code should use a proper parser)
const lines = content.split('\n');
const imports: string[] = [];
const other: string[] = [];
let inImportBlock = false;

for (const line of lines) {
if (line.trim().startsWith('import ')) {
imports.push(line);
inImportBlock = true;
} else if (inImportBlock && line.trim() === '') {
other.push(line);
inImportBlock = false;
} else {
other.push(line);
}
}

// Sort imports
const externalImports = imports.filter(i => !i.includes('./') && !i.includes('../'));
const internalImports = imports.filter(i => i.includes('./') || i.includes('../'));

externalImports.sort();
internalImports.sort();

return [...externalImports, '', ...internalImports, '', ...other].join('\n');
}

private ensureJSDoc(content: string): string {
// Simplified - real implementation would use AST
// Look for exported functions without JSDoc
const functionRegex = /export (async )?function (\w+)/g;

let result = content;
let match;

while ((match = functionRegex.exec(content)) !== null) {
const funcName = match[2];
const funcStart = match.index;

// Check if JSDoc exists above
const beforeFunc = content.substring(Math.max(0, funcStart - 200), funcStart);

if (!beforeFunc.includes('/**')) {
const insertion = `\n/**\n * TODO: Add documentation for ${funcName}\n */\n`;
result = result.substring(0, funcStart) + insertion + result.substring(funcStart);
}
}

return result;
}

/**
* Вывести отчет
*/
printReport(): void {
console.log('\n' + '='.repeat(80));
console.log('CODE STANDARDIZATION REPORT');
console.log('='.repeat(80));

console.log('\nSUMMARY:');
console.log(` Files Processed: ${this.report.filesProcessed}`);
console.log(` Files Modified: ${this.report.filesModified}`);
console.log(` Total Issues: ${this.report.issues.length}`);

const byType = this.groupIssuesByType();

console.log('\nISSUES BY TYPE:');
for (const [type, issues] of Object.entries(byType)) {
console.log(` ${type}: ${issues.length}`);
}

const unfixed = this.report.issues.filter(i => !i.fixed);
if (unfixed.length > 0) {
console.log(`\nUNFIXED ISSUES: ${unfixed.length}`);

// Show first 10
unfixed.slice(0, 10).forEach(issue => {
console.log(`\n ${issue.file}`);
console.log(` ${issue.description}`);
});

if (unfixed.length > 10) {
console.log(`\n ... and ${unfixed.length - 10} more`);
}
}

console.log('\n' + '='.repeat(80));
}

private groupIssuesByType(): Record<string, any[]> {
return this.report.issues.reduce((acc, issue) => {
if (!acc[issue.type]) {
acc[issue.type] = [];
}
acc[issue.type].push(issue);
return acc;
}, {} as Record<string, any[]>);
}
}

// CLI
async function main() {
const standardizer = new CodeStandardizer();

const command = process.argv[2] || 'check';
const projectPath = process.argv[3] || process.cwd();

switch (command) {
case 'check':
await standardizer.standardizeProject(projectPath, {
fix: false,
dryRun: true
});
break;

case 'fix':
await standardizer.standardizeProject(projectPath, {
fix: true,
dryRun: false
});
break;

case 'dry-run':
await standardizer.standardizeProject(projectPath, {
fix: true,
dryRun: true
});
break;

default:
console.log('Usage:');
console.log(' npm run standardize check - Check for issues');
console.log(' npm run standardize fix - Fix issues');
console.log(' npm run standardize dry-run - Show what would be fixed');
return;
}

standardizer.printReport();
}

main().catch(console.error);

Pre-commit hook для стандартизации:


#!/bin/bash

# .git/hooks/pre-commit

echo "Running code standardization checks..."

# Get staged files
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(ts|tsx|js|jsx)$')

if [ -z "$STAGED_FILES" ]; then
echo "No JavaScript/TypeScript files to check"
exit 0
fi

# Run standardization check
npm run standardize check

if [ $? -ne 0 ]; then
echo ""
echo "Code standardization failed"
echo ""
echo "Fix issues with:"
echo " npm run standardize fix"
echo ""
echo "Or commit anyway with:"
echo " git commit --no-verify"
exit 1
fi

echo "Code standardization passed"
exit 0

12.5. Онбординг новых разработчиков

Автоматизированный онбординг с AI


# docs/onboarding-with-cursor.md


# Developer Onboarding Guide

Welcome to the team! This guide will help you get set up with Cursor
and our development environment.

## Day 1: Setup

### 1. Install Tools

```bash
# Install Cursor
# Download from https://cursor.sh

# Install Node.js (via nvm)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install 18
nvm use 18

# Install dependencies
npm install -g pnpm typescript ts-node

2. Clone Repositories


# Main application

git clone git@github.com:company/main-app.git
cd main-app

# Install dependencies
pnpm install

# Setup environment
cp .env.example .env
# Edit .env with your local config

3. Setup Cursor Team Config


# Clone team configuration

git clone git@github.com:company/cursor-team-config.git

# Install team settings
cd cursor-team-config
./scripts/install.sh

# Select your project path when prompted

4. Verify Setup


# Run tests

pnpm test

# Start development server
pnpm dev

# Verify Cursor rules loaded
# Open Cursor, check status bar for "Rules: Active"