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).

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.
!! to send it exactly as written.sonnet tier. Both are only made for long prompts.MIN_WORDS, BYPASS and MODEL are constants at the top of hooks/register.ts.
/plugin install brain-dump --marketplace FCisco95/claude-code-mods
Or copy plugins/brain-dump to ~/.claude/skills/brain-dump (see the repo README).
hooks/register.ts 164 lines1import 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