Files
ratatoskr-go/internal/storage/models.go
Hermes bd3d825738
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
feat: множественные репозитории (Repos) и клонирование в воркере
- 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
2026-08-16 09:18:15 +05:00

139 lines
5.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package storage
import "encoding/json"
// Status — статус задачи (state machine).
type Status string
const (
StatusDraft Status = "draft" // только что создана
StatusCollecting Status = "collecting" // аналитик собирает детали
StatusReady Status = "ready" // черновик готов, ждёт запуска
StatusRunning Status = "running" // opencode работает
StatusSuccess Status = "success" // задача выполнена
StatusFailed Status = "failed" // ошибка выполнения
StatusTimeout Status = "timeout" // таймаут opencode
StatusCancelled Status = "cancelled" // отменена пользователем
StatusAborted Status = "aborted" // сбой сбора, черновик выброшен
StatusClosed Status = "closed" // закрыта вручную
)
// AllStatuses — все возможные статусы для валидации.
var AllStatuses = []Status{
StatusDraft, StatusCollecting, StatusReady,
StatusRunning, StatusSuccess, StatusFailed, StatusTimeout,
StatusCancelled, StatusAborted, StatusClosed,
}
// validTransitions задаёт разрешённые переходы статусов.
var validTransitions = map[Status][]Status{
StatusDraft: {StatusCollecting, StatusCancelled, StatusAborted},
StatusCollecting: {StatusReady, StatusDraft, StatusCancelled, StatusAborted},
StatusReady: {StatusRunning, StatusCancelled, StatusAborted, StatusClosed, StatusCollecting}, // правка готового
StatusRunning: {StatusSuccess, StatusFailed, StatusTimeout, StatusCancelled},
StatusSuccess: {StatusClosed},
StatusFailed: {StatusReady, StatusClosed, StatusCancelled}, // retry
StatusTimeout: {StatusReady, StatusClosed, StatusCancelled}, // retry
StatusCancelled: {StatusClosed},
StatusAborted: {StatusClosed},
StatusClosed: {}, // терминальный
}
// IsValidTransition проверяет, допустим ли переход from → to.
func IsValidTransition(from, to Status) bool {
allowed, ok := validTransitions[from]
if !ok {
return false
}
for _, s := range allowed {
if s == to {
return true
}
}
return false
}
// IsTerminal возвращает true, если статус терминальный.
func IsTerminal(s Status) bool {
return s == StatusSuccess || s == StatusCancelled ||
s == StatusAborted || s == StatusClosed
}
// Task — запись задачи в БД.
type Task struct {
ID int64 `json:"id"`
ChatID string `json:"chat_id"` // tg://<id>
Title string `json:"title"`
Goal string `json:"goal"`
Repo string `json:"repo"` // обратная совместимость: одиночный репозиторий
Repos []string `json:"repos"` // список репозиториев (основной)
Why string `json:"why"`
AC string `json:"ac"` // acceptance criteria
TaskTag string `json:"task_tag"` // UUID, стабильный на всю жизнь
Status Status `json:"status"`
CreatedAt SQLiteTime `json:"created_at"`
UpdatedAt SQLiteTime `json:"updated_at"`
}
// ReposJoined возвращает repos как одну строку (JSON-массив) для хранения в БД.
// Пустой список → пустая строка.
func (t *Task) ReposJoined() string {
if len(t.Repos) == 0 {
return ""
}
b, _ := json.Marshal(t.Repos)
return string(b)
}
// EffectiveRepos возвращает сам репо: если Repos пуст, но Repo задан —
// подтягивает одиночный (обратная совместимость).
func (t *Task) EffectiveRepos() []string {
if len(t.Repos) > 0 {
return t.Repos
}
if t.Repo != "" {
return []string{t.Repo}
}
return nil
}
// SetReposFromDB заполняет Repos из сохранённой строки (JSON), либо
// из одиночного repo (обратная совместимость).
func (t *Task) SetReposFromDB(repos string) {
if repos == "" {
t.Repos = nil
return
}
_ = json.Unmarshal([]byte(repos), &t.Repos)
}
// Trace — запись трассировки выполнения.
type TraceStatus string
const (
TraceRunning TraceStatus = "running"
TraceSuccess TraceStatus = "success"
TraceFailed TraceStatus = "failed"
TraceTimeout TraceStatus = "timeout"
)
// Trace — лог одного субагента.
type Trace struct {
ID int64 `json:"id"`
TaskID int64 `json:"task_id"`
Agent string `json:"agent"` // analyst | researcher | dev | reviewer
SessionID string `json:"session_id"` // opencode session_id
Prompt string `json:"prompt"`
Output string `json:"output"` // полный NDJSON или summary
Status TraceStatus `json:"status"`
StartedAt SQLiteTime `json:"started_at"`
FinishedAt NullSQLiteTime `json:"finished_at,omitempty"`
}
// TaskFilter — параметры фильтрации списка задач.
type TaskFilter struct {
ChatID string
Status Status
Limit int
Offset int
}