feat: expand magic prompts coverage and split generation prompts (#835)

- Add configurable visible/instructions prompt families for commit/PR generation, PR checks/comments flows, and git conflict resolution helpers.
- Refactor prompt sending to explicit visible + synthetic parts instead of newline-based splitting, with legacy override migration for old keys.
- Polish Magic Prompts settings UX with grouped sidebar entries, tooltip-based descriptions, AI icon, and validation that visible prompts cannot be empty across web and VS Code runtimes.
This commit is contained in:
Bohdan Triapitsyn
2026-04-07 22:51:14 +03:00
committed by GitHub
parent 2e5b02e753
commit 5f0d1623ae
21 changed files with 1415 additions and 279 deletions
@@ -14,6 +14,7 @@ import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
import { toast } from '@/components/ui';
import { getConflictDetails, type MergeConflictDetails } from '@/lib/gitApi';
import { renderMagicPrompt } from '@/lib/magicPrompts';
interface ConflictDialogProps {
open: boolean;
@@ -65,36 +66,29 @@ export const ConflictDialog: React.FC<ConflictDialogProps> = ({
});
}, [open, directory]);
const buildConflictContext = React.useCallback((): {
const buildConflictContext = React.useCallback(async (): Promise<{
visibleText: string;
instructionsText: string;
payloadText: string;
} | null => {
} | null> => {
if (!conflictDetails) return null;
const operationLabel = operation === 'merge' ? 'merge' : 'rebase';
const headRef = conflictDetails.headInfo || (operation === 'merge' ? 'MERGE_HEAD' : 'REBASE_HEAD');
const continueCmd = operation === 'merge' ? 'git commit --no-edit' : 'git rebase --continue';
const visibleText = `Resolve ${operationLabel} conflicts, stage the resolved files, and complete the ${operationLabel}. Preserve the intent of changes from ${headRef}.`;
const visibleText = await renderMagicPrompt('git.conflict.resolve.visible', {
operation_label: operationLabel,
head_ref: headRef,
});
const instructionsText = `Git ${operationLabel} operation is in progress with conflicts.
- Directory: ${directory}
- Operation: ${operation}
- Head Info: ${conflictDetails.headInfo || 'N/A'}
Required steps:
1. Read each conflicted file to understand the conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
2. Edit each file to resolve conflicts by choosing the correct code or merging both changes appropriately
3. Stage all resolved files with: git add <file>
4. Complete the ${operationLabel} with: ${continueCmd}
Important:
- Remove ALL conflict markers from files (<<<<<<< HEAD, =======, >>>>>>>)
- Make sure the final code is syntactically correct and preserves intent from both sides
- Do not leave any files with unresolved conflict markers
- After completing all steps, confirm the ${operationLabel} was successful
`;
const instructionsText = await renderMagicPrompt('git.conflict.resolve.instructions', {
operation_label: operationLabel,
directory,
operation,
head_info: conflictDetails.headInfo || 'N/A',
continue_cmd: continueCmd,
});
const payloadText = `${operationLabel} conflict context (JSON)\n${JSON.stringify(
{
@@ -122,8 +116,8 @@ Important:
onOpenChange(false);
};
const handleResolveInCurrentSession = () => {
const context = buildConflictContext();
const handleResolveInCurrentSession = async () => {
const context = await buildConflictContext();
if (!context) {
toast.error('No conflict details available');
return;
@@ -146,8 +140,8 @@ Important:
onOpenChange(false);
};
const handleResolveInNewSession = () => {
const context = buildConflictContext();
const handleResolveInNewSession = async () => {
const context = await buildConflictContext();
if (!context) {
toast.error('No conflict details available');
return;
@@ -234,7 +228,9 @@ Important:
<div className="flex flex-col gap-2 pt-2">
<Button
variant="default"
onClick={handleResolveInNewSession}
onClick={() => {
void handleResolveInNewSession();
}}
disabled={isLoading || !conflictDetails}
className="w-full gap-2"
>
@@ -19,6 +19,7 @@ import { useSessionUIStore } from '@/sync/session-ui-store';
import { useInputStore } from '@/sync/input-store';
import { useUIStore } from '@/stores/useUIStore';
import { execCommand } from '@/lib/execCommands';
import { renderMagicPrompt } from '@/lib/magicPrompts';
import {
abortIntegrate,
computeIntegratePlan,
@@ -184,29 +185,18 @@ export const IntegrateCommitsSection: React.FC<{
const openNewSessionDraft = useSessionUIStore((s) => s.openNewSessionDraft);
const buildConflictContext = React.useCallback((payload: { state: IntegrateInProgress; details: IntegrateConflictDetails }) => {
const visibleText = `Resolve cherry-pick conflicts, stage the resolved files, and continue the cherry-pick. Keep intent of commit ${payload.state.currentCommit} onto branch ${payload.state.targetBranch}.`;
const instructionsText = `Worktree commit integration (cherry-pick) is in progress with conflicts.
- Repo root: ${payload.state.repoRoot}
- Temp target worktree: ${payload.state.tempWorktreePath}
- Source branch: ${payload.state.sourceBranch}
- Target branch: ${payload.state.targetBranch}
- Current commit: ${payload.state.currentCommit}
Required steps:
1. Read each conflicted file in the temp worktree to understand the conflict markers (<<<<<<< HEAD, =======, >>>>>>> ...)
2. Edit each file to resolve conflicts by choosing the correct code or merging both changes appropriately
3. Stage all resolved files with: git add <file>
4. Complete the cherry-pick with: git cherry-pick --continue
Important:
- Work inside the temp worktree directory: ${payload.state.tempWorktreePath}
- Remove ALL conflict markers from files (<<<<<<< HEAD, =======, >>>>>>>)
- Preserve the intent of the commit being applied
- Make sure the final code is syntactically correct
- Do not leave any files with unresolved conflict markers
- After completing all steps, confirm the cherry-pick was successful
`;
const buildConflictContext = React.useCallback(async (payload: { state: IntegrateInProgress; details: IntegrateConflictDetails }) => {
const visibleText = await renderMagicPrompt('git.integrate.cherrypick.resolve.visible', {
current_commit: payload.state.currentCommit,
target_branch: payload.state.targetBranch,
});
const instructionsText = await renderMagicPrompt('git.integrate.cherrypick.resolve.instructions', {
repo_root: payload.state.repoRoot,
temp_worktree_path: payload.state.tempWorktreePath,
source_branch: payload.state.sourceBranch,
target_branch: payload.state.targetBranch,
current_commit: payload.state.currentCommit,
});
const payloadText = `Cherry-pick conflict context (JSON)\n${JSON.stringify({
repoRoot: payload.state.repoRoot,
tempWorktreePath: payload.state.tempWorktreePath,
@@ -227,11 +217,11 @@ Important:
const setPendingInputText = useInputStore((s) => s.setPendingInputText);
const setPendingSyntheticParts = useInputStore((s) => s.setPendingSyntheticParts);
const handleResolveWithAi = React.useCallback((
const handleResolveWithAi = React.useCallback(async (
payload: { state: IntegrateInProgress; details: IntegrateConflictDetails },
useNewSession: boolean
) => {
const context = buildConflictContext(payload);
const context = await buildConflictContext(payload);
if (useNewSession) {
// Open new session with the conflict context as initial prompt + synthetic parts
@@ -45,6 +45,7 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible';
import { generatePullRequestDescription } from '@/lib/gitApi';
import { renderMagicPrompt } from '@/lib/magicPrompts';
import { openExternalUrl } from '@/lib/url';
import { useRuntimeAPIs } from '@/hooks/useRuntimeAPIs';
import { useDeviceInfo } from '@/lib/device';
@@ -831,13 +832,8 @@ export const PullRequestSection: React.FC<{
return;
}
const visibleText = 'Review these PR failed checks and propose likely fixes. Do not implement until I confirm.';
const instructionsText = `Use the attached checks payload.
- Summarize what is failing.
- Prioritize check annotations/errors over generic status text.
- Identify likely root cause(s).
- Propose a minimal fix plan and verification steps.
- No speculation: ask for missing info if needed.`;
const visibleText = await renderMagicPrompt('github.pr.checks.review.visible');
const instructionsText = await renderMagicPrompt('github.pr.checks.review.instructions');
const failedAnnotations = failed.flatMap((run) => {
const annotations = Array.isArray(run.annotations) ? run.annotations : [];
return annotations.map((annotation) => ({
@@ -888,12 +884,8 @@ export const PullRequestSection: React.FC<{
return;
}
const visibleText = 'Review these PR comments and propose the required changes and next actions. Do not implement until I confirm.';
const instructionsText = `Use the attached comments payload.
- Identify required vs optional changes.
- Call out intent/implementation mismatch if present.
- Propose a minimal plan and verification steps.
- No speculation: ask for missing info if needed.`;
const visibleText = await renderMagicPrompt('github.pr.comments.review.visible');
const instructionsText = await renderMagicPrompt('github.pr.comments.review.instructions');
const payloadText = `GitHub PR comments (JSON)\n${JSON.stringify({
repo: context.repo ?? null,
pr: context.pr ?? null,
@@ -908,7 +900,7 @@ export const PullRequestSection: React.FC<{
}
}, [directory, dispatchSyntheticPrompt, github, pr, resolveChatDispatchTarget, setActiveMainTab]);
const sendSingleCommentToChat = React.useCallback((comment: TimelineCommentItem) => {
const sendSingleCommentToChat = React.useCallback(async (comment: TimelineCommentItem) => {
setCommentsDialogOpen(false);
setActiveMainTab('chat');
@@ -917,12 +909,8 @@ export const PullRequestSection: React.FC<{
return;
}
const visibleText = 'Address this comment from PR and propose required changes. Do not implement until I confirm.';
const instructionsText = `Use the attached single-comment payload.
- Explain what the reviewer is asking for.
- Identify exact code areas likely impacted.
- Propose a minimal implementation plan and verification steps.
- Call out ambiguity and ask focused follow-up questions if needed.`;
const visibleText = await renderMagicPrompt('github.pr.comment.single.visible');
const instructionsText = await renderMagicPrompt('github.pr.comment.single.instructions');
const payloadText = `GitHub PR comment (JSON)\n${JSON.stringify({
repo: commentsDetails?.repo ?? null,
pr: commentsDetails?.pr ?? pr ?? null,
@@ -1947,7 +1935,9 @@ export const PullRequestSection: React.FC<{
variant="ghost"
size="sm"
className="h-6 px-0 has-[>svg]:px-0 sm:px-2 sm:has-[>svg]:px-2.5 text-[var(--status-success)] hover:bg-[var(--status-success-background)] hover:text-[var(--status-success)] justify-start"
onClick={() => sendSingleCommentToChat(comment)}
onClick={() => {
void sendSingleCommentToChat(comment);
}}
aria-label="Send this comment to agent"
>
<RiAiGenerate2 className="size-3.5" />