Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 38 additions & 4 deletions cmd/kai/review_commit.go
Original file line number Diff line number Diff line change
Expand Up @@ -455,10 +455,11 @@ func runReviewCommit(cmd *cobra.Command, args []string) error {
// not finish" (kai-desktop#304, kai-server#186, 2026-09-08).
out, err := json.MarshalIndent(struct {
finding.Finding
Review string `json:"review,omitempty"`
Depth string `json:"depth,omitempty"`
Incomplete bool `json:"incomplete,omitempty"`
}{f, prose, depth, incomplete}, "", " ")
Review string `json:"review,omitempty"`
Depth string `json:"depth,omitempty"`
Incomplete bool `json:"incomplete,omitempty"`
Coverage *rcCoverage `json:"coverage,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new coverage JSON tag could silently collide with a same-named field on the embedded finding.Finding (engine module v0.6.56, not readable from this workspace); confirm the engine's Finding has no coverage tag or untagged Coverage field, since encoding/json would drop both rather than error.

}{f, prose, depth, incomplete, rcCoverageOf(inc)}, "", " ")
if err != nil {
return fmt.Errorf("marshaling finding: %w", err)
}
Expand Down Expand Up @@ -802,6 +803,39 @@ type rcIncomplete struct {
FilesRead []string
}

// rcCoverage is the machine-written record of what a review actually did:
// which files it opened, over how many turns, in how long.
//
// It exists because the reviewer's own "Scope:" paragraph is model-authored
// prose, and prose can be wrong about itself — reviews on kai-desktop#288 and
// #300 named the repository they were reading as kai-engine and kai-server.
// A reader cannot tell a confident clean verdict that read everything from a
// confident clean verdict that read two files, and neither can we. This is the
// half of the answer that cannot hallucinate.
//
// These facts were already gathered on every grounded run and thrown away
// unless the run died (see rcIncomplete). Now they always ship.
type rcCoverage struct {
FilesRead []string `json:"filesRead,omitempty"`
Turns int `json:"turns,omitempty"`
Seconds int `json:"seconds,omitempty"`
}

// rcCoverageOf converts the run's own account of itself into the bundle's
// coverage record. Nil in, nil out: the fast pass makes one call over the diff
// and opens nothing, so it has no manifest to publish and omitempty drops the
// field entirely rather than claiming it read zero files.
func rcCoverageOf(inc *rcIncomplete) *rcCoverage {
if inc == nil {
return nil
}
return &rcCoverage{
FilesRead: inc.FilesRead,
Turns: inc.Turns,
Seconds: int(inc.Elapsed.Round(time.Second).Seconds()),
}
}

// rcFilesRead pulls the distinct paths the run actually opened out of its tool
// calls. Deliberately cheap and schema-loose, like rcChangedSymbols: any tool
// that names a file names it in a "path" or "file_path" field, and a missed one
Expand Down
28 changes: 28 additions & 0 deletions cmd/kai/review_commit_incomplete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,31 @@ func TestIncompleteBundleCarriesTheFlag(t *testing.T) {
t.Errorf("a complete review's bundle mentions incomplete:\n%s", done)
}
}

// The run's account of itself was already gathered on every grounded review and

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestCoverageShipsOnEveryGroundedRun only unit-tests rcCoverageOf and never marshals the bundle struct literal at review_commit.go:456, so it would pass with the Coverage field removed entirely; the "ships on every grounded run" behavior is not actually tested.

// thrown away unless the review died. Now it always ships, so a reader can tell
// a clean verdict that read the whole change from a clean verdict that read two
// files.
func TestCoverageShipsOnEveryGroundedRun(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new test asserts rcCoverageOf's output but never marshals the production bundle struct, so removing the rcCoverageOf(inc) call from the marshal literal at review_commit.go:460 would leave every test green; add an end-to-end assertion that the emitted JSON contains "coverage".

got := rcCoverageOf(&rcIncomplete{
Elapsed: 4*time.Minute + 19*time.Second,
Turns: 27,
FilesRead: []string{"a.go", "b.go"},
})
if got == nil {
t.Fatal("rcCoverageOf dropped a run's manifest")
}
if got.Seconds != 259 {
t.Errorf("Seconds = %d, want 259", got.Seconds)
}
if got.Turns != 27 || len(got.FilesRead) != 2 {
t.Errorf("coverage = %+v, want the run's turns and files", got)
}

// The fast pass opens nothing and has no manifest to publish. Nil out
// keeps `omitempty` dropping the field, rather than publishing a record
// that claims zero files were read.
if rcCoverageOf(nil) != nil {
t.Error("rcCoverageOf(nil) invented a manifest for a pass that opened nothing")
}
}