From 2852b2901f19cf58fa485af4d25d289f1f0e29d3 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sat, 2 Aug 2025 10:33:28 -0700 Subject: [PATCH] feat: improve add_tasks output behavior - Show verbose instructions + task list when no existing tasks - Show brief task list output when tasks already exist - Extract shared formatting logic into reusable formatTaskList method - Update documentation to reflect new behavior --- AGENTS.md | 2 +- README.md | 31 +++++++++------------------- internal/mcp/server.go | 25 +++++++++++++++-------- internal/planning/manager.go | 39 ++++++++++++++++++++++++++---------- 4 files changed, 56 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 140a69435800d31f19047d15e87542d0ba2e0bdd..3a536c7e728be87e264c9e110cb69bec3bdaf94c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -54,7 +54,7 @@ The project requires license headers (SPDX format) on all source files and uses The server exposes four MCP tools that map directly to planning manager methods: - `update_goal(goal: string)`: Sets overarching goal with length validation -- `add_tasks(tasks: []TaskInput)`: Batch task creation with duplicate detection. Encourages breaking tasks down into smallest units of work and regular progress tracking. +- `add_tasks(tasks: []TaskInput)`: Batch task creation with duplicate detection. Encourages breaking tasks down into smallest units of work and regular progress tracking. Output behavior depends on existing tasks: shows verbose instructions + task list when no tasks existed previously, shows brief task list (like `get_tasks`) when tasks already existed. - `get_tasks()`: Returns markdown-formatted task list with legend and sorted by creation time. Should be called frequently to stay organized. - `update_task_status(task_id: string, status: string)`: Updates task status and returns full list. Helps maintain planning workflow by tracking progress. diff --git a/README.md b/README.md index 9492220b2dcf2cb1fe9f6bc9edf3f45ce9fa0d01..bdb575458d920bfad72107e1fb9b0efbce721b8c 100644 --- a/README.md +++ b/README.md @@ -93,28 +93,17 @@ Response: `Goal "Create a comprehensive MCP server for task planning and managem ### Adding Tasks -```json -{ - "name": "add_tasks", - "arguments": { - "tasks": [ - { - "title": "Set up project structure", - "description": "Create Go module, directories, and basic files" - }, - { - "title": "Implement core planning logic", - "description": "Create Goal and Task data structures with deterministic IDs" - }, - { - "title": "Build MCP server integration" - } - ] - } -} -``` +When you first add tasks to an empty planning session, the tool provides guidance and shows your complete plan: + +- Adds your tasks to the planning session +- Shows helpful instructions for getting started +- Displays your goal and current task list with status indicators + +When adding tasks to an existing planning session, the tool keeps things brief: -Response: `Tasks added successfully! Get started on your first one once you're ready, and call get_tasks frequently to remind yourself where you are in the process. Reminder that your overarching goal is "Create a comprehensive MCP server for task planning and management".` +- Adds your tasks seamlessly +- Shows your updated task list (same format as `get_tasks`) +- No repetitive instructions - just your updated plan ### Getting Task Status diff --git a/internal/mcp/server.go b/internal/mcp/server.go index 642ad5304d98bec9d43239bf43bf6887de356ef3..eceb4deda143ade4752225846a9ab4e8d3bf6b79 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -228,7 +228,8 @@ func (s *Server) handleAddTasks(ctx context.Context, request mcp.CallToolRequest } // Add tasks - if err := s.planner.AddTasks(tasks); err != nil { + result, err := s.planner.AddTasks(tasks) + if err != nil { s.logger.Error("Failed to add tasks", "error", err) return &mcp.CallToolResult{ Content: []mcp.Content{ @@ -241,14 +242,22 @@ func (s *Server) handleAddTasks(ctx context.Context, request mcp.CallToolRequest }, nil } - // Get current goal for reminder - goal := s.planner.GetGoal() - goalText := "your planning session" - if goal != nil { - goalText = fmt.Sprintf("\"%s\"", goal.Text) - } + // Get the full task list with goal and legend + taskList := s.planner.GetTasks() - response := fmt.Sprintf("Tasks added successfully! Get started on your first one once you're ready, and call `get_tasks` frequently to remind yourself where you are in the process. Reminder that your overarching goal is %s.", goalText) + var response string + if !result.HadExistingTasks { + // No existing tasks - show verbose instructions + task list + goal := s.planner.GetGoal() + goalText := "your planning session" + if goal != nil { + goalText = fmt.Sprintf("\"%s\"", goal.Text) + } + response = fmt.Sprintf("Tasks added successfully! Get started on your first one once you're ready, and call `get_tasks` frequently to remind yourself where you are in the process. Reminder that your overarching goal is %s.\n\n%s", goalText, taskList) + } else { + // Had existing tasks - just show the task list (like get_tasks) + response = taskList + } return &mcp.CallToolResult{ Content: []mcp.Content{ mcp.TextContent{ diff --git a/internal/planning/manager.go b/internal/planning/manager.go index bda09b0e70ce033ec8ef9e675bfa2954ef5a767b..26cf92526f57855ed6a5dd561840a62597bf55f9 100644 --- a/internal/planning/manager.go +++ b/internal/planning/manager.go @@ -52,14 +52,23 @@ func (m *Manager) UpdateGoal(goalText string) error { return nil } +// AddTasksResult contains the result of adding tasks +type AddTasksResult struct { + AddedTasks []*Task + HadExistingTasks bool +} + // AddTasks adds one or more tasks -func (m *Manager) AddTasks(tasks []TaskInput) error { +func (m *Manager) AddTasks(tasks []TaskInput) (*AddTasksResult, error) { m.mu.Lock() defer m.mu.Unlock() + // Check if there were existing tasks before adding new ones + hadExistingTasks := len(m.tasks) > 0 + // Check task limits if len(m.tasks)+len(tasks) > m.config.Planning.MaxTasks { - return fmt.Errorf("too many tasks (max %d)", m.config.Planning.MaxTasks) + return nil, fmt.Errorf("too many tasks (max %d)", m.config.Planning.MaxTasks) } addedTasks := make([]*Task, 0, len(tasks)) @@ -67,15 +76,15 @@ func (m *Manager) AddTasks(tasks []TaskInput) error { for _, taskInput := range tasks { // Validate task input if taskInput.Title == "" { - return fmt.Errorf("task title cannot be empty") + return nil, fmt.Errorf("task title cannot be empty") } if len(taskInput.Title) > m.config.Planning.MaxTaskLength { - return fmt.Errorf("task title too long (max %d characters)", m.config.Planning.MaxTaskLength) + return nil, fmt.Errorf("task title too long (max %d characters)", m.config.Planning.MaxTaskLength) } if len(taskInput.Description) > m.config.Planning.MaxTaskLength { - return fmt.Errorf("task description too long (max %d characters)", m.config.Planning.MaxTaskLength) + return nil, fmt.Errorf("task description too long (max %d characters)", m.config.Planning.MaxTaskLength) } // Create task @@ -92,14 +101,15 @@ func (m *Manager) AddTasks(tasks []TaskInput) error { } m.logger.Info("Tasks added", "count", len(addedTasks)) - return nil + return &AddTasksResult{ + AddedTasks: addedTasks, + HadExistingTasks: hadExistingTasks, + }, nil } -// GetTasks returns a markdown-formatted list of tasks -func (m *Manager) GetTasks() string { - m.mu.RLock() - defer m.mu.RUnlock() - +// formatTaskList returns a markdown-formatted list of tasks +// This method assumes the mutex is already locked +func (m *Manager) formatTaskList() string { var lines []string // Add goal if it exists @@ -159,6 +169,13 @@ func (m *Manager) GetTasks() string { return strings.Join(lines, "\n") } +// GetTasks returns a markdown-formatted list of tasks +func (m *Manager) GetTasks() string { + m.mu.RLock() + defer m.mu.RUnlock() + return m.formatTaskList() +} + // UpdateTaskStatus updates the status of a specific task func (m *Manager) UpdateTaskStatus(taskID string, status TaskStatus) error { m.mu.Lock()