internal/analyst: Decider через opencode (A1–A4)
All checks were successful
build-test / build (push) Successful in 1m15s
All checks were successful
build-test / build (push) Successful in 1m15s
- Analyst struct: строит Go-template промпт из history+draft - opencode.Runner.Run (agent=analyst) → ExtractVerdict → ExtractJSON - Валидация phase/полей → core.Decision - A1 DecodeFail, A2 RunError, A3 Validation, A4 NotReady - MockRunner в тестах (11 тестов, зелёные) - core.runDecide оборачивает ошибки аналитика в D1 ErrDecideFailed - allow ready→collecting (фикс для правки готового черновика)
This commit is contained in:
74
internal/analyst/prompt.go
Normal file
74
internal/analyst/prompt.go
Normal file
@@ -0,0 +1,74 @@
|
||||
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 | цель задачи: что именно нужно сделать |
|
||||
| repo | путь к репозиторию (относительно /opt/data/src/) |
|
||||
| why | зачем это нужно, контекст |
|
||||
| ac | acceptance criteria — конкретный результат, что считается готовым |
|
||||
|
||||
{{- if .Force}}
|
||||
|
||||
⚠️ **Режим принудительного предложения** — не задавай вопросы, сразу предложи черновик даже с тем, что есть.
|
||||
{{- end}}
|
||||
|
||||
**Переписка с пользователем:**
|
||||
{{.History}}
|
||||
|
||||
**Текущий черновик:**
|
||||
{{if .Title}} title: {{.Title}}{{else}} title: (не задано){{end}}
|
||||
{{if .Goal}} goal: {{.Goal}}{{else}} goal: (не задано){{end}}
|
||||
{{if .Repo}} repo: {{.Repo}}{{else}} repo: (не задано){{end}}
|
||||
{{if .Why}} why: {{.Why}}{{else}} why: (не задано){{end}}
|
||||
{{if .AC}} ac: {{.AC}}{{else}} ac: (не задано){{end}}
|
||||
|
||||
**Ответь строго JSON-объектом, без лишнего текста:**
|
||||
{
|
||||
"phase": "ask|propose|abort",
|
||||
"title": "название (только если меняешь)",
|
||||
"goal": "цель (только если меняешь)",
|
||||
"repo": "путь к репозиторию (только если меняешь)",
|
||||
"why": "зачем (только если меняешь)",
|
||||
"ac": "критерии (только если меняешь)",
|
||||
"questions": ["вопрос 1", "вопрос 2"],
|
||||
"chat_reply": "твой ответ пользователю (на русском, естественно)",
|
||||
"abort_reason": "если phase=abort — причина"
|
||||
}
|
||||
`))
|
||||
|
||||
// TemplateData — данные для рендера шаблона промпта.
|
||||
type TemplateData struct {
|
||||
Title string
|
||||
Goal string
|
||||
Repo 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
|
||||
}
|
||||
Reference in New Issue
Block a user