feat(analyst): этапы задачи — аналитик раскладывает на steps с критериями готовности, dev идёт по ним
This commit is contained in:
@@ -53,10 +53,11 @@ type AnalystResponse struct {
|
||||
Phase string `json:"phase"`
|
||||
Title string `json:"title"`
|
||||
Goal string `json:"goal"`
|
||||
Repo string `json:"repo"` // одиночный репо (обратная совместимость)
|
||||
Repos json.RawMessage `json:"repos"` // список репо (основной); устойчив к строке
|
||||
Repo string `json:"repo"` // одиночный репо (обратная совместимость)
|
||||
Repos json.RawMessage `json:"repos"` // список репо (основной); устойчив к строке
|
||||
Why string `json:"why"`
|
||||
AC string `json:"ac"`
|
||||
Steps []storage.Step `json:"steps"`
|
||||
Questions []string `json:"questions"`
|
||||
ChatReply string `json:"chat_reply"`
|
||||
AbortReason string `json:"abort_reason"`
|
||||
@@ -86,6 +87,22 @@ func (r *AnalystResponse) reposList() []string {
|
||||
return out
|
||||
}
|
||||
|
||||
// stepsList возвращает этапы: модель может вернуть массив объектов либо
|
||||
// пустой/отсутствующий — тогда nil. Записи без title отбрасываются.
|
||||
func (r *AnalystResponse) stepsList() []storage.Step {
|
||||
if r.Steps == nil {
|
||||
return nil
|
||||
}
|
||||
var out []storage.Step
|
||||
for _, st := range r.Steps {
|
||||
if st.Title == "" {
|
||||
continue
|
||||
}
|
||||
out = append(out, st)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Decide реализует core.Decider через открытый код.
|
||||
func (a *Analyst) Decide(ctx context.Context, history []core.Message, draft storage.Task, force bool) (core.Decision, error) {
|
||||
agent := a.Agent
|
||||
@@ -109,6 +126,7 @@ func (a *Analyst) Decide(ctx context.Context, history []core.Message, draft stor
|
||||
Repos: draft.EffectiveRepos(),
|
||||
Why: draft.Why,
|
||||
AC: draft.AC,
|
||||
Steps: draft.Steps,
|
||||
History: hist,
|
||||
Force: force,
|
||||
}
|
||||
@@ -176,6 +194,9 @@ func (a *Analyst) Decide(ctx context.Context, history []core.Message, draft stor
|
||||
if ar.AC != "" {
|
||||
dec.Draft.AC = ar.AC
|
||||
}
|
||||
if steps := ar.stepsList(); len(steps) > 0 {
|
||||
dec.Draft.Steps = steps
|
||||
}
|
||||
|
||||
return dec, nil
|
||||
}
|
||||
@@ -204,7 +225,7 @@ func validateResponse(ar *AnalystResponse) error {
|
||||
return fmt.Errorf("phase=ask, но нет ни chat_reply, ни questions")
|
||||
}
|
||||
case "propose":
|
||||
if ar.Title == "" && ar.Goal == "" && len(ar.Repos) == 0 && ar.Why == "" && ar.AC == "" {
|
||||
if ar.Title == "" && ar.Goal == "" && len(ar.Repos) == 0 && ar.Why == "" && ar.AC == "" && len(ar.stepsList()) == 0 {
|
||||
return fmt.Errorf("phase=propose, но нет ни одного изменённого поля")
|
||||
}
|
||||
case "ready":
|
||||
@@ -257,9 +278,22 @@ func formatVerdict(ar *AnalystResponse) string {
|
||||
b.WriteString(", ac=")
|
||||
b.WriteString(ar.AC)
|
||||
}
|
||||
if steps := ar.stepsList(); len(steps) > 0 {
|
||||
b.WriteString(", steps=[")
|
||||
var parts []string
|
||||
for _, st := range steps {
|
||||
s := st.Title
|
||||
if st.AC != "" {
|
||||
s += " → " + st.AC
|
||||
}
|
||||
parts = append(parts, s)
|
||||
}
|
||||
b.WriteString(strings.Join(parts, " | "))
|
||||
b.WriteString("]")
|
||||
}
|
||||
if ar.AbortReason != "" {
|
||||
b.WriteString(", abort_reason=")
|
||||
b.WriteString(ar.AbortReason)
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user