summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/api/elaborate.go11
-rw-r--r--internal/api/stories.go27
-rw-r--r--internal/api/stories_test.go49
3 files changed, 70 insertions, 17 deletions
diff --git a/internal/api/elaborate.go b/internal/api/elaborate.go
index dd51c7d..0cb298d 100644
--- a/internal/api/elaborate.go
+++ b/internal/api/elaborate.go
@@ -282,9 +282,10 @@ type elaboratedStorySubtask struct {
// elaboratedStoryTask is one independently-deployable unit in a story plan.
type elaboratedStoryTask struct {
- Name string `json:"name"`
- Instructions string `json:"instructions"`
- Subtasks []elaboratedStorySubtask `json:"subtasks"`
+ Name string `json:"name"`
+ Instructions string `json:"instructions"`
+ AcceptanceCriteria string `json:"acceptance_criteria"`
+ Subtasks []elaboratedStorySubtask `json:"subtasks"`
}
// elaboratedStoryValidation describes how to verify the story was successful.
@@ -313,6 +314,7 @@ Output ONLY valid JSON matching this schema:
{
"name": "task name",
"instructions": "detailed instructions including file paths and what to change",
+ "acceptance_criteria": "specific, verifiable conditions a separate reviewer can check — e.g. 'run go test ./... and verify all pass; confirm GET /api/foo returns 200 with expected JSON shape'",
"subtasks": [
{ "name": "subtask name", "instructions": "..." }
]
@@ -330,7 +332,8 @@ Rules:
- Subtasks within a task are order-dependent and run sequentially
- Instructions must include specific file paths, function names, and exact changes
- Instructions must end with: git add -A && git commit -m "..." && git push origin <branch>
-- Validation should match the scope: small change = build check; new feature = smoke test`
+- Validation should match the scope: small change = build check; new feature = smoke test
+- acceptance_criteria must be concrete and verifiable by a separate agent — no vague assertions like "code looks good"`
}
func (s *Server) elaborateStoryWithClaude(ctx context.Context, workDir, goal string) (*elaboratedStory, error) {
diff --git a/internal/api/stories.go b/internal/api/stories.go
index 1743dbe..fa10ccd 100644
--- a/internal/api/stories.go
+++ b/internal/api/stories.go
@@ -254,19 +254,20 @@ func (s *Server) handleApproveStory(w http.ResponseWriter, r *http.Request) {
var prevTaskID string
for _, tp := range input.Tasks {
t := &task.Task{
- ID: uuid.New().String(),
- Name: tp.Name,
- Project: input.ProjectID,
- RepositoryURL: repoURL,
- StoryID: story.ID,
- Agent: task.AgentConfig{Type: "claude", Instructions: tp.Instructions},
- Priority: task.PriorityNormal,
- Tags: []string{},
- DependsOn: []string{},
- Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "exponential"},
- State: task.StatePending,
- CreatedAt: time.Now().UTC(),
- UpdatedAt: time.Now().UTC(),
+ ID: uuid.New().String(),
+ Name: tp.Name,
+ Project: input.ProjectID,
+ RepositoryURL: repoURL,
+ StoryID: story.ID,
+ Agent: task.AgentConfig{Type: "claude", Instructions: tp.Instructions},
+ AcceptanceCriteria: tp.AcceptanceCriteria,
+ Priority: task.PriorityNormal,
+ Tags: []string{},
+ DependsOn: []string{},
+ Retry: task.RetryConfig{MaxAttempts: 1, Backoff: "exponential"},
+ State: task.StatePending,
+ CreatedAt: time.Now().UTC(),
+ UpdatedAt: time.Now().UTC(),
}
if prevTaskID != "" {
t.DependsOn = []string{prevTaskID}
diff --git a/internal/api/stories_test.go b/internal/api/stories_test.go
index 342840b..f43ad86 100644
--- a/internal/api/stories_test.go
+++ b/internal/api/stories_test.go
@@ -258,6 +258,55 @@ func TestHandleStoryApprove_SetsRepositoryURL(t *testing.T) {
}
}
+func TestApproveStory_AcceptanceCriteriaStored(t *testing.T) {
+ srv, store := testServer(t)
+
+ proj := &task.Project{
+ ID: "ac-proj", Name: "test", RemoteURL: "https://github.com/x/y",
+ Type: "web", DeployScript: "",
+ }
+ store.CreateProject(proj)
+
+ body := `{
+ "name": "AC Story",
+ "branch_name": "story/ac-test",
+ "project_id": "ac-proj",
+ "tasks": [
+ {
+ "name": "Add feature",
+ "instructions": "implement the thing",
+ "acceptance_criteria": "run go test ./... and verify all pass",
+ "subtasks": []
+ }
+ ],
+ "validation": {"type": "test", "steps": [], "success_criteria": "tests pass"}
+ }`
+ req := httptest.NewRequest("POST", "/api/stories/approve", strings.NewReader(body))
+ req.Header.Set("Content-Type", "application/json")
+ w := httptest.NewRecorder()
+ srv.mux.ServeHTTP(w, req)
+
+ if w.Code != http.StatusCreated {
+ t.Fatalf("expected 201, got %d: %s", w.Code, w.Body.String())
+ }
+
+ var resp struct {
+ TaskIDs []string `json:"task_ids"`
+ }
+ json.NewDecoder(w.Body).Decode(&resp)
+ if len(resp.TaskIDs) == 0 {
+ t.Fatal("expected task_ids in response")
+ }
+
+ tk, err := store.GetTask(resp.TaskIDs[0])
+ if err != nil {
+ t.Fatalf("GetTask: %v", err)
+ }
+ if tk.AcceptanceCriteria != "run go test ./... and verify all pass" {
+ t.Errorf("expected acceptance criteria stored on task, got %q", tk.AcceptanceCriteria)
+ }
+}
+
func TestHandleStoryDeploymentStatus(t *testing.T) {
srv, store := testServer(t)