diff options
Diffstat (limited to 'internal/storage/db.go')
| -rw-r--r-- | internal/storage/db.go | 76 |
1 files changed, 51 insertions, 25 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go index 4eebd04..d388f72 100644 --- a/internal/storage/db.go +++ b/internal/storage/db.go @@ -48,6 +48,7 @@ func (s *DB) migrate() error { retry_json TEXT NOT NULL DEFAULT '{}', tags_json TEXT NOT NULL DEFAULT '[]', depends_on_json TEXT NOT NULL DEFAULT '[]', + acceptance_criteria_json TEXT NOT NULL DEFAULT '[]', parent_task_id TEXT, state TEXT NOT NULL DEFAULT 'PENDING', created_at DATETIME NOT NULL, @@ -198,6 +199,7 @@ func (s *DB) migrate() error { // system-authored fallback answer, so a human can find and double-check // it later via GET /api/tasks?needs_review=true. `ALTER TABLE tasks ADD COLUMN needs_review BOOLEAN NOT NULL DEFAULT 0`, + `ALTER TABLE tasks ADD COLUMN acceptance_criteria_json TEXT NOT NULL DEFAULT '[]'`, } for _, m := range migrations { if _, err := s.db.Exec(m); err != nil { @@ -233,6 +235,14 @@ func (s *DB) CreateTask(t *task.Task) error { if err != nil { return fmt.Errorf("marshaling depends_on: %w", err) } + acceptanceCriteria := t.AcceptanceCriteria + if acceptanceCriteria == nil { + acceptanceCriteria = []string{} + } + acceptanceCriteriaJSON, err := json.Marshal(acceptanceCriteria) + if err != nil { + return fmt.Errorf("marshaling acceptance_criteria: %w", err) + } tx, err := s.db.Begin() if err != nil { @@ -241,10 +251,10 @@ func (s *DB) CreateTask(t *task.Task) error { defer tx.Rollback() //nolint:errcheck if _, err = tx.Exec(` - INSERT INTO tasks (id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + INSERT INTO tasks (id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, acceptance_criteria_json, parent_task_id, state, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, t.ID, t.Name, t.Description, t.ElaborationInput, t.Project, t.RepositoryURL, string(configJSON), string(t.Priority), - t.Timeout.Duration.Nanoseconds(), string(retryJSON), string(tagsJSON), string(depsJSON), + t.Timeout.Duration.Nanoseconds(), string(retryJSON), string(tagsJSON), string(depsJSON), string(acceptanceCriteriaJSON), t.ParentTaskID, string(t.State), t.CreatedAt.UTC(), t.UpdatedAt.UTC(), ); err != nil { return err @@ -270,15 +280,23 @@ func (s *DB) CreateTask(t *task.Task) error { return tx.Commit() } +// taskSelectColumns is the shared SELECT column list every task-reading +// query in this file uses — extracted so a future column addition is one +// edit instead of five kept in sync by hand. Mirrors storySelectColumns in +// internal/storage/story.go. +func taskSelectColumns() string { + return `SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, acceptance_criteria_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review` +} + // GetTask retrieves a task by ID. func (s *DB) GetTask(id string) (*task.Task, error) { - row := s.db.QueryRow(`SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review FROM tasks WHERE id = ?`, id) + row := s.db.QueryRow(taskSelectColumns()+` FROM tasks WHERE id = ?`, id) return scanTask(row) } // ListTasks returns tasks matching the given filter. func (s *DB) ListTasks(filter TaskFilter) ([]*task.Task, error) { - query := `SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review FROM tasks WHERE 1=1` + query := taskSelectColumns() + ` FROM tasks WHERE 1=1` var args []interface{} if filter.State != "" { @@ -318,7 +336,7 @@ func (s *DB) ListTasks(filter TaskFilter) ([]*task.Task, error) { // ListSubtasks returns all tasks whose parent_task_id matches the given ID. func (s *DB) ListSubtasks(parentID string) ([]*task.Task, error) { - rows, err := s.db.Query(`SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review FROM tasks WHERE parent_task_id = ? ORDER BY created_at ASC`, parentID) + rows, err := s.db.Query(taskSelectColumns()+` FROM tasks WHERE parent_task_id = ? ORDER BY created_at ASC`, parentID) if err != nil { return nil, err } @@ -343,7 +361,7 @@ func (s *DB) ListSubtasks(parentID string) ([]*task.Task, error) { // Only direct dependents are returned — callers that need the full // transitive downstream subtree (e.g. cascade-cancellation) must recurse. func (s *DB) ListDependents(taskID string) ([]*task.Task, error) { - rows, err := s.db.Query(`SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review FROM tasks`) + rows, err := s.db.Query(taskSelectColumns() + ` FROM tasks`) if err != nil { return nil, err } @@ -418,7 +436,7 @@ func (s *DB) ResetTaskForRetry(id string) (*task.Task, error) { } defer tx.Rollback() //nolint:errcheck - t, err := scanTask(tx.QueryRow(`SELECT id, name, description, elaboration_input, project, repository_url, config_json, priority, timeout_ns, retry_json, tags_json, depends_on_json, parent_task_id, state, created_at, updated_at, rejection_comment, question_json, summary, interactions_json, needs_review FROM tasks WHERE id = ?`, id)) + t, err := scanTask(tx.QueryRow(taskSelectColumns()+` FROM tasks WHERE id = ?`, id)) if err != nil { if err == sql.ErrNoRows { return nil, fmt.Errorf("task %q not found", id) @@ -1168,26 +1186,27 @@ type scanner interface { func scanTask(row scanner) (*task.Task, error) { var ( - t task.Task - configJSON string - retryJSON string - tagsJSON string - depsJSON string - state string - priority string - timeoutNS int64 - parentTaskID sql.NullString - elaborationInput sql.NullString - project sql.NullString - repositoryURL sql.NullString - rejectionComment sql.NullString - questionJSON sql.NullString - summary sql.NullString - interactionsJSON sql.NullString + t task.Task + configJSON string + retryJSON string + tagsJSON string + depsJSON string + acceptanceCriteriaJSON string + state string + priority string + timeoutNS int64 + parentTaskID sql.NullString + elaborationInput sql.NullString + project sql.NullString + repositoryURL sql.NullString + rejectionComment sql.NullString + questionJSON sql.NullString + summary sql.NullString + interactionsJSON sql.NullString ) err := row.Scan( &t.ID, &t.Name, &t.Description, &elaborationInput, &project, &repositoryURL, - &configJSON, &priority, &timeoutNS, &retryJSON, &tagsJSON, &depsJSON, + &configJSON, &priority, &timeoutNS, &retryJSON, &tagsJSON, &depsJSON, &acceptanceCriteriaJSON, &parentTaskID, &state, &t.CreatedAt, &t.UpdatedAt, &rejectionComment, &questionJSON, &summary, &interactionsJSON, &t.NeedsReview, ) @@ -1216,6 +1235,13 @@ func scanTask(row scanner) (*task.Task, error) { if err := json.Unmarshal([]byte(depsJSON), &t.DependsOn); err != nil { return nil, fmt.Errorf("unmarshaling depends_on: %w", err) } + acRaw := acceptanceCriteriaJSON + if acRaw == "" { + acRaw = "[]" + } + if err := json.Unmarshal([]byte(acRaw), &t.AcceptanceCriteria); err != nil { + return nil, fmt.Errorf("unmarshaling acceptance_criteria: %w", err) + } raw := interactionsJSON.String if raw == "" { raw = "[]" |
