refactor(mcp): use go-lunatask v0.1.0-rc9.1 API

Amolith created

Replace deprecated ParseDeepLink with ParseReference across all
reference-handling code. Replace local task filtering logic with
lunatask.FilterTasks in both CLI and MCP list handlers.

Extract parsing and builder application into separate helpers
(parseCreateInput, parseUpdateInput, applyToTaskBuilder,
applyToTaskUpdateBuilder) to reduce handler complexity.

Add nilerr exclusion for internal/mcp/ since MCP handlers return errors
in the result tuple rather than as Go errors.

Assisted-by: Claude Sonnet 4 via Crush

Change summary

.golangci.yaml                          |   3 
cmd/init/ui.go                          |   2 
cmd/task/list.go                        |  40 +----
go.mod                                  |   2 
go.sum                                  |   4 
internal/mcp/tools/habit/track.go       |   2 
internal/mcp/tools/task/create.go       | 157 +++++++++++++++-------
internal/mcp/tools/task/delete.go       |   5 
internal/mcp/tools/task/list.go         |  70 +++-------
internal/mcp/tools/task/show.go         |   4 
internal/mcp/tools/task/update.go       | 178 ++++++++++++++++----------
internal/mcp/tools/timestamp/handler.go |   1 
internal/validate/validate.go           |   2 
13 files changed, 256 insertions(+), 214 deletions(-)

Detailed changes

.golangci.yaml 🔗

@@ -148,6 +148,9 @@ linters:
           - dupl              # Builder types differ but share method signatures
       - path: cmd/
         text: unused-parameter  # Cobra callback signatures can't be changed
+      - path: internal/mcp/
+        linters:
+          - nilerr              # MCP handlers return errors in result, not as Go error
       - path: internal/ui/
         linters:
           - gochecknoglobals  # Style constants are package-level vars

cmd/init/ui.go 🔗

@@ -166,7 +166,7 @@ func validateReference(input string, _ bool) (string, error) {
 		return "", errRefRequired
 	}
 
-	_, id, err := lunatask.ParseDeepLink(input)
+	_, id, err := lunatask.ParseReference(input)
 	if err != nil {
 		return "", errRefFormat
 	}

cmd/task/list.go 🔗

