@@ -3,8 +3,15 @@ import { ApiClient, ApiError } from '../lib/api'
33import { alsoKnownAs , apiRoutes } from '../lib/help'
44import { createWipCommit , currentBranch , pushReviewBranch , repoFromCwd } from '../lib/laptop'
55import { formatTs , printJson , printTable , relativeAge , runAction , usdFromMillicents } from '../lib/output'
6+ import { resolveRepoFlag } from './config'
67import { watchSessionStreaming } from './session'
7- import type { CreateReviewRequest , Finding , Review , ReviewScope } from '../lib/types'
8+ import type {
9+ CodeReviewDefaultView ,
10+ CreateReviewRequest ,
11+ Finding ,
12+ Review ,
13+ ReviewScope ,
14+ } from '../lib/types'
815
916// `agent review`: ask for a code review now, instead of waiting for a push to
1017// trigger one. Two targets —
@@ -168,6 +175,158 @@ export function registerReview(program: Command): void {
168175 )
169176 } )
170177 } )
178+
179+ registerReviewDefaults ( review )
180+ }
181+
182+ // `agent review default`: which code review pipeline runs when an explicit
183+ // review names none — a two-rung ladder (account default + per-repo defaults,
184+ // repo wins) mirroring `agent config default`, with the same --repo
185+ // semantics. Only explicit reviews read it: webhook reviews keep matching the
186+ // pipelines' own `pull_requests:` filters.
187+ function registerReviewDefaults ( review : Command ) : void {
188+ const defaults = apiRoutes (
189+ alsoKnownAs (
190+ review
191+ . command ( 'default' )
192+ . description ( 'Show or set which code review pipeline runs when a review names none' ) ,
193+ 'defaults' ,
194+ ) ,
195+ 'GET /v1/reviews/defaults' ,
196+ )
197+ . option ( '--json' , 'output raw JSON' )
198+ // Bare `agent review default`: the effective default for the repo you're
199+ // standing in, computed locally from GET /v1/reviews/defaults + the origin
200+ // remote (the same ladder the server resolves at review start).
201+ . action ( async ( opts : { json ?: boolean } ) => {
202+ await runAction ( async ( ) => {
203+ const rungs = await new ApiClient ( ) . listReviewDefaults ( )
204+ const repo = repoFromCwd ( process . cwd ( ) )
205+ const repoRung = repo
206+ ? rungs . find ( ( d ) => d . repository ?. toLowerCase ( ) === repo . toLowerCase ( ) )
207+ : undefined
208+ const accountRung = rungs . find ( ( d ) => d . repository === null )
209+ const effective = repoRung ?? accountRung
210+ if ( opts . json ) {
211+ printJson ( { repository : repo ?? null , effective : effective ?? null } )
212+ return
213+ }
214+ if ( ! effective ) {
215+ console . log (
216+ repo
217+ ? `no default set for ${ repo } or the account (reviews run the synced pipeline, or the platform defaults)`
218+ : 'no account default set (reviews run the synced pipeline, or the platform defaults)' ,
219+ )
220+ return
221+ }
222+ const rung = effective . repository
223+ ? `repo default for ${ effective . repository } `
224+ : 'account default'
225+ console . log (
226+ `using pipeline "${ defaultName ( effective ) } " (${ rung } )${ brokenSuffix ( effective ) } ` ,
227+ )
228+ } )
229+ } )
230+
231+ apiRoutes (
232+ alsoKnownAs (
233+ defaults
234+ . command ( 'list' )
235+ . description ( 'List every default that is set, account rung and per-repo rungs' ) ,
236+ 'ls' ,
237+ ) ,
238+ 'GET /v1/reviews/defaults' ,
239+ )
240+ . option ( '--json' , 'output raw JSON' )
241+ // The group also defines --json (for the bare view), and commander parses
242+ // parent options even when they follow the subcommand name — so read the
243+ // merged view, not just this command's own opts.
244+ . action ( async ( _opts : { json ?: boolean } , cmd : Command ) => {
245+ await runAction ( async ( ) => {
246+ const rungs = await new ApiClient ( ) . listReviewDefaults ( )
247+ if ( cmd . optsWithGlobals ( ) . json ) {
248+ printJson ( rungs )
249+ return
250+ }
251+ if ( rungs . length === 0 ) {
252+ console . log (
253+ 'No defaults set. Reviews run the synced pipeline, or the platform defaults.' ,
254+ )
255+ return
256+ }
257+ printTable (
258+ [ 'RUNG' , 'PIPELINE' , 'CONFIG ID' , 'STATUS' , 'UPDATED' ] ,
259+ rungs . map ( ( d ) => [
260+ d . repository ?? 'account' ,
261+ d . config_name ?? '—' ,
262+ d . config_id ,
263+ d . broken ? `broken: ${ d . broken } ` : 'ok' ,
264+ formatTs ( d . updated_at ) ,
265+ ] ) ,
266+ )
267+ } )
268+ } )
269+
270+ apiRoutes (
271+ defaults
272+ . command ( 'set <config-id>' )
273+ . description ( 'Set the account default code review pipeline, or a repo default with --repo' ) ,
274+ 'PUT /v1/reviews/defaults' ,
275+ )
276+ . option (
277+ '-r, --repo [repository]' ,
278+ 'target a repo rung: "owner/name", or no value for the repo you are standing in' ,
279+ )
280+ . option ( '--json' , 'output raw JSON' )
281+ . action (
282+ async ( configId : string , opts : { repo ?: string | boolean ; json ?: boolean } , cmd : Command ) => {
283+ await runAction ( async ( ) => {
284+ const repository = resolveRepoFlag ( opts . repo )
285+ const set = await new ApiClient ( ) . putReviewDefault ( {
286+ config_id : configId ,
287+ ...( repository ? { repository } : { } ) ,
288+ } )
289+ if ( cmd . optsWithGlobals ( ) . json ) {
290+ printJson ( set )
291+ return
292+ }
293+ const rung = set . repository ? `default for ${ set . repository } ` : 'account default'
294+ console . log ( `✓ set ${ rung } to "${ defaultName ( set ) } " (${ set . config_id } )` )
295+ } )
296+ } ,
297+ )
298+
299+ apiRoutes (
300+ alsoKnownAs (
301+ defaults
302+ . command ( 'clear' )
303+ . description ( 'Clear the account default code review pipeline, or a repo default with --repo' ) ,
304+ 'rm' ,
305+ 'delete' ,
306+ ) ,
307+ 'DELETE /v1/reviews/defaults' ,
308+ )
309+ . option (
310+ '-r, --repo [repository]' ,
311+ 'target a repo rung: "owner/name", or no value for the repo you are standing in' ,
312+ )
313+ . action ( async ( opts : { repo ?: string | boolean } ) => {
314+ await runAction ( async ( ) => {
315+ const repository = resolveRepoFlag ( opts . repo )
316+ await new ApiClient ( ) . deleteReviewDefault ( repository )
317+ console . log ( `✓ cleared ${ repository ? `default for ${ repository } ` : 'account default' } ` )
318+ } )
319+ } )
320+ }
321+
322+ function defaultName ( d : CodeReviewDefaultView ) : string {
323+ return d . config_name ?? d . config_id
324+ }
325+
326+ // A set-but-broken rung fails explicit reviews closed (never a silent run of
327+ // a different pipeline), so surface it wherever the rung is shown.
328+ function brokenSuffix ( d : CodeReviewDefaultView ) : string {
329+ return d . broken ? ` (broken: ${ d . broken } )` : ''
171330}
172331
173332interface StartOptions {
0 commit comments