main
  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}