@jagadeesh52423/atlassian-mcp-server 1.1.0 → 1.1.2

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 (98) hide show
  1. package/README.md +165 -36
  2. package/dist/formatters/adf-formatter.d.ts +30 -2
  3. package/dist/formatters/adf-formatter.d.ts.map +1 -1
  4. package/dist/formatters/adf-formatter.js +423 -107
  5. package/dist/formatters/adf-formatter.js.map +1 -1
  6. package/dist/formatters/index.d.ts +1 -1
  7. package/dist/formatters/index.d.ts.map +1 -1
  8. package/dist/formatters/index.js +1 -1
  9. package/dist/formatters/index.js.map +1 -1
  10. package/dist/handlers/base-handler.d.ts +5 -0
  11. package/dist/handlers/base-handler.d.ts.map +1 -0
  12. package/dist/handlers/base-handler.js +7 -0
  13. package/dist/handlers/base-handler.js.map +1 -0
  14. package/dist/handlers/bitbucket-handlers.d.ts +2 -1
  15. package/dist/handlers/bitbucket-handlers.d.ts.map +1 -1
  16. package/dist/handlers/bitbucket-handlers.js +31 -191
  17. package/dist/handlers/bitbucket-handlers.js.map +1 -1
  18. package/dist/handlers/confluence-handlers.d.ts +2 -1
  19. package/dist/handlers/confluence-handlers.d.ts.map +1 -1
  20. package/dist/handlers/confluence-handlers.js +46 -243
  21. package/dist/handlers/confluence-handlers.js.map +1 -1
  22. package/dist/handlers/jira-handlers.d.ts +9 -1
  23. package/dist/handlers/jira-handlers.d.ts.map +1 -1
  24. package/dist/handlers/jira-handlers.js +144 -575
  25. package/dist/handlers/jira-handlers.js.map +1 -1
  26. package/dist/index.js +111 -189
  27. package/dist/index.js.map +1 -1
  28. package/dist/registry/tool-registry.d.ts +11 -0
  29. package/dist/registry/tool-registry.d.ts.map +1 -0
  30. package/dist/registry/tool-registry.js +24 -0
  31. package/dist/registry/tool-registry.js.map +1 -0
  32. package/dist/services/bitbucket.d.ts +2 -0
  33. package/dist/services/bitbucket.d.ts.map +1 -1
  34. package/dist/services/bitbucket.js +22 -74
  35. package/dist/services/bitbucket.js.map +1 -1
  36. package/dist/services/confluence.d.ts.map +1 -1
  37. package/dist/services/confluence.js +5 -4
  38. package/dist/services/confluence.js.map +1 -1
  39. package/dist/services/jira/agile.d.ts +33 -0
  40. package/dist/services/jira/agile.d.ts.map +1 -0
  41. package/dist/services/jira/agile.js +118 -0
  42. package/dist/services/jira/agile.js.map +1 -0
  43. package/dist/services/jira/attachments.d.ts +7 -0
  44. package/dist/services/jira/attachments.d.ts.map +1 -0
  45. package/dist/services/jira/attachments.js +26 -0
  46. package/dist/services/jira/attachments.js.map +1 -0
  47. package/dist/services/jira/base.d.ts +9 -0
  48. package/dist/services/jira/base.d.ts.map +1 -0
  49. package/dist/services/jira/base.js +11 -0
  50. package/dist/services/jira/base.js.map +1 -0
  51. package/dist/services/jira/comments.d.ts +8 -0
  52. package/dist/services/jira/comments.d.ts.map +1 -0
  53. package/dist/services/jira/comments.js +33 -0
  54. package/dist/services/jira/comments.js.map +1 -0
  55. package/dist/services/jira/issues.d.ts +32 -0
  56. package/dist/services/jira/issues.d.ts.map +1 -0
  57. package/dist/services/jira/issues.js +177 -0
  58. package/dist/services/jira/issues.js.map +1 -0
  59. package/dist/services/jira/labels.d.ts +7 -0
  60. package/dist/services/jira/labels.d.ts.map +1 -0
  61. package/dist/services/jira/labels.js +21 -0
  62. package/dist/services/jira/labels.js.map +1 -0
  63. package/dist/services/jira/links.d.ts +9 -0
  64. package/dist/services/jira/links.d.ts.map +1 -0
  65. package/dist/services/jira/links.js +55 -0
  66. package/dist/services/jira/links.js.map +1 -0
  67. package/dist/services/jira/metadata.d.ts +8 -0
  68. package/dist/services/jira/metadata.d.ts.map +1 -0
  69. package/dist/services/jira/metadata.js +32 -0
  70. package/dist/services/jira/metadata.js.map +1 -0
  71. package/dist/services/jira/subtasks.d.ts +9 -0
  72. package/dist/services/jira/subtasks.d.ts.map +1 -0
  73. package/dist/services/jira/subtasks.js +32 -0
  74. package/dist/services/jira/subtasks.js.map +1 -0
  75. package/dist/services/jira/transitions.d.ts +16 -0
  76. package/dist/services/jira/transitions.d.ts.map +1 -0
  77. package/dist/services/jira/transitions.js +203 -0
  78. package/dist/services/jira/transitions.js.map +1 -0
  79. package/dist/services/jira/versions.d.ts +19 -0
  80. package/dist/services/jira/versions.d.ts.map +1 -0
  81. package/dist/services/jira/versions.js +62 -0
  82. package/dist/services/jira/versions.js.map +1 -0
  83. package/dist/services/jira/watchers.d.ts +7 -0
  84. package/dist/services/jira/watchers.d.ts.map +1 -0
  85. package/dist/services/jira/watchers.js +23 -0
  86. package/dist/services/jira/watchers.js.map +1 -0
  87. package/dist/services/jira/worklogs.d.ts +8 -0
  88. package/dist/services/jira/worklogs.d.ts.map +1 -0
  89. package/dist/services/jira/worklogs.js +37 -0
  90. package/dist/services/jira/worklogs.js.map +1 -0
  91. package/dist/services/jira.d.ts +58 -103
  92. package/dist/services/jira.d.ts.map +1 -1
  93. package/dist/services/jira.js +100 -1481
  94. package/dist/services/jira.js.map +1 -1
  95. package/dist/tools/jira-tools.d.ts.map +1 -1
  96. package/dist/tools/jira-tools.js +35 -3
  97. package/dist/tools/jira-tools.js.map +1 -1
  98. package/package.json +1 -1
@@ -1,1486 +1,105 @@
1
1
  import { JiraClient } from '../clients/jira-client.js';
2
+ import { JiraIssueService } from './jira/issues.js';
3
+ import { JiraTransitionService } from './jira/transitions.js';
4
+ import { JiraCommentService } from './jira/comments.js';
5
+ import { JiraAttachmentService } from './jira/attachments.js';
6
+ import { JiraLinkService } from './jira/links.js';
7
+ import { JiraWorklogService } from './jira/worklogs.js';
8
+ import { JiraWatcherService } from './jira/watchers.js';
9
+ import { JiraLabelService } from './jira/labels.js';
10
+ import { JiraSubtaskService } from './jira/subtasks.js';
11
+ import { JiraAgileService } from './jira/agile.js';
12
+ import { JiraVersionService } from './jira/versions.js';
13
+ import { JiraMetadataService } from './jira/metadata.js';
2
14
  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
