@mtayfur/opencode-prompt-enhancer 0.0.5 → 0.0.6
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 +1 -1
- package/plugins/prompt-enhancer.tsx +47 -67
package/package.json
CHANGED
|
@@ -7,50 +7,46 @@ import type {
|
|
|
7
7
|
TuiPromptInfo,
|
|
8
8
|
TuiPromptRef,
|
|
9
9
|
TuiRouteCurrent,
|
|
10
|
-
TuiSidebarFileItem,
|
|
11
|
-
TuiSidebarTodoItem,
|
|
12
10
|
TuiSlotPlugin,
|
|
13
11
|
} from "@opencode-ai/plugin/tui"
|
|
14
12
|
|
|
15
|
-
const MAX_RECENT_MESSAGES =
|
|
16
|
-
const MAX_CHANGED_FILES =
|
|
17
|
-
const MAX_PROMPT_PREVIEW_LENGTH =
|
|
18
|
-
const MAX_TODOS = 10
|
|
13
|
+
const MAX_RECENT_MESSAGES = 3
|
|
14
|
+
const MAX_CHANGED_FILES = 25
|
|
15
|
+
const MAX_PROMPT_PREVIEW_LENGTH = 250
|
|
19
16
|
const DIALOG_TITLE = "Enhance Prompt"
|
|
20
17
|
const TOAST_TITLE = "Prompt enhancer"
|
|
21
18
|
const TEMP_SESSION_TITLE = "Prompt Enhancer"
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
|
|
20
|
+
function makeTempSessionTitle(): string {
|
|
21
|
+
return `${TEMP_SESSION_TITLE} ${Math.random().toString(36).slice(2, 8)}`
|
|
25
22
|
}
|
|
26
23
|
|
|
27
|
-
const ENHANCER_SYSTEM_PROMPT = `You
|
|
24
|
+
const ENHANCER_SYSTEM_PROMPT = `You are a prompt editor for OpenCode, an AI coding assistant.
|
|
28
25
|
|
|
29
26
|
Goal:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
OpenCode can inspect the workspace,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
- Preserve the
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
-
|
|
42
|
-
-
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
Do not:
|
|
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:
|
|
50
46
|
- Do not invent requirements, files, APIs, dependencies, bugs, or acceptance criteria.
|
|
51
47
|
- Do not broaden scope with extra features, refactors, tests, or docs unless the draft implies them.
|
|
52
|
-
- Do not ask follow-up questions
|
|
53
|
-
- Do not mention
|
|
48
|
+
- Do not ask follow-up questions.
|
|
49
|
+
- Do not mention context, tags, instructions, or the rewriting process.
|
|
54
50
|
- Do not output explanations, markdown fences, or commentary.
|
|
55
51
|
|
|
56
52
|
Output:
|
|
@@ -118,10 +114,6 @@ function truncate(value: string, maxLength: number): string {
|
|
|
118
114
|
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value
|
|
119
115
|
}
|
|
120
116
|
|
|
121
|
-
function formatTodo(todo: TuiSidebarTodoItem): string {
|
|
122
|
-
return ` [${todo.status || "?"}] ${todo.content || "untitled"}`
|
|
123
|
-
}
|
|
124
|
-
|
|
125
117
|
function resolveEnhancerModel(api: Api, options: PluginOptions | undefined): ModelRef | undefined {
|
|
126
118
|
const override = getModelOverride(options)
|
|
127
119
|
if (override) return override
|
|
@@ -262,8 +254,7 @@ function gatherContext(api: Api): string {
|
|
|
262
254
|
const sections: string[] = []
|
|
263
255
|
|
|
264
256
|
const dir = api.state.path.directory
|
|
265
|
-
|
|
266
|
-
sections.push(`Working directory: ${dir}${branch ? ` (branch: ${branch})` : ""}`)
|
|
257
|
+
sections.push(`Working directory: ${dir}`)
|
|
267
258
|
|
|
268
259
|
const route = api.route.current
|
|
269
260
|
if (isSessionRoute(route)) {
|
|
@@ -271,7 +262,7 @@ function gatherContext(api: Api): string {
|
|
|
271
262
|
const messages = api.state.session.messages(sessionID)
|
|
272
263
|
|
|
273
264
|
const userMessages = messages.filter(isUserMessage)
|
|
274
|
-
const recent = userMessages.slice(-MAX_RECENT_MESSAGES)
|
|
265
|
+
const recent = userMessages.slice(-MAX_RECENT_MESSAGES).reverse()
|
|
275
266
|
if (recent.length > 0) {
|
|
276
267
|
const prompts: string[] = []
|
|
277
268
|
for (const msg of recent) {
|
|
@@ -281,26 +272,16 @@ function gatherContext(api: Api): string {
|
|
|
281
272
|
}
|
|
282
273
|
}
|
|
283
274
|
if (prompts.length > 0) {
|
|
284
|
-
sections.push(`Recent user prompts in this session (
|
|
275
|
+
sections.push(`Recent user prompts in this session (newest first):\n${prompts.map((p, i) => `${i + 1}. ${p}`).join("\n")}`)
|
|
285
276
|
}
|
|
286
277
|
}
|
|
287
278
|
|
|
288
279
|
const diff = api.state.session.diff(sessionID)
|
|
289
280
|
if (diff.length > 0) {
|
|
290
|
-
const files = diff.slice(0, MAX_CHANGED_FILES).map((f
|
|
291
|
-
|
|
292
|
-
)
|
|
293
|
-
const label = diff.length > MAX_CHANGED_FILES
|
|
294
|
-
? `Files changed in session (showing ${MAX_CHANGED_FILES} of ${diff.length}):`
|
|
295
|
-
: `Files changed in session:`
|
|
296
|
-
sections.push(`${label}\n${files.join("\n")}`)
|
|
281
|
+
const files = diff.slice(0, MAX_CHANGED_FILES).map((f) => ` ${f.file}`)
|
|
282
|
+
sections.push(`Files changed in session:\n${files.join("\n")}`)
|
|
297
283
|
}
|
|
298
284
|
|
|
299
|
-
const todos = api.state.session.todo(sessionID)
|
|
300
|
-
if (todos.length > 0) {
|
|
301
|
-
const todoLines = todos.slice(0, MAX_TODOS).map(formatTodo)
|
|
302
|
-
sections.push(`Active todos:\n${todoLines.join("\n")}`)
|
|
303
|
-
}
|
|
304
285
|
}
|
|
305
286
|
|
|
306
287
|
return sections.join("\n\n")
|
|
@@ -332,13 +313,13 @@ async function enhanceWithModel(
|
|
|
332
313
|
const created = await api.client.session.create(
|
|
333
314
|
{
|
|
334
315
|
directory,
|
|
335
|
-
title:
|
|
316
|
+
title: makeTempSessionTitle(),
|
|
336
317
|
},
|
|
337
318
|
{ signal, throwOnError: true },
|
|
338
319
|
)
|
|
339
320
|
|
|
340
321
|
const tempSessionID = created.data?.id
|
|
341
|
-
if (!tempSessionID) throw new Error("
|
|
322
|
+
if (!tempSessionID) throw new Error("Failed to start prompt enhancer.")
|
|
342
323
|
|
|
343
324
|
try {
|
|
344
325
|
const response = await api.client.session.prompt(
|
|
@@ -358,10 +339,10 @@ async function enhanceWithModel(
|
|
|
358
339
|
)
|
|
359
340
|
|
|
360
341
|
const parts = response.data?.parts
|
|
361
|
-
if (!parts) throw new Error("Enhancer
|
|
342
|
+
if (!parts) throw new Error("Enhancer returned no response.")
|
|
362
343
|
|
|
363
344
|
const enhanced = extractVisibleText(parts)
|
|
364
|
-
if (!enhanced) throw new Error("Enhancer
|
|
345
|
+
if (!enhanced) throw new Error("Enhancer returned no text.")
|
|
365
346
|
return enhanced
|
|
366
347
|
} finally {
|
|
367
348
|
try {
|
|
@@ -379,7 +360,7 @@ function openEnhanceDialog(
|
|
|
379
360
|
signal: AbortSignal,
|
|
380
361
|
): void {
|
|
381
362
|
if (state.enhancing) {
|
|
382
|
-
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enhancement
|
|
363
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enhancement in progress." })
|
|
383
364
|
return
|
|
384
365
|
}
|
|
385
366
|
|
|
@@ -387,7 +368,7 @@ function openEnhanceDialog(
|
|
|
387
368
|
|
|
388
369
|
const target = currentPromptTarget(api, state)
|
|
389
370
|
if (!target) {
|
|
390
|
-
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "
|
|
371
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enhancement only works from a prompt." })
|
|
391
372
|
return
|
|
392
373
|
}
|
|
393
374
|
|
|
@@ -402,19 +383,19 @@ function openEnhanceDialog(
|
|
|
402
383
|
api.ui.dialog.replace(() => (
|
|
403
384
|
<api.ui.DialogPrompt
|
|
404
385
|
title={DIALOG_TITLE}
|
|
405
|
-
placeholder="Describe
|
|
386
|
+
placeholder="Describe the task..."
|
|
406
387
|
value={initialValue}
|
|
407
388
|
onCancel={() => api.ui.dialog.clear()}
|
|
408
389
|
onConfirm={(value) => {
|
|
409
390
|
const input = value.trim()
|
|
410
391
|
if (!input) {
|
|
411
|
-
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "
|
|
392
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Enter a prompt first." })
|
|
412
393
|
api.ui.dialog.clear()
|
|
413
394
|
return
|
|
414
395
|
}
|
|
415
396
|
|
|
416
397
|
if (!isPromptHandleActive(api, state, handle)) {
|
|
417
|
-
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while
|
|
398
|
+
api.ui.toast({ variant: "warning", title: TOAST_TITLE, message: "Prompt changed while dialog was open." })
|
|
418
399
|
api.ui.dialog.clear()
|
|
419
400
|
return
|
|
420
401
|
}
|
|
@@ -448,7 +429,7 @@ function openEnhanceDialog(
|
|
|
448
429
|
api.ui.toast({
|
|
449
430
|
variant: "warning",
|
|
450
431
|
title: TOAST_TITLE,
|
|
451
|
-
message: "Enhanced prompt is ready, but
|
|
432
|
+
message: "Enhanced prompt is ready, but that prompt is no longer active.",
|
|
452
433
|
})
|
|
453
434
|
return
|
|
454
435
|
}
|
|
@@ -456,7 +437,7 @@ function openEnhanceDialog(
|
|
|
456
437
|
api.ui.toast({
|
|
457
438
|
variant: "success",
|
|
458
439
|
title: "Prompt enhanced",
|
|
459
|
-
message: "Enhanced prompt
|
|
440
|
+
message: "Enhanced prompt added to input.",
|
|
460
441
|
duration: 3000,
|
|
461
442
|
})
|
|
462
443
|
} catch (error) {
|
|
@@ -478,10 +459,10 @@ function openEnhanceDialog(
|
|
|
478
459
|
}
|
|
479
460
|
}
|
|
480
461
|
|
|
481
|
-
const baseMessage = error instanceof Error ? error.message : "
|
|
462
|
+
const baseMessage = error instanceof Error ? error.message : "Prompt enhancement failed."
|
|
482
463
|
const message = restored
|
|
483
464
|
? baseMessage
|
|
484
|
-
: `${baseMessage} Original prompt could not be restored because the
|
|
465
|
+
: `${baseMessage} Original prompt could not be restored because the prompt changed.`
|
|
485
466
|
api.ui.toast({ variant: "error", title: TOAST_TITLE, message })
|
|
486
467
|
} finally {
|
|
487
468
|
state.enhancing = false
|
|
@@ -503,7 +484,6 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
503
484
|
ref={(ref) => bindPromptRef(state, { name: "home", workspaceID: props.workspace_id }, props.ref, ref)}
|
|
504
485
|
workspaceID={props.workspace_id}
|
|
505
486
|
right={<api.ui.Slot name="home_prompt_right" workspace_id={props.workspace_id} />}
|
|
506
|
-
placeholders={HOME_PROMPT_PLACEHOLDERS}
|
|
507
487
|
/>
|
|
508
488
|
)
|
|
509
489
|
},
|
|
@@ -528,7 +508,7 @@ const tui: TuiPlugin = async (api, options) => {
|
|
|
528
508
|
{
|
|
529
509
|
title: DIALOG_TITLE,
|
|
530
510
|
value: "prompt-enhancer.enhance",
|
|
531
|
-
description: "Enhance prompt
|
|
511
|
+
description: "Enhance current prompt (Ctrl+E)",
|
|
532
512
|
category: "Prompt",
|
|
533
513
|
keybind: "ctrl+e",
|
|
534
514
|
suggested: true,
|