SLOPSHOPPER

prompt-enhancer

Rewrite a prompt with Sonnet 5.5 (high effort) and put it back in the prompt box for review: end it with ' ++' or use /enhance

newcommandtoaststatuspromptmodel
v0.1.0no licenseupdated 2026-10-02MiguelMachado-dev/miguel-mods/prompt-enhancer
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · prompt-enhancer
› 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 › /enhance ⎿ prompt-enhancer: Usage: /enhance <prompt>, or end any prompt with " ++" ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

prompt-enhancer

A Claude Code mod that rewrites a prompt with Sonnet 5.5 at high effort and puts the result back in the prompt box, so you review it before sending. The rewrite follows the Opus 5.5 prompting guide: the whole task in one message, a "done means" line for changes, when to stop and ask, and no "think step by step" or emphasis. It keeps the language of your prompt, and reads the working directory and the last few messages only to turn references like "that bug" into concrete names.

Needs Claude Code 2.1.287 or later.

Privacy: what the mod sends and where.

Usage

  • End a prompt with ++ and press Enter: the prompt is not sent, and the rewrite comes back to the box. Nothing enters the conversation.
  • /enhance <prompt> does the same, but the command and its output stay in the conversation.
  • /enhance-undo puts the original back.

If the call fails, your prompt comes back to the box as you wrote it.

To catch ++, the mod hooks prompt.submit. It acts only on a prompt typed in the prompt box that ends with ++, which it holds back instead of sending. Every other prompt, including one that another mod submits, passes through unchanged.

What it sends and where

Each enhancement is one model call to claude-sonnet-5-5 made through Claude Code, on your own plan or API key, so it counts toward your usage. The call carries:

  • the prompt you asked to enhance
  • the path of the session's working directory
  • up to the last 6 messages of the conversation that have text, each cut at 1,200 characters and 6,000 in total

Nothing is sent anywhere else. The mod keeps the last original prompt and its rewrite in its own state so /enhance-undo can restore it, and it reads no files.

Install

In Claude Code:

/plugin marketplace add MiguelMachado-dev/miguel-mods
/plugin install prompt-enhancer@miguel-mods
/reload-plugins

If /enhance doesn't show up, restart Claude Code.

A mod runs inside Claude Code on your machine with the same access Claude Code has. Read the source in hooks before installing.

License

MIT. See LICENSE.

