MidТеория8 min

Контекстная инженерия

Как правильно формировать контекст для AI-ассистентов

Контекст важнее промптов

Долгое время обсуждали "prompt engineering" -- искусство формулирования запросов к AI. Но практика показала: контекст важнее промпта. Промпт -- это инструкция ("что сделать"), а контекст -- это знания ("что ты знаешь о проекте, архитектуре, бизнесе").

Промпт без контекста:
  "Напиши сервис для обработки заказов"
  → Генерический код, не подходящий для вашего проекта

Промпт с контекстом:
  "Напиши сервис для обработки заказов.
   Проект: Symfony 7.4, PHP 8.4.
   Архитектура: Hexagonal, DDD.
   Существующие сущности: Order, OrderItem, Product.
   Бизнес-правила: заказы > 10,000 руб. требуют ручной проверки.
   Паттерны: CQRS для операций записи."
  → Код, который вписывается в ваш проект

Context engineering -- это практика систематического предоставления AI правильной информации для получения качественного вывода.

Аналогия

Представьте, что вы наняли нового разработчика. В первый день вы не говорите ему "напиши сервис заказов". Вы:

  1. Показываете кодовую базу
  2. Объясняете архитектуру
  3. Рассказываете о бизнес-правилах
  4. Показываете coding conventions
  5. Даете примеры существующего кода

AI-ассистент -- это "новый разработчик" при каждом запросе. Контекстная инженерия -- это процесс онбординга AI в ваш проект.


Типы контекста

1. Проектный контекст

Общая информация о проекте, его технологическом стеке, архитектуре и соглашениях.

# CLAUDE.md (или .cursorrules, AGENTS.md)

## Project Overview
E-commerce platform for digital products.
Backend: Symfony 7.4 + PHP 8.4
Frontend: Nuxt 4 + Vue 3.6
Database: PostgreSQL 18
Queue: RabbitMQ 4 via Symfony Messenger
Cache: Redis 7

## Architecture
- Hexagonal architecture (ports & adapters)
- CQRS for write operations
- Event-driven async processing
- Domain-Driven Design for core business logic

## Coding Standards
- PHP: PSR-12, PHPStan level 9, strict_types always
- All classes final by default
- Constructor property promotion
- Readonly properties for DTOs
- Enums for statuses (never strings/ints)

## Directory Structure
src/
  Domain/          # Entities, Value Objects, Repository Interfaces
  Application/     # Use Cases (Commands, Queries, Handlers)
  Infrastructure/  # Repository implementations, External Services
  Presentation/    # Controllers, CLI Commands

## Naming Conventions
- Services: {Domain}Service (e.g., OrderService)
- Repositories: {Entity}Repository (e.g., UserRepository)
- DTOs: {Action}{Entity}DTO (e.g., CreateOrderDTO)
- Events: {Entity}{Action}Event (e.g., OrderCreatedEvent)
- Commands: {Action}{Entity}Command (e.g., CreateOrderCommand)

Как хранить проектный контекст

Инструмент Файл контекста Описание
Claude Code CLAUDE.md В корне проекта или в .claude/
Cursor .cursorrules В корне проекта
GitHub Copilot .github/copilot-instructions.md В директории .github/
Windsurf .windsurfrules В корне проекта
Универсальный AGENTS.md Стандарт от Google (2025)
Структура файлов контекста:

project/
├── CLAUDE.md                    # Main AI instructions
├── .cursorrules                 # Cursor-specific rules
├── .github/
│   └── copilot-instructions.md  # Copilot-specific rules
├── AGENTS.md                    # Universal agent instructions
└── docs/
    ├── adr/                     # Architecture Decision Records
    │   ├── 001-hexagonal.md
    │   ├── 002-cqrs.md
    │   └── 003-event-sourcing.md
    └── coding-standards.md

2. Кодовый контекст

Релевантные файлы, интерфейсы и паттерны из вашей кодовой базы.

<?php

declare(strict_types=1);

// CONTEXT: Show AI your existing patterns

// 1. Base controller pattern
abstract class AbstractApiController extends AbstractController
{
    protected function validationError(ConstraintViolationListInterface $violations): JsonResponse
    {
        $errors = [];
        foreach ($violations as $violation) {
            $errors[$violation->getPropertyPath()][] = $violation->getMessage();
        }
        return $this->json(['errors' => $errors], Response::HTTP_UNPROCESSABLE_ENTITY);
    }

    protected function notFound(string $message): JsonResponse
    {
        return $this->json(['error' => $message], Response::HTTP_NOT_FOUND);
    }
}

