inspect: add output destination (local or post to trail finding) · Entire
inspect: add output destination (local or post to trail finding)
3e20abd→main·
dipree·1mo ago·7 files·+284 added/-12 removed
Profiles can now choose where the final verdict is delivered:
- local (default): printed + saved to the local review manifest.
- trail: additionally posted to the branch's trail as a finding via the data API (entire trail finding).
Wiring:
- settings: new ReviewProfileConfig.Output ("output", omitempty).
- review: ReviewOutputLocal/Trail consts, profileOutput resolver, normalizeReviewOutput validation.
- guided setup: promptForOutputMode step after the judge pick.
- scripted: --set-output local|trail (validated).
- --list / discovery catalog show the destination.
- runtime: runReview resolves the mode and both run paths call maybePostReviewToTrail after writing the local manifest; for trail it invokes the injected Deps.PostReviewToTrail hook, implemented in review_bridge.go via resolveTrailReviewTarget + createTrailReviewFinding. Single-inspector runs (no synthesized verdict) post the combined inspector narratives. Posting never fails the run; a nil hook or error degrades to a notice.
Tests for profileOutput + scripted --set-output (valid/invalid/local). Docs updated.
Sessions
54a857d99cf5View transcript
Changes
7
- cmd/entire/cli
- review
- Mcmd.go+90/-7
- Mconfigure_test.go+71
- Mpicker.go+34
- Mprofile.go+30
- Mreview_bridge.go+42
- settings
- Msettings.go+5
- review
- docs/architecture
- Mreview-command.md+12/-5
60 unmodified lines
61
62
63
64
65
66
67
68
69
70
71
72
73
13 unmodified lines
87
88
89
90
91
92
93
20 unmodified lines
114
115
116
117
118
119
120
64 unmodified lines
185
186
187
188
189
190
191
12 unmodified lines
204
205
206
207
208
209
210
18 unmodified lines
229
230
231
232
233
234
235
236
237
238
227
239
240
241
242
160 unmodified lines
403
404
405
406
407
408
409
103 unmodified lines
513
514
515
516
517
518
519
520
521
107 unmodified lines
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
124 unmodified lines
772
773
774
775
776
777
778
6 unmodified lines
785
786
787
758
788
789
790
791
12 unmodified lines
804
805
806
777
807
808
809
810
14 unmodified lines
825
826
827
798
828
829
830
831
60 unmodified lines
892
893
894
865
895
896
897
898
100 unmodified lines
999
1000
1001
1002
1003
1004
1005
40 unmodified lines
1046
1047
1048
1018
1019
1049
1050
1051
1052
1053
1 unmodified line
1055
1056
1057
1058
1059
1060
1061
102 unmodified lines
1164
1165
1166
1167
1168
1169
1170
84 unmodified lines
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
60 unmodified lines
// per-agent reviewer packages import review (for ComposeReviewPrompt /
// AppendReviewEnv), so review/cmd.go cannot import them back.
ReviewerFor func(agentName string) reviewtypes.AgentReviewer
// PostReviewToTrail posts the final review verdict to the current branch's
// trail as a finding (the "trail" output destination). Injected from the cli
// package because the data API + auth live there. It prints its own success
// line to out. nil when trail delivery is unavailable (e.g. tests), in which
// case the run falls back to local output with a notice.
PostReviewToTrail func(ctx context.Context, out io.Writer, profileName, verdict string) error
}
// NewCommand returns the `entire review` cobra command wired with the
var listProfiles bool
var setAgents []string
var setJudge string
var setOutput string
var setTask string
var setModels []string
var setSlots []string
return runReviewConfigure(ctx, cmd, profileName, reviewConfigureOptions{
Agents: setAgents,
Judge: setJudge,
Output: setOutput,
Task: setTask,
Models: setModels,
Slots: setSlots,
})
cmd.Flags().BoolVar(&configure, "configure", false, "set up a review profile; shows available agents and accepts --set-* flags for non-interactive config")
cmd.Flags().StringSliceVar(&setAgents, "set-agents", nil, "with --configure: inspector agents for the profile (comma-separated)")
cmd.Flags().StringVar(&setJudge, "set-judge", "", "with --configure: the consolidating judge as agent[=model]")
cmd.Flags().StringVar(&setOutput, "set-output", "", "with --configure: where the verdict is delivered (local or trail)")
cmd.Flags().StringVar(&setTask, "set-task", "", "with --configure: the profile's canonical task text")
cmd.Flags().StringArrayVar(&setModels, "set-model", nil, "with --configure: per-inspector model as agent=model (repeatable)")
cmd.Flags().StringArrayVar(&setSlots, "set-slot", nil, "with --configure: an inspector slot as agent[=model] (repeatable; same agent/model may repeat)")
func (o reviewConfigureOptions) scripted() bool {
return len(o.Agents) > 0 || o.Judge != "" || o.Output != "" || o.Task != "" || len(o.Models) > 0 || len(o.Slots) > 0
}
func runReviewConfigure(ctx context.Context, cmd *cobra.Command, profileOverride string, opts reviewConfigureOptions, deps Deps) error {
if j, ok := profileJudge(p); ok {
fmt.Fprintf(out, " judge: %%s\n", judgeLabel(j))
}
fmt.Fprintf(out, " output: %%s\n", profileOutput(p))
}
func maybePostReviewToTrail(
ctx context.Context,
out io.Writer,
deps Deps,
outputMode, profileName string,
summary reviewtypes.RunSummary,
aggregateOutput string,
) {
if outputMode != ReviewOutputTrail || summary.Cancelled {
return
}
verdict := strings.TrimSpace(aggregateOutput)
if verdict == "" {
verdict = combinedReviewNarratives(summary)
}
if verdict == "" {
fmt.Fprintln(out, "Nothing to post to the trail (no review output produced).")
return
}
if deps.PostReviewToTrail == nil {
fmt.Fprintln(out, "Trail output is not available here; keeping the review local.")
return
}
if err := deps.PostReviewToTrail(ctx, out, profileName, verdict); err != nil {
fmt.Fprintf(out, "Could not post the review to the trail: %%v\n", err)
}
}
// combinedReviewNarratives joins the inspectors' narratives into one document,
// used as the trail-posting body for single-inspector runs (which have no
// synthesized verdict) and as a fallback when synthesis produced nothing.
func combinedReviewNarratives(summary reviewtypes.RunSummary) string {
var b strings.Builder
for _, run := range usableAgentRuns(summary) {
narrative := joinAssistantText(run.Buffer)
if narrative == "" {
continue
}
if b.Len() > 0 {
b.WriteString("\n\n")
}
fmt.Fprintf(&b, "## %%s\n\n%%s", run.Name, narrative)
}
return strings.TrimSpace(b.String())
}
Mcmd/entire/cli/review/cmd.go+90/-7
188 unmodified lines
289-272 محد/entire/cli/review/cmd.go
}