feat: фаза ready — черновик готов как есть, без требований к изменённым полям
Some checks failed
CI / test (push) Failing after 52s
CI / build-and-package (amd64, linux) (push) Failing after 27s
CI / build-and-package (amd64, windows) (push) Failing after 28s

Аналитик теперь может вернуть phase=ready, когда черновик уже полный и
менять нечего. Раньше модель не могла это выразить и возвращала пустой
propose, который валидатор резал A3 (нет изменённых полей).

- analyst: case ready в validateResponse (без требований к полям)
- core: propose и ready обрабатываются одинаково (applyDraft + E1 + ready)
- prompt/analyst.md: контракт фаз обновлён (ask|propose|ready|abort)
- тест TestDecideReady + README: раздел фаз аналитика
This commit is contained in:
Hermes
2026-08-17 14:07:05 +05:00
parent 168b0042c7
commit 46065f84fd
7 changed files with 58 additions and 10 deletions

View File

@@ -173,6 +173,8 @@ func validateResponse(ar *AnalystResponse) error {
if ar.Title == "" && ar.Goal == "" && len(ar.Repos) == 0 && ar.Why == "" && ar.AC == "" {
return fmt.Errorf("phase=propose, но нет ни одного изменённого поля")
}
case "ready":
// черновик уже полный и готов как есть — изменённых полей НЕ требуется
case "abort":
// abort_reason — не обязателен, но желателен
default:

View File

@@ -87,6 +87,23 @@ func TestDecideAbort(t *testing.T) {
}
}
func TestDecideReady(t *testing.T) {
// ready с пустыми изменёнными полями — ВАЛИДНО (черновик готов как есть)
a := &Analyst{Runner: &mockRunner{result: &opencode.Result{
RC: 0,
Stdout: `{"type":"text","part":{"text":"{\"phase\":\"ready\",\"chat_reply\":\"Черновик готов, запускаю.\"}"}}`,
}}, Worktree: "/tmp"}
history := []core.Message{{Role: "user", Content: "Сделай калькулятор"}}
dec, err := a.Decide(context.Background(), history, storage.Task{}, false)
if err != nil {
t.Fatalf("Decide err: %v", err)
}
if dec.Phase != "ready" {
t.Errorf("Phase = %q, want ready", dec.Phase)
}
}
func TestDecodeFail(t *testing.T) {
a := &Analyst{Runner: &mockRunner{result: &opencode.Result{
RC: 0,

View File

@@ -24,6 +24,14 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
| why | зачем это нужно, контекст |
| ac | acceptance criteria — конкретный результат, что считается готовым |
Изменяй в JSON только те поля, которые надо поменять; что менять не надо — пустой строкой.
**Фаза ответа (phase):**
- `ask` — данных не хватает: задай вопросы (до 3), черновик не меняешь.
- `propose` — черновик ДОСТАТОЧЕН и ты ВНОСИШЬ правки: заполни изменяемые поля (title/goal/repos/why/ac), в chat_reply — краткое резюме.
- `ready` — черновик уже ПОЛНЫЙ и готов как есть, менять НЕЧЕГО: phase="ready", все поля изменений оставь пустыми, в chat_reply — резюме и подтверждение готовности. Задачу можно отправлять агенту-кодеру.
- `abort` — тема не про код / не подходит: phase="abort" с abort_reason.
{{- if .Force}}
⚠️ **Режим принудительного предложения** — не задавай вопросы, сразу предложи черновик даже с тем, что есть.
@@ -46,7 +54,7 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
**Ответь строго JSON-объектом, без лишнего текста:**
{
"phase": "ask|propose|abort",
"phase": "ask|propose|ready|abort",
"title": "название (только если меняешь)",
"goal": "цель (только если меняешь)",
"repos": ["имя_репо_1", "имя_репо_2"],