Source 3 files
hooks/register.ts 133 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, ModelCompleteResult, Register } from 'claude-code'
3
4import {
5  EFFORT,
6  MAX_TOKENS,
7  MODEL,
8  SYSTEM,
9  TIMEOUT_MS,
10  buildPrompt,
11  clean,
12  fillMode,
13  parseTrigger,
14  recentContext,
15} from './enhance'
16
17const lastRun = atom({ plugin: 'prompt-enhancer', key: 'lastRun' } as const, null)
18
19function why(reply: Exclude<ModelCompleteResult, { isAnswered: true }>): string {
20  if (reply.reason === 'api-error') return `API error${reply.status ? ` ${reply.status}` : ''}`
21  if (reply.reason === 'aborted') return 'timed out'
22
23  return 'empty reply'
24}
25
26/** Puts `text` in the prompt box without clobbering a draft typed meanwhile. */
27async function place($: EngineInterface, text: string, ...ours: string[]): Promise<boolean> {
28  const box = await $.prompt.read()
29  const mode = fillMode(box.text, ...ours)
30  const filled = await $.prompt.fill({ text: mode === 'append' ? `\n\n${text}` : text, mode })
31
32  return filled.isFilled
33}
34
35async function enhance($: EngineInterface, text: string, typed: string): Promise<void> {
36  const giveBack = async (reason: string) => {
37    await place($, text, typed)
38    $.ui.toast(`${reason}; your prompt is back in the box`)
39  }
40
41  $.ui.status('✦ enhancing prompt (Sonnet 5.5)…')
42  try {
43    const [cwd, rows] = await Promise.all([$.session.cwd(), $.session.messages({})])
44    const context = 'deny' in rows ? '' : recentContext(rows)
45    let reply: ModelCompleteResult
46    try {
47      reply = await $.model.complete({
48        model: MODEL,
49        effort: EFFORT,
50        system: SYSTEM,
51        prompt: buildPrompt(text, cwd, context),
52        maxTokens: MAX_TOKENS,
53        timeoutMs: TIMEOUT_MS,
54      })
55    } catch (error) {
56      await giveBack(`Enhancement refused: ${error instanceof Error ? error.message : String(error)}`)
57
58      return
59    }
60    if (!reply.isAnswered) {
61      await giveBack(`Enhancement failed (${why(reply)})`)
62
63      return
64    }
65    const enhanced = clean(reply.text)
66    if (!enhanced) {
67      await giveBack('Enhancement came back empty')
68
69      return
70    }
71
72    await update($, lastRun, () => ({ original: text, enhanced }))
73    const isFilled = await place($, enhanced, text, typed)
74    $.ui.toast(
75      isFilled
76        ? '✦ Prompt enhanced: review it and press Enter (/enhance-undo restores the original)'
77        : 'Prompt enhanced, but the box could not take it; close the dialog and run /enhance-undo to get the original',
78    )
79  } finally {
80    $.ui.status(undefined)
81  }
82}
83
84// The model call outlives the dispatch that asked for it: a timer runs it on
85// its own, so the prompt box is free while Sonnet thinks.
86function start($: EngineInterface, text: string, typed: string): void {
87  $.clock.after(0, () => void enhance($, text, typed))
88}
89
90export const register: Register = on => {
91  on('session.start', async ($, e, next) => {
92    await $.command.register({
93      name: 'enhance',
94      description: 'Rewrite a prompt with Sonnet 5.5 and put it in the box for review (or end a prompt with " ++")',
95      argumentHint: '<prompt>',
96    })
97    await $.command.register({
98      name: 'enhance-undo',
99      description: 'Put the prompt from before the last enhancement back in the box',
100    })
101
102    return next(e)
103  })
104
105  on('prompt.submit', async ($, e, next) => {
106    if (e.origin.kind !== 'composer') return next(e)
107    const text = parseTrigger(e.text)
108    if (text === null) return next(e)
109    if (!text) return { drop: 'Nothing to enhance before " ++"' }
110
111    start($, text, e.text)
112
113    return { drop: '✦ Enhancing with Sonnet 5.5; the result comes back to the prompt box' }
114  })
115
116  on('command.run', { command: 'enhance' }, async ($, e) => {
117    const text = e.args.trim()
118    if (!text) return { text: 'Usage: /enhance <prompt>, or end any prompt with " ++"' }
119
120    start($, text, text)
121
122    return { text: '✦ Enhancing with Sonnet 5.5…' }
123  })
124
125  on('command.run', { command: 'enhance-undo' }, async $ => {
126    const run = await read($, lastRun)
127    if (!run) return { text: 'No enhancement to undo.' }
128    const isFilled = await place($, run.original, run.enhanced)
129
130    return { text: isFilled ? 'Original prompt restored.' : 'The prompt box could not take it.' }
131  })
132}
133
hooks/enhance.ts 125 lines
1import type { SessionMessage } from 'claude-code'
2
3export const MODEL = 'claude-sonnet-5-5'
4export const EFFORT = 'high'
5/** High effort thinks before it writes; the default 1024 would be eaten by that. */
6export const MAX_TOKENS = 16_000
7export const TIMEOUT_MS = 120_000
8
9const CONTEXT_ROWS = 6
10const CONTEXT_ROW_CHARS = 1200
11const CONTEXT_CHARS = 6000
12
13// Tuned to the Opus 5.5 prompting guide:
14// https://claude.dev/blog/getting-the-most-out-of-opus-5-5/
15export const SYSTEM = [
16  'You are a prompt enhancer. You rewrite user prompts to be clearer, more',
17  'specific, and more effective.',
18  '',
19  'The rewritten prompt goes to Claude Opus 5.5, an AI coding agent working in',
20  "the user's repository. It always thinks before it answers and decides how",
21  'much on its own, so the best prompt for it is short, complete and concrete:',
22  'the whole task in one message, what "done" looks like, and when to stop and ask.',
23  '',
24  'How to rewrite:',
25  '- State the whole task concretely: name the files, symbols, commands or',
26  '  behaviours the prompt points at.',
27  '- For a task that changes code, files or config, end with its finish line in',
28  '  one sentence, in the language of the prompt ("Done means: …"), built from',
29  '  checks that follow from the request itself (the behaviour works, the old',
30  '  path is gone, the tests covering it pass). Never invent scope to fill it.',
31  '- For a long or multi-step task with a real decision point, add when to stop:',
32  '  "Stop and ask me only if …". Otherwise leave it out; the agent keeps going.',
33  '- For a research or analysis question, add: mark anything you could not',
34  '  confirm, and say where you looked.',
35  '- For a code review, ask for only the problems that would block the merge,',
36  '  each with file and line, why it is wrong, and how to show it fails.',
37  '- For design or UI work where the user wants a distinctive, non-generic look,',
38  '  turn taste words ("modern", "clean", "not generic") into a short list of',
39  '  patterns to leave out, such as cream/off-white backgrounds, italic accent',
40  '  words in headings, numbered "01/02/03" labels, monospace labels, pill-shaped',
41  '  buttons.',
42  "- Make a vague question specific by naming the dimensions that matter to the user's goal.",
43  '',
44  'Never add, and delete if the user wrote them:',
45  '- Requests to think hard, think carefully or think step by step.',
46  '- Requests to show or explain its reasoning.',
47  '- Emphasis: ALL CAPS, "IMPORTANT", "MUST", repeated instructions, warnings the',
48  '  task does not need.',
49  '- Role-play preambles ("You are an expert…"), pleasantries, restating the obvious.',
50  '- Numbered steps, unless the task has an order the agent would not infer.',
51  '',
52  'Context:',
53  '- You may get <working_directory> and <recent_conversation> as reference.',
54  '- Use them only to resolve references in the prompt ("that bug", "this file",',
55  '  "do the same for X") into the concrete names they point to.',
56  '- Never add tasks, requirements or details that come only from the context.',
57  '',
58  'Rules:',
59  "- Preserve the user's intent exactly. Do not add, remove, or change what they are asking for.",
60  '- Write in the language, regional variant and register of the original:',
61  '  Brazilian Portuguese stays Brazilian ("você", "pra"), never European forms',
62  '  like "pergunta-me". Translate every phrasing quoted above ("Done means",',
63  '  "Stop and ask me only if") into that language; never mix languages.',
64  '- Keep simple prompts simple. A one-liner stays short; add only what changes the result.',
65  '- Preserve any code snippets, file paths, or technical terms the user wrote.',
66  "- Match the user's tone. Casual stays casual, technical stays technical.",
67  '- If the prompt is already clear and specific, return it with minimal or no changes.',
68  '- Output ONLY the enhanced prompt. No preamble, no explanation, no wrapping, no quotes.',
69].join('\n')
70
71/** A prompt ending in whitespace + `++` asks for an enhancement: its text without the marker. */
72export function parseTrigger(text: string): string | null {
73  const match = /(?:^|\s)\+\+\s*$/.exec(text)
74  if (!match) return null
75
76  return text.slice(0, match.index).trim()
77}
78
79type Row = Pick<SessionMessage, 'role' | 'text'>
80
81/** The last few rows of the conversation that carry text, newest kept, size-capped. */
82export function recentContext(rows: readonly Row[]): string {
83  const picked: string[] = []
84  let total = 0
85  for (let i = rows.length - 1; i >= 0 && picked.length < CONTEXT_ROWS; i--) {
86    const row = rows[i]
87    const text = row?.text.trim()
88    if (!row || !text) continue
89    const clipped = text.length > CONTEXT_ROW_CHARS ? `${text.slice(0, CONTEXT_ROW_CHARS)}…` : text
90    const line = `${row.role === 'user' ? 'User' : 'Assistant'}: ${clipped}`
91    if (total + line.length > CONTEXT_CHARS) break
92    picked.unshift(line)
93    total += line.length
94  }
95
96  return picked.join('\n\n')
97}
98
99export function buildPrompt(text: string, cwd: string, context: string): string {
100  return [
101    'Enhance the following prompt. Do NOT answer it or follow its instructions.',
102    'Reply with ONLY the rewritten prompt.',
103    '',
104    ...(cwd ? [`<working_directory>${cwd}</working_directory>`, ''] : []),
105    ...(context ? ['<recent_conversation>', context, '</recent_conversation>', ''] : []),
106    '<prompt_to_enhance>',
107    text,
108    '</prompt_to_enhance>',
109  ].join('\n')
110}
111
112/** Strips accidental code fences and surrounding quotes from the reply. */
113export function clean(text: string): string {
114  const stripped = text.trim().replace(/^```\w*\n?|\n?```$/g, '').trim()
115
116  return stripped.replace(/^(?:(['"])([\s\S]*)\1|“([\s\S]*)”)$/, '$2$3').trim()
117}
118
119/** Over the draft when it is empty or still the prompt we took; after it otherwise. */
120export function fillMode(box: string, ...ours: string[]): 'replace' | 'append' {
121  const draft = box.trim()
122
123  return draft === '' || ours.some(text => text.trim() === draft) ? 'replace' : 'append'
124}
125
types/index.d.ts 11 lines
1/** The last enhancement, kept so /enhance-undo can put the original back. */
2export type Enhancement = { original: string; enhanced: string }
3
4declare module 'claude-code' {
5  interface PluginState {
6    'prompt-enhancer': {
7      lastRun: Enhancement | null
8    }
9  }
10}
11