feat: множественные репозитории (Repos) и клонирование в воркере
All checks were successful
CI / test (push) Successful in 46s
CI / build-and-package (amd64, darwin) (push) Successful in 36s
CI / build-and-package (amd64, linux) (push) Successful in 37s
CI / build-and-package (amd64, windows) (push) Successful in 44s
CI / build-and-package (arm64, darwin) (push) Successful in 35s
CI / build-and-package (arm64, linux) (push) Successful in 36s

- Task.Repos []string (XML-колонка repos, обратная совместимость с repo)
- config: блок git {base_url, token}
- аналитик: ответ repos[], шаблон показывает список
- core: propose без repos → возврат в сбор (E1)
- worker вариант A: один dev из общего cwd, prepareRepos клонирует
  недостающие репо (git clone), validateRepoName (E3), ErrRepoNotGit (E4)
- ошибки E1-E4 в worker/errors.go
This commit is contained in:
Hermes
2026-08-16 09:18:15 +05:00
parent 7be75fe14f
commit bd3d825738
14 changed files with 346 additions and 71 deletions

View File

@@ -31,7 +31,8 @@ type AnalystResponse struct {
Phase string `json:"phase"`
Title string `json:"title"`
Goal string `json:"goal"`
Repo string `json:"repo"`
Repo string `json:"repo"` // одиночный репо (обратная совместимость)
Repos []string `json:"repos"` // список репо (основной)
Why string `json:"why"`
AC string `json:"ac"`
Questions []string `json:"questions"`
@@ -57,7 +58,7 @@ func (a *Analyst) Decide(ctx context.Context, history []core.Message, draft stor
td := TemplateData{
Title: draft.Title,
Goal: draft.Goal,
Repo: draft.Repo,
Repos: draft.EffectiveRepos(),
Why: draft.Why,
AC: draft.AC,
History: hist,
@@ -114,6 +115,11 @@ func (a *Analyst) Decide(ctx context.Context, history []core.Message, draft stor
if ar.Repo != "" {
dec.Draft.Repo = ar.Repo
}
if len(ar.Repos) > 0 {
dec.Draft.Repos = ar.Repos
// Синхронизируем одиночный repo для старых потребителей.
dec.Draft.Repo = strings.Join(ar.Repos, ",")
}
if ar.Why != "" {
dec.Draft.Why = ar.Why
}
@@ -148,7 +154,7 @@ func validateResponse(ar *AnalystResponse) error {
return fmt.Errorf("phase=ask, но нет ни chat_reply, ни questions")
}
case "propose":
if ar.Title == "" && ar.Goal == "" && ar.Repo == "" && ar.Why == "" && ar.AC == "" {
if ar.Title == "" && ar.Goal == "" && len(ar.Repos) == 0 && ar.Why == "" && ar.AC == "" {
return fmt.Errorf("phase=propose, но нет ни одного изменённого поля")
}
case "abort":

View File

@@ -20,7 +20,7 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
|------|----------|
| title | краткое название (14 слова) |
| goal | цель задачи: что именно нужно сделать |
| repo | путь к репозиторию (относительно /opt/data/src/) |
| repos | список репозиториев (имена на git-хосте; для связанных — все сразу) |
| why | зачем это нужно, контекст |
| ac | acceptance criteria — конкретный результат, что считается готовым |
@@ -35,7 +35,12 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
**Текущий черновик:**
{{if .Title}} title: {{.Title}}{{else}} title: (не задано){{end}}
{{if .Goal}} goal: {{.Goal}}{{else}} goal: (не задано){{end}}
{{if .Repo}} repo: {{.Repo}}{{else}} repo: (не задано){{end}}
{{if .Repos}}
repos:
{{- range .Repos}}
- {{.}}
{{- end}}
{{- else}} repos: (не задано){{end}}
{{if .Why}} why: {{.Why}}{{else}} why: (не задано){{end}}
{{if .AC}} ac: {{.AC}}{{else}} ac: (не задано){{end}}
@@ -44,7 +49,7 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
"phase": "ask|propose|abort",
"title": "название (только если меняешь)",
"goal": "цель (только если меняешь)",
"repo": "путь к репозиторию (только если меняешь)",
"repos": ["имя_репо_1", "имя_репо_2"],
"why": "зачем (только если меняешь)",
"ac": "критерии (только если меняешь)",
"questions": ["вопрос 1", "вопрос 2"],
@@ -57,7 +62,7 @@ var promptTemplate = template.Must(template.New("analyst").Parse(`Ты — ан
type TemplateData struct {
Title string
Goal string
Repo string
Repos []string
Why string
AC string
History string // отформатированная переписка