# inspect: accept runner-style structured trail findings

`956ae3b`→[main](/content/gh/entireio/cli/commits/main/index.html)·

dipree·4w ago·4 files·+209 added/-15 removed

Use the existing Trail PR Review runner output shape as the preferred trail posting format: if the judge output ends with a JSON object containing comments, convert those comments directly to native trail review findings, preserving severity, confidence, and line/range/file locations.

Keep the markdown fallback for custom or human-readable prompts, but enrich split bullets with inferred severity and file:line anchors when possible. Also nudge the judge prompt to put one issue per bullet with severity and file:line pointers so trail output is cleaner even before the fallback runs.

## Sessions

77524f2a0762View transcript

## Changes

4

- cmd/entire/cli

- review

- Msynthesis_prompt.go+1/-1

- Mreview_bridge.go+165/-5

- Mreview_bridge_test.go+39/-5

- Mtrail_review_cmd.go+4/-4

```
80 unmodified lines

Output exactly this, nothing else:
  - One line: the verdict (approve / approve with nits / request changes) and a one-sentence reason.
  - Then a short bullet list of actionable findings, most important first, one issue per bullet, one line each with a file/symbol pointer. Omit the list entirely when nothing is actionable.
  - Then a short bullet list of actionable findings, most important first, one issue per bullet, one line each. Start each bullet with [high], [medium], or [low], and include a file:line pointer when you can identify one. Omit the list entirely when nothing is actionable.

No preamble, no section headings, no restating the diff or task, no filler. Be proportional: a clean change is a single line.`)
```

Mcmd/entire/cli/review/synthesis_prompt.go+1/-1

```
8 unmodified lines

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strconv"
	"strings"

"github.com/entireio/cli/cmd/entire/cli/agent"
	5 unmodified lines

reviewtypes "github.com/entireio/cli/cmd/entire/cli/review/types"
)

const (
	reviewTrailGranularityWholeChange = "whole_change"
	reviewTrailGranularityFile        = "file"
	reviewTrailGranularityLine        = "line"
	reviewTrailGranularityRange       = "range"
)

// buildReviewDeps builds the review.Deps struct used by review.NewCommand.
func buildReviewDeps() cliReview.Deps {
	return cliReview.Deps{
	}
}

// reviewTrailFindingInputs turns a final review verdict into trail findings. If
// the verdict contains multiple top-level bullet findings, post them separately
// so a custom or weak judge prompt cannot create one mega-finding on the trail.
// reviewTrailFindingInputs turns a final review verdict into trail findings.
// It first accepts the runner-style last JSON line format
// {"summary":"","comments":[...]}; when absent, it falls back to splitting
// top-level markdown bullets. This keeps trail output structurally correct even
// when custom judge prompts produce prose.
func reviewTrailFindingInputs(profileName, verdict string) []api.TrailReviewCommentInput {
	if inputs, ok := reviewTrailFindingInputsFromJSON(verdict); ok {
		return inputs
	}
	items := splitReviewVerdictFindings(verdict)
	if len(items) <= 1 {
		return []api.TrailReviewCommentInput{reviewTrailFindingInput(profileName, verdict)}
	}
	inputs := make([]api.TrailReviewCommentInput, 0, len(items))
	for _, item := range items {
		inputs = append(inputs, reviewTrailFindingInputWithKind(profileName, item, "finding"))
		input := reviewTrailFindingInputWithKind(profileName, item, "finding")
		enrichReviewTrailFindingInputFromMarkdown(&input, item)
		inputs = append(inputs, input)
	}
	return inputs
}

func reviewTrailFindingInputWithKind(profileName, text, kind string) api.TrailReviewCommentInput {
	body := strings.TrimSpace(text)
	return api.TrailReviewCommentInput{
		ClientID: generateTrailReviewClientID(),
		Body:     stringPtr(body),
		Location: api.TrailReviewLocationCreateRequest{Granularity: reviewTrailGranularityWholeChange},
	}
}

// test functions here...

```
