@jagadeesh52423/atlassian-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.env.example +26 -0
  2. package/LICENSE +21 -0
  3. package/README.md +349 -0
  4. package/dist/atlassian-client.d.ts +10 -0
  5. package/dist/atlassian-client.d.ts.map +1 -0
  6. package/dist/atlassian-client.js +38 -0
  7. package/dist/atlassian-client.js.map +1 -0
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +60 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/confluence-client.d.ts +11 -0
  13. package/dist/confluence-client.d.ts.map +1 -0
  14. package/dist/confluence-client.js +47 -0
  15. package/dist/confluence-client.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +2592 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/jira-client.d.ts +11 -0
  21. package/dist/jira-client.d.ts.map +1 -0
  22. package/dist/jira-client.js +47 -0
  23. package/dist/jira-client.js.map +1 -0
  24. package/dist/services/bitbucket.d.ts +21 -0
  25. package/dist/services/bitbucket.d.ts.map +1 -0
  26. package/dist/services/bitbucket.js +176 -0
  27. package/dist/services/bitbucket.js.map +1 -0
  28. package/dist/services/confluence.d.ts +41 -0
  29. package/dist/services/confluence.d.ts.map +1 -0
  30. package/dist/services/confluence.js +348 -0
  31. package/dist/services/confluence.js.map +1 -0
  32. package/dist/services/jira.d.ts +58 -0
  33. package/dist/services/jira.d.ts.map +1 -0
  34. package/dist/services/jira.js +1115 -0
  35. package/dist/services/jira.js.map +1 -0
  36. package/dist/types.d.ts +120 -0
  37. package/dist/types.d.ts.map +1 -0
  38. package/dist/types.js +2 -0
  39. package/dist/types.js.map +1 -0
  40. package/package.json +61 -0
