papayu/docs/PROTOCOL_V2_PLAN.md
Yuriy 65e95a458d feat: мульти-провайдер LLM, тренды дизайна, Snyk/Documatic sync, личная автоматизация
- Мульти-провайдер: PAPAYU_LLM_PROVIDERS — сбор планов от нескольких ИИ (Claude, OpenAI), агрегация
- Тренды дизайна и иконок: вкладка в модалке, поиск по безопасным доменам (Tavily include_domains)
- Snyk Code: PAPAYU_SNYK_SYNC, REST API issues → snyk_findings в agent-sync
- Documatic: architecture_summary из .papa-yu/architecture.md в agent-sync
- Личная автоматизация: capability personal-automation (терминал git/npm/cargo, открытие URL)
- agent_sync расширен: snyk_findings, architecture_summary; analyze_project_cmd и run_batch пишут sync
- Документация: SNYK_AND_DOCUMATIC_SYNC.md, SECURITY_AND_PERSONAL_AUTOMATION.md, обновлён CLAUDE_AND_AGENT_SYNC

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-10 15:05:39 +03:00

285 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# План Protocol v2
Минимальный набор изменений для v2 — без «воды».
---
## Diff v1 → v2 (схема)
| v1 | v2 |
|----|-----|
| `oneOf` (root array \| object) | всегда **объект** |
| `proposed_changes.actions` | только `actions` в корне |
| `UPDATE_FILE` с `content` | `PATCH_FILE` с `patch` + `base_sha256` (по умолчанию) |
| 5 kinds | 6 kinds (+ PATCH_FILE) |
| `content` для CREATE/UPDATE | `content` для CREATE/UPDATE; `patch`+`base_sha256` для PATCH |
Добавлено: `patch`, `base_sha256` (hex 64), взаимоисключающие правила (content vs patch/base).
---
## Главная цель v2
Снизить риск/стоимость «UPDATE_FILE целиком» и улучшить точность правок:
- частичные патчи,
- «операции редактирования» вместо полной перезаписи.
---
## Минимальный набор изменений
### A) Новый action kind: `PATCH_FILE`
Вместо полного `content`, передаётся unified diff:
```json
{ "kind": "PATCH_FILE", "path": "src/app.py", "patch": "@@ -1,3 +1,4 @@\n..." }
```
- Валидация патча локально.
- Применение патча транзакционно.
- Preview diff становится тривиальным.
### B) Новый action kind: `REPLACE_RANGE`
Если unified diff сложен:
```json
{
"kind": "REPLACE_RANGE",
"path": "src/app.py",
"start_line": 120,
"end_line": 180,
"content": "новый блок"
}
```
Плюсы: проще валидировать. Минусы: зависит от line numbers (хрупко при изменениях).
### C) «Base hash» для UPDATE/PATCH
Исключить race (файл изменился между plan/apply):
```json
{ "kind": "PATCH_FILE", "path": "...", "base_sha256": "...", "patch": "..." }
```
Если hash не совпал → Err и переход в PLAN.
---
## Совместимость v1/v2
- `schema_version=1` → нынешний формат (UPDATE_FILE, CREATE_FILE, …).
- `schema_version=2` → допускает `PATCH_FILE` / `REPLACE_RANGE` и расширенные поля.
В коде:
- Компилировать обе схемы: `llm_response_schema.json` (v1), `llm_response_schema_v2.json`.
- Выбор активной по env: `PAPAYU_PROTOCOL_DEFAULT` или `PAPAYU_PROTOCOL_VERSION` (default 2).
- Валидация/парсер: сначала проверить schema v2 (если включена), иначе v1.
---
## Порядок внедрения v2 без риска
1. Добавить v2 schema + валидаторы + apply engine.
2. Добавить «LLM prompt v2» (рекомендовать PATCH_FILE вместо UPDATE_FILE).
3. Golden traces v2.
4. **v2 default** с автоматическим fallback на v1 (реализовано).
---
## v2 default + fallback (реализовано)
- **PAPAYU_PROTOCOL_DEFAULT** (или PAPAYU_PROTOCOL_VERSION): default 2.
- **PAPAYU_PROTOCOL_FALLBACK_TO_V1**: default 1 (включён). При ошибках v2 (ERR_PATCH_APPLY_FAILED, ERR_NON_UTF8_FILE, ERR_V2_UPDATE_EXISTING_FORBIDDEN) — автоматический retry с v1.
- Fallback только для APPLY (plan остаётся по выбранному протоколу).
- Trace: `protocol_default`, `protocol_attempts`, `protocol_fallback_reason`.
- Лог: `[trace] PROTOCOL_FALLBACK from=v2 to=v1 reason=ERR_...`
**Compatibility:** Default protocol — v2. Apply может fallback на v1 при специфичных кодах ошибок (ERR_PATCH_APPLY_FAILED, ERR_NON_UTF8_FILE, ERR_V2_UPDATE_EXISTING_FORBIDDEN).
### Метрики для анализа (grep по trace / логам)
- `fallback_rate = fallback_count / apply_count`
- `fallback_rate_excluding_non_utf8` — исключить ERR_NON_UTF8_FILE (не провал v2, ограничение данных)
- Распределение причин fallback:
- ERR_PATCH_APPLY_FAILED
- ERR_NON_UTF8_FILE
- ERR_V2_UPDATE_EXISTING_FORBIDDEN
Trace-поля: `protocol_repair_attempt` (0|1), `protocol_fallback_stage` (apply|preview|validate|schema).
Цель: понять, что мешает v2 стать единственным.
### Graduation criteria (когда отключать fallback / v2-only)
За последние 100 APPLY:
- `fallback_rate < 1%`
- **ERR_PATCH_APPLY_FAILED** < 1% и чаще лечится repair, чем fallback
- **ERR_V2_UPDATE_EXISTING_FORBIDDEN** стремится к 0 (после tightening/repair)
- **ERR_NON_UTF8_FILE** не считается «провалом v2» (ограничение формата; можно отдельно)
- Для честной оценки v2 использовать `fallback_rate_excluding_non_utf8`
Тогда: `PAPAYU_PROTOCOL_FALLBACK_TO_V1=0` и, при необходимости, v2-only.
**protocol_fallback_stage** (где произошло падение): `apply` (сейчас), `preview` (если preview patch не применился), `validate` (семантика), `schema` (валидация JSON) добавить при расширении.
### Fallback: однократность и repair-first
- **Однократность:** в одном APPLY нельзя зациклиться; если v1 fallback тоже не помог Err.
- **Repair-first:** для ERR_PATCH_APPLY_FAILED и ERR_V2_UPDATE_EXISTING_FORBIDDEN сначала repair v2, потом fallback. Для ERR_NON_UTF8_FILE fallback сразу.
- **Trace:** `protocol_repair_attempt` (0|1), `protocol_fallback_attempted`, `protocol_fallback_stage` (apply|preview|validate|schema).
### Еженедельный отчёт (grep/jq)
Пример пайплайна для анализа трасс (trace JSON в одной строке на файл):
```bash
# APPLY count
grep -l '"event":"LLM_PLAN_OK"' traces/*.json 2>/dev/null | wc -l
# fallback_count (protocol_fallback_attempted)
grep '"protocol_fallback_attempted":true' traces/*.json 2>/dev/null | wc -l
# breakdown по причинам
grep -oh '"protocol_fallback_reason":"[^"]*"' traces/*.json 2>/dev/null | sort | uniq -c
# repair_success (protocol_repair_attempt=0 и нет fallback в следующей трассе) — требует связки
jq -s '[.[] | select(.event=="LLM_PLAN_OK" and .protocol_repair_attempt==0)] | length' traces/*.json 2>/dev/null
# top paths по repair_injected_sha256
grep -oh '"repair_injected_paths":\[[^]]*\]' traces/*.json 2>/dev/null | sort | uniq -c | sort -rn | head -20
```
**System prompt v2** (`FIX_PLAN_SYSTEM_PROMPT_V2`): жёсткие правила PATCH_FILE, base_sha256, object-only, NO_CHANGES. Включается при `PAPAYU_PROTOCOL_VERSION=2` и режиме fix-plan/fixit.
**Формат FILE-блока v2:**
```
FILE[path/to/file.py] (sha256=7f3f2a0c9f8b1a0c9b4c0f9e3d8a4b2d8c9e7f1a0b3c4d5e6f7a8b9c0d1e2f3a):
<content>
```
sha256 от полного содержимого файла; **не обрезается** при context-diet. Модель копирует его в `base_sha256` для PATCH_FILE.
### Prompt rules (оптимизация v2)
- Патч должен быть **минимальным** меняй только нужные строки, не форматируй файл целиком.
- Каждый `@@` hunk должен иметь 13 строки контекста до/после изменения.
- Не делай массовых форматирований и EOL-изменений.
- Если файл не UTF-8 или слишком большой/генерируемый верни PLAN (actions=[]) и запроси альтернативу.
**Авто-эскалация при ERR_PATCH_APPLY_FAILED** (опционально): при repair retry добавить «Увеличь контекст hunks до 3 строк, не меняй соседние блоки
---
## PATCH_FILE engine (реализовано)
- **Модуль `patch`:** sha256_hex, is_valid_sha256_hex, looks_like_unified_diff, apply_unified_diff_to_text (diffy)
- **tx::apply_patch_file_impl:** проверка base_sha256 применение diff EOL нормализация запись
- **Preview:** preview_patch_file проверяет base_sha256 и применимость, возвращает patch в DiffItem
- **Коды ошибок:** ERR_PATCH_NOT_UNIFIED, ERR_BASE_MISMATCH, ERR_PATCH_APPLY_FAILED, ERR_BASE_SHA256_INVALID, ERR_NON_UTF8_FILE
- **Repair hints:** REPAIR_ERR_* для repair flow / UI
---
## ERR_NON_UTF8_FILE и ERR_V2_UPDATE_EXISTING_FORBIDDEN
**ERR_NON_UTF8_FILE:** PATCH_FILE работает только по UTF-8 тексту. Для бинарных/не-UTF8 файлов только CREATE_FILE (если явно нужно), иначе отказ/PLAN. Сообщение для UI: «Файл не UTF-8. PATCH_FILE недоступен. Перейди в PLAN и выбери другой подход
**ERR_V2_UPDATE_EXISTING_FORBIDDEN:** В v2 UPDATE_FILE запрещён для существующих файлов. Семантический гейт: если UPDATE_FILE и файл существует ошибка. Repair: «Сгенерируй PATCH_FILE вместо UPDATE_FILE».
---
## Рекомендации для v2
- В v2 модификация существующих файлов **по умолчанию** через `PATCH_FILE`.
- `base_sha256` обязателен для `PATCH_FILE` и проверяется приложением.
- При `ERR_BASE_MISMATCH` требуется новый PLAN (файл изменился).
- В APPLY отсутствие изменений оформляется через `NO_CHANGES:` и `actions: []`.
---
## Примеры v2 ответов
### PLAN (v2): план без изменений
```json
{
"actions": [],
"summary": "Диагноз: падает из-за неверной обработки None.\nПлан:\n1) Прочитать src/parser.py вокруг функции parse().\n2) Добавить проверку на None и поправить тест.\nПроверка: pytest -q",
"context_requests": [
{ "type": "read_file", "path": "src/parser.py", "start_line": 1, "end_line": 260 },
{ "type": "read_file", "path": "tests/test_parser.py", "start_line": 1, "end_line": 200 }
],
"memory_patch": {}
}
```
### APPLY (v2): PATCH_FILE на существующий файл
`base_sha256` должен совпасть с хэшем текущего файла.
```json
{
"actions": [
{
"kind": "PATCH_FILE",
"path": "src/parser.py",
"base_sha256": "7f3f2a0c9f8b1a0c9b4c0f9e3d8a4b2d8c9e7f1a0b3c4d5e6f7a8b9c0d1e2f3a",
"patch": "--- a/src/parser.py\n+++ b/src/parser.py\n@@ -41,6 +41,10 @@ def parse(value):\n- return value.strip()\n+ if value is None:\n+ return \"\"\n+ return value.strip()\n"
},
{
"kind": "PATCH_FILE",
"path": "tests/test_parser.py",
"base_sha256": "0a1b2c3d4e5f60718293a4b5c6d7e8f90123456789abcdef0123456789abcdef0",
"patch": "--- a/tests/test_parser.py\n+++ b/tests/test_parser.py\n@@ -10,7 +10,7 @@ def test_parse_none():\n- assert parse(None) is None\n+ assert parse(None) == \"\"\n"
}
],
"summary": "Исправлено: parse(None) теперь возвращает пустую строку. Обновлён тест.\nПроверка: pytest -q",
"context_requests": [],
"memory_patch": {}
}
```
### APPLY (v2): создание файлов (как в v1)
```json
{
"actions": [
{ "kind": "CREATE_DIR", "path": "src" },
{
"kind": "CREATE_FILE",
"path": "README.md",
"content": "# My Project\n\nRun: `make run`\n"
}
],
"summary": "Созданы папка src и README.md.",
"context_requests": [],
"memory_patch": {}
}
```
### APPLY (v2): NO_CHANGES
```json
{
"actions": [],
"summary": "NO_CHANGES: Код уже соответствует требованиям, правки не нужны.\nПроверка: pytest -q",
"context_requests": [],
"memory_patch": {}
}
```
---
## Ошибки движка v2
| Код | Когда | Действие |
|-----|-------|----------|
| `ERR_BASE_MISMATCH` | Файл изменился между PLAN и APPLY, sha256 не совпал | Вернуться в PLAN, перечитать файл, обновить base_sha256 |
| `ERR_PATCH_APPLY_FAILED` | Hunks не применились (контекст не совпал) | Вернуться в PLAN, запросить более точный контекст, перегенерировать патч |
| `ERR_PATCH_NOT_UNIFIED` | LLM прислал не unified diff | Repair-ретрай с требованием unified diff |