feat(analyst): этапы задачи — аналитик раскладывает на steps с критериями готовности, dev идёт по ним
This commit is contained in:
@@ -3,6 +3,8 @@ package worker
|
||||
import (
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/kamelion/ratatoskr-go/internal/storage"
|
||||
)
|
||||
|
||||
// devPromptTemplate — промпт для dev-агента при запуске задачи.
|
||||
@@ -20,6 +22,12 @@ var devPromptTemplate = template.Must(template.New("dev").Parse(`Ты — dev-а
|
||||
{{if .Why}}Зачем: {{.Why}}{{end}}
|
||||
{{if .AC}}Критерии готовности:
|
||||
{{.AC}}{{end}}
|
||||
{{if .Steps}}
|
||||
**Этапы (выполняй по порядку, у каждого свой критерий готовности):**
|
||||
{{- range .Steps}}
|
||||
{{.Title}}{{if .AC}} — готово, когда: {{.AC}}{{end}}
|
||||
{{- end}}
|
||||
{{end}}
|
||||
|
||||
**Инструкции:**
|
||||
1. Рабочий каталог — общий корень, в котором лежат все репозитории по именам.
|
||||
@@ -38,6 +46,7 @@ type DevPromptData struct {
|
||||
Repos []string
|
||||
Why string
|
||||
AC string
|
||||
Steps []storage.Step
|
||||
|
||||
// ReviewFeedback — замечания ревьюера при повторном прогоне dev
|
||||
// (не пусто → dev должен исправить именно это).
|
||||
@@ -124,4 +133,4 @@ func reviewFeedbackList(branch string, comments []string) []string {
|
||||
return nil
|
||||
}
|
||||
return []string{s}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,6 +255,7 @@ func (w *Worker) runTask(ctx context.Context, task *storage.Task) (err error) {
|
||||
Repos: repos,
|
||||
Why: task.Why,
|
||||
AC: task.AC,
|
||||
Steps: task.Steps,
|
||||
Branch: branch,
|
||||
ReviewFeedback: reviewFeedbackList(branch, feedback),
|
||||
}
|
||||
|
||||
@@ -725,6 +725,7 @@ func TestWorkerBadStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorkerPromptRendered — dev-промпт собирается из полей задачи.
|
||||
func TestWorkerPromptRendered(t *testing.T) {
|
||||
s := setupWorkerDB(t)
|
||||
task := createReadyTask(t, s, "prompt-test")
|
||||
@@ -758,6 +759,63 @@ func TestWorkerPromptRendered(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestWorkerPromptIncludesSteps — этапы задачи попадают в dev-промпт по порядку
|
||||
// с критериями готовности.
|
||||
func TestWorkerPromptIncludesSteps(t *testing.T) {
|
||||
s := setupWorkerDB(t)
|
||||
task := createReadyTask(t, s, "steps-test")
|
||||
|
||||
ctx := context.Background()
|
||||
task.Steps = []storage.Step{
|
||||
{Title: "Модель", AC: "операции готовы"},
|
||||
{Title: "UI"},
|
||||
}
|
||||
if err := s.UpdateTask(ctx, task); err != nil {
|
||||
t.Fatalf("update steps: %v", err)
|
||||
}
|
||||
|
||||
w := &Worker{
|
||||
Store: s,
|
||||
Runner: &mockRunnerWorker{result: &opencode.Result{RC: 0, Stdout: "ok", SessionID: "s"}},
|
||||
Worktree: t.TempDir(),
|
||||
}
|
||||
seedFakeRepo(t, w.Worktree, "steps-test")
|
||||
|
||||
_ = w.runTask(ctx, task)
|
||||
|
||||
traces, err := s.GetTraces(ctx, task.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("get traces: %v", err)
|
||||
}
|
||||
tr := traces[0]
|
||||
for _, want := range []string{"Этапы", "Модель", "операции готовы", "UI"} {
|
||||
if !strings.Contains(tr.Prompt, want) {
|
||||
t.Errorf("dev-промпт не содержит %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRenderDevPromptSteps — прямой рендер dev-промпта с этапами.
|
||||
func TestRenderDevPromptSteps(t *testing.T) {
|
||||
prompt, err := RenderDevPrompt(DevPromptData{
|
||||
Title: "Калькулятор",
|
||||
AC: "работает",
|
||||
Steps: []storage.Step{
|
||||
{Title: "Модель", AC: "операции готовы"},
|
||||
{Title: "UI"},
|
||||
},
|
||||
Branch: "feat/abc",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("render: %v", err)
|
||||
}
|
||||
for _, want := range []string{"Этапы", "Модель — готово, когда: операции готовы", "UI"} {
|
||||
if !strings.Contains(prompt, want) {
|
||||
t.Errorf("prompt не содержит %q", want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFeatureBranchName(t *testing.T) {
|
||||
cases := []struct {
|
||||
tag string
|
||||
|
||||
Reference in New Issue
Block a user