Глава 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 passesOnboarding
New team members should:
- Read this document
- Setup Cursor with team settings
- Review team conventions
- Pair with senior developer for first week
- Complete onboarding tasks using AI
Version Control
This rules file is versioned. When updating:
- Create PR with changes
- Get team approval
- Update version number
- Notify team in Slack
- 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
-
Copy rules to your project:
cp -r rules/* /path/to/your/project/.cursor/
-
Copy snippets to Cursor:
cp snippets/* ~/.cursor/snippets/
-
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:
- Create branch:
git checkout -b improve/description - Make changes
- Create PR
- Get team approval
- 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 worksCheck 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-nameAI 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 neededExample AI Review Session
Reviewer:
You: Review this PR for issues
@components/UserProfile.tsx
@components/UserProfile.test.tsxAI 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 namesReviewer continues:
You: Generate test cases for the error scenarios you identifiedAI 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"