@mtayfur/opencode-prompt-enhancer 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,20 @@
1
1
  # opencode-prompt-enhancer
2
2
 
3
- OpenCode TUI plugin that rewrites rough prompt drafts into stronger prompts with workspace context.
3
+ OpenCode TUI plugin that rewrites rough prompt drafts into clearer, stronger prompts.
4
4
 
5
- ## Package
5
+ ## What it does
6
6
 
7
- - npm: `opencode-prompt-enhancer`
8
- - GitHub: `mtayfur/opencode-prompt-enhancer`
7
+ - Rewrites rough prompt drafts into clearer, stronger prompts.
8
+ - Uses lightweight workspace context.
9
+ - Keeps the original intent and language, and does not read file contents.
10
+
11
+ ## Context used
12
+
13
+ The enhancer uses:
14
+
15
+ - the current working directory
16
+ - recent user prompts in the current session
17
+ - files changed in the current session
9
18
 
10
19
  ## Install
11
20
 
@@ -14,20 +23,20 @@ Add the package to OpenCode's `tui.json` plugin list:
14
23
  ```jsonc
15
24
  {
16
25
  "plugin": [
17
- "opencode-prompt-enhancer@latest"
26
+ "@mtayfur/opencode-prompt-enhancer@latest"
18
27
  ]
19
28
  }
20
29
  ```
21
30
 
22
- ## Use
31
+ OpenCode `>=1.3.14` is required.
23
32
 
24
- After restarting OpenCode, press `Ctrl+E` or run `/enhance`.
25
-
26
- The plugin opens a dialog, rewrites your draft with workspace context, and writes the enhanced prompt back into the current input.
27
-
28
- ## Context
33
+ ## Use
29
34
 
30
- The enhancer uses the current working directory, branch, recent user prompts, changed files, and active todos. It passes lightweight context only and does not read file contents.
35
+ 1. Open OpenCode in a workspace.
36
+ 2. Enter a rough prompt in the TUI prompt.
37
+ 3. Press `Ctrl+E` or run `/enhance`.
38
+ 4. Review the prefilled dialog, edit it if needed, and confirm.
39
+ 5. The enhanced prompt replaces the current input.
31
40
 
32
41
  ## Development
33
42
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtayfur/opencode-prompt-enhancer",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
4
4
  "description": "OpenCode plugin that rewrites rough drafts into stronger prompts.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -0,0 +1,30 @@
1
+ export const ENHANCER_SYSTEM_PROMPT = `You are a prompt editor for OpenCode, an AI coding assistant.
2
+
3
+ Goal:
4
+ Rewrite the user's draft into the strongest possible next prompt for OpenCode. Preserve the user's intent, scope, priorities, and language. Improve clarity, specificity, and execution readiness without changing the requested outcome.
5
+
6
+ Capabilities:
7
+ OpenCode can inspect the workspace, edit files, run commands, and verify changes. When the draft does not explicitly ask for discussion or planning only, rewrite it so OpenCode can act directly.
8
+
9
+ Rewrite rules:
10
+ - Preserve the request form. Questions stay questions; bug-fix requests stay bug-fix requests; review requests stay review requests.
11
+ - Preserve the language of the draft. Do not translate.
12
+ - Preserve inline code, file paths, command strings, error messages, identifiers, and quoted phrases verbatim when they matter.
13
+ - Preserve explicit constraints, file names, commands, acceptance criteria, and user wording.
14
+ - Use workspace context only when it directly clarifies or narrows the task.
15
+ - Resolve vague references like "this", "that bug", or "the plugin" only when context makes them clear.
16
+ - Do not invent details when context is ambiguous.
17
+ - Expand vague verbs into concrete actions when it helps OpenCode act immediately, such as identify the root cause, apply the smallest correct fix, remove dead code, keep scope tight, preserve behavior, follow local conventions, and verify the changed path.
18
+ - Keep the rewrite proportional to the draft. Do not add background, motivation, or steps the draft did not imply.
19
+ - Match the draft's structure and tone. Keep prose as prose and lists as lists.
20
+ - If the draft is already precise, make only minimal cleanup.
21
+
22
+ Hard constraints:
23
+ - Do not invent requirements, files, APIs, dependencies, bugs, or acceptance criteria.
24
+ - Do not broaden scope with extra features, refactors, tests, or docs unless the draft implies them.
25
+ - Do not ask follow-up questions.
26
+ - Do not mention context, tags, instructions, or the rewriting process.
27
+ - Do not output explanations, markdown fences, or commentary.
28
+
29
+ Output:
30
+ Return exactly one enhanced user prompt as plain text.`
@@ -9,48 +9,13 @@ import type {
9
9
  TuiRouteCurrent,
10
10
  TuiSlotPlugin,
11
11
  } from "@opencode-ai/plugin/tui"
12
+ import { ENHANCER_SYSTEM_PROMPT } from "./enhancer-system-prompt"
12
13
 
