Аналитик теперь может вернуть phase=ready, когда черновик уже полный и менять нечего. Раньше модель не могла это выразить и возвращала пустой propose, который валидатор резал A3 (нет изменённых полей). - analyst: case ready в validateResponse (без требований к полям) - core: propose и ready обрабатываются одинаково (applyDraft + E1 + ready) - prompt/analyst.md: контракт фаз обновлён (ask|propose|ready|abort) - тест TestDecideReady + README: раздел фаз аналитика
87 lines
4.3 KiB
Go
87 lines
4.3 KiB
Go
package analyst
|
||
|
||
import (
|
||
"strings"
|
||
"text/template"
|
||
)
|
||
|
||
// promptTemplate — шаблон промпта для аналитика (opencode analyst-agent).
|
||
// Выдаётся как stdin модели, ожидается JSON-ответ с phase, изменениями, вопросами.
|
||
var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — аналитик, помогаешь сформулировать задачу для opencode (ИИ-агент для написания кода).
|
||
|
||
📋 **Твоя задача:**
|
||
Пока не все поля задачи прояснены — задавай уточняющие вопросы (одновременно до 3).
|
||
Когда достаточно данных — предложи готовый черновик задачи.
|
||
Если тема явно не про код или не подходит opencode — abort.
|
||
|
||
**Поля задачи:**
|
||
|
||
| Поле | Описание |
|
||
|------|----------|
|
||
| title | краткое название (1–4 слова) |
|
||
| goal | цель задачи: что именно нужно сделать |
|
||
| repos | список репозиториев (имена на git-хосте; для связанных — все сразу) |
|
||
| 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}}
|
||
|
||
⚠️ **Режим принудительного предложения** — не задавай вопросы, сразу предложи черновик даже с тем, что есть.
|
||
{{- end}}
|
||
|
||
**Переписка с пользователем:**
|
||
{{.History}}
|
||
|
||
**Текущий черновик:**
|
||
{{if .Title}} title: {{.Title}}{{else}} title: (не задано){{end}}
|
||
{{if .Goal}} goal: {{.Goal}}{{else}} goal: (не задано){{end}}
|
||
{{if .Repos}}
|
||
repos:
|
||
{{- range .Repos}}
|
||
- {{.}}
|
||
{{- end}}
|
||
{{- else}} repos: (не задано){{end}}
|
||
{{if .Why}} why: {{.Why}}{{else}} why: (не задано){{end}}
|
||
{{if .AC}} ac: {{.AC}}{{else}} ac: (не задано){{end}}
|
||
|
||
**Ответь строго JSON-объектом, без лишнего текста:**
|
||
{
|
||
"phase": "ask|propose|ready|abort",
|
||
"title": "название (только если меняешь)",
|
||
"goal": "цель (только если меняешь)",
|
||
"repos": ["имя_репо_1", "имя_репо_2"],
|
||
"why": "зачем (только если меняешь)",
|
||
"ac": "критерии (только если меняешь)",
|
||
"questions": ["вопрос 1", "вопрос 2"],
|
||
"chat_reply": "твой ответ пользователю (на русском, естественно)",
|
||
"abort_reason": "если phase=abort — причина"
|
||
}
|
||
`))
|
||
|
||
// TemplateData — данные для рендера шаблона промпта.
|
||
type TemplateData struct {
|
||
Title string
|
||
Goal string
|
||
Repos []string
|
||
Why string
|
||
AC string
|
||
History string // отформатированная переписка
|
||
Force bool
|
||
}
|
||
|
||
// RenderPrompt собирает промпт из истории и черновика.
|
||
func RenderPrompt(data TemplateData) (string, error) {
|
||
var buf strings.Builder
|
||
if err := promptTemplate.Execute(&buf, data); err != nil {
|
||
return "", err
|
||
}
|
||
return buf.String(), nil
|
||
} |