Flags replies that claim something was tested or verified when nothing was run in the turn to check it: a warning under the message and a toast.

Catches "tested", "verified", "all tests pass", "it works now" (or "testé", "vérifié", "ça marche") in a reply when nothing was run in that turn to check it.
⏺ I updated the checkout. Tested, it works now.
⚠ claims a check, but nothing was run in this turn to verify it
/plugin install jr-done-check --marketplace jrlprost/claude-code-mods
Reads the tool names called in each turn and Claude's final reply. Draws a line under assistant messages. Never blocks anything, runs nothing, sends nothing over the network.
hooks/register.tsx 50 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import { claims, isVerifying, keyOf, plain } from './claims'
5
6const flagged = atom({ plugin: 'jr-done-check', key: 'flagged' } as const, [])
7// Whether the main loop's current turn ran anything that can check a claim.
8const turn = { hasVerified: false }
9
10export const register: Register = on => {
11 on('turn.start', async ($, e, next) => {
12 if (e.agentId === undefined) turn.hasVerified = false
13 return next(e)
14 })
15
16 on('tool.call', async ($, e, next) => {
17 if (isVerifying(String(e.tool))) turn.hasVerified = true
18 return next(e)
19 }).catch(($, e, next) => next(e))
20
21 on('turn.complete', async ($, e, next) => {
22 const done = await next(e)
23 if (e.agentId !== undefined || turn.hasVerified) return done
24 const found = claims(e.answer ?? '')
25 if (found.length === 0) return done
26 await update($, flagged, list => [...(list ?? []), ...found.map(keyOf)].slice(-100))
27 $.ui.toast(`jr done-check · the reply says "${found[0]!.slice(0, 50)}" but nothing was run this turn to check it`)
28 return done
29 })
30
31 // Under the block of the reply that holds the claim: one warning line. The stored message is unchanged.
32 on('ui.render', { component: 'AssistantMessage' }, async ($, e, next) => {
33 const drawn = await next(e)
34 const keys = await read($, flagged)
35 if (keys.length === 0) return drawn
36 const text = plain(e.props.text)
37 if (!keys.some(k => text.includes(k))) return drawn
38 const { Box, Text } = $.ui.resolve(e)
39 return (
40 <Box flexDirection="column">
41 {drawn}
42 <Text>
43 <Text color="#ff8c42" bold>{' ⚠ '}</Text>
44 <Text color="#ff8c42">claims a check, but nothing was run in this turn to verify it</Text>
45 </Text>
46 </Box>
47 )
48 })
49}
50hooks/claims.ts 27 lines1// A claim that something was checked, in English or French.
2// Letter-aware edges: \b does not see accented letters such as the é of "testé".
3const CLAIM =
4 /(?<![\p{L}])(tested|verified|validated|confirmed working|all tests pass(ed|ing)?|tests (are )?(green|passing)|(it|this|that) works( now)?|works now|testé(e|s)?|vérifié(e|s)?|validé(e|s)?|les tests passent|tests? au vert|ça marche|ça fonctionne|fonctionne (maintenant|bien|correctement))(?![\p{L}])/iu
5
6// Said of what was NOT done: "not tested", "couldn't verify", "pas testé", "non vérifié"…
7const NEGATED =
8 /\b(not|never|couldn'?t|could not|can'?t|cannot|unable to|without|untested|no way to|pas|jamais|non|sans|impossible de|pu)\s+(\w+\s+){0,2}$/i
9
10/** The sentences of `text` that claim a verification, negated ones left out. */
11export const claims = (text: string) =>
12 text
13 .split(/(?<=[.!?\n])\s+/)
14 .map(s => s.trim())
15 .filter(s => {
16 const m = CLAIM.exec(s)
17 return m !== null && !NEGATED.test(s.slice(0, m.index))
18 })
19
20/** A tool call that can check something: a shell command, a test runner, a browser or screenshot tool. */
21export const isVerifying = (tool: string) => tool === 'Bash' || /test|playwright|browser|screenshot|snapshot|lighthouse|a11y/i.test(tool)
22
23/** A short, stable key for a sentence, to find it again in the message as drawn. */
24export const keyOf = (sentence: string) => sentence.replace(/[*_`#>]/g, '').replace(/\s+/g, ' ').trim().slice(0, 60)
25
26export const plain = (text: string) => text.replace(/[*_`#>]/g, '').replace(/\s+/g, ' ')
27types/index.d.ts 9 lines1/** Keys of the claim sentences, in replies of this session, that nothing in their turn verified. */
2export type JrDoneCheckFlags = string[]
3
4declare module 'claude-code' {
5 interface PluginState {
6 'jr-done-check': { flagged: JrDoneCheckFlags }
7 }
8}
9