- Add auth.RequireScope() to all handler routes for proper authorization - Add SDLC OpenAPI endpoint documentation (state, features, tasks, branches, merge, archive, orchestrator) - Add SDLC documentation guides (getting-started, cli-reference, api-reference, command-catalog) - Add artifact_test.go for SDLC artifact coverage - Add CLAUDE.md rules: auth scopes requirement, error wrapping with %w - Fix error wrapping to use %w instead of %v throughout codebase - Improve CLI merge command with conflict detection and resolution - Fix handler tests to include auth middleware for RequireScope - Add cookbook tree runner scripts for automated testing Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
3.8 KiB
Go
200 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/orchard9/rdev/internal/sdlc"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var queryCmd = &cobra.Command{
|
|
Use: "query",
|
|
Short: "Query SDLC state",
|
|
}
|
|
|
|
var queryBlockedCmd = &cobra.Command{
|
|
Use: "blocked",
|
|
Short: "List all blocked items",
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
root := mustResolveRoot()
|
|
|
|
features, err := sdlc.ListFeatures(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type blockedInfo struct {
|
|
Slug string `json:"slug"`
|
|
Phase string `json:"phase"`
|
|
Blockers []string `json:"blockers"`
|
|
}
|
|
|
|
var blocked []blockedInfo
|
|
for _, f := range features {
|
|
if f.IsBlocked() {
|
|
blocked = append(blocked, blockedInfo{
|
|
Slug: f.Slug,
|
|
Phase: string(f.Phase),
|
|
Blockers: f.Blockers,
|
|
})
|
|
}
|
|
}
|
|
|
|
if jsonOutput {
|
|
return printJSON(blocked)
|
|
}
|
|
|
|
if len(blocked) == 0 {
|
|
fmt.Println("No blocked items.")
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Blocked Items:")
|
|
for _, b := range blocked {
|
|
fmt.Printf(" %s [%s]:\n", b.Slug, b.Phase)
|
|
for _, reason := range b.Blockers {
|
|
fmt.Printf(" - %s\n", reason)
|
|
}
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var queryReadyCmd = &cobra.Command{
|
|
Use: "ready",
|
|
Short: "List items ready for work",
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
root := mustResolveRoot()
|
|
|
|
state, err := sdlc.LoadState(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg, err := sdlc.LoadConfig(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
features, err := sdlc.ListFeatures(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
classifier := sdlc.NewClassifier()
|
|
|
|
type readyInfo struct {
|
|
Slug string `json:"slug"`
|
|
Phase string `json:"phase"`
|
|
Action string `json:"action"`
|
|
}
|
|
|
|
var ready []readyInfo
|
|
for _, f := range features {
|
|
if f.IsBlocked() {
|
|
continue
|
|
}
|
|
cl := classifier.Classify(&sdlc.EvalContext{
|
|
State: state,
|
|
Feature: f,
|
|
Config: cfg,
|
|
Root: root,
|
|
})
|
|
if cl.Action != sdlc.ActionIdle && cl.Action != sdlc.ActionBlocked && cl.Action != sdlc.ActionAwaitApproval {
|
|
ready = append(ready, readyInfo{
|
|
Slug: f.Slug,
|
|
Phase: string(f.Phase),
|
|
Action: string(cl.Action),
|
|
})
|
|
}
|
|
}
|
|
|
|
if jsonOutput {
|
|
return printJSON(ready)
|
|
}
|
|
|
|
if len(ready) == 0 {
|
|
fmt.Println("No items ready for work.")
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Ready for Work:")
|
|
for _, r := range ready {
|
|
fmt.Printf(" %-20s [%-15s] -> %s\n", r.Slug, r.Phase, r.Action)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
var queryNeedsApprovalCmd = &cobra.Command{
|
|
Use: "needs-approval",
|
|
Short: "List items awaiting approval",
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
root := mustResolveRoot()
|
|
|
|
state, err := sdlc.LoadState(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cfg, err := sdlc.LoadConfig(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
features, err := sdlc.ListFeatures(root)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
classifier := sdlc.NewClassifier()
|
|
|
|
type approvalInfo struct {
|
|
Slug string `json:"slug"`
|
|
Phase string `json:"phase"`
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
var pending []approvalInfo
|
|
for _, f := range features {
|
|
cl := classifier.Classify(&sdlc.EvalContext{
|
|
State: state,
|
|
Feature: f,
|
|
Config: cfg,
|
|
Root: root,
|
|
})
|
|
if cl.Action == sdlc.ActionAwaitApproval {
|
|
pending = append(pending, approvalInfo{
|
|
Slug: f.Slug,
|
|
Phase: string(f.Phase),
|
|
Message: cl.Message,
|
|
})
|
|
}
|
|
}
|
|
|
|
if jsonOutput {
|
|
return printJSON(pending)
|
|
}
|
|
|
|
if len(pending) == 0 {
|
|
fmt.Println("No items awaiting approval.")
|
|
return nil
|
|
}
|
|
|
|
fmt.Println("Awaiting Approval:")
|
|
for _, p := range pending {
|
|
fmt.Printf(" %-20s [%-15s] %s\n", p.Slug, p.Phase, p.Message)
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
queryCmd.AddCommand(
|
|
queryBlockedCmd,
|
|
queryReadyCmd,
|
|
queryNeedsApprovalCmd,
|
|
)
|
|
rootCmd.AddCommand(queryCmd)
|
|
}
|