feat(agentic): add task command surface
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
9f4c956393
commit
b3bb77570c
5 changed files with 196 additions and 0 deletions
|
|
@ -28,6 +28,7 @@ func (s *PrepSubsystem) registerCommands(ctx context.Context) {
|
|||
c.Command("prompt", core.Command{Description: "Build and display an agent prompt for a repo", Action: s.cmdPrompt})
|
||||
c.Command("extract", core.Command{Description: "Extract a workspace template to a directory", Action: s.cmdExtract})
|
||||
s.registerPlanCommands()
|
||||
s.registerTaskCommands()
|
||||
}
|
||||
|
||||
// ctx := s.commandContext()
|
||||
|
|
|
|||
103
pkg/agentic/commands_task.go
Normal file
103
pkg/agentic/commands_task.go
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func (s *PrepSubsystem) registerTaskCommands() {
|
||||
c := s.Core()
|
||||
c.Command("task", core.Command{Description: "Manage plan tasks", Action: s.cmdTask})
|
||||
c.Command("task/update", core.Command{Description: "Update a plan task status or notes", Action: s.cmdTaskUpdate})
|
||||
c.Command("task/toggle", core.Command{Description: "Toggle a plan task between pending and completed", Action: s.cmdTaskToggle})
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdTask(options core.Options) core.Result {
|
||||
action := optionStringValue(options, "action")
|
||||
switch action {
|
||||
case "toggle":
|
||||
return s.cmdTaskToggle(options)
|
||||
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 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 toggle <plan> --phase=1 --task=1")
|
||||
return core.Result{Value: core.E("agentic.cmdTask", core.Concat("unknown task command: ", action), nil), OK: false}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdTaskUpdate(options core.Options) core.Result {
|
||||
planSlug := optionStringValue(options, "plan_slug", "plan", "slug", "_arg")
|
||||
phaseOrder := optionIntValue(options, "phase_order", "phase")
|
||||
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\"]")
|
||||
return core.Result{Value: core.E("agentic.cmdTaskUpdate", "plan_slug, phase_order, and task_identifier are required", nil), OK: false}
|
||||
}
|
||||
|
||||
result := s.handleTaskUpdate(s.commandContext(), core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: planSlug},
|
||||
core.Option{Key: "phase_order", Value: phaseOrder},
|
||||
core.Option{Key: "task_identifier", Value: taskIdentifier},
|
||||
core.Option{Key: "status", Value: optionStringValue(options, "status")},
|
||||
core.Option{Key: "notes", Value: optionStringValue(options, "notes")},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskUpdate", result)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
output, ok := result.Value.(TaskOutput)
|
||||
if !ok {
|
||||
err := core.E("agentic.cmdTaskUpdate", "invalid task update output", nil)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
core.Print(nil, "task: %s", output.Task.Title)
|
||||
core.Print(nil, "status: %s", output.Task.Status)
|
||||
if output.Task.Notes != "" {
|
||||
core.Print(nil, "notes: %s", output.Task.Notes)
|
||||
}
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
|
||||
func (s *PrepSubsystem) cmdTaskToggle(options core.Options) core.Result {
|
||||
planSlug := optionStringValue(options, "plan_slug", "plan", "slug", "_arg")
|
||||
phaseOrder := optionIntValue(options, "phase_order", "phase")
|
||||
taskIdentifier := optionAnyValue(options, "task_identifier", "task")
|
||||
|
||||
if planSlug == "" || phaseOrder == 0 || taskIdentifierValue(taskIdentifier) == "" {
|
||||
core.Print(nil, "usage: core-agent task toggle <plan> --phase=1 --task=1")
|
||||
return core.Result{Value: core.E("agentic.cmdTaskToggle", "plan_slug, phase_order, and task_identifier are required", nil), OK: false}
|
||||
}
|
||||
|
||||
result := s.handleTaskToggle(s.commandContext(), core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: planSlug},
|
||||
core.Option{Key: "phase_order", Value: phaseOrder},
|
||||
core.Option{Key: "task_identifier", Value: taskIdentifier},
|
||||
))
|
||||
if !result.OK {
|
||||
err := commandResultError("agentic.cmdTaskToggle", result)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
output, ok := result.Value.(TaskOutput)
|
||||
if !ok {
|
||||
err := core.E("agentic.cmdTaskToggle", "invalid task toggle output", nil)
|
||||
core.Print(nil, "error: %v", err)
|
||||
return core.Result{Value: err, OK: false}
|
||||
}
|
||||
|
||||
core.Print(nil, "task: %s", output.Task.Title)
|
||||
core.Print(nil, "status: %s", output.Task.Status)
|
||||
return core.Result{Value: output, OK: true}
|
||||
}
|
||||
86
pkg/agentic/commands_task_test.go
Normal file
86
pkg/agentic/commands_task_test.go
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCommands_TaskCommand_Good_Update(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",
|
||||
Description: "Update task through CLI command",
|
||||
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)
|
||||
|
||||
r := s.cmdTaskUpdate(core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: plan.Slug},
|
||||
core.Option{Key: "phase_order", Value: 1},
|
||||
core.Option{Key: "task_identifier", Value: "1"},
|
||||
core.Option{Key: "status", Value: "completed"},
|
||||
core.Option{Key: "notes", Value: "Done"},
|
||||
))
|
||||
require.True(t, r.OK)
|
||||
|
||||
output, ok := r.Value.(TaskOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "completed", output.Task.Status)
|
||||
assert.Equal(t, "Done", output.Task.Notes)
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Bad_MissingRequiredFields(t *testing.T) {
|
||||
s := newTestPrep(t)
|
||||
|
||||
r := s.cmdTaskUpdate(core.NewOptions(
|
||||
core.Option{Key: "phase_order", Value: 1},
|
||||
core.Option{Key: "task_identifier", Value: "1"},
|
||||
))
|
||||
|
||||
assert.False(t, r.OK)
|
||||
assert.Contains(t, r.Value.(error).Error(), "required")
|
||||
}
|
||||
|
||||
func TestCommands_TaskCommand_Ugly_ToggleCriteriaFallback(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
||||
s := newTestPrep(t)
|
||||
_, created, err := s.planCreate(context.Background(), nil, PlanCreateInput{
|
||||
Title: "Task Toggle",
|
||||
Description: "Toggle criteria-derived task",
|
||||
Phases: []Phase{
|
||||
{Name: "Setup", Criteria: []string{"Review RFC"}},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
plan, err := readPlan(PlansRoot(), created.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
r := s.cmdTaskToggle(core.NewOptions(
|
||||
core.Option{Key: "plan_slug", Value: plan.Slug},
|
||||
core.Option{Key: "phase_order", Value: 1},
|
||||
core.Option{Key: "task_identifier", Value: 1},
|
||||
))
|
||||
require.True(t, r.OK)
|
||||
|
||||
output, ok := r.Value.(TaskOutput)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "completed", output.Task.Status)
|
||||
assert.Equal(t, "Review RFC", output.Task.Title)
|
||||
}
|
||||
|
|
@ -875,6 +875,9 @@ func TestCommands_RegisterCommands_Good_AllRegistered(t *testing.T) {
|
|||
assert.Contains(t, cmds, "plan/list")
|
||||
assert.Contains(t, cmds, "plan/show")
|
||||
assert.Contains(t, cmds, "plan/status")
|
||||
assert.Contains(t, cmds, "task")
|
||||
assert.Contains(t, cmds, "task/update")
|
||||
assert.Contains(t, cmds, "task/toggle")
|
||||
}
|
||||
|
||||
// --- CmdExtract Bad/Ugly ---
|
||||
|
|
|
|||
|
|
@ -583,6 +583,9 @@ func TestPrep_OnStartup_Good_RegistersGenerateCommand(t *testing.T) {
|
|||
assert.Contains(t, c.Commands(), "brain/ingest")
|
||||
assert.Contains(t, c.Commands(), "brain/seed-memory")
|
||||
assert.Contains(t, c.Commands(), "plan-cleanup")
|
||||
assert.Contains(t, c.Commands(), "task")
|
||||
assert.Contains(t, c.Commands(), "task/update")
|
||||
assert.Contains(t, c.Commands(), "task/toggle")
|
||||
}
|
||||
|
||||
func TestPrep_OnStartup_Bad(t *testing.T) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue