Commit 5e18c48aa596
Changed files (6)
dots
pi
agent
extensions
dots/pi/agent/extensions/jira/epic-actions.ts
@@ -106,8 +106,8 @@ export async function handleLinkToEpic(
onUpdate?.({ content: [{ type: "text", text: `Linking ${params.issue} to epic ${params.epic}...` }] });
- // Link issue to epic using edit command
- const result = await pi.exec("jira", ["issue", "edit", params.issue, "--epic", params.epic], {
+ // Link issue to epic using edit command with --parent flag
+ const result = await pi.exec("jira", ["issue", "edit", params.issue, "-P", params.epic, "--no-input"], {
signal,
timeout: 20000,
});
dots/pi/agent/extensions/jira/index.ts
@@ -244,7 +244,7 @@ export default function (pi: ExtensionAPI) {
),
linkType: Type.Optional(
Type.String({
- description: "Link type: blocks, is blocked by, relates to, duplicates, is duplicated by",
+ description: "Link type name (case-insensitive): Blocks, Related, Duplicate, Cloners, Depend, Causality, Document, Incorporates, Informs, Triggers",
}),
),
dots/pi/agent/extensions/jira/link-actions.ts
@@ -6,6 +6,65 @@ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-age
import type { JiraDetails } from "./types";
import { getErrorMessage } from "./utils";
+/**
+ * Map common link type aliases/labels to the correct Jira link type names.
+ * The jira CLI expects the link type *name* (e.g., "Blocks", "Related"),
+ * not the inward/outward relationship labels (e.g., "is blocked by", "relates to").
+ */
+const LINK_TYPE_ALIASES: Record<string, string> = {
+ // Aliases for "Blocks"
+ "blocks": "Blocks",
+ "is blocked by": "Blocks",
+ "blocked by": "Blocks",
+ // Aliases for "Related"
+ "relates to": "Related",
+ "related": "Related",
+ "related to": "Related",
+ // Aliases for "Duplicate"
+ "duplicates": "Duplicate",
+ "is duplicated by": "Duplicate",
+ "duplicate": "Duplicate",
+ // Aliases for "Cloners"
+ "clones": "Cloners",
+ "is cloned by": "Cloners",
+ "cloners": "Cloners",
+ // Aliases for "Depend"
+ "depends on": "Depend",
+ "is depended on by": "Depend",
+ "depend": "Depend",
+ // Aliases for "Causality"
+ "causes": "Causality",
+ "is caused by": "Causality",
+ "causality": "Causality",
+ // Aliases for "Document"
+ "documents": "Document",
+ "is documented by": "Document",
+ "document": "Document",
+ // Aliases for "Incorporates"
+ "incorporates": "Incorporates",
+ "is incorporated by": "Incorporates",
+ // Aliases for "Informs"
+ "informs": "Informs",
+ "is informed by": "Informs",
+ // Aliases for "Triggers"
+ "triggers": "Triggers",
+ "is triggered by": "Triggers",
+};
+
+/**
+ * Resolve a link type string to the correct Jira link type name.
+ * Handles case-insensitive matching and common aliases.
+ */
+function resolveLinkType(linkType: string): string {
+ // Try direct alias lookup (case-insensitive)
+ const normalized = linkType.toLowerCase().trim();
+ if (LINK_TYPE_ALIASES[normalized]) {
+ return LINK_TYPE_ALIASES[normalized];
+ }
+ // Return as-is (the jira CLI is case-insensitive for valid names)
+ return linkType;
+}
+
/**
* Link two issues together
*/
@@ -40,12 +99,15 @@ export async function handleLink(
};
}
+ // Resolve link type aliases to correct Jira link type names
+ const resolvedLinkType = resolveLinkType(params.linkType);
+
// APPROVAL GATE
if (ctx.hasUI) {
const confirmMessage =
`From: ${params.from}\n` +
`To: ${params.to}\n` +
- `Link type: ${params.linkType}\n\n` +
+ `Link type: ${resolvedLinkType}\n\n` +
`This will create an issue link.`;
const confirmed = await ctx.ui.confirm("Link issues?", confirmMessage);
@@ -64,7 +126,7 @@ export async function handleLink(
// Use jira CLI to create link
const result = await pi.exec(
"jira",
- ["issue", "link", params.from, params.to, params.linkType],
+ ["issue", "link", params.from, params.to, resolvedLinkType],
{ signal, timeout: 20000 },
);
@@ -77,7 +139,7 @@ export async function handleLink(
}
return {
- content: [{ type: "text", text: `Linked ${params.from} ${params.linkType} ${params.to}` }],
+ content: [{ type: "text", text: `Linked ${params.from} ${resolvedLinkType} ${params.to}` }],
details: {
action: "link",
output: result.stdout,
dots/pi/agent/extensions/jira/package.json
@@ -3,9 +3,10 @@
"version": "1.0.0",
"type": "module",
"scripts": {
- "test": "bun test test.ts"
+ "test": "bun test jira.test.ts"
},
"devDependencies": {
+ "@mariozechner/pi-coding-agent": "*",
"bun-types": "^1.0.0"
}
}
dots/pi/agent/extensions/jira/tsconfig.json
@@ -1,13 +1,13 @@
{
"compilerOptions": {
- "target": "ESNext",
- "module": "ESNext",
+ "types": ["bun-types"],
+ "module": "esnext",
"moduleResolution": "bundler",
+ "target": "esnext",
+ "strict": false,
+ "noImplicitAny": false,
"esModuleInterop": true,
- "strict": true,
- "skipLibCheck": true,
- "noEmit": true,
- "types": ["bun-types"]
+ "skipLibCheck": true
},
"include": ["*.ts"]
}
dots/pi/agent/extensions/jira/types.ts
@@ -88,7 +88,7 @@ export interface JiraLinkParams {
action: "link";
from: string;
to: string;
- linkType: "blocks" | "is blocked by" | "relates to" | "duplicates" | "is duplicated by";
+ linkType: string; // Valid Jira link type names: Blocks, Related, Duplicate, Cloners, Depend, etc.
}
export interface JiraUnlinkParams {