@@ -158,43 +158,21 @@ func resolveStatusFilter(cmd *cobra.Command) (string, error) {
 }
 
 func applyFilters(tasks []lunatask.Task, areaID, statusFilter string, showAll bool) []lunatask.Task {
-	filtered := make([]lunatask.Task, 0, len(tasks))
-	today := time.Now().Truncate(24 * time.Hour)
-
-	for _, task := range tasks {
-		if !matchesFilters(task, areaID, statusFilter, showAll, today) {
-			continue
-		}
-
-		filtered = append(filtered, task)
-	}
-
-	return filtered
-}
-
-func matchesFilters(task lunatask.Task, areaID, statusFilter string, showAll bool, today time.Time) bool {
-	if areaID != "" && (task.AreaID == nil || *task.AreaID != areaID) {
-		return false
-	}
-
-	if statusFilter != "" && (task.Status == nil || string(*task.Status) != statusFilter) {
-		return false
+	opts := &lunatask.TaskFilterOptions{
+		IncludeCompleted: showAll,
+		Today:            time.Now(),
 	}
 
-	// Default filter: exclude completed tasks unless completed today
-	if !showAll && statusFilter == "" && isOldCompleted(task, today) {
-		return false
+	if areaID != "" {
+		opts.AreaID = &areaID
 	}
 
-	return true
-}
-
-func isOldCompleted(task lunatask.Task, today time.Time) bool {
-	if task.Status == nil || *task.Status != lunatask.StatusCompleted {
-		return false
+	if statusFilter != "" {
+		s := lunatask.TaskStatus(statusFilter)
+		opts.Status = &s
 	}
 
-	return task.CompletedAt == nil || task.CompletedAt.Before(today)
+	return lunatask.FilterTasks(tasks, opts)
 }
 
 func outputJSON(cmd *cobra.Command, tasks []lunatask.Task) error {

go.mod 🔗

@@ -7,7 +7,7 @@ module git.secluded.site/lune
 go 1.25.5
 
 require (
-	git.secluded.site/go-lunatask v0.1.0-rc10
+	git.secluded.site/go-lunatask v0.1.0-rc9.1
 	github.com/BurntSushi/toml v1.6.0
 	github.com/charmbracelet/fang v0.4.4
 	github.com/charmbracelet/huh v0.8.0

go.sum 🔗

@@ -2,8 +2,8 @@ al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXy
 al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
 charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 h1:D9PbaszZYpB4nj+d6HTWr1onlmlyuGVNfL9gAi8iB3k=
 charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410/go.mod h1:1qZyvvVCenJO2M1ac2mX0yyiIZJoZmDM4DG4s0udJkU=
-git.secluded.site/go-lunatask v0.1.0-rc10 h1:KKkYNs/cipNjIlRPXAvpPm5QcWSuA3REcG8XZ8sALk4=
-git.secluded.site/go-lunatask v0.1.0-rc10/go.mod h1:rxps7BBqF+BkY8VN5E7J9zSOzSbtZ1hDmLEOHxjTHZQ=
+git.secluded.site/go-lunatask v0.1.0-rc9.1 h1:6dJcP3P+2QraPQ/wfPjCWaXv2mr1B4lMvBuQCNZd1t8=
+git.secluded.site/go-lunatask v0.1.0-rc9.1/go.mod h1:rxps7BBqF+BkY8VN5E7J9zSOzSbtZ1hDmLEOHxjTHZQ=
 github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
 github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
 github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=

internal/mcp/tools/habit/track.go 🔗

@@ -56,8 +56,6 @@ func NewHandler(accessToken string, habits []shared.HabitProvider) *Handler {
 }
 
 // HandleTrack records a habit activity.
-//
-//nolint:nilerr // MCP returns errors in result, not Go error.
 func (h *Handler) HandleTrack(
 	ctx context.Context,
 	_ *mcp.CallToolRequest,

internal/mcp/tools/task/create.go 🔗

@@ -58,6 +58,21 @@ type CreateOutput struct {
 	DeepLink string `json:"deep_link"`
 }
 
+// parsedCreateInput holds validated and parsed create input fields.
+type parsedCreateInput struct {
+	Name        string
+	AreaID      *string
+	GoalID      *string
+	Status      *lunatask.TaskStatus
+	Note        *string
+	Priority    *lunatask.Priority
+	Estimate    *int
+	Motivation  *lunatask.Motivation
+	Important   *bool
+	Urgent      *bool
+	ScheduledOn *lunatask.Date
+}
+
 // Handler handles task-related MCP tool requests.
 type Handler struct {
 	client *lunatask.Client
@@ -73,110 +88,148 @@ func NewHandler(accessToken string, areas []shared.AreaProvider) *Handler {
 }
 
 // HandleCreate creates a new task.
-//
-//nolint:cyclop,funlen,gocognit,nilerr // MCP error pattern; repetitive field handling.
 func (h *Handler) HandleCreate(
 	ctx context.Context,
 	_ *mcp.CallToolRequest,
 	input CreateInput,
 ) (*mcp.CallToolResult, CreateOutput, error) {
+	parsed, errResult := parseCreateInput(input)
+	if errResult != nil {
+		return errResult, CreateOutput{}, nil
+	}
+
+	builder := h.client.NewTask(parsed.Name)
+	applyToTaskBuilder(builder, parsed)
+
+	task, err := builder.Create(ctx)
+	if err != nil {
+		return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+	}
+
+	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
+
+	return nil, CreateOutput{
+		ID:       task.ID,
+		DeepLink: deepLink,
+	}, nil
+}
+
+//nolint:cyclop,funlen
+func parseCreateInput(input CreateInput) (*parsedCreateInput, *mcp.CallToolResult) {
+	parsed := &parsedCreateInput{
+		Name:      input.Name,
+		AreaID:    input.AreaID,
+		GoalID:    input.GoalID,
+		Note:      input.Note,
+		Estimate:  input.Estimate,
+		Important: input.Important,
+		Urgent:    input.Urgent,
+	}
+
 	if input.AreaID != nil {
 		if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
-			return shared.ErrorResult("invalid area_id: expected UUID"), CreateOutput{}, nil
+			return nil, shared.ErrorResult("invalid area_id: expected UUID")
 		}
 	}
 
 	if input.GoalID != nil {
 		if err := lunatask.ValidateUUID(*input.GoalID); err != nil {
-			return shared.ErrorResult("invalid goal_id: expected UUID"), CreateOutput{}, nil
+			return nil, shared.ErrorResult("invalid goal_id: expected UUID")
 		}
 	}
 
 	if input.Estimate != nil {
 		if err := shared.ValidateEstimate(*input.Estimate); err != nil {
-			return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 	}
 
-	builder := h.client.NewTask(input.Name)
-
-	if input.AreaID != nil {
-		builder.InArea(*input.AreaID)
-	}
-
-	if input.GoalID != nil {
-		builder.InGoal(*input.GoalID)
-	}
-
 	if input.Status != nil {
 		status, err := lunatask.ParseTaskStatus(*input.Status)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.WithStatus(status)
-	}
-
-	if input.Note != nil {
-		builder.WithNote(*input.Note)
+		parsed.Status = &status
 	}
 
 	if input.Priority != nil {
 		priority, err := lunatask.ParsePriority(*input.Priority)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.Priority(priority)
-	}
-
-	if input.Estimate != nil {
-		builder.WithEstimate(*input.Estimate)
+		parsed.Priority = &priority
 	}
 
 	if input.Motivation != nil {
 		motivation, err := lunatask.ParseMotivation(*input.Motivation)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.WithMotivation(motivation)
+		parsed.Motivation = &motivation
 	}
 
-	if input.Important != nil {
-		if *input.Important {
+	if input.ScheduledOn != nil {
+		date, err := dateutil.Parse(*input.ScheduledOn)
+		if err != nil {
+			return nil, shared.ErrorResult(err.Error())
+		}
+
+		parsed.ScheduledOn = &date
+	}
+
+	return parsed, nil
+}
+
+//nolint:cyclop
+func applyToTaskBuilder(builder *lunatask.TaskBuilder, parsed *parsedCreateInput) {
+	if parsed.AreaID != nil {
+		builder.InArea(*parsed.AreaID)
+	}
+
+	if parsed.GoalID != nil {
+		builder.InGoal(*parsed.GoalID)
+	}
+
+	if parsed.Status != nil {
+		builder.WithStatus(*parsed.Status)
+	}
+
+	if parsed.Note != nil {
+		builder.WithNote(*parsed.Note)
+	}
+
+	if parsed.Priority != nil {
+		builder.Priority(*parsed.Priority)
+	}
+
+	if parsed.Estimate != nil {
+		builder.WithEstimate(*parsed.Estimate)
+	}
+
+	if parsed.Motivation != nil {
+		builder.WithMotivation(*parsed.Motivation)
+	}
+
+	if parsed.Important != nil {
+		if *parsed.Important {
 			builder.Important()
 		} else {
 			builder.NotImportant()
 		}
 	}
 
-	if input.Urgent != nil {
-		if *input.Urgent {
+	if parsed.Urgent != nil {
+		if *parsed.Urgent {
 			builder.Urgent()
 		} else {
 			builder.NotUrgent()
 		}
 	}
 
-	if input.ScheduledOn != nil {
-		date, err := dateutil.Parse(*input.ScheduledOn)
-		if err != nil {
-			return shared.ErrorResult(err.Error()), CreateOutput{}, nil
-		}
-
-		builder.ScheduledOn(date)
-	}
-
-	task, err := builder.Create(ctx)
-	if err != nil {
-		return shared.ErrorResult(err.Error()), CreateOutput{}, nil
+	if parsed.ScheduledOn != nil {
+		builder.ScheduledOn(*parsed.ScheduledOn)
 	}
-
-	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
-
-	return nil, CreateOutput{
-		ID:       task.ID,
-		DeepLink: deepLink,
-	}, nil
 }

internal/mcp/tools/task/delete.go 🔗

@@ -7,6 +7,7 @@ package task
 import (
 	"context"
 
+	"git.secluded.site/go-lunatask"
 	"git.secluded.site/lune/internal/mcp/shared"
 	"github.com/modelcontextprotocol/go-sdk/mcp"
 )
@@ -39,9 +40,9 @@ func (h *Handler) HandleDelete(
 	_ *mcp.CallToolRequest,
 	input DeleteInput,
 ) (*mcp.CallToolResult, DeleteOutput, error) {
-	id, err := resolveID(input.ID)
+	_, id, err := lunatask.ParseReference(input.ID)
 	if err != nil {
-		return shared.ErrorResult(err.Error()), DeleteOutput{}, nil
+		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"), DeleteOutput{}, nil
 	}
 
 	if _, err := h.client.DeleteTask(ctx, id); err != nil {

internal/mcp/tools/task/list.go 🔗

@@ -54,11 +54,7 @@ type Summary struct {
 	GoalID      *string `json:"goal_id,omitempty"`
 }
 
-const hoursPerDay = 24
-
 // HandleList lists tasks.
-//
-//nolint:cyclop,funlen,nilerr // Complexity from field handling; MCP returns errors in result, not Go error.
 func (h *Handler) HandleList(
 	ctx context.Context,
 	_ *mcp.CallToolRequest,
@@ -81,22 +77,30 @@ func (h *Handler) HandleList(
 		return shared.ErrorResult(err.Error()), ListOutput{}, nil
 	}
 
-	includeCompleted := input.IncludeCompleted != nil && *input.IncludeCompleted
-	today := time.Now().Truncate(hoursPerDay * time.Hour)
+	opts := &lunatask.TaskFilterOptions{
+		AreaID:           input.AreaID,
+		IncludeCompleted: input.IncludeCompleted != nil && *input.IncludeCompleted,
+		Today:            time.Now(),
+	}
 
-	filtered := make([]lunatask.Task, 0, len(tasks))
+	if input.Status != nil {
+		s := lunatask.TaskStatus(*input.Status)
+		opts.Status = &s
+	}
 
-	for _, task := range tasks {
-		if !matchesFilters(task, input.AreaID, input.Status, includeCompleted, today) {
-			continue
-		}
+	filtered := lunatask.FilterTasks(tasks, opts)
+	summaries := buildSummaries(filtered)
 
-		filtered = append(filtered, task)
-	}
+	return nil, ListOutput{
+		Tasks: summaries,
+		Count: len(summaries),
+	}, nil
+}
 
-	summaries := make([]Summary, 0, len(filtered))
+func buildSummaries(tasks []lunatask.Task) []Summary {
+	summaries := make([]Summary, 0, len(tasks))
 
-	for _, task := range filtered {
+	for _, task := range tasks {
 		summary := Summary{
 			ID:        task.ID,
 			CreatedAt: task.CreatedAt.Format(time.RFC3339),
@@ -124,39 +128,5 @@ func (h *Handler) HandleList(
 		summaries = append(summaries, summary)
 	}
 
-	return nil, ListOutput{
-		Tasks: summaries,
-		Count: len(summaries),
-	}, nil
-}
-
-func matchesFilters(
-	task lunatask.Task,
-	areaID, status *string,
-	includeCompleted bool,
-	today time.Time,
-) bool {
-	if areaID != nil && (task.AreaID == nil || *task.AreaID != *areaID) {
-		return false
-	}
-
-	if status != nil {
-		if task.Status == nil || string(*task.Status) != *status {
-			return false
-		}
-	}
-
-	if !includeCompleted && isOldCompleted(task, today) {
-		return false
-	}
-
-	return true
-}
-
-func isOldCompleted(task lunatask.Task, today time.Time) bool {
-	if task.Status == nil || *task.Status != lunatask.StatusCompleted {
-		return false
-	}
-
-	return task.CompletedAt == nil || task.CompletedAt.Before(today)
+	return summaries
 }

internal/mcp/tools/task/show.go 🔗

@@ -53,9 +53,9 @@ func (h *Handler) HandleShow(
 	_ *mcp.CallToolRequest,
 	input ShowInput,
 ) (*mcp.CallToolResult, ShowOutput, error) {
-	id, err := resolveID(input.ID)
+	_, id, err := lunatask.ParseReference(input.ID)
 	if err != nil {
-		return shared.ErrorResult(err.Error()), ShowOutput{}, nil
+		return shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link"), ShowOutput{}, nil
 	}
 
 	task, err := h.client.GetTask(ctx, id)

internal/mcp/tools/task/update.go 🔗

@@ -6,7 +6,6 @@ package task
 
 import (
 	"context"
-	"fmt"
 
 	"git.secluded.site/go-lunatask"
 	"git.secluded.site/lune/internal/dateutil"
@@ -60,134 +59,175 @@ type UpdateOutput struct {
 	DeepLink string `json:"deep_link"`
 }
 
+// parsedUpdateInput holds validated and parsed update input fields.
+type parsedUpdateInput struct {
+	ID          string
+	Name        *string
+	AreaID      *string
+	GoalID      *string
+	Status      *lunatask.TaskStatus
+	Note        *string
+	Priority    *lunatask.Priority
+	Estimate    *int
+	Motivation  *lunatask.Motivation
+	Important   *bool
+	Urgent      *bool
+	ScheduledOn *lunatask.Date
+}
+
 // HandleUpdate updates an existing task.
-//
-//nolint:cyclop,funlen,gocognit,nilerr // MCP error pattern; repetitive field handling.
 func (h *Handler) HandleUpdate(
 	ctx context.Context,
 	_ *mcp.CallToolRequest,
 	input UpdateInput,
 ) (*mcp.CallToolResult, UpdateOutput, error) {
-	id, err := resolveID(input.ID)
+	parsed, errResult := parseUpdateInput(input)
+	if errResult != nil {
+		return errResult, UpdateOutput{}, nil
+	}
+
+	builder := h.client.NewTaskUpdate(parsed.ID)
+	applyToTaskUpdateBuilder(builder, parsed)
+
+	task, err := builder.Update(ctx)
 	if err != nil {
 		return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
 	}
 
+	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
+
+	return nil, UpdateOutput{
+		ID:       task.ID,
+		DeepLink: deepLink,
+	}, nil
+}
+
+//nolint:cyclop,funlen
+func parseUpdateInput(input UpdateInput) (*parsedUpdateInput, *mcp.CallToolResult) {
+	_, id, err := lunatask.ParseReference(input.ID)
+	if err != nil {
+		return nil, shared.ErrorResult("invalid ID: expected UUID or lunatask:// deep link")
+	}
+
+	parsed := &parsedUpdateInput{
+		ID:        id,
+		Name:      input.Name,
+		AreaID:    input.AreaID,
+		GoalID:    input.GoalID,
+		Note:      input.Note,
+		Estimate:  input.Estimate,
+		Important: input.Important,
+		Urgent:    input.Urgent,
+	}
+
 	if input.AreaID != nil {
 		if err := lunatask.ValidateUUID(*input.AreaID); err != nil {
-			return shared.ErrorResult("invalid area_id: expected UUID"), UpdateOutput{}, nil
+			return nil, shared.ErrorResult("invalid area_id: expected UUID")
 		}
 	}
 
 	if input.GoalID != nil {
 		if err := lunatask.ValidateUUID(*input.GoalID); err != nil {
-			return shared.ErrorResult("invalid goal_id: expected UUID"), UpdateOutput{}, nil
+			return nil, shared.ErrorResult("invalid goal_id: expected UUID")
 		}
 	}
 
 	if input.Estimate != nil {
 		if err := shared.ValidateEstimate(*input.Estimate); err != nil {
-			return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 	}
 
-	builder := h.client.NewTaskUpdate(id)
-
-	if input.Name != nil {
-		builder.Name(*input.Name)
-	}
-
-	if input.AreaID != nil {
-		builder.InArea(*input.AreaID)
-	}
-
-	if input.GoalID != nil {
-		builder.InGoal(*input.GoalID)
-	}
-
 	if input.Status != nil {
 		status, err := lunatask.ParseTaskStatus(*input.Status)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.WithStatus(status)
-	}
-
-	if input.Note != nil {
-		builder.WithNote(*input.Note)
+		parsed.Status = &status
 	}
 
 	if input.Priority != nil {
 		priority, err := lunatask.ParsePriority(*input.Priority)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.Priority(priority)
-	}
-
-	if input.Estimate != nil {
-		builder.WithEstimate(*input.Estimate)
+		parsed.Priority = &priority
 	}
 
 	if input.Motivation != nil {
 		motivation, err := lunatask.ParseMotivation(*input.Motivation)
 		if err != nil {
-			return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
+			return nil, shared.ErrorResult(err.Error())
 		}
 
-		builder.WithMotivation(motivation)
+		parsed.Motivation = &motivation
 	}
 
-	if input.Important != nil {
-		if *input.Important {
-			builder.Important()
-		} else {
-			builder.NotImportant()
+	if input.ScheduledOn != nil {
+		date, err := dateutil.Parse(*input.ScheduledOn)
+		if err != nil {
+			return nil, shared.ErrorResult(err.Error())
 		}
+
+		parsed.ScheduledOn = &date
 	}
 
-	if input.Urgent != nil {
-		if *input.Urgent {
-			builder.Urgent()
-		} else {
-			builder.NotUrgent()
-		}
+	return parsed, nil
+}
+
+//nolint:cyclop
+func applyToTaskUpdateBuilder(builder *lunatask.TaskUpdateBuilder, parsed *parsedUpdateInput) {
+	if parsed.Name != nil {
+		builder.Name(*parsed.Name)
 	}
 
-	if input.ScheduledOn != nil {
-		date, err := dateutil.Parse(*input.ScheduledOn)
-		if err != nil {
-			return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
-		}
+	if parsed.AreaID != nil {
+		builder.InArea(*parsed.AreaID)
+	}
 
-		builder.ScheduledOn(date)
+	if parsed.GoalID != nil {
+		builder.InGoal(*parsed.GoalID)
 	}
 
-	task, err := builder.Update(ctx)
-	if err != nil {
-		return shared.ErrorResult(err.Error()), UpdateOutput{}, nil
+	if parsed.Status != nil {
+		builder.WithStatus(*parsed.Status)
 	}
 
-	deepLink, _ := lunatask.BuildDeepLink(lunatask.ResourceTask, task.ID)
+	if parsed.Note != nil {
+		builder.WithNote(*parsed.Note)
+	}
 
-	return nil, UpdateOutput{
-		ID:       task.ID,
-		DeepLink: deepLink,
-	}, nil
-}
+	if parsed.Priority != nil {
+		builder.Priority(*parsed.Priority)
+	}
 
-// resolveID extracts a UUID from either a raw UUID or a lunatask:// deep link.
-func resolveID(input string) (string, error) {
-	_, id, err := lunatask.ParseDeepLink(input)
-	if err == nil {
-		return id, nil
+	if parsed.Estimate != nil {
+		builder.WithEstimate(*parsed.Estimate)
 	}
 
-	if err := lunatask.ValidateUUID(input); err != nil {
-		return "", fmt.Errorf("invalid ID: %w", err)
+	if parsed.Motivation != nil {
+		builder.WithMotivation(*parsed.Motivation)
 	}
 
-	return input, nil
+	if parsed.Important != nil {
+		if *parsed.Important {
+			builder.Important()
+		} else {
+			builder.NotImportant()
+		}
+	}
+
+	if parsed.Urgent != nil {
+		if *parsed.Urgent {
+			builder.Urgent()
+		} else {
+			builder.NotUrgent()
+		}
+	}
+
+	if parsed.ScheduledOn != nil {
+		builder.ScheduledOn(*parsed.ScheduledOn)
+	}
 }

internal/mcp/tools/timestamp/handler.go 🔗

@@ -63,7 +63,6 @@ func (h *Handler) Handle(
 ) (*mcp.CallToolResult, Output, error) {
 	parsed, err := dateutil.Parse(input.Date)
 	if err != nil {
-		//nolint:nilerr // MCP pattern: user errors in CallToolResult, nil Go error
 		return &mcp.CallToolResult{
 			IsError: true,
 			Content: []mcp.Content{

internal/validate/validate.go 🔗

@@ -20,7 +20,7 @@ var ErrInvalidReference = errors.New("invalid reference: expected UUID or lunata
 //   - UUID: "3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
 //   - Deep link: "lunatask://areas/3bbf1923-64ae-4bcf-96a9-9bb86c799dab"
 func Reference(input string) (string, error) {
-	_, id, err := lunatask.ParseDeepLink(input)
+	_, id, err := lunatask.ParseReference(input)
 	if err != nil {
 		return "", fmt.Errorf("%w: %s", ErrInvalidReference, input)
 	}