package storage import ( "encoding/json" "github.com/kamelion/ratatoskr-go/internal/model" ) // Status — алиас доменного статуса задачи. // // Совместимый мост: весь внешний код продолжает использовать storage.Status // (например «storage.StatusRunning»), но единый источник истины — model.Status. type Status = model.Status // Статусы задачи — re-export из model. const ( StatusDraft = model.StatusDraft StatusCollecting = model.StatusCollecting StatusReady = model.StatusReady StatusApproved = model.StatusApproved StatusRunning = model.StatusRunning StatusSuccess = model.StatusSuccess StatusFailed = model.StatusFailed StatusTimeout = model.StatusTimeout StatusCancelled = model.StatusCancelled StatusAborted = model.StatusAborted StatusClosed = model.StatusClosed ) // AllStatuses — все возможные статусы для валидации. var AllStatuses = model.AllStatuses // IsValidTransition проверяет, допустим ли переход from → to. func IsValidTransition(from, to Status) bool { return model.IsValidTransition(from, to) } // IsTerminal возвращает true, если статус терминальный. func IsTerminal(s Status) bool { return model.IsTerminal(s) } // Step — этап задачи с собственным критерием готовности. type Step struct { Title string `json:"title"` AC string `json:"ac"` // acceptance criterion этапа } // Task — запись задачи в БД. type Task struct { ID int64 `json:"id"` ChatID string `json:"chat_id"` // tg:// 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 Steps []Step `json:"steps"` // этапы задачи (опционально) 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) } // StepsJoined возвращает steps как одну строку (JSON-массив) для хранения в БД. // Пустой список → пустая строка. func (t *Task) StepsJoined() string { if len(t.Steps) == 0 { return "" } b, _ := json.Marshal(t.Steps) return string(b) } // SetStepsFromDB заполняет Steps из сохранённой строки (JSON). func (t *Task) SetStepsFromDB(steps string) { if steps == "" { t.Steps = nil return } _ = json.Unmarshal([]byte(steps), &t.Steps) } // TraceStatus — алиас доменного статуса трассировки. type TraceStatus = model.TraceStatus const ( TraceRunning TraceStatus = model.TraceRunning TraceSuccess TraceStatus = model.TraceSuccess TraceFailed TraceStatus = model.TraceFailed TraceTimeout TraceStatus = model.TraceTimeout ) // 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 }