Name the contracts the reviewer cannot read, before it guesses at them - #99
Conversation
CLI#95 and TUI#92 (2026-09-08) both moved kai-engine forward and switched
four call sites onto kaipath.UserPath. Neither review could read that
function — 17 lines, in another module — and both spent most of their
output on it anyway: whether it is variadic, whether zero trailing
components are supported, whether an empty override preserves the default,
whether the one-argument call is valid. TUI#92 escalated the arity
question to "That's a real defect, not a...".
Every one of those was speculation about a file nobody could open,
rendered indistinguishably from the findings around it.
The reviewer was not being careless. It is told to ground every claim, and
it was handed a diff whose correctness genuinely rests on a contract it
had no way to see. What it lacked was permission to say so once and move
on.
## Why this does not fetch the source
The review pod is debian:bookworm-slim carrying `kai` and `kit`. There is
no Go toolchain and no module cache, and the workflow never runs
`go mod download` — which for a private module would need GOPRIVATE and
auth besides. Sibling module source is not merely unfetched, it is absent.
So this names the gap instead of pretending to close it. The block lists
each module the diff moves, the version change, the commit inside a
pseudo-version, and which of its packages this diff imports — then says
that one scoped sentence is the whole correct response:
An unread contract is a LIMITATION, not a defect. Say it ONCE [...]
Do not open a concern per call site, do not ask the author to confirm
a signature you could not read, and do not call it a defect: you have
no evidence either way.
The instruction is the load-bearing half. The list alone would just tell
the reviewer what it already knew it could not see.
## Groundwork, not a detour
The parsing is the parsing a real fetch needs: module path, version, and
the commit inside the pseudo-version. TUI#92's diff carried
v0.6.59-0.20260908192613-5ab1b102fc2f — the exact source that would have
answered every one of its questions, identified in the input it was given.
When there is a way to read it, that commit is already resolved.
Reads go.mod only; go.sum restates every version and would double each
entry. A module that appears only on a "-" line was removed, and a removed
dependency is not a contract the change rests on.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
jschatz1 has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Kai review
Kai Summary
Nothing I'd call a defect, but 1 decision to say yes to. 👇
Scope: reviewing kaicontext/kai-cli at the merged commit (19c2db1). The change is fully contained in two new files (cmd/kai/review_commit_deps.go, cmd/kai/review_commit_deps_test.go) and one one-line call site at cmd/kai/review_commit.go:678. Nothing outside this repo is read; the Go pseudo-version format that rcPseudoCommit parses is an external fact I reasoned over but did not fetch.
What it does, and my take: This adds a pre-review prompt step that parses go.mod diff hunks, extracts which module versions moved (and the 12-hex commit inside any pseudo-version), lists the packages the diff imports from those modules, and emits a prompt block telling the review agent that an unread sibling-module contract is a limitation, not a defect — say it once and move on. The motivation is concrete and stated: two prior reviews (CLI#95, TUI#92) burned output speculating about kaipath.UserPath, a 17-line function in another module they couldn't open. The code is careful about the edge cases I can construct, and the tests genuinely fail on the unfixed code. This is a sound prompt-engineering change with no defects.
No real concerns. I walked the recurring failure shapes against this diff:
- Index/nil safety:
line[6:]is guarded byHasPrefix(..., "+++ b/")/"--- a/"(both 6 chars).line[0]access is guarded by thelen(line) == 0check.moved[mod]is initialized before assignment. Safe. - File tracking:
inGoModresets on every+++ b/,--- a/, anddiff --gitline.go.sumis correctly excluded — thediff --gitline sets it false (suffixgo.sum≠go.mod), and the---/+++lines confirm it.TestChangedDepsReadsOnlyGoModwould fail if the guard were removed. - Removed/unchanged deps:
TestChangedDepsSkipsRemovedAndUnchangedcatches the bug — remove thev.to == "" || v.to == v.fromguard and bothold/gone(removal) andsame/pinned(no-op) enterout, failinglen(deps) == 0. This is a real test, not a discards-the-answer test. rcPseudoCommit: The 12-hex-char check andlen(parts) < 3guard cover plain semver, two-part pre-releases, and non-hex suffixes. A pathological pre-release likev1.2.3-rc.1-abcabcabcabcwould falsely yield a commit, but that isn't a valid Go module version in practice and the consequence (naming a commit in a limitation block) is benign.- Empty case:
rcDepLimitsBlock(nil)returns"", so the call site atreview_commit.go:678is a clean no-op for diffs touching no dependencies, andTestDepLimitsBlockSaysItOnceOrNotAtAllverifies this. The trailing\n\nproperly separates fromINTENT:. - Callers: Within kaicontext/kai-cli, the only non-test caller of
rcChangedDepsandrcDepLimitsBlockisreview_commit.go:678;rcImportedPkgsis called only fromreview_commit_deps.go:144. Sibling repos or binaries I cannot see don't appear to reach thesemain-package symbols, but I can only state that within this repo.
DECISIONS:
- The injected instruction — "do not call it a defect: you have no evidence either way" and "do not open a concern per call site" — is a review-policy choice that changes what every future dependency-bumping review reports. It affects every review whose diff moves a
go.moddependency: where the agent previously produced per-call-site speculation (sometimes escalated to a "real defect"), it will now emit one scoped LIMITATION sentence and stop. This is the author's intent and the right call for the failure mode it targets, but it also suppresses the rare case where a contract violation is detectable from the diff alone (e.g., a call site passing the wrong argument count to a function whose signature changed between two pseudo-version commits both visible in the diff). Scoping the instruction to "an unread contract" rather than "any dependency change" mitigates this, and the agent is still told to "review what you can actually see." The author should own that this tradeoff is now live for every review run.
Merge it. The code is correct, the tests verify the behavior they claim to, and the one policy decision above is the author's own and clearly intended.
Important files changed
| File | Change |
|---|---|
cmd/kai/review_commit.go |
modified · +4 −0 |
cmd/kai/review_commit_deps.go |
modified · +232 −0 |
cmd/kai/review_commit_deps_test.go |
modified · +121 −0 |
What I opened — 4 files, 12 turns, 9m26s
cmd/kaicmd/kai/review_commit.gocmd/kai/review_commit_deps.gocmd/kai/review_commit_deps_test.go
Decisions
Correct as written, but somebody should say yes to these:
- The injected instruction ("do not call it a defect… do not open a concern per call site") is a review-policy choice that changes what every dependency-bumping review reports going forward — it suppresses per-call-site speculation about unread contracts, which is the intent, but it also suppresses the rare case where a contract violation is detectable from the diff alone; the author should own thi…
+357 −0 · 3 files · reaches 11 · the full analysis
💬 Reply to any of my comments and I'll answer, or say @kaicontext anywhere on this PR — a question, or "take another look at the retry logic".
First slice of the retrieval work. It turned out the premise needed correcting, so this closes the gap it can actually close and lays the parsing for the rest.
The case
CLI #95 and TUI #92 (2026-09-08) both moved kai-engine forward and switched four call sites onto
kaipath.UserPath. Neither review could read that function — 17 lines, in another module — and both spent most of their output on it anyway: whether it is variadic, whether zero trailing components are supported, whether an empty override preserves the default, whether the one-argument call is valid. TUI #92 escalated the arity question to "That's a real defect, not a…".Every one of those was speculation about a file nobody could open, rendered indistinguishably from the findings around it.
The reviewer was not being careless. It is told to ground every claim, and it was handed a diff whose correctness genuinely rests on a contract it had no way to see. What it lacked was permission to say so once and move on.
Why this does not fetch the source
I checked, and the original plan was wrong about this. The review pod is
debian:bookworm-slimcarryingkaiandkit— no Go toolchain, no module cache — and the workflow never runsgo mod download, which for a private module would needGOPRIVATEand auth besides. Sibling module source is not merely unfetched, it is absent. A "just read the pinned file" change would have had nothing to read.So this names the gap instead of pretending to close it:
The instruction is the load-bearing half. The list alone would only tell the reviewer what it already knew it could not see.
Groundwork, not a detour
The parsing is exactly what a real fetch needs: module path, version, and the commit inside a pseudo-version. TUI #92's diff carried
v0.6.59-0.20260908192613-5ab1b102fc2f— the precise source that would have answered every one of its questions, sitting in the input it was handed. When there is a way to read it, that commit is already resolved.Scope
Reads
go.modonly —go.sumrestates every version and would double each entry. A module appearing only on a-line was removed, and a removed dependency is not a contract the change rests on. Modules the diff actually imports sort first, so a transitive bump never buries the one the code reaches. Bounded at 6 modules and 5 packages each.Verified
Five tests built on the real TUI #92 diff: the module/version/commit/package extraction,
go.sumexclusion, removals and no-ops skipped, pseudo-version parsing including three shapes that must not yield a commit, and the block's content. Block assertions run against whitespace-collapsed text so they check what it says, not where it wraps.go test ./cmd/...green (exit 0).🤖 Generated with Claude Code