Add council review start command
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package orch
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
func newCouncilCmd(root *rootOptions) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "council",
|
||||
Short: "Council review workflow commands",
|
||||
}
|
||||
|
||||
cmd.AddCommand(newCouncilStartCmd(root))
|
||||
return cmd
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package orch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"ai-workflow-skill/internal/protocol"
|
||||
"ai-workflow-skill/internal/store"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type councilStartOptions struct {
|
||||
runID string
|
||||
target string
|
||||
targetFile string
|
||||
repoPath string
|
||||
targetTaskID string
|
||||
targetType string
|
||||
mode string
|
||||
outputMode string
|
||||
onlyUnanimous bool
|
||||
}
|
||||
|
||||
func newCouncilStartCmd(root *rootOptions) *cobra.Command {
|
||||
opts := &councilStartOptions{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Create and dispatch a three-reviewer council run",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
ctx := cmd.Context()
|
||||
|
||||
sqlDB, err := openOrchDB(ctx, root.dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
result, err := store.NewOrchStore(sqlDB).StartCouncil(ctx, store.CouncilStartInput{
|
||||
RunID: opts.runID,
|
||||
Target: opts.target,
|
||||
TargetFile: opts.targetFile,
|
||||
RepoPath: opts.repoPath,
|
||||
TargetTaskID: opts.targetTaskID,
|
||||
TargetType: opts.targetType,
|
||||
Mode: opts.mode,
|
||||
OutputMode: opts.outputMode,
|
||||
OnlyUnanimous: opts.onlyUnanimous,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := protocol.Success{
|
||||
OK: true,
|
||||
Command: "council start",
|
||||
Data: map[string]any{
|
||||
"run_id": result.Run.RunID,
|
||||
"mode": result.Run.Mode,
|
||||
"target_type": result.Run.TargetType,
|
||||
"output": result.Run.OutputMode,
|
||||
"only_unanimous": result.Run.OnlyUnanimous,
|
||||
"target": result.Input,
|
||||
"reviewers": result.Reviewers,
|
||||
},
|
||||
}
|
||||
if root.json {
|
||||
return protocol.WriteJSON(cmd.OutOrStdout(), resp)
|
||||
}
|
||||
|
||||
_, err = fmt.Fprintf(cmd.OutOrStdout(), "started council run %s with %d reviewers\n", result.Run.RunID, len(result.Reviewers))
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&opts.runID, "run", "", "Run ID")
|
||||
cmd.Flags().StringVar(&opts.target, "target", "", "Inline target prompt")
|
||||
cmd.Flags().StringVar(&opts.targetFile, "target-file", "", "Optional target context file")
|
||||
cmd.Flags().StringVar(&opts.repoPath, "repo-path", "", "Optional repository path for review context")
|
||||
cmd.Flags().StringVar(&opts.targetTaskID, "task-id", "", "Optional related task ID")
|
||||
cmd.Flags().StringVar(&opts.targetType, "target-type", "mixed", "Target type: text, repo, or mixed")
|
||||
cmd.Flags().StringVar(&opts.mode, "mode", "brainstorm", "Council mode: brainstorm or review")
|
||||
cmd.Flags().StringVar(&opts.outputMode, "output", "both", "Output mode: markdown, json, or both")
|
||||
cmd.Flags().BoolVar(&opts.onlyUnanimous, "only-unanimous", false, "Show only unanimous recommendations in downstream report defaults")
|
||||
_ = cmd.MarkFlagRequired("run")
|
||||
|
||||
return cmd
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package orch
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -1396,3 +1397,146 @@ func TestOrchCleanupRemovesCompletedWorktree(t *testing.T) {
|
||||
t.Fatalf("expected cleaned worktree path to be removed, err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchCouncilStartDispatchesThreeReviewers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
dbPath := filepath.Join(t.TempDir(), "coord.db")
|
||||
|
||||
startOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"council", "start",
|
||||
"--run", "council_blog_001",
|
||||
"--target", "Review the current blog architecture and propose optimizations.",
|
||||
"--target-type", "mixed",
|
||||
"--output", "both",
|
||||
)
|
||||
|
||||
var startResp map[string]any
|
||||
mustDecodeJSON(t, startOut, &startResp)
|
||||
if got := nestedString(t, startResp, "data", "run_id"); got != "council_blog_001" {
|
||||
t.Fatalf("expected council run id, got %q", got)
|
||||
}
|
||||
if got := nestedString(t, startResp, "data", "mode"); got != "brainstorm" {
|
||||
t.Fatalf("expected default council mode brainstorm, got %q", got)
|
||||
}
|
||||
|
||||
reviewers := nestedArray(t, startResp, "data", "reviewers")
|
||||
if len(reviewers) != 3 {
|
||||
t.Fatalf("expected three council reviewers, got %#v", reviewers)
|
||||
}
|
||||
|
||||
sqlDB, err := openOrchDB(t.Context(), dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("open orch db: %v", err)
|
||||
}
|
||||
defer sqlDB.Close()
|
||||
|
||||
var (
|
||||
mode string
|
||||
targetType string
|
||||
outputMode string
|
||||
onlyUnanimous int
|
||||
)
|
||||
if err := sqlDB.QueryRowContext(
|
||||
t.Context(),
|
||||
`SELECT mode, target_type, output_mode, only_unanimous
|
||||
FROM council_runs
|
||||
WHERE run_id = ?`,
|
||||
"council_blog_001",
|
||||
).Scan(&mode, &targetType, &outputMode, &onlyUnanimous); err != nil {
|
||||
t.Fatalf("query council_runs: %v", err)
|
||||
}
|
||||
if mode != "brainstorm" || targetType != "mixed" || outputMode != "both" || onlyUnanimous != 0 {
|
||||
t.Fatalf("unexpected council run metadata: mode=%q targetType=%q outputMode=%q onlyUnanimous=%d", mode, targetType, outputMode, onlyUnanimous)
|
||||
}
|
||||
|
||||
var (
|
||||
prompt string
|
||||
targetFile string
|
||||
repoPath string
|
||||
targetTaskID string
|
||||
)
|
||||
if err := sqlDB.QueryRowContext(
|
||||
t.Context(),
|
||||
`SELECT prompt, target_file, repo_path, target_task_id
|
||||
FROM council_inputs
|
||||
WHERE run_id = ?`,
|
||||
"council_blog_001",
|
||||
).Scan(&prompt, &targetFile, &repoPath, &targetTaskID); err != nil {
|
||||
t.Fatalf("query council_inputs: %v", err)
|
||||
}
|
||||
if prompt == "" || targetFile != "" || repoPath != "" || targetTaskID != "" {
|
||||
t.Fatalf("unexpected council input row: prompt=%q targetFile=%q repoPath=%q targetTaskID=%q", prompt, targetFile, repoPath, targetTaskID)
|
||||
}
|
||||
|
||||
rows, err := sqlDB.QueryContext(
|
||||
t.Context(),
|
||||
`SELECT reviewer_role, task_id, status
|
||||
FROM council_reviewers
|
||||
WHERE run_id = ?
|
||||
ORDER BY reviewer_role ASC`,
|
||||
"council_blog_001",
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("query council_reviewers: %v", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var reviewerRows int
|
||||
for rows.Next() {
|
||||
var (
|
||||
reviewerRole string
|
||||
taskID string
|
||||
status string
|
||||
)
|
||||
if err := rows.Scan(&reviewerRole, &taskID, &status); err != nil {
|
||||
t.Fatalf("scan council reviewer row: %v", err)
|
||||
}
|
||||
reviewerRows++
|
||||
if status != "dispatched" {
|
||||
t.Fatalf("expected council reviewer status dispatched, got %q for %s", status, reviewerRole)
|
||||
}
|
||||
|
||||
var worktreePath sql.NullString
|
||||
if err := sqlDB.QueryRowContext(
|
||||
t.Context(),
|
||||
`SELECT a.worktree_path
|
||||
FROM task_attempts a
|
||||
WHERE a.run_id = ? AND a.task_id = ? AND a.attempt_no = 1`,
|
||||
"council_blog_001",
|
||||
taskID,
|
||||
).Scan(&worktreePath); err != nil {
|
||||
t.Fatalf("query council attempt worktree path for %s: %v", taskID, err)
|
||||
}
|
||||
if worktreePath.Valid && worktreePath.String != "" {
|
||||
t.Fatalf("expected council reviewer task %s to avoid worktree allocation, got %q", taskID, worktreePath.String)
|
||||
}
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
t.Fatalf("iterate council reviewers: %v", err)
|
||||
}
|
||||
if reviewerRows != 3 {
|
||||
t.Fatalf("expected three stored council reviewers, got %d", reviewerRows)
|
||||
}
|
||||
|
||||
statusOut := runOrchCommand(
|
||||
t,
|
||||
"--db", dbPath,
|
||||
"--json",
|
||||
"status",
|
||||
"--run", "council_blog_001",
|
||||
)
|
||||
|
||||
var statusResp map[string]any
|
||||
mustDecodeJSON(t, statusOut, &statusResp)
|
||||
if got := nestedString(t, statusResp, "data", "run", "status"); got != "running" {
|
||||
t.Fatalf("expected council run status running, got %q", got)
|
||||
}
|
||||
tasks := nestedArray(t, statusResp, "data", "tasks")
|
||||
if len(tasks) != 3 {
|
||||
t.Fatalf("expected three council tasks, got %#v", tasks)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ func NewRootCmd() *cobra.Command {
|
||||
cmd.AddCommand(newReassignCmd(opts))
|
||||
cmd.AddCommand(newCancelCmd(opts))
|
||||
cmd.AddCommand(newCleanupCmd(opts))
|
||||
cmd.AddCommand(newCouncilCmd(opts))
|
||||
cmd.AddCommand(newBlockedCmd(opts))
|
||||
cmd.AddCommand(newAnswerCmd(opts))
|
||||
cmd.AddCommand(newStatusCmd(opts))
|
||||
|
||||
Reference in New Issue
Block a user