- ];
15
+ issues;
16
+ transitions;
17
+ comments;
18
+ attachments;
19
+ links;
20
+ worklogs;
21
+ watchers;
22
+ labels;
23
+ subtasks;
24
+ agile;
25
+ versions;
26
+ metadata;
29
27
  constructor() {
30
- this.client = new JiraClient();
31
- }
32
- async searchIssues(options = {}) {
33
- const { query, assignee, status, project, labels, reporter, createdAfter, updatedAfter, jql: rawJql, fields: extraFields, limit = 50, startAt = 0 } = options;
34
- let jql;
35
- if (rawJql) {
36
- // Use raw JQL directly when provided
37
- jql = rawJql;
38
- }
39
- else {
40
- // Build JQL from individual filters
41
- const conditions = [];
42
- if (query) {
43
- conditions.push(`text ~ "${query}"`);
44
- }
45
- if (assignee) {
46
- conditions.push(`assignee = ${assignee}`);
47
- }
48
- if (status) {
49
- conditions.push(`status = "${status}"`);
50
- }
51
- if (project) {
52
- conditions.push(`project = "${project}"`);
53
- }
54
- if (labels && labels.length > 0) {
55
- conditions.push(labels.map(l => `labels = "${l}"`).join(' AND '));
56
- }
57
- if (reporter) {
58
- conditions.push(`reporter = ${reporter}`);
59
- }
60
- if (createdAfter) {
61
- // Support relative days (e.g., "10d") or absolute dates (e.g., "2026-01-15")
62
- const dateValue = /^\d+d$/.test(createdAfter) ? `-${createdAfter}` : createdAfter;
63
- conditions.push(`created >= "${dateValue}"`);
64
- }
65
- if (updatedAfter) {
66
- const dateValue = /^\d+d$/.test(updatedAfter) ? `-${updatedAfter}` : updatedAfter;
67
- conditions.push(`updated >= "${dateValue}"`);
68
- }
69
- // Only apply 90-day bound when no specific filters are provided
70
- if (conditions.length === 0) {
71
- conditions.push('created >= -90d');
72
- }
73
- jql = `${conditions.join(' AND ')} ORDER BY updated DESC`;
74
- }
75
- const defaultFields = ['summary', 'description', 'status', 'assignee', 'labels', 'created', 'updated'];
76
- const requestFields = extraFields && extraFields.length > 0
77
- ? [...new Set(extraFields)]
78
- : defaultFields;
79
- const params = {
80
- jql,
81
- maxResults: limit,
82
- startAt,
83
- fields: requestFields.join(',')
84
- };
85
- const response = await this.client.get('/rest/api/3/search/jql', params);
86
- return response.issues.map((issue) => {
87
- const result = {
88
- key: issue.key,
89
- };
90
- for (const field of requestFields) {
91
- const value = issue.fields[field];
92
- if (value === undefined || value === null) {
93
- result[field] = null;
94
- }
95
- else if (field === 'description') {
96
- result[field] = this.extractTextFromADF(value);
97
- }
98
- else if (typeof value === 'object' && !Array.isArray(value)) {
99
- result[field] = value.name || value.displayName || value.value || value;
100
- }
101
- else if (Array.isArray(value)) {
102
- result[field] = value.map((v) => typeof v === 'object' ? (v.name || v.displayName || v.value || v) : v);
103
- }
104
- else {
105
- result[field] = value;
106
- }
107
- }
108
- return result;
109
- });
110
- }
111
- async getIssue(issueKey) {
112
- const params = {
113
- fields: ['summary', 'description', 'status', 'assignee', 'labels', 'created', 'updated']
114
- };
115
- const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, params);
116
- return {
117
- id: issue.id,
118
- key: issue.key,
119
- summary: issue.fields.summary || '',
120
- description: this.extractTextFromADF(issue.fields.description),
121
- status: issue.fields.status?.name || '',
122
- assignee: issue.fields.assignee?.displayName || '',
123
- labels: issue.fields.labels || [],
124
- created: issue.fields.created || '',
125
- updated: issue.fields.updated || ''
126
- };
127
- }
128
- async createIssue(projectKey, summary, description, issueType = 'Task', additionalFields) {
129
- const fields = {
130
- project: { key: projectKey },
131
- summary,
132
- description: this.parseDescriptionToADF(description),
133
- issuetype: { name: issueType }
134
- };
135
- // Add optional fields if provided
136
- if (additionalFields) {
137
- // Due date (format: YYYY-MM-DD)
138
- if (additionalFields.duedate) {
139
- fields.duedate = additionalFields.duedate;
140
- }
141
- // Priority (by name)
142
- if (additionalFields.priority) {
143
- fields.priority = { name: additionalFields.priority };
144
- }
145
- // Labels (array of strings)
146
- if (additionalFields.labels && additionalFields.labels.length > 0) {
147
- fields.labels = additionalFields.labels;
148
- }
149
- // Components (by name)
150
- if (additionalFields.components && additionalFields.components.length > 0) {
151
- fields.components = additionalFields.components.map(name => ({ name }));
152
- }
153
- // Assignee (by account ID)
154
- if (additionalFields.assignee) {
155
- fields.assignee = { accountId: additionalFields.assignee };
156
- }
157
- // Custom fields (merge directly into fields object)
158
- if (additionalFields.customFields) {
159
- Object.assign(fields, additionalFields.customFields);
160
- }
161
- }
162
- const data = { fields };
163
- const response = await this.client.post('/rest/api/3/issue', data);
164
- return this.getIssue(response.key);
165
- }
166
- async updateIssue(issueKey, fields) {
167
- const updateFields = {};
168
- if (fields.summary) {
169
- updateFields.summary = fields.summary;
170
- }
171
- if (fields.description) {
172
- // Use proper ADF format for description updates
173
- updateFields.description = this.parseDescriptionToADF(fields.description);
174
- }
175
- if (fields.assignee) {
176
- updateFields.assignee = { accountId: fields.assignee };
177
- }
178
- const data = { fields: updateFields };
179
- await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
180
- return this.getIssue(issueKey);
181
- }
182
- async transitionIssue(issueKey, transitionId, fieldValues) {
183
- try {
184
- const data = {
185
- transition: { id: transitionId }
186
- };
187
- // Add field values if provided
188
- if (fieldValues) {
189
- data.fields = fieldValues;
190
- }
191
- await this.client.post(`/rest/api/3/issue/${issueKey}/transitions`, data);
192
- return {
193
- success: true,
194
- message: `Issue ${issueKey} successfully transitioned`
195
- };
196
- }
197
- catch (error) {
198
- // Check if this is a required field error
199
- if (error.response?.status === 400) {
200
- const requiredFields = await this.handleTransitionError(issueKey, transitionId, error);
201
- if (requiredFields.length > 0) {
202
- return {
203
- success: false,
204
- requiresInput: true,
205
- requiredFields,
206
- message: 'Transition requires additional fields. Please provide values for the required fields.',
207
- issueKey,
208
- transitionId
209
- };
210
- }
211
- }
212
- // Re-throw if not a field validation error
213
- throw error;
214
- }
215
- }
216
- async getIssueTransitions(issueKey) {
217
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/transitions`);
218
- return response.transitions;
219
- }
220
- async addComment(issueKey, comment) {
221
- const data = {
222
- body: this.parseDescriptionToADF(comment)
223
- };
224
- await this.client.post(`/rest/api/3/issue/${issueKey}/comment`, data);
225
- }
226
- async getComments(issueKey) {
227
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/comment`);
228
- return response.comments.map((comment) => ({
229
- id: comment.id,
230
- author: comment.author?.displayName || 'Unknown',
231
- authorAccountId: comment.author?.accountId,
232
- body: this.extractTextFromADF(comment.body),
233
- created: comment.created,
234
- updated: comment.updated,
235
- updateAuthor: comment.updateAuthor?.displayName
236
- }));
237
- }
238
- async updateComment(issueKey, commentId, comment) {
239
- const data = {
240
- body: this.parseDescriptionToADF(comment)
241
- };
242
- const response = await this.client.put(`/rest/api/3/issue/${issueKey}/comment/${commentId}`, data);
243
- return {
244
- id: response.id,
245
- author: response.author?.displayName || 'Unknown',
246
- body: this.extractTextFromADF(response.body),
247
- created: response.created,
248
- updated: response.updated,
249
- updateAuthor: response.updateAuthor?.displayName
250
- };
251
- }
252
- async deleteComment(issueKey, commentId) {
253
- await this.client.delete(`/rest/api/3/issue/${issueKey}/comment/${commentId}`);
254
- }
255
- extractTextFromADF(body) {
256
- if (!body)
257
- return '';
258
- // If body is already a string (wiki markup), return it directly
259
- if (typeof body === 'string') {
260
- return this.formatWikiMarkup(body);
261
- }
262
- // If body doesn't have content property, it might be wiki markup in another format
263
- if (!body.content) {
264
- return this.formatWikiMarkup(String(body));
265
- }
266
- let text = '';
267
- const extractFromContent = (content) => {
268
- for (const item of content) {
269
- if (item.type === 'text' && item.text) {
270
- text += item.text;
271
- }
272
- else if (item.type === 'hardBreak') {
273
- text += '\n';
274
- }
275
- else if (item.type === 'paragraph') {
276
- if (item.content) {
277
- extractFromContent(item.content);
278
- }
279
- }
280
- else if (item.type === 'heading') {
281
- const level = item.attrs?.level || 1;
282
- const prefix = '#'.repeat(level);
283
- text += `${prefix} `;
284
- if (item.content) {
285
- extractFromContent(item.content);
286
- }
287
- }
288
- else if (item.type === 'bulletList' || item.type === 'orderedList') {
289
- if (item.content) {
290
- extractFromContent(item.content);
291
- }
292
- }
293
- else if (item.type === 'listItem') {
294
- text += '- ';
295
- if (item.content) {
296
- extractFromContent(item.content);
297
- }
298
- }
299
- else if (item.type === 'codeBlock') {
300
- text += '```';
301
- if (item.content) {
302
- extractFromContent(item.content);
303
- }
304
- text += '```';
305
- }
306
- else if (item.type === 'table') {
307
- // Extract table as markdown
308
- if (item.content) {
309
- const rows = [];
310
- item.content.forEach((row) => {
311
- if (row.type === 'tableRow' && row.content) {
312
- const cells = [];
313
- row.content.forEach((cell) => {
314
- if ((cell.type === 'tableCell' || cell.type === 'tableHeader') && cell.content) {
315
- let cellText = '';
316
- const extractCellText = (cellContent) => {
317
- cellContent.forEach((cellItem) => {
318
- if (cellItem.type === 'paragraph' && cellItem.content) {
319
- cellItem.content.forEach((textItem) => {
320
- if (textItem.type === 'text' && textItem.text) {
321
- cellText += textItem.text;
322
- }
323
- });
324
- }
325
- });
326
- };
327
- extractCellText(cell.content);
328
- cells.push(cellText.trim());
329
- }
330
- });
331
- if (cells.length > 0) {
332
- rows.push(cells);
333
- }
334
- }
335
- });
336
- // Format as markdown table
337
- if (rows.length > 0) {
338
- const [headerRow, ...dataRows] = rows;
339
- // Header row
340
- text += '\n| ' + headerRow.join(' | ') + ' |\n';
341
- // Separator row
342
- text += '|' + headerRow.map(() => '--------').join('|') + '|\n';
343
- // Data rows
344
- dataRows.forEach(row => {
345
- text += '| ' + row.join(' | ') + ' |\n';
346
- });
347
- }
348
- }
349
- }
350
- else if (item.content) {
351
- extractFromContent(item.content);
352
- }
353
- }
354
- };
355
- extractFromContent(body.content);
356
- return this.formatWikiMarkup(text.trim());
357
- }
358
- formatWikiMarkup(text) {
359
- if (!text)
360
- return '';
361
- return text
362
- // Convert Confluence headers to markdown headers
363
- .replace(/^h([1-6])\.\s*/gm, (match, level) => '#'.repeat(parseInt(level)) + ' ')
364
- // Convert horizontal rules
365
- .replace(/^----+$/gm, '---')
366
- // Handle panels - convert to blockquotes for better readability
367
- .replace(/\{panel:title=([^|]*)[^}]*\}/g, '\n> **$1**\n>')
368
- .replace(/\{panel\}/g, '\n')
369
- // Handle colored text/backgrounds - keep the text but remove styling
370
- .replace(/\{color:[^}]*\}([^{]*)\{color\}/g, '$1')
371
- // Convert checkboxes
372
- .replace(/☐/g, '- [ ]')
373
- .replace(/☑/g, '- [x]')
374
- // Clean up multiple newlines
375
- .replace(/\n{3,}/g, '\n\n')
376
- // Ensure proper spacing around headers
377
- .replace(/\n(#{1,6}\s)/g, '\n\n$1')
378
- .trim();
379
- }
380
- async getProjects() {
381
- const response = await this.client.get('/rest/api/3/project');
382
- return response;
383
- }
384
- async getIssueTypes(projectKey) {
385
- const response = await this.client.get(`/rest/api/3/issue/createmeta/${projectKey}/issuetypes`);
386
- return response.issueTypes;
387
- }
388
- async assignIssue(issueKey, accountId) {
389
- const data = { accountId };
390
- await this.client.put(`/rest/api/3/issue/${issueKey}/assignee`, data);
391
- }
392
- async getUsersByQuery(query) {
393
- const params = {
394
- query,
395
- maxResults: 10
396
- };
397
- const response = await this.client.get('/rest/api/3/user/search', params);
398
- return response;
399
- }
400
- // Attachment Methods
401
- async getAttachments(issueKey) {
402
- const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
403
- fields: ['attachment']
404
- });
405
- return issue.fields.attachment?.map((att) => ({
406
- id: att.id,
407
- filename: att.filename,
408
- author: att.author?.displayName || 'Unknown',
409
- created: att.created,
410
- size: att.size,
411
- mimeType: att.mimeType,
412
- content: att.content
413
- })) || [];
414
- }
415
- async addAttachment(issueKey, filename, fileContent) {
416
- // Note: fileContent should be base64 encoded
417
- const FormData = (await import('form-data')).default;
418
- const form = new FormData();
419
- const buffer = Buffer.from(fileContent, 'base64');
420
- form.append('file', buffer, { filename });
421
- const response = await this.client.postFormData(`/rest/api/3/issue/${issueKey}/attachments`, form);
422
- return response;
423
- }
424
- async deleteAttachment(attachmentId) {
425
- await this.client.delete(`/rest/api/3/attachment/${attachmentId}`);
426
- }
427
- // Issue Linking Methods
428
- async getIssueLinks(issueKey) {
429
- const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
430
- fields: ['issuelinks']
431
- });
432
- return issue.fields.issuelinks?.map((link) => ({
433
- id: link.id,
434
- type: link.type?.name || 'Unknown',
435
- inward: link.inwardIssue ? {
436
- key: link.inwardIssue.key,
437
- summary: link.inwardIssue.fields?.summary,
438
- status: link.inwardIssue.fields?.status?.name
439
- } : null,
440
- outward: link.outwardIssue ? {
441
- key: link.outwardIssue.key,
442
- summary: link.outwardIssue.fields?.summary,
443
- status: link.outwardIssue.fields?.status?.name
444
- } : null
445
- })) || [];
446
- }
447
- async createIssueLink(inwardIssueKey, outwardIssueKey, linkType) {
448
- const data = {
449
- type: { name: linkType },
450
- inwardIssue: { key: inwardIssueKey },
451
- outwardIssue: { key: outwardIssueKey }
452
- };
453
- const response = await this.client.post('/rest/api/3/issueLink', data);
454
- return response;
455
- }
456
- async deleteIssueLink(linkId) {
457
- await this.client.delete(`/rest/api/3/issueLink/${linkId}`);
458
- }
459
- async getIssueLinkTypes() {
460
- const response = await this.client.get('/rest/api/3/issueLinkType');
461
- return response.issueLinkTypes.map((type) => ({
462
- id: type.id,
463
- name: type.name,
464
- inward: type.inward,
465
- outward: type.outward
466
- }));
467
- }
468
- // Work Log Methods
469
- async getWorkLogs(issueKey) {
470
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/worklog`);
471
- return response.worklogs?.map((worklog) => ({
472
- id: worklog.id,
473
- author: worklog.author?.displayName || 'Unknown',
474
- authorAccountId: worklog.author?.accountId,
475
- comment: this.extractTextFromADF(worklog.comment),
476
- created: worklog.created,
477
- updated: worklog.updated,
478
- started: worklog.started,
479
- timeSpent: worklog.timeSpent,
480
- timeSpentSeconds: worklog.timeSpentSeconds
481
- })) || [];
482
- }
483
- async addWorkLog(issueKey, timeSpentSeconds, comment, started) {
484
- const data = {
485
- timeSpentSeconds
486
- };
487
- if (started) {
488
- data.started = started;
489
- }
490
- if (comment) {
491
- data.comment = this.parseDescriptionToADF(comment);
492
- }
493
- const response = await this.client.post(`/rest/api/3/issue/${issueKey}/worklog`, data);
494
- return response;
495
- }
496
- async updateWorkLog(issueKey, worklogId, timeSpentSeconds, comment) {
497
- const data = {};
498
- if (timeSpentSeconds !== undefined) {
499
- data.timeSpentSeconds = timeSpentSeconds;
500
- }
501
- if (comment) {
502
- data.comment = this.parseDescriptionToADF(comment);
503
- }
504
- const response = await this.client.put(`/rest/api/3/issue/${issueKey}/worklog/${worklogId}`, data);
505
- return response;
506
- }
507
- async deleteWorkLog(issueKey, worklogId) {
508
- await this.client.delete(`/rest/api/3/issue/${issueKey}/worklog/${worklogId}`);
509
- }
510
- // Watchers Methods
511
- async getWatchers(issueKey) {
512
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/watchers`);
513
- return {
514
- watchCount: response.watchCount,
515
- isWatching: response.isWatching,
516
- watchers: response.watchers?.map((watcher) => ({
517
- accountId: watcher.accountId,
518
- displayName: watcher.displayName,
519
- emailAddress: watcher.emailAddress,
520
- active: watcher.active
521
- })) || []
522
- };
523
- }
524
- async addWatcher(issueKey, accountId) {
525
- await this.client.post(`/rest/api/3/issue/${issueKey}/watchers`, JSON.stringify(accountId));
526
- }
527
- async removeWatcher(issueKey, accountId) {
528
- await this.client.delete(`/rest/api/3/issue/${issueKey}/watchers?accountId=${accountId}`);
529
- }
530
- // Field Discovery Methods
531
- async getFields(type) {
532
- const response = await this.client.get('/rest/api/3/field');
533
- let fields = response.map((f) => ({
534
- id: f.id,
535
- name: f.name,
536
- custom: f.custom || false,
537
- schema: f.schema?.type || null,
538
- }));
539
- if (type === 'standard') {
540
- fields = fields.filter((f) => !f.custom);
541
- }
542
- else if (type === 'custom') {
543
- fields = fields.filter((f) => f.custom);
544
- }
545
- return fields.sort((a, b) => a.name.localeCompare(b.name));
546
- }
547
- // Label Methods
548
- async getLabels(query, maxResults = 50) {
549
- const params = { maxResults };
550
- if (query) {
551
- params.query = query;
552
- }
553
- const response = await this.client.get('/rest/api/3/label', params);
554
- return response.values || [];
555
- }
556
- async addLabels(issueKey, labels) {
557
- const data = {
558
- update: {
559
- labels: labels.map(label => ({ add: label }))
560
- }
561
- };
562
- await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
563
- }
564
- async removeLabels(issueKey, labels) {
565
- const data = {
566
- update: {
567
- labels: labels.map(label => ({ remove: label }))
568
- }
569
- };
570
- await this.client.put(`/rest/api/3/issue/${issueKey}`, data);
571
- }
572
- // Sub-task Methods
573
- async getSubTasks(issueKey) {
574
- const issue = await this.client.get(`/rest/api/3/issue/${issueKey}`, {
575
- fields: ['subtasks']
576
- });
577
- return issue.fields.subtasks?.map((subtask) => ({
578
- id: subtask.id,
579
- key: subtask.key,
580
- summary: subtask.fields?.summary,
581
- status: subtask.fields?.status?.name,
582
- assignee: subtask.fields?.assignee?.displayName
583
- })) || [];
584
- }
585
- async createSubTask(parentKey, summary, description) {
586
- const data = {
587
- fields: {
588
- project: { key: parentKey.split('-')[0] },
589
- parent: { key: parentKey },
590
- summary,
591
- description: this.parseDescriptionToADF(description),
592
- issuetype: { name: 'Sub-task' }
593
- }
594
- };
595
- const response = await this.client.post('/rest/api/3/issue', data);
596
- return this.getIssue(response.key);
597
- }
598
- // Issue History Methods
599
- async getIssueHistory(issueKey) {
600
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/changelog`);
601
- return response.values?.map((change) => ({
602
- id: change.id,
603
- author: change.author?.displayName || 'Unknown',
604
- authorAccountId: change.author?.accountId,
605
- created: change.created,
606
- items: change.items?.map((item) => ({
607
- field: item.field,
608
- fieldtype: item.fieldtype,
609
- from: item.fromString,
610
- to: item.toString
611
- }))
612
- })) || [];
613
- }
614
- // Agile Methods
615
- async getAgileBoards(options) {
616
- const params = { maxResults: 50 };
617
- if (options?.name)
618
- params.name = options.name;
619
- if (options?.type)
620
- params.type = options.type;
621
- if (options?.projectKeyOrId)
622
- params.projectKeyOrId = options.projectKeyOrId;
623
- const response = await this.client.get('/rest/agile/1.0/board', params);
624
- return response.values?.map((board) => ({
625
- id: board.id,
626
- name: board.name,
627
- type: board.type,
628
- location: board.location ? {
629
- projectId: board.location.projectId,
630
- projectKey: board.location.projectKey,
631
- projectName: board.location.projectName,
632
- } : null,
633
- })) || [];
634
- }
635
- async getBoardIssues(boardId, options) {
636
- const params = { maxResults: options?.maxResults || 50 };
637
- if (options?.jql)
638
- params.jql = options.jql;
639
- if (options?.startAt)
640
- params.startAt = options.startAt;
641
- const response = await this.client.get(`/rest/agile/1.0/board/${boardId}/issue`, params);
642
- return {
643
- total: response.total,
644
- issues: response.issues?.map((issue) => ({
645
- key: issue.key,
646
- summary: issue.fields?.summary || '',
647
- status: issue.fields?.status?.name || '',
648
- assignee: issue.fields?.assignee?.displayName || '',
649
- priority: issue.fields?.priority?.name || '',
650
- })) || [],
651
- };
652
- }
653
- async getSprints(boardId, state) {
654
- const params = { maxResults: 50 };
655
- if (state)
656
- params.state = state;
657
- const response = await this.client.get(`/rest/agile/1.0/board/${boardId}/sprint`, params);
658
- return response.values?.map((sprint) => ({
659
- id: sprint.id,
660
- name: sprint.name,
661
- state: sprint.state,
662
- startDate: sprint.startDate || null,
663
- endDate: sprint.endDate || null,
664
- completeDate: sprint.completeDate || null,
665
- goal: sprint.goal || '',
666
- })) || [];
667
- }
668
- async getSprintIssues(sprintId, options) {
669
- const params = { maxResults: options?.maxResults || 50 };
670
- if (options?.jql)
671
- params.jql = options.jql;
672
- if (options?.startAt)
673
- params.startAt = options.startAt;
674
- const response = await this.client.get(`/rest/agile/1.0/sprint/${sprintId}/issue`, params);
675
- return {
676
- total: response.total,
677
- issues: response.issues?.map((issue) => ({
678
- key: issue.key,
679
- summary: issue.fields?.summary || '',
680
- status: issue.fields?.status?.name || '',
681
- assignee: issue.fields?.assignee?.displayName || '',
682
- priority: issue.fields?.priority?.name || '',
683
- })) || [],
684
- };
685
- }
686
- // Batch Create
687
- async batchCreateIssues(issues) {
688
- if (issues.length > 50) {
689
- throw new Error('Batch create supports a maximum of 50 issues at a time');
690
- }
691
- const issueUpdates = issues.map(issue => ({
692
- fields: {
693
- project: { key: issue.projectKey },
694
- summary: issue.summary,
695
- description: this.parseDescriptionToADF(issue.description),
696
- issuetype: { name: issue.issueType || 'Task' },
697
- ...issue.additionalFields,
698
- },
699
- }));
700
- const response = await this.client.post('/rest/api/3/issue/bulk', { issueUpdates });
701
- return {
702
- issues: response.issues?.map((i) => ({ id: i.id, key: i.key, self: i.self })) || [],
703
- errors: response.errors || [],
704
- };
705
- }
706
- // Dev Status
707
- async getDevStatus(issueId, applicationType, dataType) {
708
- const params = { issueId };
709
- if (applicationType)
710
- params.applicationType = applicationType;
711
- if (dataType)
712
- params.dataType = dataType;
713
- const response = await this.client.get('/rest/dev-status/latest/issue/detail', params);
714
- return response;
715
- }
716
- // Delete Issue
717
- async deleteIssue(issueKey, deleteSubtasks = false) {
718
- const query = deleteSubtasks ? '?deleteSubtasks=true' : '';
719
- await this.client.delete(`/rest/api/3/issue/${issueKey}${query}`);
720
- }
721
- // Sprint Management
722
- async createSprint(boardId, name, options) {
723
- const data = {
724
- name,
725
- originBoardId: boardId,
726
- };
727
- if (options?.startDate)
728
- data.startDate = options.startDate;
729
- if (options?.endDate)
730
- data.endDate = options.endDate;
731
- if (options?.goal)
732
- data.goal = options.goal;
733
- const response = await this.client.post('/rest/agile/1.0/sprint', data);
734
- return {
735
- id: response.id,
736
- name: response.name,
737
- state: response.state,
738
- startDate: response.startDate || null,
739
- endDate: response.endDate || null,
740
- goal: response.goal || '',
741
- };
742
- }
743
- async updateSprint(sprintId, updates) {
744
- const data = {};
745
- if (updates.name)
746
- data.name = updates.name;
747
- if (updates.state)
748
- data.state = updates.state;
749
- if (updates.startDate)
750
- data.startDate = updates.startDate;
751
- if (updates.endDate)
752
- data.endDate = updates.endDate;
753
- if (updates.goal !== undefined)
754
- data.goal = updates.goal;
755
- const response = await this.client.post(`/rest/agile/1.0/sprint/${sprintId}`, data);
756
- return {
757
- id: response.id,
758
- name: response.name,
759
- state: response.state,
760
- startDate: response.startDate || null,
761
- endDate: response.endDate || null,
762
- goal: response.goal || '',
763
- };
764
- }
765
- // Version/Release Management
766
- async getProjectVersions(projectKey) {
767
- const response = await this.client.get(`/rest/api/3/project/${projectKey}/versions`);
768
- return response.map((v) => ({
769
- id: v.id,
770
- name: v.name,
771
- description: v.description || '',
772
- released: v.released || false,
773
- archived: v.archived || false,
774
- startDate: v.startDate || null,
775
- releaseDate: v.releaseDate || null,
776
- }));
777
- }
778
- async createVersion(projectKey, name, options) {
779
- const data = {
780
- project: projectKey,
781
- name,
782
- };
783
- if (options?.description)
784
- data.description = options.description;
785
- if (options?.startDate)
786
- data.startDate = options.startDate;
787
- if (options?.releaseDate)
788
- data.releaseDate = options.releaseDate;
789
- if (options?.released !== undefined)
790
- data.released = options.released;
791
- const response = await this.client.post('/rest/api/3/version', data);
792
- return {
793
- id: response.id,
794
- name: response.name,
795
- description: response.description || '',
796
- released: response.released || false,
797
- archived: response.archived || false,
798
- startDate: response.startDate || null,
799
- releaseDate: response.releaseDate || null,
800
- };
801
- }
802
- async updateVersion(versionId, updates) {
803
- const data = {};
804
- if (updates.name)
805
- data.name = updates.name;
806
- if (updates.description !== undefined)
807
- data.description = updates.description;
808
- if (updates.startDate)
809
- data.startDate = updates.startDate;
810
- if (updates.releaseDate)
811
- data.releaseDate = updates.releaseDate;
812
- if (updates.released !== undefined)
813
- data.released = updates.released;
814
- if (updates.archived !== undefined)
815
- data.archived = updates.archived;
816
- const response = await this.client.put(`/rest/api/3/version/${versionId}`, data);
817
- return {
818
- id: response.id,
819
- name: response.name,
820
- description: response.description || '',
821
- released: response.released || false,
822
- archived: response.archived || false,
823
- startDate: response.startDate || null,
824
- releaseDate: response.releaseDate || null,
825
- };
826
- }
827
- // Epic Linking
828
- async linkToEpic(issueKey, epicKey) {
829
- // In next-gen projects, Epic is the parent. In classic, it's the Epic Link custom field.
830
- // Try parent field first (works for next-gen/team-managed), fall back to Epic Link.
831
- try {
832
- await this.client.put(`/rest/api/3/issue/${issueKey}`, {
833
- fields: { parent: { key: epicKey } }
834
- });
835
- }
836
- catch {
837
- // Fallback: find the Epic Link custom field ID and use that
838
- const fields = await this.client.get('/rest/api/3/field');
839
- const epicLinkField = fields.find((f) => f.name === 'Epic Link' || f.clauseNames?.includes('cf[10014]'));
840
- if (epicLinkField) {
841
- await this.client.put(`/rest/api/3/issue/${issueKey}`, {
842
- fields: { [epicLinkField.id]: epicKey }
843
- });
844
- }
845
- else {
846
- throw new Error('Could not find Epic Link field. Ensure the issue type supports epic linking.');
847
- }
848
- }
849
- }
850
- // Smart Field Handling Methods
851
- async handleTransitionError(issueKey, transitionId, error) {
852
- try {
853
- // Get transition metadata with field information
854
- const response = await this.client.get(`/rest/api/3/issue/${issueKey}/transitions?expand=transitions.fields`);
855
- const transition = response.transitions.find((t) => t.id === transitionId);
856
- if (!transition || !transition.fields) {
857
- return [];
858
- }
859
- // Get issue details for context
860
- const issue = await this.getIssue(issueKey);
861
- // Analyze required fields and fields that may need values
862
- const requiredFields = [];
863
- for (const [fieldKey, fieldInfo] of Object.entries(transition.fields)) {
864
- // Process all fields that have validation requirements or are commonly needed
865
- const field = this.analyzeField(fieldKey, fieldInfo, issue);
866
- requiredFields.push(field);
867
- }
868
- return requiredFields;
869
- }
870
- catch (err) {
871
- console.error('Error analyzing transition fields:', err);
872
- return [];
873
- }
874
- }
875
- analyzeField(fieldKey, fieldInfo, issue) {
876
- const field = {
877
- key: fieldKey,
878
- name: fieldInfo.name || fieldKey,
879
- type: fieldInfo.schema?.type || 'unknown',
880
- required: fieldInfo.required || false
881
- };
882
- // Extract options for select fields
883
- if (fieldInfo.allowedValues) {
884
- field.options = fieldInfo.allowedValues.map((val) => ({
885
- id: val.id,
886
- value: val.value
887
- }));
888
- }
889
- // Generate smart suggestions
890
- field.suggestion = this.generateFieldSuggestion(fieldInfo.name, field.options, issue);
891
- return field;
892
- }
893
- generateFieldSuggestion(fieldName, options, issue) {
894
- if (!fieldName)
895
- return undefined;
896
- // Apply suggestion rules
897
- for (const rule of this.fieldSuggestionRules) {
898
- if (rule.pattern.test(fieldName)) {
899
- // Check context rules first
900
- if (rule.contextRules && issue) {
901
- for (const contextRule of rule.contextRules) {
902
- if (contextRule.condition(issue)) {
903
- // Find matching option ID if available
904
- const matchingOption = options?.find(opt => opt.value === contextRule.value);
905
- return {
906
- value: contextRule.value,
907
- id: matchingOption?.id,
908
- reason: contextRule.reason
909
- };
910
- }
911
- }
912
- }
913
- // Fall back to default value
914
- const matchingOption = options?.find(opt => opt.value === rule.defaultValue);
915
- return {
916
- value: rule.defaultValue,
917
- id: matchingOption?.id,
918
- reason: `Default suggestion for ${fieldName.toLowerCase()}`
919
- };
920
- }
921
- }
922
- // Generic suggestions based on field name patterns
923
- if (options && options.length > 0) {
924
- if (/required|mandatory|needed/i.test(fieldName)) {
925
- const yesOption = options.find(opt => /yes|true|required/i.test(opt.value));
926
- if (yesOption) {
927
- return {
928
- value: yesOption.value,
929
- id: yesOption.id,
930
- reason: 'Field name suggests this is typically required'
931
- };
932
- }
933
- }
934
- if (/optional|not.*required|skip/i.test(fieldName)) {
935
- const noOption = options.find(opt => /no|false|not.*required|skip/i.test(opt.value));
936
- if (noOption) {
937
- return {
938
- value: noOption.value,
939
- id: noOption.id,
940
- reason: 'Field name suggests this is typically not required'
941
- };
942
- }
943
- }
944
- }
945
- return undefined;
946
- }
947
- // Enhanced transition method with smart field handling
948
- async transitionIssueInteractive(issueKey, transitionId, providedFields) {
949
- // First attempt with provided fields
950
- const result = await this.transitionIssue(issueKey, transitionId, providedFields);
951
- if (result.success || !result.requiresInput) {
952
- return result;
953
- }
954
- // If user provided fields but still failed, try to auto-fill based on suggestions
955
- if (result.requiredFields && result.requiredFields.length > 0) {
956
- const autoFields = { ...providedFields };
957
- let hasAutoSuggestions = false;
958
- for (const field of result.requiredFields) {
959
- if (field.suggestion && !autoFields[field.key]) {
960
- if (field.suggestion.id) {
961
- autoFields[field.key] = { id: field.suggestion.id };
962
- }
963
- else {
964
- autoFields[field.key] = field.suggestion.value;
965
- }
966
- hasAutoSuggestions = true;
967
- }
968
- }
969
- // Try transition again with auto-filled suggestions
970
- if (hasAutoSuggestions) {
971
- const retryResult = await this.transitionIssue(issueKey, transitionId, autoFields);
972
- if (retryResult.success) {
973
- retryResult.message = `${retryResult.message} (auto-filled based on smart suggestions)`;
974
- }
975
- return retryResult;
976
- }
977
- }
978
- return result;
979
- }
980
- // Convert description to wiki markup text format
981
- parseDescriptionToWikiMarkup(description) {
982
- const lines = description.split('\n');
983
- const result = [];
984
- for (let i = 0; i < lines.length; i++) {
985
- const line = lines[i].trim();
986
- if (line === '') {
987
- result.push('');
988
- continue;
989
- }
990
- // Handle markdown headers
991
- if (line.startsWith('###')) {
992
- result.push('h3. ' + line.substring(3).trim());
993
- }
994
- else if (line.startsWith('##')) {
995
- result.push('h2. ' + line.substring(2).trim());
996
- }
997
- else if (line.startsWith('#')) {
998
- result.push('h1. ' + line.substring(1).trim());
999
- }
1000
- // Handle markdown tables
1001
- else if (line.includes('|') && line.trim() !== '|') {
1002
- const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
1003
- if (cells.length >= 2) {
1004
- // Collect table rows
1005
- const allTableRows = [];
1006
- let currentLine = i;
1007
- while (currentLine < lines.length) {
1008
- const tableLine = lines[currentLine].trim();
1009
- if (tableLine === '') {
1010
- currentLine++;
1011
- continue;
1012
- }
1013
- if (!tableLine.includes('|')) {
1014
- break;
1015
- }
1016
- // Skip separator lines
1017
- if (tableLine.match(/^\|?[\s\-:|]+\|?$/)) {
1018
- currentLine++;
1019
- continue;
1020
- }
1021
- const rowCells = tableLine.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
1022
- if (rowCells.length >= 2) {
1023
- allTableRows.push(rowCells);
1024
- }
1025
- else {
1026
- break;
1027
- }
1028
- currentLine++;
1029
- }
1030
- // Convert to Confluence wiki markup
1031
- if (allTableRows.length > 0) {
1032
- // Header row
1033
- if (allTableRows.length > 0) {
1034
- result.push('||*' + allTableRows[0].join('*||*') + '*||');
1035
- // Add separator line based on number of columns
1036
- const numColumns = allTableRows[0].length;
1037
- const separator = '|' + '-|'.repeat(numColumns);
1038
- result.push(separator);
1039
- }
1040
- // Data rows
1041
- for (let rowIdx = 1; rowIdx < allTableRows.length; rowIdx++) {
1042
- result.push('|' + allTableRows[rowIdx].join('|') + '|');
1043
- }
1044
- // Skip processed lines
1045
- i = currentLine - 1;
1046
- continue;
1047
- }
1048
- }
1049
- // If not a valid table, treat as regular text
1050
- result.push(line);
1051
- }
1052
- // Handle bullet points
1053
- else if (line.startsWith('- ') || line.startsWith('* ')) {
1054
- result.push('* ' + line.substring(2).trim());
1055
- }
1056
- // Regular text
1057
- else {
1058
- result.push(line);
1059
- }
1060
- }
1061
- return result.join('\n');
1062
- }
1063
- // Enhanced description parser that converts text to proper ADF format
1064
- parseDescriptionToADF(description) {
1065
- const lines = description.split('\n');
1066
- const content = [];
1067
- for (let i = 0; i < lines.length; i++) {
1068
- const line = lines[i].trim();
1069
- if (line === '') {
1070
- // Only add an empty paragraph for double blank lines (true paragraph breaks)
1071
- // Single blank lines are natural separators between block elements and don't need extra spacing
1072
- const nextLine = i + 1 < lines.length ? lines[i + 1].trim() : '';
1073
- if (nextLine === '') {
1074
- const lastNode = content[content.length - 1];
1075
- const isLastEmpty = lastNode && lastNode.type === 'paragraph' && (!lastNode.content || lastNode.content.length === 0);
1076
- if (!isLastEmpty) {
1077
- content.push({ type: 'paragraph', content: [] });
1078
- }
1079
- }
1080
- continue;
1081
- }
1082
- // Handle Confluence wiki markup headers (h1., h2., h3., etc.)
1083
- const confluenceHeaderMatch = line.match(/^h([1-6])\.\s*(.*)$/);
1084
- if (confluenceHeaderMatch) {
1085
- const level = parseInt(confluenceHeaderMatch[1]);
1086
- const headerText = confluenceHeaderMatch[2];
1087
- content.push({
1088
- type: 'heading',
1089
- attrs: { level },
1090
- content: [{
1091
- type: 'text',
1092
- text: headerText
1093
- }]
1094
- });
1095
- continue;
1096
- }
1097
- // Handle markdown headers (## Header, ### Header)
1098
- if (line.startsWith('###')) {
1099
- content.push({
1100
- type: 'heading',
1101
- attrs: { level: 3 },
1102
- content: [{
1103
- type: 'text',
1104
- text: line.substring(3).trim()
1105
- }]
1106
- });
1107
- }
1108
- else if (line.startsWith('##')) {
1109
- content.push({
1110
- type: 'heading',
1111
- attrs: { level: 2 },
1112
- content: [{
1113
- type: 'text',
1114
- text: line.substring(2).trim()
1115
- }]
1116
- });
1117
- }
1118
- else if (line.startsWith('#')) {
1119
- content.push({
1120
- type: 'heading',
1121
- attrs: { level: 1 },
1122
- content: [{
1123
- type: 'text',
1124
- text: line.substring(1).trim()
1125
- }]
1126
- });
1127
- }
1128
- // Handle horizontal rules (---- or ----)
1129
- else if (/^-{4,}$/.test(line)) {
1130
- content.push({
1131
- type: 'rule'
1132
- });
1133
- }
1134
- // Handle Confluence panels
1135
- else if (line.match(/^\{panel:/)) {
1136
- const panelMatch = line.match(/^\{panel:title=([^|]*)[^}]*\}$/);
1137
- if (panelMatch) {
1138
- const title = panelMatch[1];
1139
- // Start collecting panel content
1140
- const panelContent = [];
1141
- i++; // Move to next line
1142
- // Collect content until we hit {panel}
1143
- while (i < lines.length && !lines[i].trim().match(/^\{panel\}$/)) {
1144
- const panelLine = lines[i].trim();
1145
- if (panelLine) {
1146
- panelContent.push({
1147
- type: 'paragraph',
1148
- content: this.parseInlineFormatting(panelLine)
1149
- });
1150
- }
1151
- i++;
1152
- }
1153
- // Create panel as an info panel (closest ADF equivalent)
1154
- content.push({
1155
- type: 'panel',
1156
- attrs: { panelType: 'info' },
1157
- content: [
1158
- {
1159
- type: 'paragraph',
1160
- content: [{
1161
- type: 'text',
1162
- text: title,
1163
- marks: [{ type: 'strong' }]
1164
- }]
1165
- },
1166
- ...panelContent
1167
- ]
1168
- });
1169
- }
1170
- }
1171
- // Handle bullet points
1172
- else if (line.startsWith('- ') || line.startsWith('* ')) {
1173
- // Check if we need to start a new bullet list or continue existing one
1174
- const lastItem = content[content.length - 1];
1175
- const bulletText = line.substring(2).trim();
1176
- if (lastItem && lastItem.type === 'bulletList') {
1177
- lastItem.content.push({
1178
- type: 'listItem',
1179
- content: [{
1180
- type: 'paragraph',
1181
- content: this.parseInlineFormatting(bulletText)
1182
- }]
1183
- });
1184
- }
1185
- else {
1186
- content.push({
1187
- type: 'bulletList',
1188
- content: [{
1189
- type: 'listItem',
1190
- content: [{
1191
- type: 'paragraph',
1192
- content: this.parseInlineFormatting(bulletText)
1193
- }]
1194
- }]
1195
- });
1196
- }
1197
- }
1198
- // Handle numbered lists
1199
- else if (/^\d+\.\s/.test(line)) {
1200
- const match = line.match(/^\d+\.\s(.+)$/);
1201
- if (match) {
1202
- const listText = match[1];
1203
- const lastItem = content[content.length - 1];
1204
- if (lastItem && lastItem.type === 'orderedList') {
1205
- lastItem.content.push({
1206
- type: 'listItem',
1207
- content: [{
1208
- type: 'paragraph',
1209
- content: this.parseInlineFormatting(listText)
1210
- }]
1211
- });
1212
- }
1213
- else {
1214
- content.push({
1215
- type: 'orderedList',
1216
- content: [{
1217
- type: 'listItem',
1218
- content: [{
1219
- type: 'paragraph',
1220
- content: this.parseInlineFormatting(listText)
1221
- }]
1222
- }]
1223
- });
1224
- }
1225
- }
1226
- }
1227
- // Handle code blocks (```)
1228
- else if (line.startsWith('```')) {
1229
- const codeLines = [];
1230
- i++; // Skip the opening ```
1231
- while (i < lines.length && !lines[i].trim().startsWith('```')) {
1232
- codeLines.push(lines[i]);
1233
- i++;
1234
- }
1235
- content.push({
1236
- type: 'codeBlock',
1237
- content: [{
1238
- type: 'text',
1239
- text: codeLines.join('\n')
1240
- }]
1241
- });
1242
- }
1243
- // Handle markdown tables - convert to proper ADF table structure
1244
- else if (line.includes('|') && line.trim() !== '|') {
1245
- const cells = line.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
1246
- if (cells.length >= 2) {
1247
- // Collect all table rows from current position
1248
- const allTableRows = [];
1249
- // Start from current line and collect consecutive table rows
1250
- let tableEndIndex = i;
1251
- for (let tableIdx = i; tableIdx < lines.length; tableIdx++) {
1252
- const tableLine = lines[tableIdx].trim();
1253
- // Skip empty lines
1254
- if (tableLine === '') {
1255
- tableEndIndex = tableIdx;
1256
- continue;
1257
- }
1258
- // Stop if no pipes (end of table)
1259
- if (!tableLine.includes('|')) {
1260
- tableEndIndex = tableIdx - 1;
1261
- break;
1262
- }
1263
- // Skip separator lines like |-|-|
1264
- if (tableLine.match(/^\|?[\s\-:|]+\|?$/)) {
1265
- tableEndIndex = tableIdx;
1266
- continue;
1267
- }
1268
- // Parse row cells
1269
- const rowCells = tableLine.split('|').map(cell => cell.trim()).filter(cell => cell !== '');
1270
- if (rowCells.length >= 2) {
1271
- allTableRows.push(rowCells);
1272
- tableEndIndex = tableIdx;
1273
- }
1274
- else {
1275
- tableEndIndex = tableIdx - 1;
1276
- break; // End of table
1277
- }
1278
- }
1279
- // Convert to proper ADF table structure if we have table rows
1280
- if (allTableRows.length > 0) {
1281
- // Create proper ADF table structure
1282
- const tableRows = [];
1283
- // Header row (first row)
1284
- if (allTableRows.length > 0) {
1285
- const headerCells = allTableRows[0].map(cellText => ({
1286
- type: 'tableHeader',
1287
- attrs: {},
1288
- content: [{
1289
- type: 'paragraph',
1290
- content: this.parseInlineFormatting(cellText)
1291
- }]
1292
- }));
1293
- tableRows.push({
1294
- type: 'tableRow',
1295
- content: headerCells
1296
- });
1297
- }
1298
- // Data rows (remaining rows)
1299
- for (let rowIdx = 1; rowIdx < allTableRows.length; rowIdx++) {
1300
- const dataCells = allTableRows[rowIdx].map(cellText => ({
1301
- type: 'tableCell',
1302
- attrs: {},
1303
- content: [{
1304
- type: 'paragraph',
1305
- content: this.parseInlineFormatting(cellText)
1306
- }]
1307
- }));
1308
- tableRows.push({
1309
- type: 'tableRow',
1310
- content: dataCells
1311
- });
1312
- }
1313
- // Create the complete table structure
1314
- content.push({
1315
- type: 'table',
1316
- attrs: {
1317
- isNumberColumnEnabled: false,
1318
- layout: 'default'
1319
- },
1320
- content: tableRows
1321
- });
1322
- // Skip the lines we've processed
1323
- i = tableEndIndex; // Skip the table rows we just processed
1324
- continue;
1325
- }
1326
- }
1327
- // If we reach here, it's not a valid table, treat as regular paragraph
1328
- content.push({
1329
- type: 'paragraph',
1330
- content: this.parseInlineFormatting(line)
1331
- });
1332
- }
1333
- // Regular paragraph
1334
- else {
1335
- content.push({
1336
- type: 'paragraph',
1337
- content: this.parseInlineFormatting(line)
1338
- });
1339
- }
1340
- }
1341
- // If no content was parsed, add a simple paragraph
1342
- if (content.length === 0) {
1343
- content.push({
1344
- type: 'paragraph',
1345
- content: [{
1346
- type: 'text',
1347
- text: description || ''
1348
- }]
1349
- });
1350
- }
1351
- return {
1352
- type: 'doc',
1353
- version: 1,
1354
- content
1355
- };
1356
- }
1357
- // Parse inline formatting like **bold**, *italic*, `code`, {{code}}, emojis
1358
- parseInlineFormatting(text) {
1359
- const result = [];
1360
- let currentPos = 0;
1361
- // Process patterns in order of precedence to avoid conflicts
1362
- // **bold** should be processed before *italic* to handle **text** correctly
1363
- const patterns = [
1364
- { regex: /\*\*(.*?)\*\*/g, type: 'strong' }, // **bold** (process first)
1365
- { regex: /`([^`]+?)`/g, type: 'code' }, // `code` (markdown style)
1366
- { regex: /\{\{(.+?)\}\}/g, type: 'code' }, // {{code}} (Confluence/Jira wiki style)
1367
- ];
1368
- // Link patterns (markdown [text](url) and Jira wiki [text|url])
1369
- const linkPatterns = [
1370
- { regex: /\[([^\]]+?)\]\(([^)]+?)\)/g }, // [text](url) - markdown style
1371
- { regex: /\[([^|\]]+?)\|([^\]]+?)\]/g }, // [text|url] - Jira wiki style
1372
- ];
1373
- let matches = [];
1374
- // Find all formatting matches
1375
- patterns.forEach(pattern => {
1376
- let match;
1377
- const regex = new RegExp(pattern.regex.source, 'g');
1378
- while ((match = regex.exec(text)) !== null) {
1379
- matches.push({
1380
- start: match.index,
1381
- end: match.index + match[0].length,
1382
- text: match[1],
1383
- type: pattern.type,
1384
- fullMatch: match[0]
1385
- });
1386
- }
1387
- });
1388
- // Find all link matches
1389
- linkPatterns.forEach(pattern => {
1390
- let match;
1391
- const regex = new RegExp(pattern.regex.source, 'g');
1392
- while ((match = regex.exec(text)) !== null) {
1393
- matches.push({
1394
- start: match.index,
1395
- end: match.index + match[0].length,
1396
- text: match[1],
1397
- type: 'link',
1398
- href: match[2],
1399
- fullMatch: match[0]
1400
- });
1401
- }
1402
- });
1403
- // Sort matches by position and remove overlapping matches
1404
- matches.sort((a, b) => a.start - b.start);
1405
- // After processing bold and code, handle italic patterns that don't overlap
1406
- let tempText = text;
1407
- // Remove already matched bold and code patterns from consideration
1408
- matches.forEach(match => {
1409
- tempText = tempText.substring(0, match.start) + ' '.repeat(match.end - match.start) + tempText.substring(match.end);
1410
- });
1411
- // Now find italic patterns in the remaining text
1412
- const italicRegex = /\*([^*]+?)\*/g;
1413
- let italicMatch;
1414
- while ((italicMatch = italicRegex.exec(tempText)) !== null) {
1415
- matches.push({
1416
- start: italicMatch.index,
1417
- end: italicMatch.index + italicMatch[0].length,
1418
- text: italicMatch[1],
1419
- type: 'em',
1420
- fullMatch: italicMatch[0]
1421
- });
1422
- }
1423
- // Sort matches by position and remove overlapping matches
1424
- matches.sort((a, b) => a.start - b.start);
1425
- // Filter out overlapping matches (keep the first one in case of conflict)
1426
- const filteredMatches = [];
1427
- for (const match of matches) {
1428
- const isOverlapping = filteredMatches.some(existing => (match.start >= existing.start && match.start < existing.end) ||
1429
- (match.end > existing.start && match.end <= existing.end) ||
1430
- (match.start <= existing.start && match.end >= existing.end));
1431
- if (!isOverlapping) {
1432
- filteredMatches.push(match);
1433
- }
1434
- }
1435
- // Sort filtered matches by position for proper processing
1436
- filteredMatches.sort((a, b) => a.start - b.start);
1437
- // Process text with formatting
1438
- filteredMatches.forEach(match => {
1439
- // Add text before the match
1440
- if (match.start > currentPos) {
1441
- const beforeText = text.substring(currentPos, match.start);
1442
- if (beforeText) {
1443
- result.push({
1444
- type: 'text',
1445
- text: beforeText
1446
- });
1447
- }
1448
- }
1449
- // Add formatted text
1450
- if (match.type === 'link') {
1451
- result.push({
1452
- type: 'text',
1453
- text: match.text,
1454
- marks: [{ type: 'link', attrs: { href: match.href } }]
1455
- });
1456
- }
1457
- else {
1458
- result.push({
1459
- type: 'text',
1460
- text: match.text,
1461
- marks: [{ type: match.type }]
1462
- });
1463
- }
1464
- currentPos = match.end;
1465
- });
1466
- // Add remaining text
1467
- if (currentPos < text.length) {
1468
- const remainingText = text.substring(currentPos);
1469
- if (remainingText) {
1470
- result.push({
1471
- type: 'text',
1472
- text: remainingText
1473
- });
1474
- }
1475
- }
1476
- // If no formatting was found, return simple text
1477
- if (result.length === 0) {
1478
- result.push({
1479
- type: 'text',
1480
- text: text
1481
- });
1482
- }
1483
- return result;
1484
- }
28
+ const client = new JiraClient();
29
+ this.issues = new JiraIssueService(client);
30
+ this.comments = new JiraCommentService(client);
31
+ this.attachments = new JiraAttachmentService(client);
32
+ this.links = new JiraLinkService(client);
33
+ this.worklogs = new JiraWorklogService(client);
34
+ this.watchers = new JiraWatcherService(client);
35
+ this.labels = new JiraLabelService(client);
36
+ this.subtasks = new JiraSubtaskService(client, this.issues);
37
+ this.agile = new JiraAgileService(client);
38
+ this.versions = new JiraVersionService(client);
39
+ this.metadata = new JiraMetadataService(client);
40
+ this.transitions = new JiraTransitionService(client, this.issues);
41
+ }
42
+ // Issues
43
+ searchIssues(opts) { return this.issues.searchIssues(opts); }
44
+ getIssue(key, fields) { return this.issues.getIssue(key, fields); }
45
+ createIssue(...args) { return this.issues.createIssue(...args); }
46
+ updateIssue(...args) { return this.issues.updateIssue(...args); }
47
+ deleteIssue(key, deleteSubtasks) { return this.issues.deleteIssue(key, deleteSubtasks); }
48
+ assignIssue(key, accountId) { return this.issues.assignIssue(key, accountId); }
49
+ batchCreateIssues(...args) { return this.issues.batchCreateIssues(...args); }
50
+ getIssueHistory(key) { return this.issues.getIssueHistory(key); }
51
+ getDevStatus(id, appType, dataType) { return this.issues.getDevStatus(id, appType, dataType); }
52
+ // Transitions
53
+ getIssueTransitions(key) { return this.transitions.getIssueTransitions(key); }
54
+ transitionIssue(key, transitionId, fields) { return this.transitions.transitionIssue(key, transitionId, fields); }
55
+ transitionIssueInteractive(key, transitionId, fields) { return this.transitions.transitionIssueInteractive(key, transitionId, fields); }
56
+ // Comments
57
+ addComment(key, comment) { return this.comments.addComment(key, comment); }
58
+ getComments(key) { return this.comments.getComments(key); }
59
+ updateComment(key, commentId, comment) { return this.comments.updateComment(key, commentId, comment); }
60
+ deleteComment(key, commentId) { return this.comments.deleteComment(key, commentId); }
61
+ // Attachments
62
+ getAttachments(key) { return this.attachments.getAttachments(key); }
63
+ addAttachment(key, filename, content) { return this.attachments.addAttachment(key, filename, content); }
64
+ deleteAttachment(attachmentId) { return this.attachments.deleteAttachment(attachmentId); }
65
+ // Links
66
+ getIssueLinks(key) { return this.links.getIssueLinks(key); }
67
+ createIssueLink(inward, outward, type) { return this.links.createIssueLink(inward, outward, type); }
68
+ deleteIssueLink(linkId) { return this.links.deleteIssueLink(linkId); }
69
+ getIssueLinkTypes() { return this.links.getIssueLinkTypes(); }
70
+ linkToEpic(key, epicKey) { return this.links.linkToEpic(key, epicKey); }
71
+ // Worklogs
72
+ getWorkLogs(key) { return this.worklogs.getWorkLogs(key); }
73
+ addWorkLog(key, seconds, comment, started) { return this.worklogs.addWorkLog(key, seconds, comment, started); }
74
+ updateWorkLog(key, worklogId, seconds, comment) { return this.worklogs.updateWorkLog(key, worklogId, seconds, comment); }
75
+ deleteWorkLog(key, worklogId) { return this.worklogs.deleteWorkLog(key, worklogId); }
76
+ // Watchers
77
+ getWatchers(key) { return this.watchers.getWatchers(key); }
78
+ addWatcher(key, accountId) { return this.watchers.addWatcher(key, accountId); }
79
+ removeWatcher(key, accountId) { return this.watchers.removeWatcher(key, accountId); }
80
+ // Labels
81
+ getLabels(query, maxResults) { return this.labels.getLabels(query, maxResults); }
82
+ addLabels(key, labels) { return this.labels.addLabels(key, labels); }
83
+ removeLabels(key, labels) { return this.labels.removeLabels(key, labels); }
84
+ // Subtasks
85
+ getSubTasks(key) { return this.subtasks.getSubTasks(key); }
86
+ createSubTask(parentKey, summary, description) { return this.subtasks.createSubTask(parentKey, summary, description); }
87
+ // Agile
88
+ getAgileBoards(opts) { return this.agile.getAgileBoards(opts); }
89
+ getBoardIssues(boardId, opts) { return this.agile.getBoardIssues(boardId, opts); }
90
+ getSprints(boardId, state) { return this.agile.getSprints(boardId, state); }
91
+ getSprintIssues(sprintId, opts) { return this.agile.getSprintIssues(sprintId, opts); }
92
+ moveIssuesToSprint(sprintId, issueKeys) { return this.agile.moveIssuesToSprint(sprintId, issueKeys); }
93
+ createSprint(boardId, name, opts) { return this.agile.createSprint(boardId, name, opts); }
94
+ updateSprint(sprintId, updates) { return this.agile.updateSprint(sprintId, updates); }
95
+ // Versions
96
+ getProjectVersions(projectKey) { return this.versions.getProjectVersions(projectKey); }
97
+ createVersion(projectKey, name, opts) { return this.versions.createVersion(projectKey, name, opts); }
98
+ updateVersion(versionId, updates) { return this.versions.updateVersion(versionId, updates); }
99
+ // Metadata
100
+ getProjects() { return this.metadata.getProjects(); }
101
+ getIssueTypes(projectKey) { return this.metadata.getIssueTypes(projectKey); }
102
+ getFields(type, query) { return this.metadata.getFields(type, query); }
103
+ getUsersByQuery(query) { return this.metadata.getUsersByQuery(query); }
1485
104
  }
1486
105
  //# sourceMappingURL=jira.js.map