Skip to content

Commit cde63c5

Browse files
authored
Merge pull request #30 from Avivbens/fix/jira-master-fixes
2 parents f384eca + 4ac8ff7 commit cde63c5

File tree

5 files changed

+56
-29
lines changed

5 files changed

+56
-29
lines changed

projects/packages/jira-master/info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ https://github.com/Avivbens/alfredo</string>
483483
</dict>
484484
</array>
485485
<key>version</key>
486-
<string>1.0.0</string>
486+
<string>1.1.0</string>
487487
<key>webaddress</key>
488488
<string>https://github.com/Avivbens/alfredo</string>
489489
</dict>

projects/packages/jira-master/src/common/prompts/extract-ticket.prompt.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ export const EXTRACT_TICKET_SYSTEM_PROMPT = (useTitleFormat: boolean) =>
66
pipelinePrompts: [TITLE_FORMAT_SYSTEM_PROMPT_PARAM],
77
finalPrompt:
88
PromptTemplate.fromTemplate(`You are an AI assistant that extracts structured Jira ticket information from Slack threads, issue descriptions, or any text input.
9+
Your task is to analyze the provided text and generate a list of tickets.
10+
When multiple distinct issues or tasks are mentioned, create separate tickets for each.
11+
When details span a single cohesive issue, keep them in one ticket.
912
1013
Current date: {currentDate}
1114
12-
Your task is to analyze the provided text and extract:
15+
For each ticket, extract:
1316
1. **Title**: A concise, one-liner summary that captures the essence of the issue
1417
2. **Description**: A concise, to-the-point description of the issue
1518
3. **Story Points**: An estimate of the work effort (use Fibonacci scale: 1, 2, 3, 5, 8, 13, or null if uncertain)
@@ -25,6 +28,8 @@ Guidelines:
2528
- For story points, consider complexity and effort. Use null if the work scope is unclear
2629
- For issue type: Story = new functionality, Task = work to do, Bug = something broken, Epic = large initiative
2730
- For priority, base it on urgency keywords in the text, or use null if not mentioned
31+
- When multiple issues are mentioned, associate details (description, priority, story points) with the correct ticket
32+
- Your response must be ONLY the list of tickets
2833
2934
${useTitleFormat ? '{TITLE_FORMAT_SYSTEM_PROMPT}' : ''}
3035
@@ -33,7 +38,7 @@ Extract structured information accurately and thoughtfully.`),
3338

