From e6174c4a79dbbd5da00189bc2a69284bc6d2c54b Mon Sep 17 00:00:00 2001 From: Amolith Date: Tue, 23 Dec 2025 21:17:58 -0700 Subject: [PATCH] refactor(mcp): use go-lunatask v0.1.0-rc9.1 API 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 --- .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(-) diff --git a/.golangci.yaml b/.golangci.yaml index 55ca3552f4099bb0a4dc9ac24d0af7ed2ae75968..7148025487c5f0e3acd8019869fc5d235af73527 100644 --- a/.golangci.yaml +++ b/.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 diff --git a/cmd/init/ui.go b/cmd/init/ui.go index 76977b34dcf238c7299067e9917de81d3bd17613..f3d7d2e58b028d4b1094a9e3766372cd5a03ad87 100644 --- a/cmd/init/ui.go +++ b/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 } diff --git a/cmd/task/list.go b/cmd/task/list.go index e4374772cc4e782c63932daa67c47fec7ca9b9f3..a775205662dd9ef121f05ac4f1e9f581f0076633 100644 --- a/cmd/task/list.go +++ b/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 { diff --git a/go.mod b/go.mod index 3880e5f1115675bebdc0f2ec7047948ef2713078..d01b0c01f77ccacec6cc39b3e99a6b67d11b9045 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 2c72bcb29411b484e6363f53aa06cb42574387f8..0d5d2d714b6f3353fb7f4b9ada9604c2053e432b 100644 --- a/go.sum +++ b/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= diff --git a/internal/mcp/tools/habit/track.go b/internal/mcp/tools/habit/track.go index 3947045efedf04f5c44453080d93338762a2140d..91340de227e611b8c440de7062d2fd3ae41e2334 100644 --- a/internal/mcp/tools/habit/track.go +++ b/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, diff --git a/internal/mcp/tools/task/create.go b/internal/mcp/tools/task/create.go index 12dabb752fb24dc210086224efb8ecc6c7f0b1be..a99a324d62638529a1972ce636d9541ac504cc67 100644 --- a/internal/mcp/tools/task/create.go +++ b/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 } diff --git a/internal/mcp/tools/task/delete.go b/internal/mcp/tools/task/delete.go index dbe1bfc7e00d0ceb1a53a72d13adc5eb4d4abd69..64502cf2b830b4c373bf5e63bc31d9037cd4b1a7 100644 --- a/internal/mcp/tools/task/delete.go +++ b/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 { diff --git a/internal/mcp/tools/task/list.go b/internal/mcp/tools/task/list.go index c5b1feb479e09eb21688b81a609e4bc3ca52d3ab..c1c588ef49ce41696e3d344388d78289690fc332 100644 --- a/internal/mcp/tools/task/list.go +++ b/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 } diff --git a/internal/mcp/tools/task/show.go b/internal/mcp/tools/task/show.go index 303e3968a7da2bd4ceeec25a887a4323562ec9f2..6f90f0090ed67dc8b2f05c825d320ae67639e659 100644 --- a/internal/mcp/tools/task/show.go +++ b/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) diff --git a/internal/mcp/tools/task/update.go b/internal/mcp/tools/task/update.go index c4b2428decf7f50ec98c4928959aa3986f3e7278..e9fd19e7dcec018268c71907414ecede5e2dfcae 100644 --- a/internal/mcp/tools/task/update.go +++ b/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) + } } diff --git a/internal/mcp/tools/timestamp/handler.go b/internal/mcp/tools/timestamp/handler.go index d6a166052d4c3935f1ca7cd5a0950ddd50988de6..808a0ba4533de6715cf9ba2fd515a020890c02b3 100644 --- a/internal/mcp/tools/timestamp/handler.go +++ b/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{ diff --git a/internal/validate/validate.go b/internal/validate/validate.go index 2abfff983dc750b59911fd064fd7f913e4409d1b..3e0f3030007db74178cdf482efd8de755bf1e75c 100644 --- a/internal/validate/validate.go +++ b/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) }