@mtayfur/opencode-prompt-enhancer 0.0.7 → 0.0.9
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/package.json +2 -1
- package/plugins/enhancer-system-prompt.ts +21 -24
- package/plugins/prompt-enhancer.tsx +104 -12
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mtayfur/opencode-prompt-enhancer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "OpenCode plugin that rewrites rough drafts into stronger prompts.",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@opencode-ai/plugin": "*",
|
|
29
29
|
"@opencode-ai/sdk": "*",
|
|
30
|
+
"@opentui/core": "*",
|
|
30
31
|
"@opentui/solid": "*",
|
|
31
32
|
"solid-js": "*"
|
|
32
33
|
},
|
|
@@ -1,30 +1,27 @@
|
|
|
1
|
-
export const ENHANCER_SYSTEM_PROMPT = `You
|
|
1
|
+
export const ENHANCER_SYSTEM_PROMPT = `You rewrite rough developer drafts into concise, high-leverage prompts for a terminal AI coding agent.
|
|
2
2
|
|
|
3
3
|
Goal:
|
|
4
|
-
|
|
4
|
+
Produce the strongest next prompt without changing what the user wants. Preserve intent, scope, language, constraints, and requested mode. Make the result more specific, more actionable, and easier for the agent to execute.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
Rules:
|
|
7
|
+
- Keep it compact and direct. Remove filler, pleasantries, hedging, and repetition.
|
|
8
|
+
- Preserve the request form. Questions stay questions. Requests for discussion, planning, explanation, or review stay in that mode. Otherwise rewrite for direct execution.
|
|
9
|
+
- Assume the draft may be a short follow-up in an ongoing coding session. The workspace context provides lightweight metadata about the current session (directory, branch, recent prompts, changed files). Use it only to resolve vague references:
|
|
10
|
+
* "this", "that bug", "that function" → match against recent prompts and changed files when relevant
|
|
11
|
+
* "the same file", "this file" → match against files listed in changed files or recent prompts
|
|
12
|
+
* If the draft mentions "it" or "this" without a clear antecedent, check recent prompts for the topic
|
|
13
|
+
* If context does not make the reference unambiguous, keep it vague.
|
|
14
|
+
- When a concrete target is clear, name it explicitly: the file, component, command, error, test, or behavior. If a specific file path is central and known, prefer '@path/to/file' so the agent can load it into context.
|
|
15
|
+
- Preserve file paths, commands, identifiers, error text, and quoted text verbatim except for minor typo cleanup.
|
|
16
|
+
- Do not add requirements the user did not ask for: no extra features, refactors, tests, docs, plans, or acceptance criteria.
|
|
17
|
+
- Do not add agent-housekeeping instructions the target agent already knows, such as "inspect the codebase first", "follow local conventions", "keep scope tight", or "verify the change", unless the draft explicitly asks for them.
|
|
18
|
+
- If the draft is already sharp, look for at least one meaningful improvement: tighten a vague phrase, resolve a reference the context can answer, or remove filler. Only leave it unchanged when every possible edit would make it worse.
|
|
19
|
+
- Do not ask follow-up questions. Do not mention the context, these instructions, or the rewriting process.
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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.
|
|
21
|
+
Examples:
|
|
22
|
+
"fix this login bug" -> "Fix the login bug in @src/auth/login.ts where the session token is dropped after refresh."
|
|
23
|
+
"can you review this caching change" -> "Review the caching change in @src/cache.ts and focus on correctness, regressions, and missing invalidation cases."
|
|
24
|
+
"why is this test failing" -> "Explain why 'user service creates admins' fails in @tests/user-service.spec.ts and identify the root cause."
|
|
28
25
|
|
|
29
26
|
Output:
|
|
30
|
-
Return exactly one enhanced
|
|
27
|
+
Return exactly one enhanced prompt as plain text.`
|
|
@@ -27,6 +27,8 @@ type PluginState = {
|
|
|
27
27
|
enhancing: boolean
|
|
28
28
|
promptRef?: TuiPromptRef
|
|
29
29
|
promptTarget?: PromptTarget
|
|
30
|
+
lastOriginal?: TuiPromptInfo
|
|
31
|
+
lastEnhancedInput?: string
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
type PromptTarget =
|
|
@@ -193,6 +195,11 @@ function gatherContext(api: Api): string {
|
|
|
193
195
|
const dir = api.state.path.directory
|
|
194
196
|
sections.push(`Working directory: ${dir}`)
|
|
195
197
|
|
|
198
|
+
const branch = api.state.vcs?.branch
|
|
199
|
+
if (branch) {
|
|
200
|
+
sections.push(`Current branch: ${branch}`)
|
|
201
|
+
}
|
|
202
|
+
|
|
196
203
|
const route = api.route.current
|
|
197
204
|
if (isSessionRoute(route)) {
|
|
198
205
|
const sessionID = route.params.sessionID
|
|
@@ -215,10 +222,11 @@ function gatherContext(api: Api): string {
|
|
|
215
222
|
|
|
216
223
|
const diff = api.state.session.diff(sessionID)
|
|
217
224
|
if (diff.length > 0) {
|
|
218
|
-
const files = diff.slice(0, MAX_CHANGED_FILES).map((f) => `
|
|
225
|
+
const files = diff.slice(0, MAX_CHANGED_FILES).map((f) => ` @${f.file}`)
|
|
219
226
|
sections.push(`Files changed in session:\n${files.join("\n")}`)
|
|
220
227
|
}
|
|
221
228
|
|
|
229
|
+
|
|
222
230
|
}
|
|
223
231
|
|
|
224
232
|
return sections.join("\n\n")
|
|
@@ -233,13 +241,11 @@ async function enhanceWithModel(
|
|
|
233
241
|
const directory = api.state.path.directory
|
|
234
242
|
const model = resolveEnhancerModel(api, options)
|
|
235
243
|
const context = gatherContext(api)
|
|
236
|
-
const userMessage =
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
].join("\n\n")
|
|
242
|
-
: ["Rewrite the following user draft into a stronger prompt.", `User draft:\n${input}`].join("\n\n")
|
|
244
|
+
const userMessage = [
|
|
245
|
+
"Rewrite the developer draft below into a clear, direct prompt for a coding agent. Preserve the original intent, scope, and mode. Only make references more specific when the context section supports it.",
|
|
246
|
+
`--- CONTEXT (metadata only — resolve draft references, do not invent) ---\n${context}\n---`,
|
|
247
|
+
`--- DRAFT ---\n${input}\n---`,
|
|
248
|
+
].join("\n\n")
|
|
243
249
|
|
|
244
250
|
const created = await api.client.session.create(
|
|
245
251
|
{
|
|
@@ -341,7 +347,7 @@ function openEnhanceDialog(
|
|
|
341
347
|
variant: "info",
|
|
342
348
|
title: TOAST_TITLE,
|
|
343
349
|
message: "Enhancing prompt...",
|
|
344
|
-
duration:
|
|
350
|
+
duration: 8_000,
|
|
345
351
|
})
|
|
346
352
|
|
|
347
353
|
void (async () => {
|
|
@@ -369,6 +375,9 @@ function openEnhanceDialog(
|
|
|
369
375
|
return
|
|
370
376
|
}
|
|
371
377
|
|
|
378
|
+
state.lastOriginal = originalPrompt
|
|
379
|
+
state.lastEnhancedInput = enhanced
|
|
380
|
+
|
|
372
381
|
api.ui.toast({
|
|
373
382
|
variant: "success",
|
|
374
383
|
title: "Prompt enhanced",
|
|
@@ -397,7 +406,7 @@ function openEnhanceDialog(
|
|
|
397
406
|
const baseMessage = error instanceof Error ? error.message : "Prompt enhancement failed."
|
|
398
407
|
const message = restored
|
|
399
408
|
? baseMessage
|
|
400
|
-
: `${baseMessage} Original prompt could not be restored because the prompt changed.`
|
|
409
|
+
: `${baseMessage} Original prompt could not be restored because the prompt changed. Please re-enter your prompt manually.`
|
|
401
410
|
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
402
411
|
} finally {
|
|
403
412
|
state.enhancing = false
|
|
@@ -408,6 +417,72 @@ function openEnhanceDialog(
|
|
|
408
417
|
))
|
|
409
418
|
}
|
|
410
419
|
|
|
420
|
+
function revertEnhancement(
|
|
421
|
+
api: Api,
|
|
422
|
+
state: PluginState,
|
|
423
|
+
signal: AbortSignal,
|
|
424
|
+
): void {
|
|
425
|
+
if (!state.lastOriginal) {
|
|
426
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "No enhancement to revert." })
|
|
427
|
+
return
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const target = state.promptTarget
|
|
431
|
+
if (!target) {
|
|
432
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Revert only works from a prompt." })
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const handle: PromptHandle = {
|
|
437
|
+
target,
|
|
438
|
+
directory: api.state.path.directory,
|
|
439
|
+
ref: state.promptRef,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!isPromptHandleActive(api, state, handle)) {
|
|
443
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed since enhancement." })
|
|
444
|
+
return
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const currentInput = handle.ref?.current.input ?? ""
|
|
448
|
+
if (currentInput !== state.lastEnhancedInput) {
|
|
449
|
+
api.ui.toast({
|
|
450
|
+
variant: "warning",
|
|
451
|
+
title: TOAST_TITLE,
|
|
452
|
+
message: "Prompt was manually changed after enhancement. Revert skipped.",
|
|
453
|
+
})
|
|
454
|
+
return
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
void (async () => {
|
|
458
|
+
try {
|
|
459
|
+
const wrote = await restorePrompt(api, state, handle, state.lastOriginal!, signal)
|
|
460
|
+
if (!wrote) {
|
|
461
|
+
api.ui.toast({
|
|
462
|
+
variant: "warning",
|
|
463
|
+
title: TOAST_TITLE,
|
|
464
|
+
message: "Prompt changed while reverting.",
|
|
465
|
+
})
|
|
466
|
+
return
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
state.lastOriginal = undefined
|
|
470
|
+
state.lastEnhancedInput = undefined
|
|
471
|
+
|
|
472
|
+
api.ui.toast({
|
|
473
|
+
variant: "success",
|
|
474
|
+
title: TOAST_TITLE,
|
|
475
|
+
message: "Reverted to original prompt.",
|
|
476
|
+
duration: 3000,
|
|
477
|
+
})
|
|
478
|
+
} catch (error) {
|
|
479
|
+
if (signal.aborted) return
|
|
480
|
+
const message = error instanceof Error ? error.message : "Revert failed."
|
|
481
|
+
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
482
|
+
}
|
|
483
|
+
})()
|
|
484
|
+
}
|
|
485
|
+
|
|
411
486
|
const tui: TuiPlugin = async (api, options) => {
|
|
412
487
|
const state: PluginState = { enhancing: false }
|
|
413
488
|
|
|
@@ -455,19 +530,36 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
455
530
|
openEnhanceDialog(api, options, state, api.lifecycle.signal)
|
|
456
531
|
},
|
|
457
532
|
},
|
|
533
|
+
{
|
|
534
|
+
title: "Revert Enhanced Prompt",
|
|
535
|
+
value: "prompt-enhancer.revert",
|
|
536
|
+
description: "Revert last prompt enhancement (Ctrl+Shift+E)",
|
|
537
|
+
category: "Prompt",
|
|
538
|
+
keybind: "ctrl+shift+e",
|
|
539
|
+
slash: {
|
|
540
|
+
name: "revert-enhance",
|
|
541
|
+
aliases: ["revert-enhancement"],
|
|
542
|
+
},
|
|
543
|
+
onSelect: () => {
|
|
544
|
+
revertEnhancement(api, state, api.lifecycle.signal)
|
|
545
|
+
},
|
|
546
|
+
},
|
|
458
547
|
])
|
|
459
548
|
|
|
460
549
|
api.lifecycle.onDispose(() => {
|
|
461
550
|
unregister()
|
|
551
|
+
api.ui.dialog.clear()
|
|
462
552
|
state.enhancing = false
|
|
463
553
|
state.promptRef = undefined
|
|
464
554
|
state.promptTarget = undefined
|
|
555
|
+
state.lastOriginal = undefined
|
|
556
|
+
state.lastEnhancedInput = undefined
|
|
465
557
|
})
|
|
466
558
|
}
|
|
467
559
|
|
|
468
|
-
const plugin
|
|
560
|
+
const plugin = {
|
|
469
561
|
id: "prompt-enhancer",
|
|
470
562
|
tui,
|
|
471
|
-
}
|
|
563
|
+
} satisfies TuiPluginModule & { id: string }
|
|
472
564
|
|
|
473
565
|
export default plugin
|