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}
+
- 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.
-