rdev/cmd/sdlc/cmd_task.go
jordan 425ef0f806 feat: add SDLC orchestration - library, CLI, and API integration
Implements deterministic feature lifecycle management for agent-driven
development. Agents use the CLI in pods; operators control via REST API.

Library (internal/sdlc/):
- Feature lifecycle with 10 phases (draft → released)
- Classifier engine with priority-ordered rules
- Artifact tracking with approval workflow
- Task management within features
- YAML-based state persistence

CLI (cmd/sdlc/):
- init, state, next, feature, artifact, task, query commands
- --json flag for machine-readable output
- Runs inside project pods

API (21 endpoints under /projects/{id}/sdlc/):
- State: GET /state, GET /next
- Features: CRUD + transition/block/unblock
- Artifacts: approve/reject per type
- Tasks: add/start/complete/block
- Queries: blocked/ready/needs-approval

Architecture:
- Port: SDLCExecutor interface (internal/port/)
- Adapter: kubectl exec into pods (internal/adapter/kubernetes/)
- Service: pod resolution + logging (internal/service/)
- Handlers: 5 files under 500-line limit (internal/handlers/)

Also includes template upgrades (chassis framework, UI components,
OpenAPI helpers, backend/frontend guides) and component improvements.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 09:57:05 -07:00

208 lines
4.1 KiB
Go

package main
import (
"fmt"
"github.com/orchard9/rdev/internal/sdlc"
"github.com/spf13/cobra"
)
var taskCmd = &cobra.Command{
Use: "task",
Short: "Manage feature tasks",
}
var taskListCmd = &cobra.Command{
Use: "list <feature>",
Short: "List tasks for a feature",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
f, err := sdlc.LoadFeature(root, args[0])
if err != nil {
return err
}
if jsonOutput {
printJSON(f.Tasks)
return nil
}
if len(f.Tasks) == 0 {
fmt.Println("No tasks defined.")
fmt.Printf("Add one: sdlc task add %s \"Task title\"\n", args[0])
return nil
}
summary := sdlc.SummarizeTasks(f.Tasks)
fmt.Printf("Tasks for %s (%d/%d complete):\n", f.Slug, summary.Completed, summary.Total)
for _, t := range f.Tasks {
icon := "○"
switch t.Status {
case sdlc.TaskComplete:
icon = "✓"
case sdlc.TaskInProgress:
icon = "→"
case sdlc.TaskBlocked:
icon = "✗"
}
fmt.Printf(" %s %s: %s [%s]\n", icon, t.ID, t.Title, t.Status)
}
return nil
},
}
var taskStartCmd = &cobra.Command{
Use: "start <feature> <task-id>",
Short: "Mark a task as in-progress",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, taskID := args[0], args[1]
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
f.Tasks, err = sdlc.StartTask(f.Tasks, taskID)
if err != nil {
return err
}
f.UpdateTaskSummary()
if err := f.Save(root); err != nil {
return err
}
if jsonOutput {
printJSON(map[string]string{"feature": slug, "task": taskID, "status": "in_progress"})
return nil
}
fmt.Printf("Started: %s/%s\n", slug, taskID)
return nil
},
}
var taskCompleteCmd = &cobra.Command{
Use: "complete <feature> <task-id>",
Short: "Mark a task as complete",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, taskID := args[0], args[1]
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
f.Tasks, err = sdlc.CompleteTask(f.Tasks, taskID)
if err != nil {
return err
}
f.UpdateTaskSummary()
if err := f.Save(root); err != nil {
return err
}
// Record in state
state, err := sdlc.LoadState(root)
if err != nil {
return err
}
state.RecordAction("COMPLETE_TASK", slug, "cli")
if err := state.Save(root); err != nil {
return err
}
if jsonOutput {
printJSON(map[string]string{"feature": slug, "task": taskID, "status": "complete"})
return nil
}
s := sdlc.SummarizeTasks(f.Tasks)
fmt.Printf("Completed: %s/%s (%d/%d tasks done)\n", slug, taskID, s.Completed, s.Total)
return nil
},
}
var taskBlockCmd = &cobra.Command{
Use: "block <feature> <task-id>",
Short: "Mark a task as blocked",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, taskID := args[0], args[1]
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
f.Tasks, err = sdlc.BlockTask(f.Tasks, taskID)
if err != nil {
return err
}
f.UpdateTaskSummary()
if err := f.Save(root); err != nil {
return err
}
if jsonOutput {
printJSON(map[string]string{"feature": slug, "task": taskID, "status": "blocked"})
return nil
}
fmt.Printf("Blocked: %s/%s\n", slug, taskID)
return nil
},
}
var taskAddCmd = &cobra.Command{
Use: "add <feature> <title>",
Short: "Add a new task",
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
root := mustResolveRoot()
slug, title := args[0], args[1]
f, err := sdlc.LoadFeature(root, slug)
if err != nil {
return err
}
f.Tasks = sdlc.AddTask(f.Tasks, title)
f.UpdateTaskSummary()
if err := f.Save(root); err != nil {
return err
}
newTask := f.Tasks[len(f.Tasks)-1]
if jsonOutput {
printJSON(newTask)
return nil
}
fmt.Printf("Added: %s/%s - %s\n", slug, newTask.ID, title)
return nil
},
}
func init() {
taskCmd.AddCommand(
taskListCmd,
taskStartCmd,
taskCompleteCmd,
taskBlockCmd,
taskAddCmd,
)
rootCmd.AddCommand(taskCmd)
}