feat(agentic): add task file-line references
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
b7a102cc3a
commit
06a915aafb
8 changed files with 90 additions and 13 deletions
|
|
@ -24,13 +24,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 <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"]")
|
||||
core.Print(nil, " core-agent task create <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"]")
|
||||
core.Print(nil, "usage: core-agent task update <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--file=pkg/agentic/task.go] [--line=42]")
|
||||
core.Print(nil, " core-agent task create <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--file=pkg/agentic/task.go] [--line=42]")
|
||||
core.Print(nil, " core-agent task toggle <plan> --phase=1 --task=1")
|
||||
return core.Result{OK: true}
|
||||
default:
|
||||
core.Print(nil, "usage: core-agent task update <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"]")
|
||||
core.Print(nil, " core-agent task create <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"]")
|
||||
core.Print(nil, "usage: core-agent task update <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--file=pkg/agentic/task.go] [--line=42]")
|
||||
core.Print(nil, " core-agent task create <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--file=pkg/agentic/task.go] [--line=42]")
|
||||
core.Print(nil, " core-agent task toggle <plan> --phase=1 --task=1")
|
||||
return core.Result{Value: core.E("agentic.cmdTask", core.Concat("unknown task command: ", action), nil), OK: false}
|
||||
}
|
||||
|
|
@ -42,7 +42,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 <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"]")
|
||||
core.Print(nil, "usage: core-agent task create <plan> --phase=1 --title=\"Review RFC\" [--description=\"...\"] [--status=pending] [--notes=\"...\"] [--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}
|
||||
}
|
||||
|
||||
|
|
@ -53,6 +53,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: "file", Value: optionStringValue(options, "file")},
|
||||
core.Option{Key: "line", Value: optionIntValue(options, "line")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskCreate", result)
|
||||
|
|
@ -69,6 +71,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.File != "" {
|
||||
core.Print(nil, "file: %s", output.Task.File)
|
||||
}
|
||||
if output.Task.Line > 0 {
|
||||
core.Print(nil, "line: %d", output.Task.Line)
|
||||
}
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
|
|
@ -78,7 +86,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 <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"]")
|
||||
core.Print(nil, "usage: core-agent task update <plan> --phase=1 --task=1 [--status=completed] [--notes=\"Done\"] [--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}
|
||||
}
|
||||
|
||||
|
|
@ -88,6 +96,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: "file", Value: optionStringValue(options, "file")},
|
||||
core.Option{Key: "line", Value: optionIntValue(options, "line")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskUpdate", result)
|
||||
|
|
@ -107,6 +117,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.File != "" {
|
||||
core.Print(nil, "file: %s", output.Task.File)
|
||||
}
|
||||
if output.Task.Line > 0 {
|
||||
core.Print(nil, "line: %d", output.Task.Line)
|
||||
}
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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: "file", Value: "pkg/agentic/task.go"},
|
||||
core.Option{Key: "line", Value: 128},
|
||||
))
|
||||
require.True(t, r.OK)
|
||||
|
||||
|
|
@ -41,6 +43,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, "pkg/agentic/task.go", output.Task.File)
|
||||
assert.Equal(t, 128, output.Task.Line)
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Good_Create(t *testing.T) {
|
||||
|
|
@ -67,6 +71,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: "file", Value: "pkg/agentic/task.go"},
|
||||
core.Option{Key: "line", Value: 153},
|
||||
))
|
||||
require.True(t, r.OK)
|
||||
|
||||
|
|
@ -75,6 +81,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, "pkg/agentic/task.go", output.Task.File)
|
||||
assert.Equal(t, 153, output.Task.Line)
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Bad_MissingRequiredFields(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -43,13 +43,15 @@ type Phase struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
// task := agentic.PlanTask{ID: "1", Title: "Review imports", Status: "pending"}
|
||||
// task := agentic.PlanTask{ID: "1", Title: "Review imports", Status: "pending", File: "pkg/agentic/plan.go", Line: 46}
|
||||
type PlanTask struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
}
|
||||
|
||||
// checkpoint := agentic.PhaseCheckpoint{Note: "Build passes", CreatedAt: "2026-03-31T00:00:00Z"}
|
||||
|
|
@ -658,6 +660,8 @@ func planTaskValue(value any) (PlanTask, bool) {
|
|||
Description: stringValue(typed["description"]),
|
||||
Status: stringValue(typed["status"]),
|
||||
Notes: stringValue(typed["notes"]),
|
||||
File: stringValue(typed["file"]),
|
||||
Line: intValue(typed["line"]),
|
||||
}, title != ""
|
||||
case map[string]string:
|
||||
return planTaskValue(anyMapValue(typed))
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ func TestPlan_ReadPlan_Good(t *testing.T) {
|
|||
Org: "core",
|
||||
Objective: "Verify plan reading works",
|
||||
Phases: []Phase{
|
||||
{Number: 1, Name: "Setup", Status: "done"},
|
||||
{Number: 1, Name: "Setup", Status: "done", Tasks: []PlanTask{{ID: "1", Title: "Review imports", File: "pkg/agentic/plan.go", Line: 46}}},
|
||||
{Number: 2, Name: "Implement", Status: "pending"},
|
||||
},
|
||||
Notes: "Some notes",
|
||||
|
|
@ -92,6 +92,10 @@ func TestPlan_ReadPlan_Good(t *testing.T) {
|
|||
assert.Len(t, read.Phases, 2)
|
||||
assert.Equal(t, "Setup", read.Phases[0].Name)
|
||||
assert.Equal(t, "done", read.Phases[0].Status)
|
||||
require.Len(t, read.Phases[0].Tasks, 1)
|
||||
assert.Equal(t, "Review imports", read.Phases[0].Tasks[0].Title)
|
||||
assert.Equal(t, "pkg/agentic/plan.go", read.Phases[0].Tasks[0].File)
|
||||
assert.Equal(t, 46, read.Phases[0].Tasks[0].Line)
|
||||
assert.Equal(t, "Implement", read.Phases[1].Name)
|
||||
assert.Equal(t, "pending", read.Phases[1].Status)
|
||||
assert.Equal(t, "Some notes", read.Notes)
|
||||
|
|
@ -123,7 +127,7 @@ func TestPlan_WriteRead_Good_Roundtrip(t *testing.T) {
|
|||
Org: "core",
|
||||
Objective: "Ensure write-read roundtrip works",
|
||||
Phases: []Phase{
|
||||
{Number: 1, Name: "Phase One", Status: "done", Criteria: []string{"tests pass", "coverage > 80%"}, Tests: 5},
|
||||
{Number: 1, Name: "Phase One", Status: "done", Criteria: []string{"tests pass", "coverage > 80%"}, Tests: 5, Tasks: []PlanTask{{ID: "1", Title: "tests pass", File: "pkg/agentic/plan_test.go", Line: 100}}},
|
||||
{Number: 2, Name: "Phase Two", Status: "in_progress", Notes: "Working on it"},
|
||||
{Number: 3, Name: "Phase Three", Status: "pending"},
|
||||
},
|
||||
|
|
@ -142,6 +146,9 @@ func TestPlan_WriteRead_Good_Roundtrip(t *testing.T) {
|
|||
assert.Len(t, read.Phases, 3)
|
||||
assert.Equal(t, []string{"tests pass", "coverage > 80%"}, read.Phases[0].Criteria)
|
||||
assert.Equal(t, 5, read.Phases[0].Tests)
|
||||
require.Len(t, read.Phases[0].Tasks, 1)
|
||||
assert.Equal(t, "pkg/agentic/plan_test.go", read.Phases[0].Tasks[0].File)
|
||||
assert.Equal(t, 100, read.Phases[0].Tasks[0].Line)
|
||||
assert.Equal(t, "Working on it", read.Phases[1].Notes)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,13 +10,15 @@ import (
|
|||
"github.com/modelcontextprotocol/go-sdk/mcp"
|
||||
)
|
||||
|
||||
// input := agentic.TaskUpdateInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, TaskIdentifier: "1"}
|
||||
// input := agentic.TaskUpdateInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, TaskIdentifier: "1", File: "pkg/agentic/task.go", Line: 128}
|
||||
type TaskUpdateInput struct {
|
||||
PlanSlug string `json:"plan_slug"`
|
||||
PhaseOrder int `json:"phase_order"`
|
||||
TaskIdentifier any `json:"task_identifier"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
}
|
||||
|
||||
// input := agentic.TaskToggleInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, TaskIdentifier: 1}
|
||||
|
|
@ -26,7 +28,7 @@ type TaskToggleInput struct {
|
|||
TaskIdentifier any `json:"task_identifier"`
|
||||
}
|
||||
|
||||
// input := agentic.TaskCreateInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, Title: "Review imports"}
|
||||
// input := agentic.TaskCreateInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, Title: "Review imports", File: "pkg/agentic/task.go", Line: 153}
|
||||
type TaskCreateInput struct {
|
||||
PlanSlug string `json:"plan_slug"`
|
||||
PhaseOrder int `json:"phase_order"`
|
||||
|
|
@ -34,15 +36,17 @@ type TaskCreateInput struct {
|
|||
Description string `json:"description,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
}
|
||||
|
||||
// out := agentic.TaskOutput{Success: true, Task: agentic.PlanTask{ID: "1", Title: "Review imports"}}
|
||||
// out := agentic.TaskOutput{Success: true, Task: agentic.PlanTask{ID: "1", Title: "Review imports", File: "pkg/agentic/task.go", Line: 128}}
|
||||
type TaskOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Task PlanTask `json:"task"`
|
||||
}
|
||||
|
||||
// out := agentic.TaskCreateOutput{Success: true, Task: agentic.PlanTask{ID: "3", Title: "Review imports"}}
|
||||
// out := agentic.TaskCreateOutput{Success: true, Task: agentic.PlanTask{ID: "3", Title: "Review imports", File: "pkg/agentic/task.go", Line: 153}}
|
||||
type TaskCreateOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Task PlanTask `json:"task"`
|
||||
|
|
@ -63,6 +67,8 @@ func (s *PrepSubsystem) handleTaskCreate(ctx context.Context, options core.Optio
|
|||
Description: optionStringValue(options, "description"),
|
||||
Status: optionStringValue(options, "status"),
|
||||
Notes: optionStringValue(options, "notes"),
|
||||
File: optionStringValue(options, "file"),
|
||||
Line: optionIntValue(options, "line"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -78,6 +84,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"),
|
||||
File: optionStringValue(options, "file"),
|
||||
Line: optionIntValue(options, "line"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -134,6 +142,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 file := core.Trim(input.File); file != "" {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].File = file
|
||||
}
|
||||
if input.Line > 0 {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].Line = input.Line
|
||||
}
|
||||
plan.UpdatedAt = time.Now()
|
||||
|
||||
if result := writePlanResult(PlansRoot(), plan); !result.OK {
|
||||
|
|
@ -173,6 +187,8 @@ func (s *PrepSubsystem) taskCreate(_ context.Context, _ *mcp.CallToolRequest, in
|
|||
Description: core.Trim(input.Description),
|
||||
Status: input.Status,
|
||||
Notes: core.Trim(input.Notes),
|
||||
File: core.Trim(input.File),
|
||||
Line: input.Line,
|
||||
}
|
||||
newTask = normalisePlanTask(newTask, nextIndex)
|
||||
|
||||
|
|
|
|||
|
|
@ -33,11 +33,15 @@ func TestTask_TaskUpdate_Good(t *testing.T) {
|
|||
TaskIdentifier: "1",
|
||||
Status: "completed",
|
||||
Notes: "Done",
|
||||
File: "pkg/agentic/task.go",
|
||||
Line: 128,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, output.Success)
|
||||
assert.Equal(t, "completed", output.Task.Status)
|
||||
assert.Equal(t, "Done", output.Task.Notes)
|
||||
assert.Equal(t, "pkg/agentic/task.go", output.Task.File)
|
||||
assert.Equal(t, 128, output.Task.Line)
|
||||
}
|
||||
|
||||
func TestTask_TaskCreate_Good(t *testing.T) {
|
||||
|
|
@ -64,12 +68,16 @@ func TestTask_TaskCreate_Good(t *testing.T) {
|
|||
Description: "Update the implementation",
|
||||
Status: "pending",
|
||||
Notes: "Do this first",
|
||||
File: "pkg/agentic/task.go",
|
||||
Line: 153,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, output.Success)
|
||||
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, "pkg/agentic/task.go", output.Task.File)
|
||||
assert.Equal(t, 153, output.Task.Line)
|
||||
}
|
||||
|
||||
func TestTask_TaskCreate_Bad_MissingTitle(t *testing.T) {
|
||||
|
|
@ -152,4 +160,6 @@ func TestTask_TaskCreate_Ugly_CriteriaFallback(t *testing.T) {
|
|||
require.Len(t, updated.Phases[0].Tasks, 2)
|
||||
assert.Equal(t, "Review RFC", updated.Phases[0].Tasks[0].Title)
|
||||
assert.Equal(t, "Patch code", updated.Phases[0].Tasks[1].Title)
|
||||
assert.Empty(t, updated.Phases[0].Tasks[1].File)
|
||||
assert.Zero(t, updated.Phases[0].Tasks[1].Line)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -432,6 +432,8 @@ func templatePlanTask(item any, number int) PlanTask {
|
|||
Description: stringValue(value["description"]),
|
||||
Status: status,
|
||||
Notes: stringValue(value["notes"]),
|
||||
File: stringValue(value["file"]),
|
||||
Line: intValue(value["line"]),
|
||||
}
|
||||
}
|
||||
return PlanTask{}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,20 @@ func TestTemplate_HandleTemplatePreview_Ugly_MissingVariables(t *testing.T) {
|
|||
assert.Contains(t, output.Preview, "{{ feature_name }}")
|
||||
}
|
||||
|
||||
func TestTemplate_TemplatePlanTask_Good_FileLineReference(t *testing.T) {
|
||||
task := templatePlanTask(map[string]any{
|
||||
"title": "Review RFC",
|
||||
"status": "pending",
|
||||
"file": "pkg/agentic/template.go",
|
||||
"line": 411,
|
||||
}, 1)
|
||||
|
||||
assert.Equal(t, "Review RFC", task.Title)
|
||||
assert.Equal(t, "pending", task.Status)
|
||||
assert.Equal(t, "pkg/agentic/template.go", task.File)
|
||||
assert.Equal(t, 411, task.Line)
|
||||
}
|
||||
|
||||
func TestTemplate_HandleTemplateCreatePlan_Good(t *testing.T) {
|
||||
subsystem := testPrepWithPlatformServer(t, nil, "")
|
||||
result := subsystem.handleTemplateCreatePlan(context.Background(), core.NewOptions(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue