feat: add Eisenhower Matrix field to task creation and updates

Amolith created

Add support for Eisenhower Matrix quadrant classification in both
create_task and update_task operations. The field accepts human-readable
values that are translated to API integers:
- uncategorised (0) - clears the field
- both urgent and important (1)
- urgent, but not important (2)
- important, but not urgent (3)
- neither urgent nor important (4)

Includes proper validation and helpful error messages following the
existing priority field pattern.

Change summary

cmd/lunatask-mcp-server.go |  8 ++++++++
lunatask/tasks.go          |  1 +
tools/tasks.go             | 39 +++++++++++++++++++++++++++++++++++++++
3 files changed, 48 insertions(+)

Detailed changes

cmd/lunatask-mcp-server.go 🔗

@@ -241,6 +241,10 @@ func NewMCPServer(appConfig *Config) *server.MCPServer {
 			mcp.Description("Level of importance for the task. Valid values: 'must' (critical/required), 'should' (important), 'want' (nice-to-have). Only include if the user's language suggests strong obligation ('I need to', 'I have to') vs preference ('I'd like to', 'I want to')."),
 			mcp.Enum("must", "should", "want"),
 		),
+		mcp.WithString("eisenhower",
+			mcp.Description("Eisenhower Matrix quadrant for task prioritization. Valid values: 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important', 'uncategorised'. Only include for areas which the user has indicated follow the Eisenhower workflow."),
+			mcp.Enum("both urgent and important", "urgent, but not important", "important, but not urgent", "neither urgent nor important", "uncategorised"),
+		),
 		mcp.WithString("status",
 			mcp.Description("Initial task status. Valid values: 'later' (someday/backlog), 'next' (upcoming/soon), 'started' (in progress), 'waiting' (blocked), 'completed' (finished). Infer from context: 'working on' = 'started', 'soon'/'upcoming' = 'next', 'blocked'/'waiting for' = 'waiting'. Omit for normal new tasks (defaults to appropriate status)."),
 			mcp.Enum("later", "next", "started", "waiting", "completed"),
@@ -282,6 +286,10 @@ func NewMCPServer(appConfig *Config) *server.MCPServer {
 			mcp.Description("New level of importance for the task. Valid values: 'must' (critical/required), 'should' (important), 'want' (nice-to-have), or empty string to clear. Only include if changing the motivation level."),
 			mcp.Enum("must", "should", "want", ""),
 		),
+		mcp.WithString("eisenhower",
+			mcp.Description("New Eisenhower Matrix quadrant for task prioritization. Valid values: 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important', 'uncategorised' (clears the field). Only include for areas which the user has indicated follow the Eisenhower workflow."),
+			mcp.Enum("both urgent and important", "urgent, but not important", "important, but not urgent", "neither urgent nor important", "uncategorised"),
+		),
 		mcp.WithString("status",
 			mcp.Description("New task status. Valid values: 'later' (someday/backlog), 'next' (upcoming/soon), 'started' (in progress), 'waiting' (blocked), 'completed' (finished), or empty string to clear. Only include if changing the task status."),
 			mcp.Enum("later", "next", "started", "waiting", "completed", ""),

lunatask/tasks.go 🔗

@@ -52,6 +52,7 @@ type CreateTaskRequest struct {
 	Motivation  string `json:"motivation,omitempty" validate:"omitempty,oneof=must should want unknown"`
 	Estimate    int    `json:"estimate,omitempty" validate:"omitempty,min=0,max=720"`
 	Priority    int    `json:"priority,omitempty" validate:"omitempty,min=-2,max=2"`
+	Eisenhower  int    `json:"eisenhower,omitempty" validate:"omitempty,min=0,max=4"`
 	ScheduledOn string `json:"scheduled_on,omitempty" validate:"omitempty"`
 	CompletedAt string `json:"completed_at,omitempty" validate:"omitempty"`
 	Source      string `json:"source,omitempty" validate:"omitempty"`

tools/tasks.go 🔗

@@ -72,6 +72,26 @@ func (h *Handlers) HandleCreateTask(ctx context.Context, request mcp.CallToolReq
 		arguments["priority"] = translatedPriority
 	}
 
+	eisenhowerMap := map[string]int{
+		"uncategorised":                0,
+		"both urgent and important":    1,
+		"urgent, but not important":    2,
+		"important, but not urgent":    3,
+		"neither urgent nor important": 4,
+	}
+
+	if eisenhowerArg, exists := arguments["eisenhower"]; exists && eisenhowerArg != nil {
+		eisenhowerStr, ok := eisenhowerArg.(string)
+		if !ok {
+			return reportMCPError("Invalid type for 'eisenhower' argument: expected string.")
+		}
+		translatedEisenhower, isValid := eisenhowerMap[strings.ToLower(eisenhowerStr)]
+		if !isValid {
+			return reportMCPError(fmt.Sprintf("Invalid 'eisenhower' value: '%s'. Must be one of 'uncategorised', 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important'.", eisenhowerStr))
+		}
+		arguments["eisenhower"] = translatedEisenhower
+	}
+
 	if motivationVal, exists := arguments["motivation"]; exists && motivationVal != nil {
 		if motivation, ok := motivationVal.(string); ok && motivation != "" {
 			validMotivations := map[string]bool{"must": true, "should": true, "want": true}
@@ -246,6 +266,25 @@ func (h *Handlers) HandleUpdateTask(ctx context.Context, request mcp.CallToolReq
 		updatePayload.Priority = translatedPriority
 	}
 
+	if eisenhowerArg, exists := arguments["eisenhower"]; exists && eisenhowerArg != nil {
+		eisenhowerStr, ok := eisenhowerArg.(string)
+		if !ok {
+			return reportMCPError("Invalid type for 'eisenhower' argument: expected string.")
+		}
+		eisenhowerMap := map[string]int{
+			"uncategorised":                0,
+			"both urgent and important":    1,
+			"urgent, but not important":    2,
+			"important, but not urgent":    3,
+			"neither urgent nor important": 4,
+		}
+		translatedEisenhower, isValid := eisenhowerMap[strings.ToLower(eisenhowerStr)]
+		if !isValid {
+			return reportMCPError(fmt.Sprintf("Invalid 'eisenhower' value: '%s'. Must be one of 'uncategorised', 'both urgent and important', 'urgent, but not important', 'important, but not urgent', 'neither urgent nor important'.", eisenhowerStr))
+		}
+		updatePayload.Eisenhower = translatedEisenhower
+	}
+
 	if motivationArg, exists := arguments["motivation"]; exists {
 		if motivationStr, ok := motivationArg.(string); ok {
 			if motivationStr != "" {