SLOPSHOPPER

brain-dump

Holds back long, messy prompts, asks 2-3 questions about the gaps, then fills the prompt box with a clear four-part prompt (job, why, guardrails, done means).

newtoaststatuspromptmodel
A shopper browsing a rack in a slop shop
README

Brain-Dump

A Claude Code mod that turns a long, messy prompt into a clear one before it is sent.

When you submit a prompt over 120 words from the prompt box, the mod holds it back, asks 2 to 3 short questions about the gaps, then fills the prompt box with a four-part prompt: the job, why, guardrails, and what done means. You read it, edit it and press Enter yourself.

Behaviour

  • Hold. Only prompts typed in the prompt box and over 120 words. Short prompts, slash commands and prompts sent by other mods pass through untouched.
  • Bypass. Start a prompt with !! to send it exactly as written.
  • No loop. A prompt that already has the four headings is not refined again.
  • Cost. One small model call to read the gaps and one to write the result, on the sonnet tier. Both are only made for long prompts.

Tuning

MIN_WORDS, BYPASS and MODEL are constants at the top of hooks/register.ts.

Install

/plugin install brain-dump --marketplace FCisco95/claude-code-mods

Or copy plugins/brain-dump to ~/.claude/skills/brain-dump (see the repo README).

Source 1 files
hooks/register.ts 164 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// A prompt this long (in words) is treated as a brain dump.
4const MIN_WORDS = 120
5// Start a prompt with this to send it untouched.
6const BYPASS = '!!'
7// Routing: judging gaps and restructuring is synthesis, not classification, so the Sonnet tier, not Haiku.
8const MODEL = 'sonnet'
9
10type Question = { question: string; options: string[] }
11
12const countWords = (text: string) => text.trim().split(/\s+/).filter(Boolean).length
13
14// Our own output carries these headings; seeing both means the prompt is already refined.
15const isStructured = (text: string) => /THE JOB/i.test(text) && /DONE MEANS/i.test(text)
16
17const parseQuestions = (raw: string): Question[] => {
18  const start = raw.indexOf('[')
19  const end = raw.lastIndexOf(']')
20
21  if (start === -1 || end <= start) {
22    return []
23  }
24
25  try {
26    const parsed: unknown = JSON.parse(raw.slice(start, end + 1))
27
28    if (!Array.isArray(parsed)) {
29      return []
30    }
31
32    return parsed
33      .filter(
34        (q): q is Question =>
35          typeof q?.question === 'string' &&
36          Array.isArray(q.options) &&
37          q.options.length >= 2 &&
38          q.options.every((o: unknown) => typeof o === 'string'),
39      )
40      .slice(0, 3)
41      .map(q => ({ question: q.question, options: q.options.slice(0, 4) }))
42  } catch {
43    return []
44  }
45}
46
47const QUESTIONS_SYSTEM = `You read a long, messy brain-dump prompt (often a voice transcription, so it may contain transcription errors, jumps and contradictions) that someone is about to send to a coding assistant.
48Find the 2 or 3 gaps that matter most for doing the job well: an unclear goal, a contradiction, a missing constraint, an undefined "done".
49Ask only what the text does not already answer. Never ask something the text states.
50Write each question in the same language as the brain dump, ending in a question mark, with 2 to 4 short answer options.
51Reply with a JSON array only, no prose: [{"question": "...?", "options": ["...", "..."]}]. Reply [] if nothing important is missing.`
52
53const REWRITE_SYSTEM = `You turn a messy brain-dump prompt into one clear prompt for a coding assistant.
54Write it in the same language as the brain dump, as plain text with exactly these four headings in capitals:
55
56THE JOB
57THE WHY
58THE GUARDRAILS
59DONE MEANS
60
61Rules:
62- THE JOB: what to do, as short bullets or sentences. Fix obvious voice-transcription errors using context.
63- THE WHY: the purpose or motivation behind the job, in one or two sentences.
64- THE GUARDRAILS: constraints, limits, things not to do.
65- DONE MEANS: concrete, checkable completion criteria.
66- Use the answers to the clarifying questions to resolve gaps and contradictions. An answer wins over the original text.
67- Keep every concrete detail: names, numbers, paths, tools, deadlines. Do not invent facts. If a section has nothing in the source, write one line saying it was not specified.
68- Reply with the prompt only, no preamble, no code fences.`
69
70const refine = async ($: EngineInterface, original: string) => {
71  const restore = async (message: string) => {
72    const box = await $.prompt.fill({ text: original })
73
74    if (!box.isFilled) {
75      $.ui.log(`brain-dump: could not refill the prompt box. Your original prompt:\n\n${original}`)
76    }
77
78    $.ui.toast(message)
79  }
80
81  $.ui.status('brain-dump: reading your prompt...')
82
83  try {
84    const asked = await $.model.complete({
85      model: MODEL,
86      system: QUESTIONS_SYSTEM,
87      prompt: original,
88      effort: 'low',
89      maxTokens: 800,
90      timeoutMs: 60000,
91    })
92
93    if (!asked.isAnswered) {
94      return await restore(`brain-dump: no reply (${asked.reason}). Your original prompt is back in the box.`)
95    }
96
97    const answers: { question: string; answer: string }[] = []
98
99    for (const q of parseQuestions(asked.text)) {
100      try {
101        const answer = await $.ui.ask(q.question, { options: q.options, header: 'Brain-dump' })
102        answers.push({ question: q.question, answer })
103      } catch {
104        return await restore('brain-dump cancelled. Your original prompt is back in the box.')
105      }
106    }
107
108    $.ui.status('brain-dump: writing the clear prompt...')
109
110    const qa = answers.length
111      ? answers.map(a => `Q: ${a.question}\nA: ${a.answer}`).join('\n\n')
112      : '(no clarifying questions were needed)'
113
114    const rewritten = await $.model.complete({
115      model: MODEL,
116      system: REWRITE_SYSTEM,
117      prompt: `BRAIN DUMP:\n${original}\n\nCLARIFYING ANSWERS:\n${qa}`,
118      effort: 'medium',
119      maxTokens: 3000,
120      timeoutMs: 90000,
121    })
122
123    if (!rewritten.isAnswered) {
124      return await restore(`brain-dump: rewrite failed (${rewritten.reason}). Your original prompt is back in the box.`)
125    }
126
127    const box = await $.prompt.fill({ text: rewritten.text.trim() })
128
129    if (!box.isFilled) {
130      $.ui.log(`brain-dump: could not fill the prompt box. The clear prompt:\n\n${rewritten.text.trim()}`)
131    }
132
133    $.ui.toast('Prompt ready. Review it, then press Enter.')
134  } catch (error) {
135    await restore(`brain-dump failed: ${String(error)}. Your original prompt is back in the box.`)
136  } finally {
137    $.ui.status(undefined)
138  }
139}
140
141export const register: Register = on => {
142  on('prompt.submit', ($, e, next) => {
143    const isUsersOwn = e.origin.kind === 'composer'
144    const isPlain = !e.command && !e.isMeta && !e.attachments?.length
145
146    if (!isUsersOwn || !isPlain) {
147      return next(e)
148    }
149
150    if (e.text.startsWith(BYPASS)) {
151      return next({ ...e, text: e.text.slice(BYPASS.length).trimStart() })
152    }
153
154    if (countWords(e.text) < MIN_WORDS || isStructured(e.text)) {
155      return next(e)
156    }
157
158    // Hold the prompt back; the refining runs on its own and refills the box.
159    void refine($, e.text)
160
161    return { drop: 'brain-dump: long prompt held back. Answer the questions, then review and send the clear prompt.' }
162  })
163}
164