SLOPSHOPPER

reflect

Spots corrections in your prompts and offers to save them as permanent rules in CLAUDE.md.

newbandcommandtoastpromptmodel
v0.1.0no licenseupdated 2026-10-12Isaacg1000/isaac-mods/plugins/reflect
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · reflect
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /reflect ⎿ reflect: Usage: /reflect <rule>. Reflect also offers this automatically when you correct Claude. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

isaac-mods

Eight mods for Claude Code (terminal and the desktop app's Code tab). Requires Claude Code 2.1.287 or newer.

ModWhat it doesCommands
savvy-progressProgress bar above the prompt; an Agents side panel with a pixel crab per subagent showing model, effort, task progress, context, tokens and estimated cost/savvy, /crab-style <keyword> <color> <hat>
tool-skinsThemed icons and colors for tool calls (Tokyo Night, Nord, Dracula, Gruvbox, Solarized, Catppuccin, Rose Pine)/skin <theme>
you-should-knowAfter a long reply, a Haiku side agent flags costs, risks and actions you might miss/ysk on, /ysk off, /ysk focus <text>
file-treeA panel showing which files Claude is reading or editing; turns green once committed/files
cache-taxWarns before a prompt reloads a cold prompt cache, with a token and cost estimate/keepwarm, /cachetax ttl <minutes>
blast-radiusStops rm -r, Remove-Item -Recurse, git clean -f, git reset --hard and similar, lists the files at stake, and waits for Proceed or Cancelautomatic
reflectSpots corrections in your prompts and offers to save them as rules in CLAUDE.md/reflect <rule>
terminal-browserA text-mode browser pane; Claude can open pages there too/browse <url or path>

Cost figures are estimates at API list prices. you-should-know and reflect make small Haiku calls, which count toward your usage.

Install

At a Claude Code prompt in a terminal:

/plugin install savvy-progress --marketplace Isaacg1000/isaac-mods

Answer y to add the marketplace, then pick the user scope. Repeat with each mod name you want.

Or from a shell, all at once:

claude plugin marketplace add Isaacg1000/isaac-mods
claude plugin install savvy-progress@isaac-mods --scope user
claude plugin install tool-skins@isaac-mods --scope user
claude plugin install you-should-know@isaac-mods --scope user
claude plugin install file-tree@isaac-mods --scope user
claude plugin install cache-tax@isaac-mods --scope user
claude plugin install blast-radius@isaac-mods --scope user
claude plugin install reflect@isaac-mods --scope user
claude plugin install terminal-browser@isaac-mods --scope user

Developing

Each mod lives in plugins/<name>/: hooks/register.tsx is the hooks module, types/index.d.ts its state contract. Check and test one with:

claude plugin validate plugins/<name>
claude plugin test plugins/<name>

When this folder is added as a local marketplace (claude plugin marketplace add <this folder>), edits are picked up with /reload-plugins.

Source 2 files
hooks/register.tsx 151 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Suggestion } from '../types'
5
6const suggestion = atom({ plugin: 'reflect', key: 'suggestion' } as const, null as Suggestion | null)
7
8const HEADER = '## Learned rules (Reflect)'
9
10// Phrases that usually mean the person is correcting Claude.
11const FEEDBACK = [
12  /^\s*(no|nope|nah|wrong|stop|don'?t|do not|never|always|ugh|actually)\b/i,
13  /\b(doesn'?t|does not|didn'?t) (look|work|feel) (good|right)\b/i,
14  /\bi (wish|would rather|'d rather|prefer|told you|said)\b/i,
15  /\b(instead of|rather than|not like that|that'?s not what|you should(n'?t)? have|please don'?t|stop (doing|using|adding))\b/i,
16  /\b(from now on|next time|in (the )?future|every time|going forward)\b/i,
17]
18
19const SYSTEM = `You turn a user's correction of an AI coding assistant into one durable rule for its instructions file.
20Write a single imperative sentence under 25 words, general enough to apply next time, with no preamble or quotes.
21If the message is a one-off instruction rather than a lasting preference, reply with exactly SKIP.`
22
23async function homeDir($: EngineInterface) {
24  return (await $.env.get('USERPROFILE')) ?? (await $.env.get('HOME')) ?? '~'
25}
26
27async function appendRule($: EngineInterface, path: string, rule: string) {
28  const exists = await $.fs.exists(path)
29  const text = exists ? ((await $.fs.read(path)) as string) : ''
30  const line = `- ${rule.replace(/^[-*]\s*/, '').trim()}`
31  if (text.includes(line)) return
32  const lines = text.split(/\r?\n/)
33  const at = lines.findIndex(l => l.trim() === HEADER)
34  if (at === -1) {
35    const sep = text.length === 0 ? '' : text.endsWith('\n') ? '\n' : '\n\n'
36    await $.fs.write(path, `${text}${sep}${HEADER}\n\n${line}\n`)
37    return
38  }
39  let end = at + 1
40  while (end < lines.length && (lines[end]!.trim() === '' || lines[end]!.trimStart().startsWith('- '))) end++
41  while (end > at + 1 && lines[end - 1]!.trim() === '') end--
42  lines.splice(end, 0, line)
43  await $.fs.write(path, lines.join('\n'))
44}
45
46// The person's edit of the suggested rule; kept out of state so typing doesn't redraw the field.
47let draft: string | undefined
48
49async function save($: EngineInterface, where: 'project' | 'user') {
50  const held = await read($, suggestion)
51  const s = held && draft?.trim() ? { ...held, rule: draft.trim() } : held
52  if (!s || !s.rule.trim()) return
53  const path =
54    where === 'project' ? `${(await $.session.cwd()).replace(/[\\/]$/, '')}/CLAUDE.md` : `${await homeDir($)}/.claude/CLAUDE.md`
55  try {
56    await appendRule($, path, s.rule)
57    draft = undefined
58    await update($, suggestion, v => (v ? { ...v, rule: s.rule, savedTo: path } : v))
59    $.ui.toast(`Rule saved to ${path}`)
60    $.clock.after(4000, () => void update($, suggestion, v => (v?.savedTo ? null : v)))
61  } catch (err) {
62    $.ui.toast(`Reflect could not write ${path}: ${String(err)}`)
63  }
64}
65
66export const register: Register = on => {
67  on('session.start', async ($, e, next) => {
68    await $.command.register({ name: 'reflect', description: 'Reflect: save a rule to CLAUDE.md: /reflect <rule>', argumentHint: '<rule>' })
69    return next(e)
70  })
71
72  on('command.run', { command: 'reflect' }, async ($, e) => {
73    const rule = e.args.trim()
74    if (!rule) return { text: 'Usage: /reflect <rule>. Reflect also offers this automatically when you correct Claude.' }
75    await update($, suggestion, () => ({ feedback: rule, rule, savedTo: null }))
76    return { text: 'Choose where to save it in the banner above the prompt.' }
77  })
78
79  on('prompt.submit', async ($, e, next) => {
80    const result = await next(e)
81    const kind = e.origin.kind
82    const text = e.text.trim()
83    if ((kind !== 'composer' && kind !== 'sdk' && kind !== 'bridge') || text.startsWith('/') || text.length > 1500) return result
84    if (!FEEDBACK.some(re => re.test(text))) return result
85
86    $.clock.after(0, () => {
87      void (async () => {
88        const messages = await $.session.messages().catch(() => [])
89        const lastReply = [...messages].reverse().find(m => m.role === 'assistant')?.text ?? ''
90        const reply = await $.model.complete({
91          model: 'haiku',
92          system: SYSTEM,
93          prompt: `Assistant's previous reply (for context):\n${lastReply.slice(-3000)}\n\nUser's message:\n${text}`,
94          maxTokens: 120,
95          timeoutMs: 30000,
96        })
97        if (!reply.isAnswered) return
98        const rule = reply.text.trim().split('\n')[0]!.replace(/^["'`-]\s*|["'`]$/g, '')
99        if (!rule || /^SKIP\b/i.test(rule)) return
100        draft = undefined
101        await update($, suggestion, () => ({ feedback: text, rule, savedTo: null }))
102      })()
103    })
104    return result
105  })
106
107  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
108    const s = await read($, suggestion)
109    if (!s || e.props.hasSurvey) return next(e)
110    const { Box, Text, Button, Input } = $.ui.resolve(e)
111    if (s.savedTo) {
112      return (
113        <Box>
114          <Text color="success">🪞 Saved to {s.savedTo}</Text>
115        </Box>
116      )
117    }
118    return (
119      <Box flexDirection="column" borderStyle="round" borderColor="suggestion" paddingX={1}>
120        <Text bold color="suggestion">
121          🪞 Save this as a permanent rule?
122        </Text>
123        {Input ? (
124          <Input
125            key="rule"
126            value={s.rule}
127            submitLabel="Save to project"
128            onInput={value => {
129              draft = value
130            }}
131            onSubmit={value => {
132              draft = value
133              void save($, 'project')
134            }}
135          />
136        ) : (
137          <Text italic>{s.rule}</Text>
138        )}
139        <Box flexDirection="row" gap={2}>
140          <Button key="project" label="Project CLAUDE.md" variant="primary" hotkey="p" onPress={() => save($, 'project')} />
141          <Button key="user" label="User CLAUDE.md" hotkey="u" onPress={() => save($, 'user')} />
142          <Button key="dismiss" label="Dismiss" role="dismiss" hotkey="d" onPress={() => {
143              draft = undefined
144              void update($, suggestion, () => null)
145            }} />
146        </Box>
147      </Box>
148    )
149  })
150}
151
types/index.d.ts 8 lines
1export type Suggestion = { feedback: string; rule: string; savedTo: string | null }
2
3declare module 'claude-code' {
4  interface PluginState {
5    reflect: { suggestion: Suggestion | null }
6  }
7}
8