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

@@ -11,9 +11,9 @@ import (
func (s *Storage) CreateTask(ctx context.Context, t *Task) (int64, error) {
now := Now()
res, err := s.db.ExecContext(ctx, `
INSERT INTO tasks (chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ChatID, t.Title, t.Goal, t.Repo, t.Why, t.AC,
INSERT INTO tasks (chat_id, title, goal, repo, repos, why, ac, task_tag, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t.ChatID, t.Title, t.Goal, t.Repo, t.ReposJoined(), t.Why, t.AC,
t.TaskTag, StatusDraft, now, now,
)
if err != nil {
@@ -33,10 +33,11 @@ func (s *Storage) CreateTask(ctx context.Context, t *Task) (int64, error) {
// GetTask возвращает задачу по ID.
func (s *Storage) GetTask(ctx context.Context, id int64) (*Task, error) {
t := &Task{}
var reposStr string
err := s.db.QueryRowContext(ctx, `
SELECT id, chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at
SELECT id, chat_id, title, goal, repo, repos, why, ac, task_tag, status, created_at, updated_at
FROM tasks WHERE id = ?`, id).Scan(
&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo,
&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo, &reposStr,
&t.Why, &t.AC, &t.TaskTag, &t.Status, &t.CreatedAt, &t.UpdatedAt,
)
if err == sql.ErrNoRows {
@@ -45,6 +46,7 @@ func (s *Storage) GetTask(ctx context.Context, id int64) (*Task, error) {
if err != nil {
return nil, fmt.Errorf("%w: get task %d: %w", ErrDB, id, err)
}
t.SetReposFromDB(reposStr)
return t, nil
}
@@ -72,9 +74,9 @@ func (s *Storage) UpdateTask(ctx context.Context, t *Task) error {
now := Now()
res, err := s.db.ExecContext(ctx, `
UPDATE tasks
SET title=?, goal=?, repo=?, why=?, ac=?, status=?, updated_at=?
SET title=?, goal=?, repo=?, repos=?, why=?, ac=?, status=?, updated_at=?
WHERE id=?`,
t.Title, t.Goal, t.Repo, t.Why, t.AC, t.Status, now, t.ID,
t.Title, t.Goal, t.Repo, t.ReposJoined(), t.Why, t.AC, t.Status, now, t.ID,
)
if err != nil {
return fmt.Errorf("%w: update task %d: %w", ErrDB, t.ID, err)
@@ -91,12 +93,13 @@ func (s *Storage) UpdateTask(ctx context.Context, t *Task) error {
// Терминальные статусы: success, cancelled, aborted, closed.
func (s *Storage) GetActiveTaskByChatID(ctx context.Context, chatID string) (*Task, error) {
t := &Task{}
var reposStr string
err := s.db.QueryRowContext(ctx, `
SELECT id, chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at
SELECT id, chat_id, title, goal, repo, repos, why, ac, task_tag, status, created_at, updated_at
FROM tasks
WHERE chat_id = ? AND status NOT IN ('success','cancelled','aborted','closed')
ORDER BY updated_at DESC LIMIT 1`, chatID).Scan(
&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo,
&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo, &reposStr,
&t.Why, &t.AC, &t.TaskTag, &t.Status, &t.CreatedAt, &t.UpdatedAt)
if err == sql.ErrNoRows {
return nil, fmt.Errorf("%w: no active task for chat %s", ErrNotFound, chatID)
@@ -104,6 +107,7 @@ func (s *Storage) GetActiveTaskByChatID(ctx context.Context, chatID string) (*Ta
if err != nil {
return nil, fmt.Errorf("%w: get active task %s: %w", ErrDB, chatID, err)
}
t.SetReposFromDB(reposStr)
return t, nil
}
@@ -125,7 +129,7 @@ func (s *Storage) ListTasks(ctx context.Context, filter TaskFilter) ([]*Task, er
args = append(args, filter.Limit, filter.Offset)
rows, err := s.db.QueryContext(ctx, `
SELECT id, chat_id, title, goal, repo, why, ac, task_tag, status, created_at, updated_at
SELECT id, chat_id, title, goal, repo, repos, why, ac, task_tag, status, created_at, updated_at
FROM tasks WHERE `+where+` ORDER BY updated_at DESC LIMIT ? OFFSET ?`, args...)
if err != nil {
return nil, fmt.Errorf("%w: list tasks: %w", ErrDB, err)
@@ -135,10 +139,12 @@ func (s *Storage) ListTasks(ctx context.Context, filter TaskFilter) ([]*Task, er
var tasks []*Task
for rows.Next() {
t := &Task{}
if err := rows.Scan(&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo,
var reposStr string
if err := rows.Scan(&t.ID, &t.ChatID, &t.Title, &t.Goal, &t.Repo, &reposStr,
&t.Why, &t.AC, &t.TaskTag, &t.Status, &t.CreatedAt, &t.UpdatedAt); err != nil {
return nil, fmt.Errorf("%w: scan task: %w", ErrDB, err)
}
t.SetReposFromDB(reposStr)
tasks = append(tasks, t)
}
return tasks, rows.Err()