From e18dc12e1eecb3ab1a7daab5d6f9e98d88d911a9 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 00:48:49 +0000 Subject: [PATCH] feat(agentic): add task priority and category support Co-Authored-By: Virgil --- pkg/agentic/commands_task.go | 30 +++++++++++++++++++++++------- pkg/agentic/commands_task_test.go | 8 ++++++++ pkg/agentic/plan.go | 6 ++++++ pkg/agentic/task.go | 16 ++++++++++++++++ pkg/agentic/task_test.go | 12 ++++++++++++ 5 files changed, 65 insertions(+), 7 deletions(-) diff --git a/pkg/agentic/commands_task.go b/pkg/agentic/commands_task.go index 54f3d4e..79ac27f 100644 --- a/pkg/agentic/commands_task.go +++ b/pkg/agentic/commands_task.go @@ -11,7 +11,7 @@ func (s *PrepSubsystem) registerTaskCommands() { c.Command("task", core.Command{Description: "Manage plan tasks", Action: s.cmdTask}) c.Command("agentic:task", core.Command{Description: "Manage plan tasks", Action: s.cmdTask}) c.Command("task/create", core.Command{Description: "Create a task in a plan phase", Action: s.cmdTaskCreate}) - c.Command("task/update", core.Command{Description: "Update a plan task status or notes", Action: s.cmdTaskUpdate}) + c.Command("task/update", core.Command{Description: "Update a plan task status, notes, priority, or category", Action: s.cmdTaskUpdate}) c.Command("task/toggle", core.Command{Description: "Toggle a plan task between pending and completed", Action: s.cmdTaskToggle}) } @@ -25,13 +25,13 @@ func (s *PrepSubsystem) cmdTask(options core.Options) core.Result { case "update": return s.cmdTaskUpdate(options) case "": - core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") - core.Print(nil, " core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") + core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") + core.Print(nil, " core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") core.Print(nil, " core-agent task toggle --phase=1 --task=1") return core.Result{OK: true} default: - core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") - core.Print(nil, " core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") + core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") + core.Print(nil, " core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go|--file-ref=pkg/agentic/task.go] [--line=42|--line-ref=42]") core.Print(nil, " core-agent task toggle --phase=1 --task=1") return core.Result{Value: core.E("agentic.cmdTask", core.Concat("unknown task command: ", action), nil), OK: false} } @@ -43,7 +43,7 @@ func (s *PrepSubsystem) cmdTaskCreate(options core.Options) core.Result { title := optionStringValue(options, "title", "task") if planSlug == "" || phaseOrder == 0 || title == "" { - core.Print(nil, "usage: core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--file=pkg/agentic/task.go] [--line=42]") + core.Print(nil, "usage: core-agent task create --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go] [--line=42]") return core.Result{Value: core.E("agentic.cmdTaskCreate", "plan_slug, phase_order, and title are required", nil), OK: false} } @@ -54,6 +54,8 @@ func (s *PrepSubsystem) cmdTaskCreate(options core.Options) core.Result { core.Option{Key: "description", Value: optionStringValue(options, "description")}, core.Option{Key: "status", Value: optionStringValue(options, "status")}, core.Option{Key: "notes", Value: optionStringValue(options, "notes")}, + core.Option{Key: "priority", Value: optionStringValue(options, "priority")}, + core.Option{Key: "category", Value: optionStringValue(options, "category")}, core.Option{Key: "file", Value: optionStringValue(options, "file")}, core.Option{Key: "line", Value: optionIntValue(options, "line")}, core.Option{Key: "file_ref", Value: optionStringValue(options, "file_ref", "file-ref")}, @@ -74,6 +76,12 @@ func (s *PrepSubsystem) cmdTaskCreate(options core.Options) core.Result { core.Print(nil, "task: %s", output.Task.Title) core.Print(nil, "status: %s", output.Task.Status) + if output.Task.Priority != "" { + core.Print(nil, "priority: %s", output.Task.Priority) + } + if output.Task.Category != "" { + core.Print(nil, "category: %s", output.Task.Category) + } if output.Task.File != "" { core.Print(nil, "file: %s", output.Task.File) } @@ -89,7 +97,7 @@ func (s *PrepSubsystem) cmdTaskUpdate(options core.Options) core.Result { taskIdentifier := optionAnyValue(options, "task_identifier", "task") if planSlug == "" || phaseOrder == 0 || taskIdentifierValue(taskIdentifier) == "" { - core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--file=pkg/agentic/task.go] [--line=42]") + core.Print(nil, "usage: core-agent task update --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--priority=high] [--category=security] [--file=pkg/agentic/task.go] [--line=42]") return core.Result{Value: core.E("agentic.cmdTaskUpdate", "plan_slug, phase_order, and task_identifier are required", nil), OK: false} } @@ -99,6 +107,8 @@ func (s *PrepSubsystem) cmdTaskUpdate(options core.Options) core.Result { core.Option{Key: "task_identifier", Value: taskIdentifier}, core.Option{Key: "status", Value: optionStringValue(options, "status")}, core.Option{Key: "notes", Value: optionStringValue(options, "notes")}, + core.Option{Key: "priority", Value: optionStringValue(options, "priority")}, + core.Option{Key: "category", Value: optionStringValue(options, "category")}, core.Option{Key: "file", Value: optionStringValue(options, "file")}, core.Option{Key: "line", Value: optionIntValue(options, "line")}, core.Option{Key: "file_ref", Value: optionStringValue(options, "file_ref", "file-ref")}, @@ -122,6 +132,12 @@ func (s *PrepSubsystem) cmdTaskUpdate(options core.Options) core.Result { if output.Task.Notes != "" { core.Print(nil, "notes: %s", output.Task.Notes) } + if output.Task.Priority != "" { + core.Print(nil, "priority: %s", output.Task.Priority) + } + if output.Task.Category != "" { + core.Print(nil, "category: %s", output.Task.Category) + } if output.Task.File != "" { core.Print(nil, "file: %s", output.Task.File) } diff --git a/pkg/agentic/commands_task_test.go b/pkg/agentic/commands_task_test.go index 01c4f43..0984b2a 100644 --- a/pkg/agentic/commands_task_test.go +++ b/pkg/agentic/commands_task_test.go @@ -34,6 +34,8 @@ func TestCommands_TaskCommand_Good_Update(t *testing.T) { core.Option{Key: "task_identifier", Value: "1"}, core.Option{Key: "status", Value: "completed"}, core.Option{Key: "notes", Value: "Done"}, + core.Option{Key: "priority", Value: "high"}, + core.Option{Key: "category", Value: "security"}, core.Option{Key: "file", Value: "pkg/agentic/task.go"}, core.Option{Key: "line", Value: 128}, )) @@ -43,6 +45,8 @@ func TestCommands_TaskCommand_Good_Update(t *testing.T) { require.True(t, ok) assert.Equal(t, "completed", output.Task.Status) assert.Equal(t, "Done", output.Task.Notes) + assert.Equal(t, "high", output.Task.Priority) + assert.Equal(t, "security", output.Task.Category) assert.Equal(t, "pkg/agentic/task.go", output.Task.File) assert.Equal(t, 128, output.Task.Line) } @@ -80,6 +84,8 @@ func TestCommands_TaskCommand_Good_Create(t *testing.T) { core.Option{Key: "description", Value: "Update the implementation"}, core.Option{Key: "status", Value: "pending"}, core.Option{Key: "notes", Value: "Do this first"}, + core.Option{Key: "priority", Value: "high"}, + core.Option{Key: "category", Value: "implementation"}, core.Option{Key: "file", Value: "pkg/agentic/task.go"}, core.Option{Key: "line", Value: 153}, )) @@ -90,6 +96,8 @@ func TestCommands_TaskCommand_Good_Create(t *testing.T) { assert.Equal(t, "Patch code", output.Task.Title) assert.Equal(t, "pending", output.Task.Status) assert.Equal(t, "Do this first", output.Task.Notes) + assert.Equal(t, "high", output.Task.Priority) + assert.Equal(t, "implementation", output.Task.Category) assert.Equal(t, "pkg/agentic/task.go", output.Task.File) assert.Equal(t, 153, output.Task.Line) } diff --git a/pkg/agentic/plan.go b/pkg/agentic/plan.go index 6e454d4..6cea91b 100644 --- a/pkg/agentic/plan.go +++ b/pkg/agentic/plan.go @@ -50,6 +50,8 @@ type PlanTask struct { ID string `json:"id,omitempty"` Title string `json:"title"` Description string `json:"description,omitempty"` + Priority string `json:"priority,omitempty"` + Category string `json:"category,omitempty"` Status string `json:"status,omitempty"` Notes string `json:"notes,omitempty"` File string `json:"file,omitempty"` @@ -720,6 +722,8 @@ func planTaskValue(value any) (PlanTask, bool) { ID: stringValue(typed["id"]), Title: title, Description: stringValue(typed["description"]), + Priority: stringValue(typed["priority"]), + Category: stringValue(typed["category"]), Status: stringValue(typed["status"]), Notes: stringValue(typed["notes"]), File: file, @@ -948,6 +952,8 @@ func normalisePlanTask(task PlanTask, index int) PlanTask { if task.Title == "" { task.Title = task.Description } + task.Priority = core.Trim(task.Priority) + task.Category = core.Trim(task.Category) if task.File == "" { task.File = task.FileRef } diff --git a/pkg/agentic/task.go b/pkg/agentic/task.go index 68c1743..02cb359 100644 --- a/pkg/agentic/task.go +++ b/pkg/agentic/task.go @@ -17,6 +17,8 @@ type TaskUpdateInput struct { TaskIdentifier any `json:"task_identifier"` Status string `json:"status,omitempty"` Notes string `json:"notes,omitempty"` + Priority string `json:"priority,omitempty"` + Category string `json:"category,omitempty"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` FileRef string `json:"file_ref,omitempty"` @@ -38,6 +40,8 @@ type TaskCreateInput struct { Description string `json:"description,omitempty"` Status string `json:"status,omitempty"` Notes string `json:"notes,omitempty"` + Priority string `json:"priority,omitempty"` + Category string `json:"category,omitempty"` File string `json:"file,omitempty"` Line int `json:"line,omitempty"` FileRef string `json:"file_ref,omitempty"` @@ -71,6 +75,8 @@ func (s *PrepSubsystem) handleTaskCreate(ctx context.Context, options core.Optio Description: optionStringValue(options, "description"), Status: optionStringValue(options, "status"), Notes: optionStringValue(options, "notes"), + Priority: optionStringValue(options, "priority"), + Category: optionStringValue(options, "category"), File: optionStringValue(options, "file"), Line: optionIntValue(options, "line"), FileRef: optionStringValue(options, "file_ref", "file-ref"), @@ -90,6 +96,8 @@ func (s *PrepSubsystem) handleTaskUpdate(ctx context.Context, options core.Optio TaskIdentifier: optionAnyValue(options, "task_identifier", "task"), Status: optionStringValue(options, "status"), Notes: optionStringValue(options, "notes"), + Priority: optionStringValue(options, "priority"), + Category: optionStringValue(options, "category"), File: optionStringValue(options, "file"), Line: optionIntValue(options, "line"), FileRef: optionStringValue(options, "file_ref", "file-ref"), @@ -150,6 +158,12 @@ func (s *PrepSubsystem) taskUpdate(_ context.Context, _ *mcp.CallToolRequest, in if notes := core.Trim(input.Notes); notes != "" { plan.Phases[phaseIndex].Tasks[taskIndex].Notes = notes } + if priority := core.Trim(input.Priority); priority != "" { + plan.Phases[phaseIndex].Tasks[taskIndex].Priority = priority + } + if category := core.Trim(input.Category); category != "" { + plan.Phases[phaseIndex].Tasks[taskIndex].Category = category + } if file := core.Trim(input.File); file != "" { plan.Phases[phaseIndex].Tasks[taskIndex].File = file plan.Phases[phaseIndex].Tasks[taskIndex].FileRef = file @@ -203,6 +217,8 @@ func (s *PrepSubsystem) taskCreate(_ context.Context, _ *mcp.CallToolRequest, in ID: core.Sprint(nextIndex), Title: core.Trim(input.Title), Description: core.Trim(input.Description), + Priority: core.Trim(input.Priority), + Category: core.Trim(input.Category), Status: input.Status, Notes: core.Trim(input.Notes), File: core.Trim(input.File), diff --git a/pkg/agentic/task_test.go b/pkg/agentic/task_test.go index 909fe17..d93ead8 100644 --- a/pkg/agentic/task_test.go +++ b/pkg/agentic/task_test.go @@ -33,6 +33,8 @@ func TestTask_TaskUpdate_Good(t *testing.T) { TaskIdentifier: "1", Status: "completed", Notes: "Done", + Priority: "high", + Category: "security", File: "pkg/agentic/task.go", Line: 128, }) @@ -40,6 +42,8 @@ func TestTask_TaskUpdate_Good(t *testing.T) { assert.True(t, output.Success) assert.Equal(t, "completed", output.Task.Status) assert.Equal(t, "Done", output.Task.Notes) + assert.Equal(t, "high", output.Task.Priority) + assert.Equal(t, "security", output.Task.Category) assert.Equal(t, "pkg/agentic/task.go", output.Task.File) assert.Equal(t, 128, output.Task.Line) } @@ -68,6 +72,8 @@ func TestTask_TaskCreate_Good(t *testing.T) { Description: "Update the implementation", Status: "pending", Notes: "Do this first", + Priority: "high", + Category: "implementation", File: "pkg/agentic/task.go", Line: 153, }) @@ -76,6 +82,8 @@ func TestTask_TaskCreate_Good(t *testing.T) { assert.Equal(t, "Patch code", output.Task.Title) assert.Equal(t, "pending", output.Task.Status) assert.Equal(t, "Do this first", output.Task.Notes) + assert.Equal(t, "high", output.Task.Priority) + assert.Equal(t, "implementation", output.Task.Category) assert.Equal(t, "pkg/agentic/task.go", output.Task.File) assert.Equal(t, 153, output.Task.Line) } @@ -150,10 +158,14 @@ func TestTask_TaskCreate_Ugly_CriteriaFallback(t *testing.T) { PlanSlug: plan.Slug, PhaseOrder: 1, Title: "Patch code", + Priority: "medium", + Category: "research", }) require.NoError(t, err) assert.True(t, output.Success) assert.Equal(t, "Patch code", output.Task.Title) + assert.Equal(t, "medium", output.Task.Priority) + assert.Equal(t, "research", output.Task.Category) updated, err := readPlan(PlansRoot(), plan.ID) require.NoError(t, err)