flake-update-20260505
1/**
2 * Epic-related action handlers for Jira extension
3 */
4
5import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
6import type { JiraDetails } from "./types";
7import { getErrorMessage, serializedConfirm } from "./utils";
8
9/**
10 * View epic with all child issues
11 */
12export async function handleEpicView(
13 pi: ExtensionAPI,
14 params: any,
15 signal: AbortSignal | undefined,
16 onUpdate: any,
17 ctx: ExtensionContext,
18): Promise<any> {
19 if (!params.key) {
20 return {
21 content: [{ type: "text", text: "Error: 'key' parameter is required for epic-view action" }],
22 details: { action: "epic-view", error: "missing_key" } as JiraDetails,
23 isError: true,
24 };
25 }
26
27 onUpdate?.({ content: [{ type: "text", text: `Fetching epic ${params.key}...` }] });
28
29 // First, view the epic itself
30 const epicResult = await pi.exec("jira", ["issue", "view", params.key, "--plain"], { signal, timeout: 20000 });
31
32 if (epicResult.code !== 0) {
33 return {
34 content: [{ type: "text", text: getErrorMessage(epicResult.stderr, "View epic") }],
35 details: { action: "epic-view", error: epicResult.stderr, issueKey: params.key } as JiraDetails,
36 isError: true,
37 };
38 }
39
40 onUpdate?.({ content: [{ type: "text", text: `Fetching child issues for ${params.key}...` }] });
41
42 // Then, list all issues linked to this epic
43 const childrenResult = await pi.exec(
44 "jira",
45 ["issue", "list", "--plain", "--jql", `"Epic Link" = ${params.key}`],
46 { signal, timeout: 30000 },
47 );
48
49 let output = `Epic: ${params.key}\n\n`;
50 output += `${epicResult.stdout}\n\n`;
51 output += `=`.repeat(80) + "\n\n";
52 output += `Child Issues:\n\n`;
53
54 if (childrenResult.code === 0 && childrenResult.stdout.trim()) {
55 output += childrenResult.stdout;
56 } else {
57 output += "No child issues found.";
58 }
59
60 return {
61 content: [{ type: "text", text: output }],
62 details: { action: "epic-view", output, issueKey: params.key } as JiraDetails,
63 };
64}
65
66/**
67 * Link issue to epic
68 */
69export async function handleLinkToEpic(
70 pi: ExtensionAPI,
71 params: any,
72 signal: AbortSignal | undefined,
73 onUpdate: any,
74 ctx: ExtensionContext,
75): Promise<any> {
76 if (!params.issue) {
77 return {
78 content: [{ type: "text", text: "Error: 'issue' parameter is required for link-to-epic action" }],
79 details: { action: "link-to-epic", error: "missing_issue" } as JiraDetails,
80 isError: true,
81 };
82 }
83
84 if (!params.epic) {
85 return {
86 content: [{ type: "text", text: "Error: 'epic' parameter is required for link-to-epic action" }],
87 details: { action: "link-to-epic", error: "missing_epic" } as JiraDetails,
88 isError: true,
89 };
90 }
91
92 // APPROVAL GATE
93 if (ctx.hasUI) {
94 const confirmMessage = `Issue: ${params.issue}\nEpic: ${params.epic}\n\nThis will link the issue to the epic.`;
95
96 const confirmed = await serializedConfirm(ctx, `Link ${params.issue} to epic?`, confirmMessage);
97
98 if (!confirmed) {
99 ctx.ui.notify("Link to epic cancelled", "info");
100 return {
101 content: [{ type: "text", text: "Link operation cancelled by user" }],
102 details: { action: "link-to-epic", cancelled: true } as JiraDetails,
103 };
104 }
105 }
106
107 onUpdate?.({ content: [{ type: "text", text: `Linking ${params.issue} to epic ${params.epic}...` }] });
108
109 // Link issue to epic using edit command with --parent flag
110 const result = await pi.exec("jira", ["issue", "edit", params.issue, "-P", params.epic, "--no-input"], {
111 signal,
112 timeout: 20000,
113 });
114
115 if (result.code !== 0) {
116 return {
117 content: [{ type: "text", text: getErrorMessage(result.stderr, "Link to epic") }],
118 details: { action: "link-to-epic", error: result.stderr } as JiraDetails,
119 isError: true,
120 };
121 }
122
123 return {
124 content: [{ type: "text", text: `Linked ${params.issue} to epic ${params.epic}` }],
125 details: {
126 action: "link-to-epic",
127 output: result.stdout,
128 issueKey: params.issue,
129 fromKey: params.issue,
130 toKey: params.epic,
131 } as JiraDetails,
132 };
133}