// 2. Repository interface pattern
interface OrderRepositoryInterface
{
    public function findById(Uuid $id): ?Order;
    public function save(Order $order): void;
    public function findByUser(Uuid $userId, Pagination $pagination): PaginatedResult;
}

// 3. DTO pattern
final readonly class CreateOrderDTO
{
    public function __construct(
        #[Assert\NotBlank]
        #[Assert\Valid]
        public array $items,

        #[Assert\NotBlank]
        public string $shippingAddressId,

        public ?string $couponCode = null,
    ) {}
}

Когда вы показываете AI существующие паттерны, он генерирует код, который следует тем же паттернам. Без этого контекста AI будет использовать "стандартный" стиль, который может не совпадать с вашим проектом.

3. Доменный контекст

Бизнес-правила и ограничения, которые AI не может знать из общедоступных данных:

## Business Rules: Order Processing

### Order Creation
- Minimum order amount: 500 RUB
- Maximum items per order: 50
- Orders > 10,000 RUB require manager approval
- Repeat orders within 1 hour from same user: flag for fraud review

### Order Cancellation
- Free cancellation within 24 business hours
- After 24h: 20% cancellation fee
- Shipped orders: only return process, no cancellation
- Admin can override any cancellation restriction

### Pricing
- All prices stored in minor units (kopeks, cents)
- Currency: RUB only (no multi-currency)
- Tax: included in price (not calculated separately)
- Discounts: percentage only, max 50%, never below cost price

### Compliance
- Order data retention: 5 years (152-FZ)
- PII must be encrypted at rest
- Audit log for all price changes
- No deletion of completed orders (soft delete only)

AI без доменного контекста сделает "разумные" предположения, которые будут неправильными для вашего конкретного бизнеса.

4. Задачный контекст

Конкретная информация о текущей задаче:

## Task: Add Bulk Order Import

### What
API endpoint to import orders from CSV file uploaded by admin.

### Why
Managers currently create orders manually (5 min each).
With 50+ orders/day from phone channel, this is unsustainable.

