summaryrefslogtreecommitdiff
path: root/internal/storage
diff options
context:
space:
mode:
Diffstat (limited to 'internal/storage')
-rw-r--r--internal/storage/db.go50
-rw-r--r--internal/storage/db_test.go70
2 files changed, 112 insertions, 8 deletions
diff --git a/internal/storage/db.go b/internal/storage/db.go
index 4ae0ab1..37d1ada 100644
--- a/internal/storage/db.go
+++ b/internal/storage/db.go
@@ -134,6 +134,9 @@ func (s *DB) migrate() error {
updated_at DATETIME NOT NULL
)`,
`ALTER TABLE tasks ADD COLUMN story_id TEXT`,
+ `ALTER TABLE tasks ADD COLUMN acceptance_criteria TEXT NOT NULL DEFAULT ''`,
+ `ALTER TABLE tasks ADD COLUMN checker_for_task_id TEXT NOT NULL DEFAULT ''`,
+ `ALTER TABLE tasks ADD COLUMN checker_report TEXT NOT NULL DEFAULT ''`,
}
for _, m := range migrations {
if _, err := s.db.Exec(m); err != nil {
@@ -171,24 +174,25 @@ func (s *DB) CreateTask(t *task.Task) error {
}
_, err = s.db.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, story_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ 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, story_id, acceptance_criteria, checker_for_task_id, checker_report)
+ 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.ParentTaskID, string(t.State), t.CreatedAt.UTC(), t.UpdatedAt.UTC(), t.StoryID,
+ t.AcceptanceCriteria, t.CheckerForTaskID, t.CheckerReport,
)
return err
}
// 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, story_id FROM tasks WHERE id = ?`, id)
+ 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, story_id, acceptance_criteria, checker_for_task_id, checker_report 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, story_id FROM tasks WHERE 1=1`
+ 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, story_id, acceptance_criteria, checker_for_task_id, checker_report FROM tasks WHERE 1=1`
var args []interface{}
if filter.State != "" {
@@ -224,7 +228,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, story_id FROM tasks WHERE parent_task_id = ? ORDER BY created_at ASC`, parentID)
+ 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, story_id, acceptance_criteria, checker_for_task_id, checker_report FROM tasks WHERE parent_task_id = ? ORDER BY created_at ASC`, parentID)
if err != nil {
return nil, err
}
@@ -277,7 +281,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, story_id FROM tasks WHERE id = ?`, id))
+ 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, story_id, acceptance_criteria, checker_for_task_id, checker_report FROM tasks WHERE id = ?`, id))
if err != nil {
if err == sql.ErrNoRows {
return nil, fmt.Errorf("task %q not found", id)
@@ -777,6 +781,24 @@ func (s *DB) UpdateTaskSummary(taskID, summary string) error {
return err
}
+// UpdateTaskCheckerReport sets the checker_report field on a task.
+func (s *DB) UpdateTaskCheckerReport(id, report string) error {
+ now := time.Now().UTC()
+ _, err := s.db.Exec(`UPDATE tasks SET checker_report = ?, updated_at = ? WHERE id = ?`, report, now, id)
+ return err
+}
+
+// GetCheckerTask returns the checker task for the given checked task ID,
+// or nil if no checker task exists.
+func (s *DB) GetCheckerTask(checkedTaskID 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, story_id, acceptance_criteria, checker_for_task_id, checker_report FROM tasks WHERE checker_for_task_id = ? LIMIT 1`, checkedTaskID)
+ t, err := scanTask(row)
+ if err == sql.ErrNoRows {
+ return nil, nil
+ }
+ return t, err
+}
+
// AppendTaskInteraction appends a Q&A interaction to the task's interaction history.
func (s *DB) AppendTaskInteraction(taskID string, interaction task.Interaction) error {
tx, err := s.db.Begin()
@@ -866,8 +888,17 @@ func scanTask(row scanner) (*task.Task, error) {
summary sql.NullString
interactionsJSON sql.NullString
storyID sql.NullString
+ acceptanceCriteria sql.NullString
+ checkerForTaskID sql.NullString
+ checkerReport sql.NullString
+ )
+ err := row.Scan(
+ &t.ID, &t.Name, &t.Description, &elaborationInput, &project, &repositoryURL,
+ &configJSON, &priority, &timeoutNS, &retryJSON, &tagsJSON, &depsJSON,
+ &parentTaskID, &state, &t.CreatedAt, &t.UpdatedAt,
+ &rejectionComment, &questionJSON, &summary, &interactionsJSON, &storyID,
+ &acceptanceCriteria, &checkerForTaskID, &checkerReport,
)
- err := row.Scan(&t.ID, &t.Name, &t.Description, &elaborationInput, &project, &repositoryURL, &configJSON, &priority, &timeoutNS, &retryJSON, &tagsJSON, &depsJSON, &parentTaskID, &state, &t.CreatedAt, &t.UpdatedAt, &rejectionComment, &questionJSON, &summary, &interactionsJSON, &storyID)
t.ParentTaskID = parentTaskID.String
t.ElaborationInput = elaborationInput.String
t.Project = project.String
@@ -876,6 +907,9 @@ func scanTask(row scanner) (*task.Task, error) {
t.QuestionJSON = questionJSON.String
t.Summary = summary.String
t.StoryID = storyID.String
+ t.AcceptanceCriteria = acceptanceCriteria.String
+ t.CheckerForTaskID = checkerForTaskID.String
+ t.CheckerReport = checkerReport.String
if err != nil {
return nil, err
}
@@ -1199,7 +1233,7 @@ func (s *DB) UpdateStoryStatus(id string, status task.StoryState) error {
// ListTasksByStory returns all tasks associated with a story, ordered by creation time ascending.
func (s *DB) ListTasksByStory(storyID 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, story_id FROM tasks WHERE story_id = ? ORDER BY created_at ASC`,
+ `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, story_id, acceptance_criteria, checker_for_task_id, checker_report FROM tasks WHERE story_id = ? ORDER BY created_at ASC`,
storyID,
)
if err != nil {
diff --git a/internal/storage/db_test.go b/internal/storage/db_test.go
index 0426bd0..0e67e02 100644
--- a/internal/storage/db_test.go
+++ b/internal/storage/db_test.go
@@ -1332,3 +1332,73 @@ func TestListTasksByStory(t *testing.T) {
}
}
+func TestUpdateTaskCheckerReport(t *testing.T) {
+ db := testDB(t)
+ tk := &task.Task{
+ ID: "cr-1", Name: "orig", RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "x"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(tk); err != nil {
+ t.Fatalf("CreateTask: %v", err)
+ }
+ if err := db.UpdateTaskCheckerReport("cr-1", "Tests failed: missing endpoint"); err != nil {
+ t.Fatalf("UpdateTaskCheckerReport: %v", err)
+ }
+ got, err := db.GetTask("cr-1")
+ if err != nil {
+ t.Fatalf("GetTask: %v", err)
+ }
+ if got.CheckerReport != "Tests failed: missing endpoint" {
+ t.Errorf("expected checker report, got %q", got.CheckerReport)
+ }
+}
+
+func TestGetCheckerTask(t *testing.T) {
+ db := testDB(t)
+ checked := &task.Task{
+ ID: "chk-orig", Name: "orig", RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "x"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(checked); err != nil {
+ t.Fatalf("CreateTask checked: %v", err)
+ }
+ checker := &task.Task{
+ ID: "chk-checker", Name: "Check: orig", CheckerForTaskID: "chk-orig",
+ RepositoryURL: "https://github.com/x/y",
+ Agent: task.AgentConfig{Type: "claude", Instructions: "validate"},
+ Priority: task.PriorityNormal,
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "linear"},
+ Tags: []string{}, DependsOn: []string{},
+ State: task.StatePending, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC(),
+ }
+ if err := db.CreateTask(checker); err != nil {
+ t.Fatalf("CreateTask checker: %v", err)
+ }
+
+ // Should find the checker task.
+ got, err := db.GetCheckerTask("chk-orig")
+ if err != nil {
+ t.Fatalf("GetCheckerTask: %v", err)
+ }
+ if got == nil || got.ID != "chk-checker" {
+ t.Errorf("expected checker task ID chk-checker, got %v", got)
+ }
+
+ // Should return nil when no checker exists.
+ none, err := db.GetCheckerTask("nonexistent")
+ if err != nil {
+ t.Fatalf("GetCheckerTask nonexistent: %v", err)
+ }
+ if none != nil {
+ t.Errorf("expected nil for task with no checker, got %v", none)
+ }
+}
+