11package cli
22
33import (
4+ "encoding/json"
45 "errors"
56 "fmt"
67 "io"
@@ -31,16 +32,26 @@ type reviewRun struct {
3132
3233// reviewRunResponse mirrors controllers.ReviewRunResponse.
3334type reviewRunResponse struct {
34- Review reviewRun `json:"review"`
35- ReviewerHandleID string `json:"reviewerHandleId"`
35+ Review reviewRun `json:"review"`
36+ Reviews []reviewRun `json:"reviews"`
37+ ReviewerHandleID string `json:"reviewerHandleId"`
3638}
3739
38- // submitReviewRequest mirrors controllers.SubmitReviewInput .
39- type submitReviewRequest struct {
40+ // submitReviewItem mirrors controllers.SubmitReviewItem .
41+ type submitReviewItem struct {
4042 RunID string `json:"runId"`
4143 Verdict string `json:"verdict"`
42- Body string `json:"body"`
43- GithubReviewID string `json:"githubReviewId"`
44+ Body string `json:"body,omitempty"`
45+ GithubReviewID string `json:"githubReviewId,omitempty"`
46+ }
47+
48+ // submitReviewRequest mirrors controllers.SubmitReviewInput.
49+ type submitReviewRequest struct {
50+ RunID string `json:"runId,omitempty"`
51+ Verdict string `json:"verdict,omitempty"`
52+ Body string `json:"body,omitempty"`
53+ GithubReviewID string `json:"githubReviewId,omitempty"`
54+ Reviews []submitReviewItem `json:"reviews,omitempty"`
4455}
4556
4657type reviewSubmitOptions struct {
@@ -49,6 +60,7 @@ type reviewSubmitOptions struct {
4960 verdict string
5061 body string
5162 reviewID string
63+ reviews string
5264}
5365
5466func newReviewCommand (ctx * commandContext ) * cobra.Command {
@@ -80,6 +92,7 @@ func newReviewSubmitCommand(ctx *commandContext) *cobra.Command {
8092 cmd .Flags ().StringVar (& opts .verdict , "verdict" , "" , "Review verdict: approved or changes_requested (required)" )
8193 cmd .Flags ().StringVar (& opts .body , "body" , "" , "Review body: a path to a Markdown file, or - to read from stdin (so nothing is written into the worktree)" )
8294 cmd .Flags ().StringVar (& opts .reviewID , "review-id" , "" , "Id of the GitHub PR review just posted (the .id from the gh api POST that created the review)" )
95+ cmd .Flags ().StringVar (& opts .reviews , "reviews" , "" , "JSON review results array or object: a path, or - to read from stdin" )
8396 return cmd
8497}
8598
@@ -91,6 +104,9 @@ func (c *commandContext) submitReview(cmd *cobra.Command, args []string, opts re
91104 if session == "" {
92105 return usageError {errors .New ("usage: worker session id is required (positional or --session)" )}
93106 }
107+ if strings .TrimSpace (opts .reviews ) != "" {
108+ return c .submitReviewBatch (cmd , session , opts )
109+ }
94110 runID := strings .TrimSpace (opts .runID )
95111 if runID == "" {
96112 return usageError {errors .New ("usage: --run is required" )}
@@ -124,3 +140,49 @@ func (c *commandContext) submitReview(cmd *cobra.Command, args []string, opts re
124140 _ , err := fmt .Fprintf (cmd .OutOrStdout (), "recorded %s review for %s\n " , res .Review .Verdict , session )
125141 return err
126142}
143+
144+ func (c * commandContext ) submitReviewBatch (cmd * cobra.Command , session string , opts reviewSubmitOptions ) error {
145+ if strings .TrimSpace (opts .runID ) != "" || strings .TrimSpace (opts .verdict ) != "" || strings .TrimSpace (opts .body ) != "" || strings .TrimSpace (opts .reviewID ) != "" {
146+ return usageError {errors .New ("usage: --reviews cannot be combined with --run, --verdict, --body, or --review-id" )}
147+ }
148+ reviews , err := readReviewItems (cmd , strings .TrimSpace (opts .reviews ))
149+ if err != nil {
150+ return err
151+ }
152+ path := "sessions/" + url .PathEscape (session ) + "/reviews/submit"
153+ var res reviewRunResponse
154+ if err := c .postJSON (cmd .Context (), path , submitReviewRequest {Reviews : reviews }, & res ); err != nil {
155+ return err
156+ }
157+ count := len (res .Reviews )
158+ if count == 0 {
159+ count = len (reviews )
160+ }
161+ _ , err = fmt .Fprintf (cmd .OutOrStdout (), "recorded %d review(s) for %s\n " , count , session )
162+ return err
163+ }
164+
165+ func readReviewItems (cmd * cobra.Command , path string ) ([]submitReviewItem , error ) {
166+ var raw []byte
167+ var err error
168+ if path == "-" {
169+ raw , err = io .ReadAll (cmd .InOrStdin ())
170+ } else {
171+ raw , err = os .ReadFile (path )
172+ }
173+ if err != nil {
174+ return nil , usageError {fmt .Errorf ("read review results: %w" , err )}
175+ }
176+ var req submitReviewRequest
177+ if err := json .Unmarshal (raw , & req ); err == nil && len (req .Reviews ) > 0 {
178+ return req .Reviews , nil
179+ }
180+ var reviews []submitReviewItem
181+ if err := json .Unmarshal (raw , & reviews ); err != nil {
182+ return nil , usageError {fmt .Errorf ("decode review results JSON: %w" , err )}
183+ }
184+ if len (reviews ) == 0 {
185+ return nil , usageError {errors .New ("usage: --reviews requires at least one review result" )}
186+ }
187+ return reviews , nil
188+ }
0 commit comments