The coverage manifest counts files, not directories - #100
Conversation
"What I opened — 6 files" over a list beginning `frontend` and
`frontend/dist` (kai-desktop#314). "4 files" over one containing `cmd/kai`
(kai-cli#99). Two independent sightings within a day of the manifest
shipping, so this is systematic, not incidental.
A directory listing passes the same "path" argument a file read does, and
rcFilesRead took any of them. The manifest exists so a reader can tell a
clean verdict that read the whole change from one that read two files — a
coverage report that miscounts its own coverage is the one thing it cannot
afford.
The filesystem is the authority, so ask it: the review runs inside the
checkout, and the paths are right there.
Only a CONFIRMED directory is dropped. A path stat cannot resolve — an
absolute path from another tree, a bare name — is kept, because the cost
is asymmetric. An extra entry slightly overstates what was read. A wrongly
dropped one makes changedFilesNotListed accuse the review of skipping a
file it actually opened, which is the same class of lie this whole line of
work exists to remove, pointed the other way.
TestFilesReadDropsDirectories fails on the parent with exactly the shape
seen in production:
rcFilesRead = [/elsewhere/vendored.go frontend frontend/dist
frontend/dist/app.js], want [/elsewhere/vendored.go
frontend/dist/app.js]
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
Read the whole thing — it does what it says, and nothing jumped out at me. ✅
Reviewing kaicontext/kai-cli at commit 5b567e1 — the change touches cmd/kai/review_commit.go and its test file only; I could read the whole blast radius inside this repo.
What it does. rcFilesRead (which builds the coverage manifest's "files read" list from the review run's tool calls) gains a root argument and a new helper rcIsDir. Every path collected from the transcript is statted against the reviewed checkout; a confirmed directory is dropped from the manifest, while anything stat cannot resolve (absolute path from another tree, bare name, missing file) is kept. The single production caller (rcRunReviewAgent at line 764) passes primary.Path — the workspace root the agent already ran in — and the two test call sites are updated to the new signature.
Overall take. This is a clean, well-scoped fix to a real systematic misreport, with a test that genuinely exercises the failure shape. The signature change's blast radius is fully covered (three callers, all updated). No defects found.
A note on the one thing worth being precise about, which I checked and is fine: ordering of seen[p] = true (line 877) before the directory check (line 878). A directory path is marked seen and then not appended, so a second tool call naming the same directory is skipped by the seen short-circuit rather than re-evaluated — but that's identical behavior (still dropped), so no entries are lost or duplicated. Correct.
On the test being a real regression test: the old code would have returned all four paths sorted (/elsewhere/vendored.go, frontend, frontend/dist, frontend/dist/app.js) — four entries where the new test demands two. The test would fail against the old behavior, so it actually catches the bug rather than decorating it. Good.
On the "uncertain case is kept" asymmetry: I traced it. rcIsDir returns false when root == "" and the path is relative (line 897), when stat errors for any reason (line 903), and only returns true on a confirmed fi.IsDir(). The kept-when-uncertain policy is implemented exactly as the comments claim, and the existing TestFilesReadDedupesAcrossToolCallSpellings (now passing "" as root) confirms the empty-root case keeps everything. This is the right call: an overcount is a slight overstatement, while a wrongful drop would make changedFilesNotListed accuse the review of skipping a file it opened — the inverse of the lie this feature exists to prevent.
os.Stat follows symlinks, so a symlink-to-directory is correctly dropped and a symlink-to-file is kept. No issue. The filepath.Join cleaning of relative paths is read-only stat with no write or traversal side effect; the path originates from the model's own (already-executed) tool call, so there's no new attack surface.
rcFilesRead runs after agent.Run returns (line 751 → 764), so it reads a fully-populated, no-longer-mutated transcript — no concurrency concern. primary.Path is the same value passed as agent.Options.Workspace (line 711), i.e. the checkout root the tools resolved paths against, so relative paths from tool calls resolve correctly under it.
This is solid and ready. The manifest published to PRs becomes more honest, not less; nothing here charges a customer, limits one, deletes, or changes access — so there's no decision to hand back.
Merge it — no defects, nothing here needs a human's yes.
Important files changed
| File | Change |
|---|---|
cmd/kai/review_commit.go |
modified · +37 −3 |
cmd/kai/review_commit_incomplete_test.go |
modified · +43 −1 |
What I opened — 1 file, 14 turns, 50s
1 of the 2 changed files doesn't appear below: cmd/kai/review_commit_incomplete_test.go.
cmd/kai/review_commit.go
+80 −4 · 2 files · reaches 10 · 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".
Two independent sightings within a day of the manifest shipping, so this is systematic rather than incidental.
A directory listing passes the same
pathargument a file read does, andrcFilesReadtook any of them. The manifest exists so a reader can tell a clean verdict that read the whole change from one that read two files — a coverage report that miscounts its own coverage is the one thing it cannot afford.The fix
The filesystem is the authority, so ask it. The review runs inside the checkout and the paths are right there, so
rcIsDirstats each one against the workspace root.Only confirmed directories are dropped
A path stat cannot resolve — an absolute path from another tree, a bare name, anything — is kept. The cost is asymmetric:
changedFilesNotListedsay "N of the M changed files don't appear below" about a file the review actually opened — the same class of lie this line of work exists to remove, pointed the other way.Verified
TestFilesReadDropsDirectoriesfails on the parent with exactly the shape seen in production:It covers all three cases in one fixture: two real directories dropped, the real file kept, and an unresolvable path kept. The existing
TestFilesReadDedupesAcrossToolCallSpellingspasses an empty root, where nothing resolves and every path is kept.go test ./cmd/...green (exit 0).Independent of #99 — different function, no stacking.
🤖 Generated with Claude Code