SLOPSHOPPER

reflect

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

newbandtoastpromptmodel
v0.1.0no licenseupdated 2026-10-11KianaVega/KianaVega/mods/reflect
A shopper browsing a rack in a slop shop
README

Claude Code mods

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
ModWhat it doesExtra model usage
savvy-progressProgress bar above the prompt and a live list of subagents with model, tokens and rough costnone
tool-skinsIcons and colors for tool calls in seven themes, switch with /skin <name>none
you-should-knowA second, small model reads long answers and flags anything important you might missone small call per long answer
file-treePane showing files Claude reads and edits, green once committed (/file-tree)none
cache-taxHolds a prompt that would reload a cold cache and shows an estimate. /keepwarm pings before the cache expiresnone, except keep-warm pings
blast-radiusStops recursive deletes, git clean, hard resets and shows the affected files before you press Proceednone
reflectOffers to save corrections as a rule in CLAUDE.mdone small call when you save
terminal-browser/browse <url or file> opens a pane with a screenshot (kitty or Ghostty) and the page textnone
replay-theaterReplay button after a turn that steps through each edit as a diff (/replay)none

Notes

  • Settings such as trigger phrases, price tables, skins and which model the watcher uses are constants at the top of each hooks/register.tsx. Edit and reload.
  • Cost figures are estimates from a built-in price table.
  • terminal-browser is a viewer built from a headless screenshot and page text. It is not a live, clickable browser.
  • Mods run in the terminal and the desktop app's Code tab, not the VS Code extension.
Source 2 files
hooks/register.tsx 86 lines
1import { 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}
86
types/index.d.ts 8 lines
1export type Candidate = { feedback: string; context: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    reflect: { candidate: Candidate | null; status: string }
6  }
7}
8