From 7d50eda620443fda11ec21dd1c3cdc47a7ab2f9e Mon Sep 17 00:00:00 2001 From: Ian Gulliver Date: Sat, 12 Jul 2025 14:52:47 -0700 Subject: [PATCH] Builder-style API --- taskcp.go | 44 +++++++++++++++++++++++++++++-------------- taskcp_test.go | 51 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/taskcp.go b/taskcp.go index 21f9287..a4be1ea 100644 --- a/taskcp.go +++ b/taskcp.go @@ -80,8 +80,8 @@ func (s *Service) GetProject(id int) (*Project, error) { return s.projects[id], nil } -func (p *Project) InsertTaskBefore(beforeID int, title string, instructions string, completionCallback func(project *Project, task *Task) error) *Task { - newTask := p.newTask(title, instructions, completionCallback, beforeID) +func (p *Project) InsertTaskBefore(beforeID int) *Task { + newTask := p.newTask(beforeID) if p.nextTaskID == -1 && beforeID == -1 { p.nextTaskID = newTask.ID @@ -143,21 +143,15 @@ func (p *Project) SetTaskFailure(id int, error string, notes string) (*Task, err return p.GetNextTask(), nil } -func (p *Project) newTask(title string, instructions string, completionCallback func(project *Project, task *Task) error, nextTaskID int) *Task { +func (p *Project) newTask(nextTaskID int) *Task { task := &Task{ - ID: len(p.Tasks), - State: TaskStatePending, - NextTaskID: nextTaskID, - Title: title, - Instructions: instructions, - Data: map[string]any{}, - completionCallback: completionCallback, - project: p, + ID: len(p.Tasks), + State: TaskStatePending, + NextTaskID: nextTaskID, + Data: map[string]any{}, + project: p, } - task.Instructions = strings.ReplaceAll(task.Instructions, "{SUCCESS_PROMPT}", task.SuccessPrompt()) - task.Instructions = strings.ReplaceAll(task.Instructions, "{FAILURE_PROMPT}", task.FailurePrompt()) - p.Tasks = append(p.Tasks, task) return task } @@ -188,6 +182,28 @@ func (p *Project) Summary() ProjectSummary { return ProjectSummary{Tasks: tasks} } +func (t *Task) WithTitle(title string) *Task { + t.Title = title + return t +} + +func (t *Task) WithInstructions(instructions string) *Task { + t.Instructions = instructions + t.Instructions = strings.ReplaceAll(t.Instructions, "{SUCCESS_PROMPT}", t.SuccessPrompt()) + t.Instructions = strings.ReplaceAll(t.Instructions, "{FAILURE_PROMPT}", t.FailurePrompt()) + return t +} + +func (t *Task) WithData(key string, value any) *Task { + t.Data[key] = value + return t +} + +func (t *Task) Then(completionCallback func(project *Project, task *Task) error) *Task { + t.completionCallback = completionCallback + return t +} + func (t *Task) SuccessPrompt() string { return fmt.Sprintf(`To mark this task as successful, use the MCP tool: %s.set_task_success(project_id=%d, task_id=%d, result="", notes="")`, diff --git a/taskcp_test.go b/taskcp_test.go index dd0b6ef..c6d3a24 100644 --- a/taskcp_test.go +++ b/taskcp_test.go @@ -12,7 +12,12 @@ func TestTaskPrompts(t *testing.T) { service := taskcp.New("my_service") project := service.AddProject() - task := project.InsertTaskBefore(-1, "Write unit tests", "", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task := project.InsertTaskBefore(-1). + WithTitle("Write unit tests"). + WithInstructions("This is a test task."). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + return nil + }) successPrompt := task.SuccessPrompt() require.Contains(t, successPrompt, "my_service.set_task_success") @@ -29,11 +34,21 @@ func TestPlaceholderExpansion(t *testing.T) { service := taskcp.New("my_service") project := service.AddProject() - task1 := project.InsertTaskBefore(-1, "Please complete this task.", "{SUCCESS_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task1 := project.InsertTaskBefore(-1). + WithTitle("Please complete this task."). + WithInstructions("{SUCCESS_PROMPT}"). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + return nil + }) require.Contains(t, task1.Instructions, "my_service.set_task_success") require.NotContains(t, task1.Instructions, "{SUCCESS_PROMPT}") - task2 := project.InsertTaskBefore(-1, "Try this risky operation.", "{FAILURE_PROMPT}", func(project *taskcp.Project, task *taskcp.Task) error { return nil }) + task2 := project.InsertTaskBefore(-1). + WithTitle("Try this risky operation."). + WithInstructions("{FAILURE_PROMPT}"). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + return nil + }) require.Contains(t, task2.Instructions, "my_service.set_task_failure") require.NotContains(t, task2.Instructions, "{FAILURE_PROMPT}") } @@ -44,15 +59,19 @@ func TestTaskFlow(t *testing.T) { var completed []int - task1 := project.InsertTaskBefore(-1, "First task", "", func(project *taskcp.Project, task *taskcp.Task) error { - completed = append(completed, task.ID) - return nil - }) + task1 := project.InsertTaskBefore(-1). + WithTitle("First task"). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + completed = append(completed, task.ID) + return nil + }) - task2 := project.InsertTaskBefore(-1, "Second task", "", func(project *taskcp.Project, task *taskcp.Task) error { - completed = append(completed, task.ID) - return nil - }) + task2 := project.InsertTaskBefore(-1). + WithTitle("Second task"). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + completed = append(completed, task.ID) + return nil + }) current := project.GetNextTask() require.NotNil(t, current) @@ -79,9 +98,13 @@ func TestCallbackError(t *testing.T) { expectedErr := fmt.Errorf("callback error") - task := project.InsertTaskBefore(-1, "Task with error callback", "", func(project *taskcp.Project, task *taskcp.Task) error { - return expectedErr - }) + task := project.InsertTaskBefore(-1). + WithTitle("Task with error callback"). + WithInstructions("This is a test task."). + WithData("key", "value"). + Then(func(project *taskcp.Project, task *taskcp.Task) error { + return expectedErr + }) current := project.GetNextTask() require.NotNil(t, current)