papayu/docs/FIX_PLAN_CONTRACT.md
Yuriy e76236dc55 Initial commit: papa-yu v2.4.4
- 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>
2026-01-31 11:33:19 +03:00

12 KiB
Raw Blame History

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.