feat(agentic): add task file ref aliases
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
c52ed05c83
commit
332a464cf6
5 changed files with 132 additions and 6 deletions
|
|
@ -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 <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, "usage: core-agent task update <plan> --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 <plan> --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, " 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\"] [--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, "usage: core-agent task update <plan> --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 <plan> --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, " 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}
|
||||
}
|
||||
|
|
@ -56,6 +56,8 @@ func (s *PrepSubsystem) cmdTaskCreate(options core.Options) core.Result {
|
|||
core.Option{Key: "notes", Value: optionStringValue(options, "notes")},
|
||||
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")},
|
||||
core.Option{Key: "line_ref", Value: optionIntValue(options, "line_ref", "line-ref")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskCreate", result)
|
||||
|
|
@ -99,6 +101,8 @@ func (s *PrepSubsystem) cmdTaskUpdate(options core.Options) core.Result {
|
|||
core.Option{Key: "notes", Value: optionStringValue(options, "notes")},
|
||||
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")},
|
||||
core.Option{Key: "line_ref", Value: optionIntValue(options, "line_ref", "line-ref")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskUpdate", result)
|
||||
|
|
|
|||
|
|
@ -94,6 +94,40 @@ func TestCommands_TaskCommand_Good_Create(t *testing.T) {
|
|||
assert.Equal(t, 153, output.Task.Line)
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Good_CreateFileRefAliases(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
||||
s := newTestPrep(t)
|
||||
_, created, err := s.planCreate(context.Background(), nil, PlanCreateInput{
|
||||
Title: "Task Command File Ref",
|
||||
Description: "Create task through CLI command with RFC aliases",
|
||||
Phases: []Phase{
|
||||
{Name: "Setup"},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
plan, err := readPlan(PlansRoot(), created.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := s.cmdTaskCreate(core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: plan.Slug},
|
||||
core.Option{Key: "phase_order", Value: 1},
|
||||
core.Option{Key: "title", Value: "Patch code"},
|
||||
core.Option{Key: "file_ref", Value: "pkg/agentic/task.go"},
|
||||
core.Option{Key: "line_ref", Value: 153},
|
||||
))
|
||||
require.True(t, r.OK)
|
||||
|
||||
output, ok := r.Value.(TaskCreateOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "pkg/agentic/task.go", output.Task.FileRef)
|
||||
assert.Equal(t, 153, output.Task.LineRef)
|
||||
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) {
|
||||
s := newTestPrep(t)
|
||||
|
||||
|
|
|
|||
|
|
@ -54,6 +54,8 @@ type PlanTask struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
FileRef string `json:"file_ref,omitempty"`
|
||||
LineRef int `json:"line_ref,omitempty"`
|
||||
}
|
||||
|
||||
// checkpoint := agentic.PhaseCheckpoint{Note: "Build passes", CreatedAt: "2026-03-31T00:00:00Z"}
|
||||
|
|
@ -706,14 +708,24 @@ func planTaskValue(value any) (PlanTask, bool) {
|
|||
if title == "" {
|
||||
title = stringValue(typed["name"])
|
||||
}
|
||||
file := stringValue(typed["file"])
|
||||
if file == "" {
|
||||
file = stringValue(typed["file_ref"])
|
||||
}
|
||||
line := intValue(typed["line"])
|
||||
if line == 0 {
|
||||
line = intValue(typed["line_ref"])
|
||||
}
|
||||
return PlanTask{
|
||||
ID: stringValue(typed["id"]),
|
||||
Title: title,
|
||||
Description: stringValue(typed["description"]),
|
||||
Status: stringValue(typed["status"]),
|
||||
Notes: stringValue(typed["notes"]),
|
||||
File: stringValue(typed["file"]),
|
||||
Line: intValue(typed["line"]),
|
||||
File: file,
|
||||
Line: line,
|
||||
FileRef: file,
|
||||
LineRef: line,
|
||||
}, title != ""
|
||||
case map[string]string:
|
||||
return planTaskValue(anyMapValue(typed))
|
||||
|
|
@ -936,6 +948,18 @@ func normalisePlanTask(task PlanTask, index int) PlanTask {
|
|||
if task.Title == "" {
|
||||
task.Title = task.Description
|
||||
}
|
||||
if task.File == "" {
|
||||
task.File = task.FileRef
|
||||
}
|
||||
if task.FileRef == "" {
|
||||
task.FileRef = task.File
|
||||
}
|
||||
if task.Line == 0 {
|
||||
task.Line = task.LineRef
|
||||
}
|
||||
if task.LineRef == 0 {
|
||||
task.LineRef = task.Line
|
||||
}
|
||||
return task
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ type TaskUpdateInput struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
FileRef string `json:"file_ref,omitempty"`
|
||||
LineRef int `json:"line_ref,omitempty"`
|
||||
}
|
||||
|
||||
// input := agentic.TaskToggleInput{PlanSlug: "my-plan-abc123", PhaseOrder: 1, TaskIdentifier: 1}
|
||||
|
|
@ -38,6 +40,8 @@ type TaskCreateInput struct {
|
|||
Notes string `json:"notes,omitempty"`
|
||||
File string `json:"file,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
FileRef string `json:"file_ref,omitempty"`
|
||||
LineRef int `json:"line_ref,omitempty"`
|
||||
}
|
||||
|
||||
// out := agentic.TaskOutput{Success: true, Task: agentic.PlanTask{ID: "1", Title: "Review imports", File: "pkg/agentic/task.go", Line: 128}}
|
||||
|
|
@ -69,6 +73,8 @@ func (s *PrepSubsystem) handleTaskCreate(ctx context.Context, options core.Optio
|
|||
Notes: optionStringValue(options, "notes"),
|
||||
File: optionStringValue(options, "file"),
|
||||
Line: optionIntValue(options, "line"),
|
||||
FileRef: optionStringValue(options, "file_ref", "file-ref"),
|
||||
LineRef: optionIntValue(options, "line_ref", "line-ref"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -86,6 +92,8 @@ func (s *PrepSubsystem) handleTaskUpdate(ctx context.Context, options core.Optio
|
|||
Notes: optionStringValue(options, "notes"),
|
||||
File: optionStringValue(options, "file"),
|
||||
Line: optionIntValue(options, "line"),
|
||||
FileRef: optionStringValue(options, "file_ref", "file-ref"),
|
||||
LineRef: optionIntValue(options, "line_ref", "line-ref"),
|
||||
})
|
||||
if err != nil {
|
||||
return core.Result{Value: err, OK: false}
|
||||
|
|
@ -144,9 +152,19 @@ func (s *PrepSubsystem) taskUpdate(_ context.Context, _ *mcp.CallToolRequest, in
|
|||
}
|
||||
if file := core.Trim(input.File); file != "" {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].File = file
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].FileRef = file
|
||||
}
|
||||
if fileRef := core.Trim(input.FileRef); fileRef != "" {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].FileRef = fileRef
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].File = fileRef
|
||||
}
|
||||
if input.Line > 0 {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].Line = input.Line
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].LineRef = input.Line
|
||||
}
|
||||
if input.LineRef > 0 {
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].LineRef = input.LineRef
|
||||
plan.Phases[phaseIndex].Tasks[taskIndex].Line = input.LineRef
|
||||
}
|
||||
plan.UpdatedAt = time.Now()
|
||||
|
||||
|
|
@ -189,6 +207,8 @@ func (s *PrepSubsystem) taskCreate(_ context.Context, _ *mcp.CallToolRequest, in
|
|||
Notes: core.Trim(input.Notes),
|
||||
File: core.Trim(input.File),
|
||||
Line: input.Line,
|
||||
FileRef: core.Trim(input.FileRef),
|
||||
LineRef: input.LineRef,
|
||||
}
|
||||
newTask = normalisePlanTask(newTask, nextIndex)
|
||||
|
||||
|
|
|
|||
|
|
@ -163,3 +163,47 @@ func TestTask_TaskCreate_Ugly_CriteriaFallback(t *testing.T) {
|
|||
assert.Empty(t, updated.Phases[0].Tasks[1].File)
|
||||
assert.Zero(t, updated.Phases[0].Tasks[1].Line)
|
||||
}
|
||||
|
||||
func TestTask_TaskFileRefAliases_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
||||
s := newTestPrep(t)
|
||||
_, created, err := s.planCreate(context.Background(), nil, PlanCreateInput{
|
||||
Title: "Task File Ref Aliases",
|
||||
Description: "Accept RFC task file reference names",
|
||||
Phases: []Phase{
|
||||
{Name: "Setup", Tasks: []PlanTask{{ID: "1", Title: "Review RFC"}}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
plan, err := readPlan(PlansRoot(), created.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, createdOutput, err := s.taskCreate(context.Background(), nil, TaskCreateInput{
|
||||
PlanSlug: plan.Slug,
|
||||
PhaseOrder: 1,
|
||||
Title: "Patch code",
|
||||
FileRef: "pkg/agentic/task.go",
|
||||
LineRef: 153,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "pkg/agentic/task.go", createdOutput.Task.FileRef)
|
||||
assert.Equal(t, 153, createdOutput.Task.LineRef)
|
||||
assert.Equal(t, "pkg/agentic/task.go", createdOutput.Task.File)
|
||||
assert.Equal(t, 153, createdOutput.Task.Line)
|
||||
|
||||
_, updatedOutput, err := s.taskUpdate(context.Background(), nil, TaskUpdateInput{
|
||||
PlanSlug: plan.Slug,
|
||||
PhaseOrder: 1,
|
||||
TaskIdentifier: createdOutput.Task.ID,
|
||||
FileRef: "pkg/agentic/task.go",
|
||||
LineRef: 171,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "pkg/agentic/task.go", updatedOutput.Task.FileRef)
|
||||
assert.Equal(t, 171, updatedOutput.Task.LineRef)
|
||||
assert.Equal(t, "pkg/agentic/task.go", updatedOutput.Task.File)
|
||||
assert.Equal(t, 171, updatedOutput.Task.Line)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue