feat(agentic): add phase dependencies to plans
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
88f698a608
commit
6bb355c472
2 changed files with 104 additions and 18 deletions
|
|
@ -32,15 +32,16 @@ type Plan struct {
|
|||
|
||||
// phase := agentic.Phase{Number: 1, Name: "Migrate strings", Status: "in_progress"}
|
||||
type Phase struct {
|
||||
Number int `json:"number"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Criteria []string `json:"criteria,omitempty"`
|
||||
Tasks []PlanTask `json:"tasks,omitempty"`
|
||||
Checkpoints []PhaseCheckpoint `json:"checkpoints,omitempty"`
|
||||
Tests int `json:"tests,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
Number int `json:"number"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Status string `json:"status"`
|
||||
Criteria []string `json:"criteria,omitempty"`
|
||||
Dependencies []string `json:"dependencies,omitempty"`
|
||||
Tasks []PlanTask `json:"tasks,omitempty"`
|
||||
Checkpoints []PhaseCheckpoint `json:"checkpoints,omitempty"`
|
||||
Tests int `json:"tests,omitempty"`
|
||||
Notes string `json:"notes,omitempty"`
|
||||
}
|
||||
|
||||
// task := agentic.PlanTask{ID: "1", Title: "Review imports", Status: "pending", File: "pkg/agentic/plan.go", Line: 46}
|
||||
|
|
@ -569,15 +570,16 @@ func phaseValue(value any) (Phase, bool) {
|
|||
return typed, true
|
||||
case map[string]any:
|
||||
return Phase{
|
||||
Number: intValue(typed["number"]),
|
||||
Name: stringValue(typed["name"]),
|
||||
Description: stringValue(typed["description"]),
|
||||
Status: stringValue(typed["status"]),
|
||||
Criteria: stringSliceValue(typed["criteria"]),
|
||||
Tasks: planTaskSliceValue(typed["tasks"]),
|
||||
Checkpoints: phaseCheckpointSliceValue(typed["checkpoints"]),
|
||||
Tests: intValue(typed["tests"]),
|
||||
Notes: stringValue(typed["notes"]),
|
||||
Number: intValue(typed["number"]),
|
||||
Name: stringValue(typed["name"]),
|
||||
Description: stringValue(typed["description"]),
|
||||
Status: stringValue(typed["status"]),
|
||||
Criteria: stringSliceValue(typed["criteria"]),
|
||||
Dependencies: phaseDependenciesValue(typed["dependencies"]),
|
||||
Tasks: planTaskSliceValue(typed["tasks"]),
|
||||
Checkpoints: phaseCheckpointSliceValue(typed["checkpoints"]),
|
||||
Tests: intValue(typed["tests"]),
|
||||
Notes: stringValue(typed["notes"]),
|
||||
}, true
|
||||
case map[string]string:
|
||||
return phaseValue(anyMapValue(typed))
|
||||
|
|
@ -593,6 +595,43 @@ func phaseValue(value any) (Phase, bool) {
|
|||
return Phase{}, false
|
||||
}
|
||||
|
||||
func phaseDependenciesValue(value any) []string {
|
||||
switch typed := value.(type) {
|
||||
case []string:
|
||||
return cleanStrings(typed)
|
||||
case []any:
|
||||
dependencies := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
text, ok := item.(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if text = core.Trim(text); text != "" {
|
||||
dependencies = append(dependencies, text)
|
||||
}
|
||||
}
|
||||
return dependencies
|
||||
case string:
|
||||
trimmed := core.Trim(typed)
|
||||
if trimmed == "" {
|
||||
return nil
|
||||
}
|
||||
if core.HasPrefix(trimmed, "[") {
|
||||
var dependencies []string
|
||||
if result := core.JSONUnmarshalString(trimmed, &dependencies); result.OK {
|
||||
return cleanStrings(dependencies)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return cleanStrings(core.Split(trimmed, ","))
|
||||
default:
|
||||
if text := stringValue(value); text != "" {
|
||||
return []string{text}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func planTaskSliceValue(value any) []PlanTask {
|
||||
switch typed := value.(type) {
|
||||
case []PlanTask:
|
||||
|
|
|
|||
47
pkg/agentic/plan_dependencies_test.go
Normal file
47
pkg/agentic/plan_dependencies_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package agentic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestPlanDependencies_PlanCreate_Good_PreservesPhaseDependencies(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
||||
s := newTestPrep(t)
|
||||
_, created, err := s.planCreate(context.Background(), nil, PlanCreateInput{
|
||||
Title: "Dependency Plan",
|
||||
Objective: "Keep phase dependencies in the stored plan",
|
||||
Phases: []Phase{
|
||||
{
|
||||
Name: "Build",
|
||||
Dependencies: []string{"Setup", "Lint"},
|
||||
Criteria: []string{"tests pass"},
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
plan, err := readPlan(PlansRoot(), created.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, plan.Phases, 1)
|
||||
assert.Equal(t, []string{"Setup", "Lint"}, plan.Phases[0].Dependencies)
|
||||
}
|
||||
|
||||
func TestPlanDependencies_PhaseDependenciesValue_Bad_MixedTypesReturnsNil(t *testing.T) {
|
||||
dependencies := phaseDependenciesValue([]any{"Setup", 7})
|
||||
|
||||
assert.Nil(t, dependencies)
|
||||
}
|
||||
|
||||
func TestPlanDependencies_PhaseDependenciesValue_Ugly_NilInputReturnsNil(t *testing.T) {
|
||||
dependencies := phaseDependenciesValue(nil)
|
||||
|
||||
assert.Nil(t, dependencies)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue