Verified Diff (proof panel engine)
Every agent turn that edits files is followed by an automatic verification run. The report is facts first, model claims second: the engine never asks a model whether the change is correct.
Pipeline
scope ─► checks (sequential, one run at a time per app) ─► risk tier ─► facts vs. claims ─► reportScope kinds (VerifyScope.kind): session (pending changes of a chat, optionally a subset of
changeIds), change-set, staged, working-tree, branch (vs base, default main/master),
files. Each touched file carries before/after text, the added/removed lines and a unified diff.
Checks (VerifyCheck.kind, in order; each has status pending|running|pass|warn|fail|skipped|error,
summary, findings[], durationMs, optional command/output/metrics):
| kind | what it proves | metrics | fails when |
|---|---|---|---|
diagnostics |
language-server diagnostics on touched files (waits ~1s for re-validation) | – | any error-severity diagnostic |
project-check |
whole-project type/compile check: tsc --noEmit, cargo check, ruff/pyflakes, go vet (or verify.projectCheckCommand) |
errors_touched, errors_elsewhere |
errors in touched files (pre-existing errors elsewhere → warn) |
dependencies |
every new package exists on npm/crates.io/PyPI/Go proxy, is in the lockfile, is not a near-miss of a popular name, has no OSV advisories | deps, resolved, missing, vulnerable |
package not found (critical) or near-miss (high) |
security |
~55 regex rules over added lines (secrets, injection, TLS off, weak crypto, CORS, CSRF, path traversal, curl|sh …) + semgrep if installed | lines_scanned, critical, high, medium |
critical/high finding |
tests |
runs the detected test command (npm/pnpm/yarn/bun test, cargo test, pytest, go test, or verify.testCommand) and parses the summary |
passed, failed, total |
any failure / non-zero exit |
mutation |
diff-scoped mutation score: mutates only added lines (≤ verify.mutationMaxMutants), re-runs tests, restores files (Drop guard; never clobbers user edits) |
mutants, killed, survived, score |
score < 0.3 (warn < 0.6) |
lint |
eslint / ruff / clippy on touched files (60 s cap, warn-only) | – | never (warn) |
Finding severities: critical > high > medium > low > info. Findings carry path, line,
rule, suggestion, evidence, and fixable when verify_apply_fix can rewrite the line.
Risk tier (report.riskTier 0/1/2 with riskReasons): 2 when protected paths (auth, payments,
migrations, CI, infra, lockfiles with code changes) are touched, a dependency is missing, or
critical security findings exist; 1 for large diffs, failed tests, new dependencies; 0 otherwise.
Facts vs. claims: report.facts are sentences the checks proved ("12 tests pass", "0 type errors");
report.claims are assertions extracted from the agent's final message ("I tested…", "this is safe")
so the UI can show which claims have no supporting fact.
Commands and events
| command | purpose |
|---|---|
verify_run(scope, onEvent) → reportId |
start a run; streams VerifyEvent (report, check, done, error) on the channel |
verify_get(reportId) |
full report |
verify_list(sessionId?, limit) |
recent reports for the workspace (newest first) |
verify_latest(sessionId) |
the session's last report |
verify_cancel(reportId) |
cancel a running report |
verify_apply_fix(reportId, findingId) |
apply a rule's safe rewrite to the finding's line (goes through the session change registry when the session is loaded) |
Events: caret://verify/changed ({ reportId, sessionId? }) after every run, and
AgentEvent::Verification { sessionId, report } on the chat channel so the proof panel updates in
place. ChatSessionView.lastVerificationId points at the newest report.
Reports persist under ~/.caret/verify/<workspace-hash>/<report-id>.json (index keeps 50).
Registry/OSV lookups are cached in ~/.caret/verify/registry-cache.json (7 d hit, 1 h miss, 24 h OSV)
and are skipped entirely in privacy mode.
Settings (settings.verify)
autoRunAfterTurn (true), runTests (true), mutationTesting (true), mutationMaxMutants (12),
dependencyGrounding (true), securityRules (true), projectCheckCommand, testCommand,
timeoutSecs (300), blockMergeOnFail (false), useSemgrepIfAvailable (true).
Review-on-diff commands (api.review.*)
chat_review_comments, chat_add_review_comment, chat_resolve_review_comment,
chat_delete_review_comment, chat_send_review_comments (renders unresolved comments as a
structured follow-up turn with ±3 lines of context and submits it), chat_review_groups(sessionId, by?)
with by = intent (default, one group per turn titled by the assistant's opening line), tool-call,
file-kind (types → implementation → tests → docs → config → lockfiles) or file.
Compiled test suites (build-before-test)
When a project's tests import compiled output (dist/, build/, out/, lib/) and package.json has a build
script, the engine runs <pm> run build && <pm> test for the tests check and for every mutant. Without this,
mutations of src/ never reach the test run and every mutant would falsely "survive". Override with
settings.verify.preTestCommand (any shell command run before the test command) or disable by setting
testCommand explicitly.