### Acceptance Criteria
- [ ] POST /api/admin/orders/import accepts CSV file
- [ ] CSV format: customer_email, product_sku, quantity, shipping_address
- [ ] Validate each row independently (don't reject entire file for 1 bad row)
- [ ] Return: { imported: N, failed: N, errors: [{row: N, error: "..."}] }
- [ ] Max file size: 10 MB (~50,000 orders)
- [ ] Processing time: < 60 seconds
- [ ] Send notification to admin when import completes

### Constraints
- Must use existing OrderService.create() for each order
- Must respect all business rules (min amount, stock check, etc.)
- Must be idempotent (re-importing same file doesn't create duplicates)
- Must log all imports for audit

### Edge Cases
- Empty CSV
- CSV with only headers
- Duplicate orders in same file
- Non-existent product SKU
- Customer email doesn't exist (create customer?)
- Out of stock during import
- Server crash during import (partial import recovery)

Управление контекстным окном

AI-модели имеют ограниченное контекстное окно -- максимальное количество токенов, которое модель может обработать за один запрос.

Контекстное окно (2025-2026):

┌────────────────────────────────────────────────────────────┐
│ Claude Opus 4.5:    200K tokens (~150K слов)               │
│ Claude Sonnet 4:    200K tokens (~150K слов)               │
│ GPT-4o:             128K tokens (~96K слов)                │
│ Gemini 2.0:         1M+ tokens  (~750K слов)               │
└────────────────────────────────────────────────────────────┘

Типичные размеры контекста:
  CLAUDE.md:                    ~2K tokens
  Один файл кода (200 строк):  ~1K tokens
  Спецификация задачи:         ~500 tokens
  10 релевантных файлов:       ~10K tokens

Итого типичный контекст: 15-20K tokens из 200K доступных

Приоритизация контекста

Не все данные одинаково полезны. Приоритизируйте:

Приоритет 1 (ВСЕГДА включать):
  ├─ Coding conventions и стандарты
  ├─ Архитектурные решения
  └─ Бизнес-правила для текущей задачи

Приоритет 2 (включать для связанных задач):
  ├─ Интерфейсы, которые нужно реализовать
  ├─ Существующие паттерны (примеры кода)
  └─ Тесты, которые нужно пройти

Приоритет 3 (включать при необходимости):
  ├─ Связанные сущности и сервисы
  ├─ Database schema
  └─ API-контракты зависимых сервисов

Приоритет 4 (НЕ включать без необходимости):
  ├─ Весь исходный код проекта
  ├─ Историю изменений
  └─ Документацию, не связанную с задачей

Инкрементальный контекст

Вместо того чтобы дать AI весь контекст сразу, предоставляйте его поэтапно:

Этап 1: Обзор
  "Вот структура проекта, архитектура, основные паттерны."

Этап 2: Специфика
  "Вот интерфейс, который нужно реализовать.
   Вот существующий сервис, от которого нужно наследовать логику."

Этап 3: Детали
  "Вот тесты, которые должны пройти.
   Вот edge cases, которые нужно обработать."

Анти-паттерны контекстной инженерии

1. Весь проект в контекст

# BAD: dumping entire codebase
"Вот мой проект (500 файлов). Напиши новый сервис."

Проблемы:
  - AI "тонет" в нерелевантной информации
  - Контекстное окно переполнено
  - Модель не может приоритизировать
  - Результат: генерический код, игнорирующий ваши паттерны

2. Нет контекста вообще

# BAD: no context at all
"Напиши UserService на PHP"

Проблемы:
  - AI использует "стандартный" стиль
  - Не соответствует архитектуре проекта
  - Не учитывает бизнес-правила
  - Результат: переписывать с нуля

3. Противоречивый контекст

# BAD: contradictory instructions
## In CLAUDE.md:
"Use repository pattern for all database access"

## In task description:
"Just use Doctrine EntityManager directly in the controller"

# AI не знает, чему следовать → непредсказуемый результат

4. Устаревший контекст

# BAD: outdated context file
## CLAUDE.md (not updated in 6 months):
"Database: MySQL 5.7"
"Framework: Symfony 5.4"
"PHP: 8.1"

# Реальность:
# Database: PostgreSQL 18
# Framework: Symfony 7.4
# PHP: 8.4

# AI генерирует код для устаревшего стека

5. Слишком общие инструкции

# BAD: vague instructions
"Write clean code"
"Follow best practices"
"Make it scalable"

# GOOD: specific instructions
"All classes must be final unless explicitly designed for extension"
"All methods must have return type declarations"
"Use constructor property promotion for dependency injection"
"Use cursor-based pagination (never OFFSET)"

Структура файла CLAUDE.md

Практическое руководство по созданию эффективного файла контекста:

# Project: [Name]

## Overview
[1-2 sentences about what the project does]

## Tech Stack
- Language: [version]
- Framework: [version]
- Database: [version]
- Cache: [version]
- Queue: [version]

## Architecture
[Brief description of architectural approach]

### Directory Structure
[Show key directories and their purpose]

## Coding Standards

### Naming
- Classes: PascalCase, final by default
- Methods: camelCase, verb-first (getUser, createOrder)
- Variables: camelCase, descriptive
- Constants: UPPER_SNAKE_CASE
- Database: snake_case, plural tables

### Patterns Required
- Constructor injection (never service locator)
- DTOs for data transfer (readonly)
- Enums for statuses (never magic strings)
- Value Objects for domain concepts (Money, Email, etc.)

### Patterns Forbidden
- God classes (max 300 lines)
- Service locator pattern
- Static methods for business logic
- Array as return type for structured data

## Testing
- Framework: PHPUnit 11
- Coverage: minimum 80%
- Types: unit, integration, functional
- Naming: test{Action}{Scenario}{ExpectedResult}

## Security
- All inputs validated
- SQL: parameterized queries only
- Passwords: bcrypt, cost 12
- API: JWT authentication
- Sensitive data: never in logs

## Common Commands
```bash
# Run tests
vendor/bin/phpunit

# Static analysis
vendor/bin/phpstan analyse

# Format code
vendor/bin/php-cs-fixer fix

Business Rules

[Key business rules that affect code decisions]

Known Limitations

[Technical debt, workarounds, things to be aware of]


---

## Architecture Decision Records (ADR)

ADR -- один из самых ценных типов контекста для AI. Они объясняют **почему** принято определенное решение, а не только **что** решено.

```markdown
# ADR-003: Event Sourcing for Order Processing

## Status
Accepted

## Context
Order processing requires full audit trail, ability to replay events,
and support for complex compensating transactions (refunds, partial cancellations).

## Decision
Use event sourcing for Order aggregate:
- All state changes represented as domain events
- Event store: PostgreSQL with events table
- Projection: rebuild read models from events
- Snapshots: every 100 events

## Consequences
### Positive
- Complete audit trail
- Ability to replay and debug
- Natural support for compensation

### Negative
- Higher complexity
- Eventual consistency for read models
- Need for snapshotting at scale

## Alternatives Considered
1. CRUD with audit log table -- simpler but loses event sequence
2. Change Data Capture -- infrastructure dependency, less control

## Code Pattern
```php
// Creating an order produces events
$order = Order::create($command);
// $order->getUncommittedEvents() returns [OrderCreatedEvent, ...]

// Cancelling produces compensation events
$order->cancel($reason);
// $order->getUncommittedEvents() returns [OrderCancelledEvent, ...]

Когда AI видит ADR, он понимает не только текущее состояние кода, но и **причины** архитектурных решений. Это значительно повышает качество генерации.

---

## Практические примеры

### Пример 1: Правильный контекст для нового эндпоинта

```markdown
## Задача
Добавить эндпоинт GET /api/v1/orders/{id}/invoice для генерации счета.

## Проектный контекст
См. CLAUDE.md (прикреплен к сессии)

## Кодовый контекст
Существующий контроллер (паттерн для подражания):
```php
#[Route('/api/v1/orders/{id}', methods: ['GET'])]
#[IsGranted('VIEW', subject: 'order')]
public function show(
    #[MapEntity] Order $order,
): JsonResponse {
    return $this->json(
        OrderDTO::fromEntity($order),
    );
}

Существующий сервис:

interface InvoiceGeneratorInterface
{
    public function generate(Order $order): InvoiceDTO;
}

Доменный контекст

  • Счет доступен только для оплаченных заказов (status = PAID)
  • Формат: PDF (через DomPDF) или JSON
  • Номер счета: INV-{year}-{sequence}, например INV-2026-00042
  • Счет кэшируется (immutable после генерации)

Задачный контекст

  • Формат ответа определяется заголовком Accept
  • Accept: application/pdf → PDF файл
  • Accept: application/json → JSON с данными счета
  • По умолчанию: JSON

### Пример 2: Контекст для рефакторинга

```markdown
## Задача
Отрефакторить NotificationService: разделить на отдельные каналы.

## Текущее состояние (проблема)
```php
// Current: God class with all channels mixed
final class NotificationService
{
    public function notify(User $user, Notification $notification): void
    {
        // 200+ lines mixing email, SMS, push, telegram logic
        // Hard to test, hard to extend, violates SRP
    }
}

Желаемое состояние

// Target: strategy pattern
interface NotificationChannelInterface
{
    public function supports(NotificationType $type): bool;
    public function send(User $user, Notification $notification): void;
}

// Each channel: separate class, independently testable
// Channels: EmailChannel, SmsChannel, TelegramChannel, PushChannel
// Dispatcher: iterates channels, finds supported, sends

Ограничения

  • НЕ менять публичный API NotificationService
  • Существующие тесты должны продолжать проходить
  • Постепенная миграция: сначала extract, потом рефакторинг

---

## Инструменты для управления контекстом

### Автоматическая генерация контекста

```bash
# Generate project context automatically
# Architecture overview
find src/ -name "*.php" -path "*/Interface/*" | head -20

# List all public API endpoints
grep -rn "#\[Route" src/Controller/ --include="*.php"

# List all entities
find src/Entity/ -name "*.php" -exec basename {} \; | sort

# List all services
grep -rn "^final class.*Service" src/ --include="*.php"

# Database schema
pg_dump --schema-only learning_db > schema.sql

Валидация контекста

Периодически проверяйте, что ваш CLAUDE.md актуален:

## Context Freshness Checklist (monthly)

- [ ] Tech stack versions correct?
- [ ] Directory structure matches reality?
- [ ] Coding standards reflect current practices?
- [ ] Business rules up to date?
- [ ] Common commands still work?
- [ ] Known limitations still relevant?
- [ ] ADRs cover recent decisions?

Ключевые выводы

  1. Контекст > промпт. Правильный контекст превращает генерический вывод AI в код, подходящий для вашего проекта.

  2. Четыре типа контекста: проектный (архитектура, стек), кодовый (паттерны, интерфейсы), доменный (бизнес-правила), задачный (конкретные требования).

  3. CLAUDE.md / .cursorrules -- живой документ, который нужно обновлять при каждом значимом изменении проекта.

  4. ADR -- самый ценный тип контекста, потому что объясняет "почему", а не только "что".

  5. Инкрементальный контекст эффективнее, чем весь проект сразу: давайте AI только то, что релевантно текущей задаче.

  6. Анти-паттерны убивают качество: весь проект в контекст, нет контекста вообще, противоречивые инструкции, устаревшие данные.

Контекстная инженерия -- это инвестиция. Час, потраченный на хороший CLAUDE.md, экономит десятки часов на исправлении некачественного AI-вывода. Это как документация: кажется, что замедляет, а на самом деле ускоряет.