3439
export const EXTRACT_TICKET_USER_PROMPT = new PromptTemplate({
3540
inputVariables: ['userInput'],
36-
template: `Extract a Jira ticket from the following text:
41+
template: `Extract Jira tickets from the following text:
3742
3843
{userInput}`,
3944
});

projects/packages/jira-master/src/main/extract-jira-ticket.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,37 @@ import { extractTicket } from '../services/ticket-extractor.service';
4444

4545
await setTimeout(debounceTime);
4646

47-
const ticket = await extractTicket(llmToken, selectedModel, alfredClient.input, titleExample);
47+
const { tickets } = await extractTicket(llmToken, selectedModel, alfredClient.input, titleExample);
4848

49-
const storyPointsText =
50-
ticket.storyPoints !== null && ticket.storyPoints !== undefined ? ` • ${ticket.storyPoints} points` : '';
49+
if (!tickets.length) {
50+
alfredClient.output({
51+
items: [
52+
{
53+
title: 'No tickets found',
54+
subtitle: 'Could not extract ticket information. Try rephrasing or providing more details.',
55+
valid: false,
56+
},
57+
],
58+
});
59+
return;
60+
}
5161

52-
const priorityText = ticket.priority ? ` • ${ticket.priority}` : '';
53-
const subtitle = `${ticket.title} | ${ticket.issueType}${storyPointsText}${priorityText} | Press Enter to create`;
62+
const items = tickets.map((ticket, index) => {
63+
const storyPointsText =
64+
ticket.storyPoints !== null && ticket.storyPoints !== undefined ? ` • ${ticket.storyPoints} points` : '';
5465

55-
alfredClient.output({
56-
items: [
57-
{
58-
title: ticket.title,
59-
subtitle,
60-
arg: JSON.stringify(ticket),
61-
valid: true,
62-
text: {
63-
largetype: `Title: ${ticket.title}\n\nType: ${ticket.issueType}\nStory Points: ${ticket.storyPoints || 'N/A'}\nPriority: ${ticket.priority || 'N/A'}\n\nDescription:\n${ticket.description}`,
64-
},
65-
},
66-
],
66+
const priorityText = ticket.priority ? ` • ${ticket.priority}` : '';
67+
const subtitle = `${ticket.issueType}${storyPointsText}${priorityText} | Press Enter to create`;
68+
69+
return {
70+
title: ticket.title,
71+
subtitle,
72+
arg: JSON.stringify(ticket),
73+
valid: true,
74+
};
6775
});
76+
77+
alfredClient.output({ items });
6878
} catch (error) {
6979
alfredClient.error(error);
7080
}

projects/packages/jira-master/src/models/jira-ticket.model.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,16 @@ export const GeminiJiraTicketSchema = z.object({
4141
priority: priority.optional(),
4242
});
4343

44+
// Array wrapper schemas for multiple ticket extraction
45+
export const OpenAIJiraTicketsSchema = z.object({
46+
tickets: z.array(JiraTicketSchema),
47+
});
48+
49+
export const GeminiJiraTicketsSchema = z.object({
50+
tickets: z.array(GeminiJiraTicketSchema),
51+
});
52+
4453
export type JiraTicket = z.infer<typeof JiraTicketSchema>;
54+
export type JiraTickets = {
55+
tickets: JiraTicket[];
56+
};
Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { type AvailableModels, callModelWithStructuredResponse } from '@alfredo/llm';
22
import { EXTRACT_TICKET_SYSTEM_PROMPT, EXTRACT_TICKET_USER_PROMPT } from '../common/prompts/extract-ticket.prompt';
3-
import { GeminiJiraTicketSchema, type JiraTicket, JiraTicketSchema } from '../models/jira-ticket.model';
3+
import { GeminiJiraTicketsSchema, type JiraTickets, OpenAIJiraTicketsSchema } from '../models/jira-ticket.model';
44

55
export async function extractTicket(
66
token: string,
77
model: AvailableModels,
88
input: string,
99
titleExample?: string,
10-
): Promise<JiraTicket> {
10+
): Promise<JiraTickets> {
1111
const currentDate = new Date().toISOString().split('T')[0];
1212
const useTitleFormat = Boolean(titleExample);
1313
const system = await EXTRACT_TICKET_SYSTEM_PROMPT(useTitleFormat).format({
@@ -21,16 +21,16 @@ export async function extractTicket(
2121

2222
// Select appropriate schema based on model
2323
const isGemini = model.toLowerCase().includes('gemini');
24-
const schema = isGemini ? GeminiJiraTicketSchema : JiraTicketSchema;
24+
const schema = isGemini ? GeminiJiraTicketsSchema : OpenAIJiraTicketsSchema;
2525

2626
const result = await callModelWithStructuredResponse(token, model, { system, user }, schema);
2727

2828
// Normalize the result to ensure consistent types (convert undefined to null)
29-
const ticket: JiraTicket = {
30-
...result,
31-
storyPoints: result.storyPoints ?? null,
32-
priority: result.priority ?? null,
33-
};
29+
const normalizedTickets = result.tickets.map((ticket) => ({
30+
...ticket,
31+
storyPoints: ticket.storyPoints ?? null,
32+
priority: ticket.priority ?? null,
33+
}));
3434

35-
return ticket;
35+
return { tickets: normalizedTickets };
3636
}

0 commit comments

Comments
 (0)