@@ -0,0 +1,1115 @@
1
+ import { JiraClient } from '../jira-client.js';
2
+ export class JiraService {
3
+ client;
4
+ // Smart suggestion rules for common field patterns
5
+ fieldSuggestionRules = [
6
+ {
7
+ pattern: /db.*script/i,
8
+ defaultValue: 'No',
9
+ contextRules: [
10
+ {
11
+ condition: (issue) => /template|config|notification|mofu/i.test(issue.summary),
12
+ value: 'No',
13
+ reason: 'Template/config changes are typically done via MOFU tool and do not require database scripts'
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ pattern: /test.*case|unit.*test/i,
19
+ defaultValue: 'Yes',
20
+ contextRules: [
21
+ {
22
+ condition: (issue) => /template|notification|config/i.test(issue.summary),
23
+ value: 'No',
24
+ reason: 'Template and configuration changes may not require separate unit test cases'
25
+ }
26
+ ]
27
+ }
28
+ ];
29
+ constructor() {
30
+ this.client = new JiraClient();
31
+ }
32
+ async searchIssues(options = {}) {
33
+ const { query, limit = 50, startAt = 0 } = options;
34
+ // Create bounded JQL query (Jira requires bounded queries now)
35
+ let jql = 'created >= -90d ORDER BY created DESC';
36
+ if (query) {
37
+ jql = `text ~ "${query}" AND created >= -90d ORDER BY created DESC`;
38
+ }
39
+ const params = {
40
+ jql,
41
+ maxResults: limit,
42
+ startAt,
43
+ fields: 'summary,description,status,assignee,created,updated'
44
+ };
45
+ const response = await this.client.get('/rest/api/3/search/jql', params);
46
+ return response.issues.map((issue) => ({
47
+ id: issue.id,
48
+ key: issue.key,
49
+ summary: issue.fields.summary || '',
50
+ description: this.extractTextFromADF(issue.fields.description),
51
+ status: issue.fields.status?.name || '',
52
+ assignee: issue.fields.assignee?.displayName || '',
53
+ created: issue.fields.created || '',
54
+ updated: issue.fields.updated || ''
55
+ }));
56
+ }
57
+ async getIssue(issueKey) {
58
+ const params = {
59
+ fields: ['summary', 'description', 'status', 'assignee', 'created', 'updated']
60
+ };
61
+ const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, params);
62
+ return {
63
+ id: issue.id,
64
+ key: issue.key,
65
+ summary: issue.fields.summary || '',
66
+ description: this.extractTextFromADF(issue.fields.description),
67
+ status: issue.fields.status?.name || '',
68
+ assignee: issue.fields.assignee?.displayName || '',
69
+ created: issue.fields.created || '',
70
+ updated: issue.fields.updated || ''
71
+ };
72
+ }
73
+ async createIssue(projectKey, summary, description, issueType = 'Task', additionalFields) {
74
+ const fields = {
75
+ project: { key: projectKey },
76
+ summary,
77
+ description: this.parseDescriptionToADF(description),
78
+ issuetype: { name: issueType }
79
+ };
80
+ // Add optional fields if provided
81
+ if (additionalFields) {
82
+ // Due date (format: YYYY-MM-DD)
83
+ if (additionalFields.duedate) {
84
+ fields.duedate = additionalFields.duedate;
85
+ }
86
+ // Priority (by name)
87
+ if (additionalFields.priority) {
88
+ fields.priority = { name: additionalFields.priority };
89
+ }
90
+ // Labels (array of strings)
91
+ if (additionalFields.labels && additionalFields.labels.length > 0) {
92
+ fields.labels = additionalFields.labels;
93
+ }
94
+ // Components (by name)
95
+ if (additionalFields.components && additionalFields.components.length > 0) {
96
+ fields.components = additionalFields.components.map(name => ({ name }));
97
+ }
98
+ // Assignee (by account ID)
99
+ if (additionalFields.assignee) {
100
+ fields.assignee = { accountId: additionalFields.assignee };
101
+ }
102
+ // Custom fields (merge directly into fields object)
103
+ if (additionalFields.customFields) {
104
+ Object.assign(fields, additionalFields.customFields);
105
+ }
106
+ }
107
+ const data = { fields };
108
+ const response = await this.client.post('/rest/api/3/issue', data);
109
+ return this.getIssue(response.key);
110
+ }
111
+ async updateIssue(issueKey, fields) {
112
+ const updateFields = {};
113
+ if (fields.summary) {
114
+ updateFields.summary = fields.summary;
115
+ }
116
+ if (fields.description) {
117
+ // Use proper ADF format for description updates
118
+ updateFields.description = this.parseDescriptionToADF(fields.description);
119
+ }
120
+ if (fields.assignee) {
121
+ updateFields.assignee = { accountId: fields.assignee };
122
+ }
123
+ const data = { fields: updateFields };
124
+ await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
125
+ return this.getIssue(issueKey);
126
+ }
127
+ async transitionIssue(issueKey, transitionId, fieldValues) {
128
+ try {
129
+ const data = {
130
+ transition: { id: transitionId }
131
+ };
132
+ // Add field values if provided
133
+ if (fieldValues) {
134
+ data.fields = fieldValues;
135
+ }
136
+ await this.client.post(`/rest/api/3/issue/${issueKey}/transitions`, data);
137
+ return {
138
+ success: true,
139
+ message: `Issue ${issueKey} successfully transitioned`
140
+ };
141
+ }
142
+ catch (error) {
143
+ // Check if this is a required field error
144
+ if (error.response?.status === 400) {
145
+ const requiredFields = await this.handleTransitionError(issueKey, transitionId, error);
146
+ if (requiredFields.length > 0) {
147
+ return {
148
+ success: false,
149
+ requiresInput: true,
150
+ requiredFields,
151
+ message: 'Transition requires additional fields. Please provide values for the required fields.',
152
+ issueKey,
153
+ transitionId
154
+ };
155
+ }
156
+ }
157
+ // Re-throw if not a field validation error
158
+ throw error;
159
+ }
160
+ }
161
+ async getIssueTransitions(issueKey) {
162
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/transitions`);
163
+ return response.transitions;
164
+ }
165
+ async addComment(issueKey, comment) {
166
+ const data = {
167
+ body: this.parseDescriptionToADF(comment)
168
+ };
169
+ await this.client.post(`/rest/api/3/issue/${issueKey}/comment`, data);
170
+ }
171
+ async getComments(issueKey) {
172
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/comment`);
173
+ return response.comments.map((comment) => ({
174
+ id: comment.id,
175
+ author: comment.author?.displayName || 'Unknown',
176
+ authorAccountId: comment.author?.accountId,
177
+ body: this.extractTextFromADF(comment.body),
178
+ created: comment.created,
179
+ updated: comment.updated,
180
+ updateAuthor: comment.updateAuthor?.displayName
181
+ }));
182
+ }
183
+ async updateComment(issueKey, commentId, comment) {
184
+ const data = {
185
+ body: this.parseDescriptionToADF(comment)
186
+ };
187
+ const response = await this.client.put(`/rest/api/3/issue/${issueKey}/comment/${commentId}`, data);
188
+ return {
189
+ id: response.id,
190
+ author: response.author?.displayName || 'Unknown',
191
+ body: this.extractTextFromADF(response.body),
192
+ created: response.created,
193
+ updated: response.updated,
194
+ updateAuthor: response.updateAuthor?.displayName
195
+ };
196
+ }
197
+ async deleteComment(issueKey, commentId) {
198
+ await this.client.delete(`/rest/api/3/issue/${issueKey}/comment/${commentId}`);
199
+ }
200
+ extractTextFromADF(body) {
201
+ if (!body)
202
+ return '';
203
+ // If body is already a string (wiki markup), return it directly
204
+ if (typeof body === 'string') {
205
+ return this.formatWikiMarkup(body);
206
+ }
207
+ // If body doesn't have content property, it might be wiki markup in another format
208
+ if (!body.content) {
209
+ return this.formatWikiMarkup(String(body));
210
+ }
211
+ let text = '';
212
+ const extractFromContent = (content) => {
213
+ for (const item of content) {
214
+ if (item.type === 'text' && item.text) {
215
+ text += item.text;
216
+ }
217
+ else if (item.type === 'hardBreak') {
218
+ text += '\n';
219
+ }
220
+ else if (item.type === 'paragraph') {
221
+ if (item.content) {
222
+ extractFromContent(item.content);
223
+ }
224
+ }
225
+ else if (item.type === 'heading') {
226
+ const level = item.attrs?.level || 1;
227
+ const prefix = '#'.repeat(level);
228
+ text += `${prefix} `;
229
+ if (item.content) {
230
+ extractFromContent(item.content);
231
+ }
232
+ }
233
+ else if (item.type === 'bulletList' || item.type === 'orderedList') {
234
+ if (item.content) {
235
+ extractFromContent(item.content);
236
+ }
237
+ }
238
+ else if (item.type === 'listItem') {
239
+ text += '- ';
240
+ if (item.content) {
241
+ extractFromContent(item.content);
242
+ }
243
+ }
244
+ else if (item.type === 'codeBlock') {
245
+ text += '```';
246
+ if (item.content) {
247
+ extractFromContent(item.content);
248
+ }
249
+ text += '```';
250
+ }
251
+ else if (item.type === 'table') {
252
+ // Extract table as markdown
253
+ if (item.content) {
254
+ const rows = [];
255
+ item.content.forEach((row) => {
256
+ if (row.type === 'tableRow' && row.content) {
257
+ const cells = [];
258
+ row.content.forEach((cell) => {
259
+ if ((cell.type === 'tableCell' || cell.type === 'tableHeader') && cell.content) {
260
+ let cellText = '';
261
+ const extractCellText = (cellContent) => {
262
+ cellContent.forEach((cellItem) => {
263
+ if (cellItem.type === 'paragraph' && cellItem.content) {
264
+ cellItem.content.forEach((textItem) => {
265
+ if (textItem.type === 'text' && textItem.text) {
266
+ cellText += textItem.text;
267
+ }
268
+ });
269
+ }
270
+ });
271
+ };
272
+ extractCellText(cell.content);
273
+ cells.push(cellText.trim());
274
+ }
275
+ });
276
+ if (cells.length > 0) {
277
+ rows.push(cells);
278
+ }
279
+ }
280
+ });
281
+ // Format as markdown table
282
+ if (rows.length > 0) {
283
+ const [headerRow, ...dataRows] = rows;
284
+ // Header row
285
+ text += '\n| ' + headerRow.join(' | ') + ' |\n';
286
+ // Separator row
287
+ text += '|' + headerRow.map(() => '--------').join('|') + '|\n';
288
+ // Data rows
289
+ dataRows.forEach(row => {
290
+ text += '| ' + row.join(' | ') + ' |\n';
291
+ });
292
+ }
293
+ }
294
+ }
295
+ else if (item.content) {
296
+ extractFromContent(item.content);
297
+ }
298
+ }
299
+ };
300
+ extractFromContent(body.content);
301
+ return this.formatWikiMarkup(text.trim());
302
+ }
303
+ formatWikiMarkup(text) {
304
+ if (!text)
305
+ return '';
306
+ return text
307
+ // Convert Confluence headers to markdown headers
308
+ .replace(/^h([1-6])\.\s*/gm, (match, level) => '#'.repeat(parseInt(level)) + ' ')
309
+ // Convert horizontal rules
310
+ .replace(/^----+$/gm, '---')
311
+ // Handle panels - convert to blockquotes for better readability
312
+ .replace(/\{panel:title=([^|]*)[^}]*\}/g, '\n> **$1**\n>')
313
+ .replace(/\{panel\}/g, '\n')
314
+ // Handle colored text/backgrounds - keep the text but remove styling
315
+ .replace(/\{color:[^}]*\}([^{]*)\{color\}/g, '$1')
316
+ // Convert checkboxes
317
+ .replace(/☐/g, '- [ ]')
318
+ .replace(/☑/g, '- [x]')
319
+ // Clean up multiple newlines
320
+ .replace(/\n{3,}/g, '\n\n')
321
+ // Ensure proper spacing around headers
322
+ .replace(/\n(#{1,6}\s)/g, '\n\n$1')
323
+ .trim();
324
+ }
325
+ async getProjects() {
326
+ const response = await this.client.get('/rest/api/3/project');
327
+ return response;
328
+ }
329
+ async getIssueTypes(projectKey) {
330
+ const response = await this.client.get(`/rest/api/3/issue/createmeta/${projectKey}/issuetypes`);
331
+ return response.issueTypes;
332
+ }
333
+ async assignIssue(issueKey, accountId) {
334
+ const data = { accountId };
335
+ await this.client.put(`/rest/api/3/issue/${issueKey}/assignee`, data);
336
+ }
337
+ async getUsersByQuery(query) {
338
+ const params = {
339
+ query,
340
+ maxResults: 10
341
+ };
342
+ const response = await this.client.get('/rest/api/3/user/search', params);
343
+ return response;
344
+ }
345
+ // Attachment Methods
346
+ async getAttachments(issueKey) {
347
+ const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
348
+ fields: ['attachment']
349
+ });
350
+ return issue.fields.attachment?.map((att) => ({
351
+ id: att.id,
352
+ filename: att.filename,
353
+ author: att.author?.displayName || 'Unknown',
354
+ created: att.created,
355
+ size: att.size,
356
+ mimeType: att.mimeType,
357
+ content: att.content
358
+ })) || [];
359
+ }
360
+ async addAttachment(issueKey, filename, fileContent) {
361
+ // Note: fileContent should be base64 encoded
362
+ const FormData = (await import('form-data')).default;
363
+ const form = new FormData();
364
+ const buffer = Buffer.from(fileContent, 'base64');
365
+ form.append('file', buffer, { filename });
366
+ const response = await this.client.postFormData(`/rest/api/3/issue/${issueKey}/attachments`, form);
367
+ return response;
368
+ }
369
+ async deleteAttachment(attachmentId) {
370
+ await this.client.delete(`/rest/api/3/attachment/${attachmentId}`);
371
+ }
372
+ // Issue Linking Methods
373
+ async getIssueLinks(issueKey) {
374
+ const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
375
+ fields: ['issuelinks']
376
+ });
377
+ return issue.fields.issuelinks?.map((link) => ({
378
+ id: link.id,
379
+ type: link.type?.name || 'Unknown',
380
+ inward: link.inwardIssue ? {
381
+ key: link.inwardIssue.key,
382
+ summary: link.inwardIssue.fields?.summary,
383
+ status: link.inwardIssue.fields?.status?.name
384
+ } : null,
385
+ outward: link.outwardIssue ? {
386
+ key: link.outwardIssue.key,
387
+ summary: link.outwardIssue.fields?.summary,
388
+ status: link.outwardIssue.fields?.status?.name
389
+ } : null
390
+ })) || [];
391
+ }
392
+ async createIssueLink(inwardIssueKey, outwardIssueKey, linkType) {
393
+ const data = {
394
+ type: { name: linkType },
395
+ inwardIssue: { key: inwardIssueKey },
396
+ outwardIssue: { key: outwardIssueKey }
397
+ };
398
+ const response = await this.client.post('/rest/api/3/issueLink', data);
399
+ return response;
400
+ }
401
+ async deleteIssueLink(linkId) {
402
+ await this.client.delete(`/rest/api/3/issueLink/${linkId}`);
403
+ }
404
+ async getIssueLinkTypes() {
405
+ const response = await this.client.get('/rest/api/3/issueLinkType');
406
+ return response.issueLinkTypes.map((type) => ({
407
+ id: type.id,
408
+ name: type.name,
409
+ inward: type.inward,
410
+ outward: type.outward
411
+ }));
412
+ }
413
+ // Work Log Methods
414
+ async getWorkLogs(issueKey) {
415
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/worklog`);
416
+ return response.worklogs?.map((worklog) => ({
417
+ id: worklog.id,
418
+ author: worklog.author?.displayName || 'Unknown',
419
+ authorAccountId: worklog.author?.accountId,
420
+ comment: this.extractTextFromADF(worklog.comment),
421
+ created: worklog.created,
422
+ updated: worklog.updated,
423
+ started: worklog.started,
424
+ timeSpent: worklog.timeSpent,
425
+ timeSpentSeconds: worklog.timeSpentSeconds
426
+ })) || [];
427
+ }
428
+ async addWorkLog(issueKey, timeSpentSeconds, comment, started) {
429
+ const data = {
430
+ timeSpentSeconds
431
+ };
432
+ if (started) {
433
+ data.started = started;
434
+ }
435
+ if (comment) {
436
+ data.comment = this.parseDescriptionToADF(comment);
437
+ }
438
+ const response = await this.client.post(`/rest/api/3/issue/${issueKey}/worklog`, data);
439
+ return response;
440
+ }
441
+ async updateWorkLog(issueKey, worklogId, timeSpentSeconds, comment) {
442
+ const data = {};
443
+ if (timeSpentSeconds !== undefined) {
444
+ data.timeSpentSeconds = timeSpentSeconds;
445
+ }
446
+ if (comment) {
447
+ data.comment = this.parseDescriptionToADF(comment);
448
+ }
449
+ const response = await this.client.put(`/rest/api/3/issue/${issueKey}/worklog/${worklogId}`, data);
450
+ return response;
451
+ }
452
+ async deleteWorkLog(issueKey, worklogId) {
453
+ await this.client.delete(`/rest/api/3/issue/${issueKey}/worklog/${worklogId}`);
454
+ }
455
+ // Watchers Methods
456
+ async getWatchers(issueKey) {
457
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/watchers`);
458
+ return {
459
+ watchCount: response.watchCount,
460
+ isWatching: response.isWatching,
461
+ watchers: response.watchers?.map((watcher) => ({
462
+ accountId: watcher.accountId,
463
+ displayName: watcher.displayName,
464
+ emailAddress: watcher.emailAddress,
465
+ active: watcher.active
466
+ })) || []
467
+ };
468
+ }
469
+ async addWatcher(issueKey, accountId) {
470
+ await this.client.post(`/rest/api/3/issue/${issueKey}/watchers`, JSON.stringify(accountId));
471
+ }
472
+ async removeWatcher(issueKey, accountId) {
473
+ await this.client.delete(`/rest/api/3/issue/${issueKey}/watchers?accountId=${accountId}`);
474
+ }
475
+ // Sub-task Methods
476
+ async getSubTasks(issueKey) {
477
+ const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
478
+ fields: ['subtasks']
479
+ });
480
+ return issue.fields.subtasks?.map((subtask) => ({
481
+ id: subtask.id,
482
+ key: subtask.key,
483
+ summary: subtask.fields?.summary,
484
+ status: subtask.fields?.status?.name,
485
+ assignee: subtask.fields?.assignee?.displayName
486
+ })) || [];
487
+ }
488
+ async createSubTask(parentKey, summary, description) {
489
+ const data = {
490
+ fields: {
491
+ project: { key: parentKey.split('-')[0] },
492
+ parent: { key: parentKey },
493
+ summary,
494
+ description: this.parseDescriptionToADF(description),
495
+ issuetype: { name: 'Sub-task' }
496
+ }
497
+ };
498
+ const response = await this.client.post('/rest/api/3/issue', data);
499
+ return this.getIssue(response.key);
500
+ }
501
+ // Issue History Methods
502
+ async getIssueHistory(issueKey) {
503
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/changelog`);
504
+ return response.values?.map((change) => ({
505
+ id: change.id,
506
+ author: change.author?.displayName || 'Unknown',
507
+ authorAccountId: change.author?.accountId,
508
+ created: change.created,
509
+ items: change.items?.map((item) => ({
510
+ field: item.field,
511
+ fieldtype: item.fieldtype,
512
+ from: item.fromString,
513
+ to: item.toString
514
+ }))
515
+ })) || [];
516
+ }
517
+ // Smart Field Handling Methods
518
+ async handleTransitionError(issueKey, transitionId, error) {
519
+ try {
520
+ // Get transition metadata with field information
521
+ const response = await this.client.get(`/rest/api/3/issue/${issueKey}/transitions?expand=transitions.fields`);
522
+ const transition = response.transitions.find((t) => t.id === transitionId);
523
+ if (!transition || !transition.fields) {
524
+ return [];
525
+ }
526
+ // Get issue details for context
527
+ const issue = await this.getIssue(issueKey);
528
+ // Analyze required fields and fields that may need values
529
+ const requiredFields = [];
530
+ for (const [fieldKey, fieldInfo] of Object.entries(transition.fields)) {
531
+ // Process all fields that have validation requirements or are commonly needed
532
+ const field = this.analyzeField(fieldKey, fieldInfo, issue);
533
+ requiredFields.push(field);
534
+ }
535
+ return requiredFields;
536
+ }
537
+ catch (err) {
538
+ console.error('Error analyzing transition fields:', err);
539
+ return [];
540
+ }
541
+ }
542
+ analyzeField(fieldKey, fieldInfo, issue) {
543
+ const field = {
544
+ key: fieldKey,
545
+ name: fieldInfo.name || fieldKey,
546
+ type: fieldInfo.schema?.type || 'unknown',
547
+ required: fieldInfo.required || false
548
+ };
549
+ // Extract options for select fields
550
+ if (fieldInfo.allowedValues) {
551
+ field.options = fieldInfo.allowedValues.map((val) => ({
552
+ id: val.id,
553
+ value: val.value
554
+ }));
555
+ }
556
+ // Generate smart suggestions
557
+ field.suggestion = this.generateFieldSuggestion(fieldInfo.name, field.options, issue);
558
+ return field;
559
+ }
560
+ generateFieldSuggestion(fieldName, options, issue) {
561
+ if (!fieldName)
562
+ return undefined;
563
+ // Apply suggestion rules
564
+ for (const rule of this.fieldSuggestionRules) {
565
+ if (rule.pattern.test(fieldName)) {
566
+ // Check context rules first
567
+ if (rule.contextRules && issue) {
568
+ for (const contextRule of rule.contextRules) {
569
+ if (contextRule.condition(issue)) {
570
+ // Find matching option ID if available
571
+ const matchingOption = options?.find(opt => opt.value === contextRule.value);
572
+ return {
573
+ value: contextRule.value,
574
+ id: matchingOption?.id,
575
+ reason: contextRule.reason
576
+ };
577
+ }
578
+ }
579
+ }
580
+ // Fall back to default value
581
+ const matchingOption = options?.find(opt => opt.value === rule.defaultValue);
582
+ return {
583
+ value: rule.defaultValue,
584
+ id: matchingOption?.id,
585
+ reason: `Default suggestion for ${fieldName.toLowerCase()}`
586
+ };
587
+ }
588
+ }
589
+ // Generic suggestions based on field name patterns
590
+ if (options && options.length > 0) {
591
+ if (/required|mandatory|needed/i.test(fieldName)) {
592
+ const yesOption = options.find(opt => /yes|true|required/i.test(opt.value));
593
+ if (yesOption) {
594
+ return {
595
+ value: yesOption.value,
596
+ id: yesOption.id,
597
+ reason: 'Field name suggests this is typically required'
598
+ };
599
+ }
600
+ }
601
+ if (/optional|not.*required|skip/i.test(fieldName)) {
602
+ const noOption = options.find(opt => /no|false|not.*required|skip/i.test(opt.value));
603
+ if (noOption) {
604
+ return {
605
+ value: noOption.value,
606
+ id: noOption.id,
607
+ reason: 'Field name suggests this is typically not required'
608
+ };
609
+ }
610
+ }
611
+ }
612
+ return undefined;
613
+ }
614
+ // Enhanced transition method with smart field handling
615
+ async transitionIssueInteractive(issueKey, transitionId, providedFields) {
616
+ // First attempt with provided fields
617
+ const result = await this.transitionIssue(issueKey, transitionId, providedFields);
618
+ if (result.success || !result.requiresInput) {
619
+ return result;
620
+ }
621
+ // If user provided fields but still failed, try to auto-fill based on suggestions
622
+ if (result.requiredFields && result.requiredFields.length > 0) {
623
+ const autoFields = { ...providedFields };
624
+ let hasAutoSuggestions = false;
625
+ for (const field of result.requiredFields) {
626
+ if (field.suggestion && !autoFields[field.key]) {
627
+ if (field.suggestion.id) {
628
+ autoFields[field.key] = { id: field.suggestion.id };
629
+ }
630
+ else {
631
+ autoFields[field.key] = field.suggestion.value;
632
+ }
633
+ hasAutoSuggestions = true;
634
+ }
635
+ }
636
+ // Try transition again with auto-filled suggestions
637
+ if (hasAutoSuggestions) {
638
+ const retryResult = await this.transitionIssue(issueKey, transitionId, autoFields);
639
+ if (retryResult.success) {
640
+ retryResult.message = `${retryResult.message} (auto-filled based on smart suggestions)`;
641
+ }
642
+ return retryResult;
643
+ }
644
+ }
645
+ return result;
646
+ }
647
+ // Convert description to wiki markup text format
648
+ parseDescriptionToWikiMarkup(description) {
649
+ const lines = description.split('\n');
650
+ const result = [];
651
+ for (let i = 0; i < lines.length; i++) {
652
+ const line = lines[i].trim();
653
+ if (line === '') {
654
+ result.push('');
655
+ continue;
656
+ }
657
+ // Handle markdown headers
658
+ if (line.startsWith('###')) {
659
+ result.push('h3. ' + line.substring(3).trim());
660
+ }
661
+ else if (line.startsWith('##')) {
662
+ result.push('h2. ' + line.substring(2).trim());
663
+ }
664
+ else if (line.startsWith('#')) {
665
+ result.push('h1. ' + line.substring(1).trim());
666
+ }
667
+ // Handle markdown tables
668
+ else if (line.includes('|') && line.trim() !== '|') {
669
+ const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
670
+ if (cells.length >= 2) {
671
+ // Collect table rows
672
+ const allTableRows = [];
673
+ let currentLine = i;
674
+ while (currentLine < lines.length) {
675
+ const tableLine = lines[currentLine].trim();
676
+ if (tableLine === '') {
677
+ currentLine++;
678
+ continue;
679
+ }
680
+ if (!tableLine.includes('|')) {
681
+ break;
682
+ }
683
+ // Skip separator lines
684
+ if (tableLine.match(/^\|?[\s\-:|]+\|?$/)) {
685
+ currentLine++;
686
+ continue;
687
+ }
688
+ const rowCells = tableLine.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
689
+ if (rowCells.length >= 2) {
690
+ allTableRows.push(rowCells);
691
+ }
692
+ else {
693
+ break;
694
+ }
695
+ currentLine++;
696
+ }
697
+ // Convert to Confluence wiki markup
698
+ if (allTableRows.length > 0) {
699
+ // Header row
700
+ if (allTableRows.length > 0) {
701
+ result.push('||*' + allTableRows[0].join('*||*') + '*||');
702
+ // Add separator line based on number of columns
703
+ const numColumns = allTableRows[0].length;
704
+ const separator = '|' + '-|'.repeat(numColumns);
705
+ result.push(separator);
706
+ }
707
+ // Data rows
708
+ for (let rowIdx = 1; rowIdx < allTableRows.length; rowIdx++) {
709
+ result.push('|' + allTableRows[rowIdx].join('|') + '|');
710
+ }
711
+ // Skip processed lines
712
+ i = currentLine - 1;
713
+ continue;
714
+ }
715
+ }
716
+ // If not a valid table, treat as regular text
717
+ result.push(line);
718
+ }
719
+ // Handle bullet points
720
+ else if (line.startsWith('- ') || line.startsWith('* ')) {
721
+ result.push('* ' + line.substring(2).trim());
722
+ }
723
+ // Regular text
724
+ else {
725
+ result.push(line);
726
+ }
727
+ }
728
+ return result.join('\n');
729
+ }
730
+ // Enhanced description parser that converts text to proper ADF format
731
+ parseDescriptionToADF(description) {
732
+ const lines = description.split('\n');
733
+ const content = [];
734
+ for (let i = 0; i < lines.length; i++) {
735
+ const line = lines[i].trim();
736
+ if (line === '') {
737
+ // Skip empty lines - let Claude handle spacing naturally
738
+ continue;
739
+ }
740
+ // Handle Confluence wiki markup headers (h1., h2., h3., etc.)
741
+ const confluenceHeaderMatch = line.match(/^h([1-6])\.\s*(.*)$/);
742
+ if (confluenceHeaderMatch) {
743
+ const level = parseInt(confluenceHeaderMatch[1]);
744
+ const headerText = confluenceHeaderMatch[2];
745
+ content.push({
746
+ type: 'heading',
747
+ attrs: { level },
748
+ content: [{
749
+ type: 'text',
750
+ text: headerText
751
+ }]
752
+ });
753
+ continue;
754
+ }
755
+ // Handle markdown headers (## Header, ### Header)
756
+ if (line.startsWith('###')) {
757
+ content.push({
758
+ type: 'heading',
759
+ attrs: { level: 3 },
760
+ content: [{
761
+ type: 'text',
762
+ text: line.substring(3).trim()
763
+ }]
764
+ });
765
+ }
766
+ else if (line.startsWith('##')) {
767
+ content.push({
768
+ type: 'heading',
769
+ attrs: { level: 2 },
770
+ content: [{
771
+ type: 'text',
772
+ text: line.substring(2).trim()
773
+ }]
774
+ });
775
+ }
776
+ else if (line.startsWith('#')) {
777
+ content.push({
778
+ type: 'heading',
779
+ attrs: { level: 1 },
780
+ content: [{
781
+ type: 'text',
782
+ text: line.substring(1).trim()
783
+ }]
784
+ });
785
+ }
786
+ // Handle horizontal rules (---- or ----)
787
+ else if (/^-{4,}$/.test(line)) {
788
+ content.push({
789
+ type: 'rule'
790
+ });
791
+ }
792
+ // Handle Confluence panels
793
+ else if (line.match(/^\{panel:/)) {
794
+ const panelMatch = line.match(/^\{panel:title=([^|]*)[^}]*\}$/);
795
+ if (panelMatch) {
796
+ const title = panelMatch[1];
797
+ // Start collecting panel content
798
+ const panelContent = [];
799
+ i++; // Move to next line
800
+ // Collect content until we hit {panel}
801
+ while (i < lines.length && !lines[i].trim().match(/^\{panel\}$/)) {
802
+ const panelLine = lines[i].trim();
803
+ if (panelLine) {
804
+ panelContent.push({
805
+ type: 'paragraph',
806
+ content: this.parseInlineFormatting(panelLine)
807
+ });
808
+ }
809
+ i++;
810
+ }
811
+ // Create panel as an info panel (closest ADF equivalent)
812
+ content.push({
813
+ type: 'panel',
814
+ attrs: { panelType: 'info' },
815
+ content: [
816
+ {
817
+ type: 'paragraph',
818
+ content: [{
819
+ type: 'text',
820
+ text: title,
821
+ marks: [{ type: 'strong' }]
822
+ }]
823
+ },
824
+ ...panelContent
825
+ ]
826
+ });
827
+ }
828
+ }
829
+ // Handle bullet points
830
+ else if (line.startsWith('- ') || line.startsWith('* ')) {
831
+ // Check if we need to start a new bullet list or continue existing one
832
+ const lastItem = content[content.length - 1];
833
+ const bulletText = line.substring(2).trim();
834
+ if (lastItem && lastItem.type === 'bulletList') {
835
+ lastItem.content.push({
836
+ type: 'listItem',
837
+ content: [{
838
+ type: 'paragraph',
839
+ content: this.parseInlineFormatting(bulletText)
840
+ }]
841
+ });
842
+ }
843
+ else {
844
+ content.push({
845
+ type: 'bulletList',
846
+ content: [{
847
+ type: 'listItem',
848
+ content: [{
849
+ type: 'paragraph',
850
+ content: this.parseInlineFormatting(bulletText)
851
+ }]
852
+ }]
853
+ });
854
+ }
855
+ }
856
+ // Handle numbered lists
857
+ else if (/^\d+\.\s/.test(line)) {
858
+ const match = line.match(/^\d+\.\s(.+)$/);
859
+ if (match) {
860
+ const listText = match[1];
861
+ const lastItem = content[content.length - 1];
862
+ if (lastItem && lastItem.type === 'orderedList') {
863
+ lastItem.content.push({
864
+ type: 'listItem',
865
+ content: [{
866
+ type: 'paragraph',
867
+ content: this.parseInlineFormatting(listText)
868
+ }]
869
+ });
870
+ }
871
+ else {
872
+ content.push({
873
+ type: 'orderedList',
874
+ content: [{
875
+ type: 'listItem',
876
+ content: [{
877
+ type: 'paragraph',
878
+ content: this.parseInlineFormatting(listText)
879
+ }]
880
+ }]
881
+ });
882
+ }
883
+ }
884
+ }
885
+ // Handle code blocks (```)
886
+ else if (line.startsWith('```')) {
887
+ const codeLines = [];
888
+ i++; // Skip the opening ```
889
+ while (i < lines.length && !lines[i].trim().startsWith('```')) {
890
+ codeLines.push(lines[i]);
891
+ i++;
892
+ }
893
+ content.push({
894
+ type: 'codeBlock',
895
+ content: [{
896
+ type: 'text',
897
+ text: codeLines.join('\n')
898
+ }]
899
+ });
900
+ }
901
+ // Handle markdown tables - convert to proper ADF table structure
902
+ else if (line.includes('|') && line.trim() !== '|') {
903
+ const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
904
+ if (cells.length >= 2) {
905
+ // Collect all table rows from current position
906
+ const allTableRows = [];
907
+ // Start from current line and collect consecutive table rows
908
+ let tableEndIndex = i;
909
+ for (let tableIdx = i; tableIdx < lines.length; tableIdx++) {
910
+ const tableLine = lines[tableIdx].trim();
911
+ // Skip empty lines
912
+ if (tableLine === '') {
913
+ tableEndIndex = tableIdx;
914
+ continue;
915
+ }
916
+ // Stop if no pipes (end of table)
917
+ if (!tableLine.includes('|')) {
918
+ tableEndIndex = tableIdx - 1;
919
+ break;
920
+ }
921
+ // Skip separator lines like |-|-|
922
+ if (tableLine.match(/^\|?[\s\-:|]+\|?$/)) {
923
+ tableEndIndex = tableIdx;
924
+ continue;
925
+ }
926
+ // Parse row cells
927
+ const rowCells = tableLine.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
928
+ if (rowCells.length >= 2) {
929
+ allTableRows.push(rowCells);
930
+ tableEndIndex = tableIdx;
931
+ }
932
+ else {
933
+ tableEndIndex = tableIdx - 1;
934
+ break; // End of table
935
+ }
936
+ }
937
+ // Convert to proper ADF table structure if we have table rows
938
+ if (allTableRows.length > 0) {
939
+ // Create proper ADF table structure
940
+ const tableRows = [];
941
+ // Header row (first row)
942
+ if (allTableRows.length > 0) {
943
+ const headerCells = allTableRows[0].map(cellText => ({
944
+ type: 'tableHeader',
945
+ attrs: {},
946
+ content: [{
947
+ type: 'paragraph',
948
+ content: this.parseInlineFormatting(cellText)
949
+ }]
950
+ }));
951
+ tableRows.push({
952
+ type: 'tableRow',
953
+ content: headerCells
954
+ });
955
+ }
956
+ // Data rows (remaining rows)
957
+ for (let rowIdx = 1; rowIdx < allTableRows.length; rowIdx++) {
958
+ const dataCells = allTableRows[rowIdx].map(cellText => ({
959
+ type: 'tableCell',
960
+ attrs: {},
961
+ content: [{
962
+ type: 'paragraph',
963
+ content: this.parseInlineFormatting(cellText)
964
+ }]
965
+ }));
966
+ tableRows.push({
967
+ type: 'tableRow',
968
+ content: dataCells
969
+ });
970
+ }
971
+ // Create the complete table structure
972
+ content.push({
973
+ type: 'table',
974
+ attrs: {
975
+ isNumberColumnEnabled: false,
976
+ layout: 'default'
977
+ },
978
+ content: tableRows
979
+ });
980
+ // Skip the lines we've processed
981
+ i = tableEndIndex; // Skip the table rows we just processed
982
+ continue;
983
+ }
984
+ }
985
+ // If we reach here, it's not a valid table, treat as regular paragraph
986
+ content.push({
987
+ type: 'paragraph',
988
+ content: this.parseInlineFormatting(line)
989
+ });
990
+ }
991
+ // Regular paragraph
992
+ else {
993
+ content.push({
994
+ type: 'paragraph',
995
+ content: this.parseInlineFormatting(line)
996
+ });
997
+ }
998
+ }
999
+ // If no content was parsed, add a simple paragraph
1000
+ if (content.length === 0) {
1001
+ content.push({
1002
+ type: 'paragraph',
1003
+ content: [{
1004
+ type: 'text',
1005
+ text: description || ''
1006
+ }]
1007
+ });
1008
+ }
1009
+ return {
1010
+ type: 'doc',
1011
+ version: 1,
1012
+ content
1013
+ };
1014
+ }
1015
+ // Parse inline formatting like **bold**, *italic*, `code`, {{code}}, emojis
1016
+ parseInlineFormatting(text) {
1017
+ const result = [];
1018
+ let currentPos = 0;
1019
+ // Process patterns in order of precedence to avoid conflicts
1020
+ // **bold** should be processed before *italic* to handle **text** correctly
1021
+ const patterns = [
1022
+ { regex: /\*\*(.*?)\*\*/g, type: 'strong' }, // **bold** (process first)
1023
+ { regex: /`([^`]+?)`/g, type: 'code' }, // `code` (markdown style)
1024
+ { regex: /\{\{(.+?)\}\}/g, type: 'code' }, // {{code}} (Confluence/Jira wiki style)
1025
+ ];
1026
+ let matches = [];
1027
+ // Find all formatting matches
1028
+ patterns.forEach(pattern => {
1029
+ let match;
1030
+ const regex = new RegExp(pattern.regex.source, 'g');
1031
+ while ((match = regex.exec(text)) !== null) {
1032
+ matches.push({
1033
+ start: match.index,
1034
+ end: match.index + match[0].length,
1035
+ text: match[1],
1036
+ type: pattern.type,
1037
+ fullMatch: match[0]
1038
+ });
1039
+ }
1040
+ });
1041
+ // Sort matches by position and remove overlapping matches
1042
+ matches.sort((a, b) => a.start - b.start);
1043
+ // After processing bold and code, handle italic patterns that don't overlap
1044
+ let tempText = text;
1045
+ // Remove already matched bold and code patterns from consideration
1046
+ matches.forEach(match => {
1047
+ tempText = tempText.substring(0, match.start) + ' '.repeat(match.end - match.start) + tempText.substring(match.end);
1048
+ });
1049
+ // Now find italic patterns in the remaining text
1050
+ const italicRegex = /\*([^*]+?)\*/g;
1051
+ let italicMatch;
1052
+ while ((italicMatch = italicRegex.exec(tempText)) !== null) {
1053
+ matches.push({
1054
+ start: italicMatch.index,
1055
+ end: italicMatch.index + italicMatch[0].length,
1056
+ text: italicMatch[1],
1057
+ type: 'em',
1058
+ fullMatch: italicMatch[0]
1059
+ });
1060
+ }
1061
+ // Sort matches by position and remove overlapping matches
1062
+ matches.sort((a, b) => a.start - b.start);
1063
+ // Filter out overlapping matches (keep the first one in case of conflict)
1064
+ const filteredMatches = [];
1065
+ for (const match of matches) {
1066
+ const isOverlapping = filteredMatches.some(existing => (match.start >= existing.start && match.start < existing.end) ||
1067
+ (match.end > existing.start && match.end <= existing.end) ||
1068
+ (match.start <= existing.start && match.end >= existing.end));
1069
+ if (!isOverlapping) {
1070
+ filteredMatches.push(match);
1071
+ }
1072
+ }
1073
+ // Sort filtered matches by position for proper processing
1074
+ filteredMatches.sort((a, b) => a.start - b.start);
1075
+ // Process text with formatting
1076
+ filteredMatches.forEach(match => {
1077
+ // Add text before the match
1078
+ if (match.start > currentPos) {
1079
+ const beforeText = text.substring(currentPos, match.start);
1080
+ if (beforeText) {
1081
+ result.push({
1082
+ type: 'text',
1083
+ text: beforeText
1084
+ });
1085
+ }
1086
+ }
1087
+ // Add formatted text
1088
+ result.push({
1089
+ type: 'text',
1090
+ text: match.text,
1091
+ marks: [{ type: match.type }]
1092
+ });
1093
+ currentPos = match.end;
1094
+ });
1095
+ // Add remaining text
1096
+ if (currentPos < text.length) {
1097
+ const remainingText = text.substring(currentPos);
1098
+ if (remainingText) {
1099
+ result.push({
1100
+ type: 'text',
1101
+ text: remainingText
1102
+ });
1103
+ }
1104
+ }
1105
+ // If no formatting was found, return simple text
1106
+ if (result.length === 0) {
1107
+ result.push({
1108
+ type: 'text',
1109
+ text: text
1110
+ });
1111
+ }
1112
+ return result;
1113
+ }
1114
+ }
1115
+ //# sourceMappingURL=jira.js.map