feat(agentic): extract checklist tasks from issue plans
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
6eadd70aef
commit
405cd44ac3
2 changed files with 55 additions and 2 deletions
|
|
@ -66,6 +66,7 @@ func (s *PrepSubsystem) planFromIssue(ctx context.Context, _ *mcp.CallToolReques
|
|||
if objective == "" {
|
||||
objective = issueOutput.Issue.Title
|
||||
}
|
||||
tasks := planFromIssueTasks(description)
|
||||
|
||||
phaseTitle := core.Concat("Resolve issue: ", issueOutput.Issue.Title)
|
||||
phaseCriteria := []string{"Issue is closed", "QA passes"}
|
||||
|
|
@ -93,6 +94,7 @@ func (s *PrepSubsystem) planFromIssue(ctx context.Context, _ *mcp.CallToolReques
|
|||
Name: phaseTitle,
|
||||
Description: description,
|
||||
Criteria: phaseCriteria,
|
||||
Tasks: tasks,
|
||||
},
|
||||
},
|
||||
Notes: core.Concat("Created from issue ", identifier),
|
||||
|
|
@ -122,6 +124,30 @@ func (s *PrepSubsystem) planFromIssue(ctx context.Context, _ *mcp.CallToolReques
|
|||
}, nil
|
||||
}
|
||||
|
||||
func planFromIssueTasks(body string) []PlanTask {
|
||||
var tasks []PlanTask
|
||||
for _, line := range core.Split(body, "\n") {
|
||||
trimmed := core.Trim(line)
|
||||
if title, ok := planFromIssueTaskTitle(trimmed); ok {
|
||||
tasks = append(tasks, PlanTask{Title: title})
|
||||
}
|
||||
}
|
||||
return tasks
|
||||
}
|
||||
|
||||
func planFromIssueTaskTitle(line string) (string, bool) {
|
||||
for _, prefix := range []string{"- [ ] ", "- [x] ", "- [X] ", "* [ ] ", "* [x] ", "* [X] "} {
|
||||
if core.HasPrefix(line, prefix) {
|
||||
title := core.Trim(core.TrimPrefix(line, prefix))
|
||||
if title != "" {
|
||||
return title, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// plan-from-issue fixtures should use issue slugs like `fix-auth`.
|
||||
func planFromIssueSlug(slug string) string {
|
||||
if slug == "" {
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ func TestPlanFromIssue_PlanFromIssue_Good(t *testing.T) {
|
|||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
require.Equal(t, "/v1/issues/fix-auth", r.URL.Path)
|
||||
require.Equal(t, "Bearer secret-token", r.Header.Get("Authorization"))
|
||||
_, _ = w.Write([]byte(`{"data":{"issue":{"id":17,"slug":"fix-auth","title":"Fix auth middleware","description":"Stop anonymous access to the admin route","type":"bug","status":"open","priority":"high","labels":["security","backend"],"metadata":{"source":"forge"}}}}`))
|
||||
_, _ = w.Write([]byte(`{"data":{"issue":{"id":17,"slug":"fix-auth","title":"Fix auth middleware","description":"Stop anonymous access to the admin route\n\n## Checklist\n- [ ] Keep CLI output stable","type":"bug","status":"open","priority":"high","labels":["security","backend"],"metadata":{"source":"forge"}}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
|
|
@ -38,7 +38,7 @@ func TestPlanFromIssue_PlanFromIssue_Good(t *testing.T) {
|
|||
assert.True(t, output.Success)
|
||||
assert.Equal(t, "Fix auth middleware", output.Issue.Title)
|
||||
assert.Equal(t, "issue-fix-auth", output.Plan.Slug)
|
||||
assert.Equal(t, "Stop anonymous access to the admin route", output.Plan.Objective)
|
||||
assert.Equal(t, "Stop anonymous access to the admin route\n\n## Checklist\n- [ ] Keep CLI output stable", output.Plan.Objective)
|
||||
assert.NotEmpty(t, output.Path)
|
||||
assert.True(t, fs.Exists(output.Path))
|
||||
|
||||
|
|
@ -46,6 +46,9 @@ func TestPlanFromIssue_PlanFromIssue_Good(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
assert.Equal(t, output.Plan.Slug, plan.Slug)
|
||||
assert.Equal(t, output.Issue.Slug, plan.Context["source_issue_slug"])
|
||||
require.Len(t, plan.Phases, 1)
|
||||
require.Len(t, plan.Phases[0].Tasks, 1)
|
||||
assert.Equal(t, "Keep CLI output stable", plan.Phases[0].Tasks[0].Title)
|
||||
}
|
||||
|
||||
func TestPlanFromIssue_PlanFromIssue_Bad_MissingIdentifier(t *testing.T) {
|
||||
|
|
@ -83,6 +86,30 @@ func TestPlanFromIssue_PlanFromIssue_Ugly_FallsBackToTitleObjective(t *testing.T
|
|||
assert.Equal(t, "Refine logging", output.Plan.Title)
|
||||
}
|
||||
|
||||
func TestPlanFromIssue_PlanFromIssue_Good_NoChecklistKeepsTasksEmpty(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
t.Setenv("CORE_AGENT_API_KEY", "secret-token")
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(`{"data":{"issue":{"id":31,"slug":"investigate-latency","title":"Investigate latency","description":"The dashboard is slow. Please investigate."}}}`))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
s := newTestPrep(t)
|
||||
s.brainURL = server.URL
|
||||
|
||||
result := s.handlePlanFromIssue(context.Background(), core.NewOptions(
|
||||
core.Option{Key: "slug", Value: "investigate-latency"},
|
||||
))
|
||||
require.True(t, result.OK)
|
||||
|
||||
output, ok := result.Value.(PlanFromIssueOutput)
|
||||
require.True(t, ok)
|
||||
require.Len(t, output.Plan.Phases, 1)
|
||||
assert.Empty(t, output.Plan.Phases[0].Tasks)
|
||||
}
|
||||
|
||||
func TestPlanFromIssue_CmdPlanFromIssue_Good(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
t.Setenv("CORE_WORKSPACE", dir)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue