diff --git a/apps/backend/config/prompts/jira-issue-watch-default.md b/apps/backend/config/prompts/jira-issue-watch-default.md new file mode 100644 index 0000000000..cee5e18d32 --- /dev/null +++ b/apps/backend/config/prompts/jira-issue-watch-default.md @@ -0,0 +1,24 @@ +You have been assigned a JIRA ticket to work on. + +**Ticket:** {{issue.url}} +**Key:** {{issue.key}} +**Summary:** {{issue.summary}} +**Type:** {{issue.type}} +**Status:** {{issue.status}} +**Priority:** {{issue.priority}} +**Assignee:** {{issue.assignee}} +**Reporter:** {{issue.reporter}} +**Project:** {{issue.project}} + +## Description + +{{issue.description}} + +## Instructions + +1. Read the ticket carefully and understand the requirements. +2. Explore the codebase to understand the relevant code and architecture. +3. Implement the changes described in the ticket. +4. Write or update tests to cover the changes. +5. Run the test suite to ensure nothing is broken. +6. Commit your changes with a descriptive commit message referencing the ticket key. diff --git a/apps/backend/internal/orchestrator/event_handlers_jira.go b/apps/backend/internal/orchestrator/event_handlers_jira.go index 2edc5591b5..71d9a0f49f 100644 --- a/apps/backend/internal/orchestrator/event_handlers_jira.go +++ b/apps/backend/internal/orchestrator/event_handlers_jira.go @@ -7,6 +7,7 @@ import ( "go.uber.org/zap" + promptcfg "github.com/kandev/kandev/config/prompts" "github.com/kandev/kandev/internal/events" "github.com/kandev/kandev/internal/events/bus" "github.com/kandev/kandev/internal/jira" @@ -162,12 +163,12 @@ func (s *Service) attachJiraIssueTaskID(ctx context.Context, evt *jira.NewJiraIs } // interpolateJiraPrompt replaces {{issue.*}} placeholders with ticket fields. -// When the template is empty, the agent receives a minimal default that links -// to the JIRA ticket — enough context to start without forcing every watch to -// supply boilerplate. +// When the template is empty, falls back to the embedded default in +// config/prompts/jira-issue-watch-default.md — same pattern the GitHub +// issue/PR watchers use so the default lives in one editable place. func interpolateJiraPrompt(template string, t *jira.JiraTicket) string { if strings.TrimSpace(template) == "" { - template = "Investigate JIRA ticket {{issue.key}}: {{issue.summary}}\n\n{{issue.url}}" + template = promptcfg.Get("jira-issue-watch-default") } r := strings.NewReplacer( "{{issue.key}}", t.Key, diff --git a/apps/web/components/jira/jira-issue-watch-dialog.tsx b/apps/web/components/jira/jira-issue-watch-dialog.tsx index e3f510a588..4bbbd762d4 100644 --- a/apps/web/components/jira/jira-issue-watch-dialog.tsx +++ b/apps/web/components/jira/jira-issue-watch-dialog.tsx @@ -15,11 +15,21 @@ import { } from "@kandev/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select"; import { Textarea } from "@kandev/ui/textarea"; +import { IconInfoCircle } from "@tabler/icons-react"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@kandev/ui/tooltip"; import { useAppStore } from "@/components/state-provider"; import { useSettingsData } from "@/hooks/domains/settings/use-settings-data"; import { useWorkflows } from "@/hooks/use-workflows"; import { useWorkflowSteps, stepPlaceholder } from "@/hooks/use-workflow-steps"; import { searchJiraTickets } from "@/lib/api/domains/jira-api"; +import { + ScriptEditor, + computeEditorHeight, +} from "@/components/settings/profile-edit/script-editor"; +import { + JIRA_ISSUE_WATCH_PLACEHOLDERS, + DEFAULT_JIRA_ISSUE_WATCH_PROMPT, +} from "@/components/jira/jira-issue-watch-placeholders"; import type { CreateJiraIssueWatchInput, JiraIssueWatch, @@ -52,7 +62,6 @@ type FormState = { }; const DEFAULT_JQL = `project = PROJ AND status = "Open" ORDER BY created DESC`; -const DEFAULT_PROMPT = `Investigate JIRA ticket {{issue.key}}: {{issue.summary}}\n\n{{issue.url}}`; function makeEmptyForm(workspaceId: string): FormState { return { @@ -62,7 +71,7 @@ function makeEmptyForm(workspaceId: string): FormState { workflowStepId: "", agentProfileId: "", executorProfileId: "", - prompt: DEFAULT_PROMPT, + prompt: DEFAULT_JIRA_ISSUE_WATCH_PROMPT, enabled: true, pollInterval: 300, }; @@ -76,7 +85,7 @@ function formStateFromWatch(w: JiraIssueWatch): FormState { workflowStepId: w.workflowStepId, agentProfileId: w.agentProfileId, executorProfileId: w.executorProfileId, - prompt: w.prompt || DEFAULT_PROMPT, + prompt: w.prompt.trim() ? w.prompt : DEFAULT_JIRA_ISSUE_WATCH_PROMPT, enabled: w.enabled, pollInterval: w.pollIntervalSeconds, }; @@ -183,22 +192,49 @@ function JQLField({ jql, onChange }: { jql: string; onChange: (v: string) => voi ); } +function PlaceholdersHelp() { + return ( + + + + + + +

Available placeholders:

+
    + {JIRA_ISSUE_WATCH_PLACEHOLDERS.map((p) => ( +
  • + {`{{${p.key}}}`}{" "} + {p.description} +
  • + ))} +
+
+
+
+ ); +} + function PromptField({ value, onChange }: { value: string; onChange: (v: string) => void }) { return (
- +
+ + +

- Sent to the agent for each new ticket. Placeholders: {`{{issue.key}}`},{" "} - {`{{issue.summary}}`}, {`{{issue.url}}`}, {`{{issue.status}}`}, {`{{issue.priority}}`},{" "} - {`{{issue.type}}`}, {`{{issue.assignee}}`}, {`{{issue.reporter}}`}, {`{{issue.project}}`},{" "} - {`{{issue.description}}`}. + The prompt sent to the agent for each new ticket. Type {"{{"} to insert placeholders.

-