13
14
  const MAX_RECENT_MESSAGES = 3
14
15
  const MAX_CHANGED_FILES = 25
15
16
  const MAX_PROMPT_PREVIEW_LENGTH = 250
16
17
  const DIALOG_TITLE = "Enhance Prompt"
17
18
  const TOAST_TITLE = "Prompt enhancer"
18
- const TEMP_SESSION_TITLE = "Prompt Enhancer"
19
-
20
- function makeTempSessionTitle(): string {
21
- return `${TEMP_SESSION_TITLE} ${Math.random().toString(36).slice(2, 8)}`
22
- }
23
-
24
- const ENHANCER_SYSTEM_PROMPT = `You are a prompt editor for OpenCode, an AI coding assistant.
25
-
26
- Goal:
27
- Rewrite the user's draft into the strongest possible next prompt for OpenCode. Preserve the user's intent, scope, priorities, and language. Improve clarity, specificity, and execution readiness without changing the requested outcome.
28
-
29
- Capabilities:
30
- OpenCode can inspect the workspace, edit files, run commands, and verify changes. When the draft does not explicitly ask for discussion or planning only, rewrite it so OpenCode can act directly.
31
-
32
- Rewrite rules:
33
- - Preserve the request form. Questions stay questions; bug-fix requests stay bug-fix requests; review requests stay review requests.
34
- - Preserve the language of the draft. Do not translate.
35
- - Preserve inline code, file paths, command strings, error messages, identifiers, and quoted phrases verbatim when they matter.
36
- - Preserve explicit constraints, file names, commands, acceptance criteria, and user wording.
37
- - Use workspace context only when it directly clarifies or narrows the task.
38
- - Resolve vague references like "this", "that bug", or "the plugin" only when context makes them clear.
39
- - Do not invent details when context is ambiguous.
40
- - Expand vague verbs into concrete actions when it helps OpenCode act immediately, such as identify the root cause, apply the smallest correct fix, remove dead code, keep scope tight, preserve behavior, follow local conventions, and verify the changed path.
41
- - Keep the rewrite proportional to the draft. Do not add background, motivation, or steps the draft did not imply.
42
- - Match the draft's structure and tone. Keep prose as prose and lists as lists.
43
- - If the draft is already precise, make only minimal cleanup.
44
-
45
- Hard constraints:
46
- - Do not invent requirements, files, APIs, dependencies, bugs, or acceptance criteria.
47
- - Do not broaden scope with extra features, refactors, tests, or docs unless the draft implies them.
48
- - Do not ask follow-up questions.
49
- - Do not mention context, tags, instructions, or the rewriting process.
50
- - Do not output explanations, markdown fences, or commentary.
51
-
52
- Output:
53
- Return exactly one enhanced user prompt as plain text.`
54
19
 
55
20
  type ModelRef = {
56
21
  providerID: string
@@ -85,37 +50,20 @@ function parseModelString(value: string | undefined): ModelRef | undefined {
85
50
  }
86
51
  }
87
52
 
88
- function getModelOverride(options: PluginOptions | undefined): ModelRef | undefined {
89
- const value = typeof options?.model === "string" ? options.model : undefined
90
- return parseModelString(value)
91
- }
92
-
93
53
  function isSessionRoute(route: TuiRouteCurrent): route is Extract<TuiRouteCurrent, { name: "session" }> {
94
54
  return route.name === "session"
95
55
  }
96
56
 
97
- function isUserMessage(message: Message): message is Extract<Message, { role: "user" }> {
98
- return message.role === "user"
99
- }
100
-
101
- function isVisibleTextPart(part: Part): part is TextPart {
102
- return part.type === "text" && !part.ignored
103
- }
104
-
105
57
  function extractVisibleText(parts: ReadonlyArray<Part>): string {
106
58
  return parts
107
- .filter(isVisibleTextPart)
59
+ .filter((part): part is TextPart => part.type === "text" && !part.ignored)
108
60
  .map((part) => part.text)
109
61
  .join("")
110
62
  .trim()
111
63
  }
112
64
 
