When your feedback sounds like a correction, offers to save it as a permanent rule in CLAUDE.md.

Nine mods for Claude Code (2.1.287 or newer), written from the walkthrough video. Each lives in mods/<name>.
Install one, or all, from a terminal session:
/plugin install <name> --marketplace KianaVega/KianaVega
| Mod | What it does | Extra model usage |
|---|---|---|
| savvy-progress | Progress bar above the prompt and a live list of subagents with model, tokens and rough cost | none |
| tool-skins | Icons and colors for tool calls in seven themes, switch with /skin <name> | none |
| you-should-know | A second, small model reads long answers and flags anything important you might miss | one small call per long answer |
| file-tree | Pane showing files Claude reads and edits, green once committed (/file-tree) | none |
| cache-tax | Holds a prompt that would reload a cold cache and shows an estimate. /keepwarm pings before the cache expires | none, except keep-warm pings |
| blast-radius | Stops recursive deletes, git clean, hard resets and shows the affected files before you press Proceed | none |
| reflect | Offers to save corrections as a rule in CLAUDE.md | one small call when you save |
| terminal-browser | /browse <url or file> opens a pane with a screenshot (kitty or Ghostty) and the page text | none |
| replay-theater | Replay button after a turn that steps through each edit as a diff (/replay) | none |
hooks/register.tsx. Edit and reload.hooks/register.tsx 86 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Candidate } from '../types'
5
6// Edit to taste: phrases that signal a correction worth remembering.
7const TRIGGERS = [
8 /^\s*(no|nope|nah)\b/i,
9 /\bthis (doesn't|does not|isn't|is not) (look|work|right|good)/i,
10 /\bthat'?s (wrong|not (right|what))/i,
11 /\bi wish (we|you)('d| had| would)/i,
12 /\b(don't|do not|never|stop) (do|use|add|write|make)\b/i,
13 /\binstead\b.*\b(use|do|write)\b/i,
14]
15
16const candidate = atom({ plugin: 'reflect', key: 'candidate' } as const, null as Candidate | null)
17const status = atom({ plugin: 'reflect', key: 'status' } as const, '')
18
19async function save($: EngineInterface, scope: 'project' | 'global') {
20 const c = await read($, candidate)
21 if (c === null) return
22
23 await update($, status, () => 'Writing the rule...')
24 const drafted = await $.model.complete({
25 model: 'haiku',
26 maxTokens: 120,
27 system:
28 'Turn the person\'s feedback into one short, imperative rule for a CLAUDE.md file. ' +
29 'Output only the rule as a single line starting with "- ". No preamble.',
30 prompt: `Assistant's previous reply:\n${c.context}\n\nPerson's feedback:\n${c.feedback}`,
31 })
32 const rule = drafted.isAnswered && drafted.text.trim().startsWith('-')
33 ? drafted.text.trim().split('\n')[0]!
34 : `- ${c.feedback.trim()}`
35
36 const home = (await $.env.get('HOME')) ?? ''
37 const path = scope === 'global' ? `${home}/.claude/CLAUDE.md` : `${await $.session.cwd()}/CLAUDE.md`
38 let existing = ''
39 try {
40 existing = String(await $.fs.read(path))
41 } catch {
42 existing = ''
43 }
44 const sep = existing === '' || existing.endsWith('\n') ? '' : '\n'
45 const heading = existing.includes('## Learned rules') ? '' : '\n## Learned rules\n'
46 await $.fs.write(path, `${existing}${sep}${heading}${rule}\n`)
47
48 await update($, candidate, () => null)
49 $.ui.toast(`Saved to ${path}: ${rule}`)
50}
51
52export const register: Register = on => {
53 on('prompt.submit', async ($, e, next) => {
54 if (e.text.length > 3 && e.text.length < 800 && TRIGGERS.some(t => t.test(e.text))) {
55 const rows = await $.session.messages()
56 const lastReply = [...rows].reverse().find(r => r.role === 'assistant')?.text ?? ''
57 await update($, candidate, () => ({ feedback: e.text, context: lastReply.slice(0, 1500) }))
58 await update($, status, () => '')
59 }
60
61 return next(e)
62 })
63
64 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
65 const c = await read($, candidate)
66 if (e.props.hasSurvey || e.props.isWorking || c === null) {
67 return next(e)
68 }
69 const { Box, Button, Text } = $.ui.resolve(e)
70 const note = await read($, status)
71
72 return (
73 <Box flexDirection="column">
74 <Text color="remember">Save this feedback as a permanent rule? {note}</Text>
75 <Box>
76 <Button key="project" label="Save to project CLAUDE.md" hotkey="1" onPress={() => save($, 'project')} />
77 <Text> </Text>
78 <Button key="global" label="Save to global CLAUDE.md" hotkey="2" onPress={() => save($, 'global')} />
79 <Text> </Text>
80 <Button key="skip" label="Dismiss" hotkey="3" onPress={() => update($, candidate, () => null)} />
81 </Box>
82 </Box>
83 )
84 })
85}
86types/index.d.ts 8 lines1export type Candidate = { feedback: string; context: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 reflect: { candidate: Candidate | null; status: string }
6 }
7}
8