- Schema version (x_schema_version, schema_hash) в prompt/trace - Кеш read/search/logs/env (ContextCache) в plan-цикле - Контекст-диета: MAX_FILES=8, MAX_FILE_CHARS=20k, MAX_TOTAL_CHARS=120k - Plan→Apply двухфазность, NO_CHANGES, path sanitization - Protected paths, content validation, EOL normalization - Trace (PAPAYU_TRACE), redaction (PAPAYU_TRACE_RAW) - Preview diff, undo/redo, transactional apply Co-authored-by: Cursor <cursoragent@cursor.com>
12 KiB
Fix-plan оркестратор: контракты JSON и автосбор контекста
papa-yu — Rust/Tauri, не Python. Ниже — текущий JSON-ответ, расширенный контракт Fix-plan/Apply и как это встроено в приложение.
1) Текущий JSON-ответ (как есть сейчас)
Модель возвращает один JSON. Приложение парсит и применяет действия по подтверждению пользователя.
Вариант A: массив действий
[
{ "kind": "CREATE_FILE", "path": "README.md", "content": "# Project\n" },
{ "kind": "CREATE_DIR", "path": "src" }
]
Вариант B: объект с actions + memory_patch
{
"actions": [
{ "kind": "UPDATE_FILE", "path": "src/main.py", "content": "..." }
],
"memory_patch": {
"project.default_test_command": "pytest -q"
}
}
Поля элемента actions
| Поле | Тип | Обязательность | Описание |
|---|---|---|---|
kind |
string | да | CREATE_FILE | CREATE_DIR | UPDATE_FILE | DELETE_FILE | DELETE_DIR |
path |
string | да | Относительный путь от корня проекта |
content |
string | нет | Для CREATE_FILE / UPDATE_FILE |
Результат в приложении
AgentPlan:{ ok, summary, actions, error?, error_code? }memory_patchприменяется по whitelist и сохраняется вpreferences.json/.papa-yu/project.json
2) Расширенный контракт: Fix-plan и Apply
Один JSON-объект с полем mode. Приложение понимает оба формата (текущий и расширенный).
Режим fix-plan (только план, применение после подтверждения)
{
"mode": "fix-plan",
"summary": "Коротко: почему падает и что делаем",
"questions": ["Нужен ли тест на X?"],
"context_requests": [
{ "type": "read_file", "path": "src/x.py", "start_line": 1, "end_line": 220 },
{ "type": "search", "query": "SomeSymbol", "glob": "**/*.py" },
{ "type": "logs", "source": "runtime", "last_n": 200 }
],
"plan": [
{ "step": "Диагностика", "details": "..." },
{ "step": "Правка", "details": "..." },
{ "step": "Проверка", "details": "Запустить pytest -q" }
],
"proposed_changes": {
"patch": "unified diff (optional в fix-plan)",
"actions": [
{ "kind": "UPDATE_FILE", "path": "src/x.py", "content": "..." }
],
"commands_to_run": ["pytest -q"]
},
"risks": ["Затрагивает миграции"],
"memory_patch": {
"project.default_test_command": "pytest -q"
}
}
- Если есть
context_requests, приложение подтягивает контекст (read_file, search, logs) и повторяет запрос к модели (до 2 раундов). - Действия для UI/apply берутся из
proposed_changes.actions(если есть), иначе из корневогоactions(обратная совместимость).
Режим apply (после «ок» пользователя)
{
"mode": "apply",
"summary": "Что применяем",
"patch": "unified diff (обязательно при применении diff)",
"commands_to_run": ["pytest -q", "ruff check ."],
"verification": ["Ожидаем: все тесты зелёные"],
"rollback": ["git checkout -- <files>"],
"memory_patch": {}
}
В текущей реализации применение идёт по списку actions (CREATE_FILE/UPDATE_FILE/…). Поле patch (unified diff) зарезервировано под будущую поддержку apply_patch в бэкенде.
3) System prompt под один JSON (Fix-plan)
Ядро, которое вставляется при режиме Fix-plan (переменная PAPAYU_LLM_MODE=fix-plan или отдельный промпт):
Ты — инженерный ассистент внутри программы для создания, анализа и исправления кода. Оператор один: я.
Всегда отвечай ОДНИМ валидным JSON-объектом. Никакого текста вне JSON.
Режимы:
- "fix-plan": предлагаешь план и (опционально) proposed_changes (actions, patch, commands_to_run). Ничего не применяешь.
- "apply": выдаёшь финальный patch и команды для применения/проверки (после подтверждения оператора).
Правила:
- Не выдумывай содержимое файлов/логов. Если нужно — запроси через context_requests.
- Никогда не утверждай, что тесты/команды запускались, если их не запускало приложение.
- Если данных не хватает — задай максимум 2 вопроса в questions и/или добавь context_requests.
- Минимальные изменения. Без широких рефакторингов без явного запроса.
ENGINEERING_MEMORY:
{...вставляется приложением...}
Схема JSON:
- mode: "fix-plan" | "apply" (или опусти для обратной совместимости — тогда ожидается массив actions или объект с actions)
- summary: string
- questions: string[]
- context_requests: [{ type: "read_file"|"search"|"logs"|"env", path?, start_line?, end_line?, query?, glob?, source?, last_n? }]
- plan: [{ step, details }]
- proposed_changes: { patch?, actions?, commands_to_run? }
- patch: string (обязательно в apply при применении diff)
- commands_to_run: string[]
- verification: string[]
- risks: string[]
- rollback: string[]
- memory_patch: object (только ключи из whitelist)
4) Автосбор контекста (без tools)
Приложение до первого запроса к модели собирает базовый контекст и подставляет в user-сообщение:
Базовый набор
- env: версия Python/Node/OS, venv, менеджер зависимостей (если определимо по проекту).
- project prefs: команды тестов/линта/формата из
.papa-yu/project.json(уже в ENGINEERING_MEMORY). - recent_files: список недавно открытых/изменённых файлов из
report_json(если передан). - logs: последние N строк логов (runtime/build) — если приложение имеет к ним доступ.
При ошибке/stacktrace в запросе
- Распарсить пути и номера строк из Traceback.
- Добавить в контекст фрагменты файлов ±80 строк вокруг указанных строк.
- При «падает тест X» — подтянуть файл теста и (по возможности) тестируемый модуль.
Эвристики реализованы в Rust: см. context::gather_base_context, context::fulfill_context_requests.
5) JSON Schema для response_format (OpenAI Chat Completions)
Используется endpoint Chat Completions (/v1/chat/completions). Для строгого JSON можно передать response_format: { type: "json_schema", json_schema: { ... } } (если провайдер поддерживает).
Пример схемы под объединённый контракт (и текущий, и Fix-plan):
{
"name": "papa_yu_plan_response",
"strict": true,
"schema": {
"type": "object",
"properties": {
"mode": { "type": "string", "enum": ["fix-plan", "apply"] },
"summary": { "type": "string" },
"questions": { "type": "array", "items": { "type": "string" } },
"context_requests": {
"type": "array",
"items": {
"type": "object",
"properties": {
"type": { "type": "string", "enum": ["read_file", "search", "logs", "env"] },
"path": { "type": "string" },
"start_line": { "type": "integer" },
"end_line": { "type": "integer" },
"query": { "type": "string" },
"glob": { "type": "string" },
"source": { "type": "string" },
"last_n": { "type": "integer" }
}
}
},
"plan": {
"type": "array",
"items": { "type": "object", "properties": { "step": { "type": "string" }, "details": { "type": "string" } } }
},
"actions": {
"type": "array",
"items": {
"type": "object",
"properties": {
"kind": { "type": "string", "enum": ["CREATE_FILE", "CREATE_DIR", "UPDATE_FILE", "DELETE_FILE", "DELETE_DIR"] },
"path": { "type": "string" },
"content": { "type": "string" }
},
"required": ["kind", "path"]
}
},
"proposed_changes": {
"type": "object",
"properties": {
"patch": { "type": "string" },
"actions": { "type": "array", "items": { "$ref": "#/definitions/action" } },
"commands_to_run": { "type": "array", "items": { "type": "string" } }
}
},
"patch": { "type": "string" },
"commands_to_run": { "type": "array", "items": { "type": "string" } },
"verification": { "type": "array", "items": { "type": "string" } },
"risks": { "type": "array", "items": { "type": "string" } },
"rollback": { "type": "array", "items": { "type": "string" } },
"memory_patch": { "type": "object", "additionalProperties": true }
},
"additionalProperties": true
}
}
Для «только массив actions» схему можно упростить или использовать два варианта (массив vs объект) на стороне парсера — текущий парсер в Rust принимает и массив, и объект с actions и memory_patch.
6) Режим в приложении
Переменная окружения PAPAYU_LLM_MODE:
chat(по умолчанию) — инженер-коллега, ответ массив/объект сactions.fixit— обязан вернуть патч и проверку (текущий FIXIT prompt).fix-plan— один JSON сmode,summary,context_requests,plan,proposed_changes,memory_patch; автосбор контекста и до 2 раундов поcontext_requests.
ENGINEERING_MEMORY подставляется в system prompt приложением (см. memory::build_memory_block).
7) Как подключить в UI
- «Fix (plan)» / текущий сценарий: вызов
propose_actions→ показsummary,plan,risks,questions, превью поproposed_changes.actionsилиactions. - «Применить»: вызов
apply_actions_txс выбраннымиactions(из ответа модели). Память уже обновлена поmemory_patchпри парсинге ответа.
Flow «сначала план → подтверждение → применение» обеспечивается тем, что приложение не применяет действия до явного подтверждения пользователя; модель может отдавать как короткий формат (массив/actions), так и расширенный (mode fix-plan + proposed_changes).
8) Инженерная память
- MEMORY BLOCK подставляется в system prompt.
- Модель заполняет
commands_to_runизproject.default_test_commandи т.п. - При явной просьбе «запомни …» модель возвращает
memory_patch; приложение применяет его по whitelist и сохраняет в файлы.
Whitelist и логика — в src-tauri/src/memory.rs.