113
- function truncate(value: string, maxLength: number): string {
114
- return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value
115
- }
116
-
117
65
  function resolveEnhancerModel(api: Api, options: PluginOptions | undefined): ModelRef | undefined {
118
- const override = getModelOverride(options)
66
+ const override = typeof options?.model === "string" ? parseModelString(options.model) : undefined
119
67
  if (override) return override
120
68
 
121
69
  return parseModelString(api.state.config.small_model || api.state.config.model)
@@ -148,25 +96,14 @@ function samePromptTarget(left: PromptTarget | undefined, right: PromptTarget |
148
96
  return false
149
97
  }
150
98
 
151
- function resolvePromptTarget(route: TuiRouteCurrent, workspaceID?: string): PromptTarget | undefined {
152
- if (route.name === "home") return { name: "home", workspaceID }
153
- if (isSessionRoute(route)) {
154
- return { name: "session", sessionID: route.params.sessionID }
155
- }
156
- return undefined
157
- }
158
-
159
- function isPromptTargetActive(route: TuiRouteCurrent, target: PromptTarget): boolean {
160
- if (target.name === "home") return route.name === "home"
161
- return isSessionRoute(route) && route.params.sessionID === target.sessionID
162
- }
163
-
164
- function currentPromptTarget(api: Api, state: PluginState): PromptTarget | undefined {
165
- return state.promptTarget ?? resolvePromptTarget(api.route.current)
166
- }
167
-
168
99
  function isPromptHandleActive(api: Api, state: PluginState, handle: PromptHandle): boolean {
169
- if (!isPromptTargetActive(api.route.current, handle.target)) return false
100
+ const route = api.route.current
101
+ const target = handle.target
102
+ if (target.name === "home") {
103
+ if (route.name !== "home") return false
104
+ } else if (!isSessionRoute(route) || route.params.sessionID !== target.sessionID) {
105
+ return false
106
+ }
170
107
  if (api.state.path.directory !== handle.directory) return false
171
108
 
172
109
  if (handle.ref) {
@@ -247,7 +184,7 @@ async function restorePrompt(
247
184
  return true
248
185
  }
249
186
 
250
- return writePrompt(api, state, handle, prompt.input, signal, prompt)
187
+ return false
251
188
  }
252
189
 
253
190
  function gatherContext(api: Api): string {
@@ -261,14 +198,14 @@ function gatherContext(api: Api): string {
261
198
  const sessionID = route.params.sessionID
262
199
  const messages = api.state.session.messages(sessionID)
263
200
 
264
- const userMessages = messages.filter(isUserMessage)
201
+ const userMessages = messages.filter((message): message is Extract<Message, { role: "user" }> => message.role === "user")
265
202
  const recent = userMessages.slice(-MAX_RECENT_MESSAGES).reverse()
266
203
  if (recent.length > 0) {
267
204
  const prompts: string[] = []
268
205
  for (const msg of recent) {
269
206
  const text = extractVisibleText(api.state.part(msg.id))
270
207
  if (text) {
271
- prompts.push(truncate(text, MAX_PROMPT_PREVIEW_LENGTH))
208
+ prompts.push(text.length > MAX_PROMPT_PREVIEW_LENGTH ? `${text.slice(0, MAX_PROMPT_PREVIEW_LENGTH)}...` : text)
272
209
  }
273
210
  }
274
211
  if (prompts.length > 0) {
@@ -287,19 +224,6 @@ function gatherContext(api: Api): string {
287
224
  return sections.join("\n\n")
288
225
  }
289
226
 
290
- function buildUserMessage(input: string, context: string): string {
291
- const sections = [
292
- "Rewrite the following user draft into a stronger prompt.",
293
- `User draft:\n${input}`,
294
- ]
295
-
296
- if (context) {
297
- sections.splice(1, 0, `Workspace context (use only if directly relevant):\n${context}`)
298
- }
299
-
300
- return sections.join("\n\n")
301
- }
302
-
303
227
  async function enhanceWithModel(
304
228
  api: Api,
305
229
  options: PluginOptions | undefined,
@@ -309,11 +233,18 @@ async function enhanceWithModel(
309
233
  const directory = api.state.path.directory
310
234
  const model = resolveEnhancerModel(api, options)
311
235
  const context = gatherContext(api)
236
+ const userMessage = context
237
+ ? [
238
+ "Rewrite the following user draft into a stronger prompt.",
239
+ `Workspace context (use only if directly relevant):\n${context}`,
240
+ `User draft:\n${input}`,
241
+ ].join("\n\n")
242
+ : ["Rewrite the following user draft into a stronger prompt.", `User draft:\n${input}`].join("\n\n")
312
243
 
313
244
  const created = await api.client.session.create(
314
245
  {
315
246
  directory,
316
- title: makeTempSessionTitle(),
247
+ title: `Prompt Enhancer ${Math.random().toString(36).slice(2, 8)}`,
317
248
  },
318
249
  { signal, throwOnError: true },
319
250
  )
@@ -331,7 +262,7 @@ async function enhanceWithModel(
331
262
  parts: [
332
263
  {
333
264
  type: "text",
334
- text: buildUserMessage(input, context),
265
+ text: userMessage,
335
266
  },
336
267
  ],
337
268
  },
@@ -366,7 +297,11 @@ function openEnhanceDialog(
366
297
 
367
298
  if (signal.aborted) return
368
299
 
369
- const target = currentPromptTarget(api, state)
300
+ const target = state.promptTarget ?? (api.route.current.name === "home"
301
+ ? { name: "home" as const }
302
+ : isSessionRoute(api.route.current)
303
+ ? { name: "session" as const, sessionID: api.route.current.params.sessionID }
304
+ : undefined)
370
305
  if (!target) {
371
306
  api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enhancement only works from a prompt." })
372
307
  return