prjct-cli 0.55.1 → 0.55.3

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.
@@ -1,446 +0,0 @@
1
- /**
2
- * JIRA MCP Adapter
3
- *
4
- * Provides JIRA integration via Atlassian's official MCP Server.
5
- * Used when API tokens are not available (e.g., corporate SSO environments).
6
- *
7
- * The MCP Server uses OAuth 2.1 via browser, compatible with corporate SSO.
8
- * See: https://www.atlassian.com/blog/announcements/remote-mcp-server
9
- *
10
- * Setup: Add to ~/.claude/mcp.json:
11
- * {
12
- * "mcpServers": {
13
- * "Atlassian": {
14
- * "command": "npx",
15
- * "args": ["-y", "mcp-remote@latest", "https://mcp.atlassian.com/v1/sse"]
16
- * }
17
- * }
18
- * }
19
- */
20
-
21
- import type {
22
- CreateIssueInput,
23
- FetchOptions,
24
- Issue,
25
- IssuePriority,
26
- IssueStatus,
27
- IssueTrackerProvider,
28
- IssueType,
29
- JiraConfig,
30
- } from '../issue-tracker/types'
31
-
32
- // =============================================================================
33
- // MCP Response Types (from Atlassian MCP Server)
34
- // =============================================================================
35
-
36
- interface MCPJiraIssue {
37
- id: string
38
- key: string
39
- fields: {
40
- summary: string
41
- description?: string
42
- status: {
43
- name: string
44
- statusCategory?: {
45
- key: string
46
- }
47
- }
48
- priority?: {
49
- name: string
50
- }
51
- issuetype: {
52
- name: string
53
- }
54
- assignee?: {
55
- accountId: string
56
- displayName: string
57
- emailAddress?: string
58
- }
59
- project: {
60
- id: string
61
- key: string
62
- name: string
63
- }
64
- labels?: string[]
65
- created: string
66
- updated: string
67
- }
68
- }
69
-
70
- // =============================================================================
71
- // Status/Priority Mapping (same as REST client)
72
- // =============================================================================
73
-
74
- const JIRA_STATUS_CATEGORY_MAP: Record<string, IssueStatus> = {
75
- new: 'todo',
76
- indeterminate: 'in_progress',
77
- done: 'done',
78
- }
79
-
80
- const JIRA_STATUS_NAME_MAP: Record<string, IssueStatus> = {
81
- backlog: 'backlog',
82
- open: 'backlog',
83
- 'to do': 'todo',
84
- todo: 'todo',
85
- new: 'todo',
86
- 'in progress': 'in_progress',
87
- 'in development': 'in_progress',
88
- 'in review': 'in_review',
89
- 'code review': 'in_review',
90
- review: 'in_review',
91
- done: 'done',
92
- closed: 'done',
93
- resolved: 'done',
94
- complete: 'done',
95
- completed: 'done',
96
- cancelled: 'cancelled',
97
- canceled: 'cancelled',
98
- "won't do": 'cancelled',
99
- 'wont do': 'cancelled',
100
- rejected: 'cancelled',
101
- }
102
-
103
- const JIRA_PRIORITY_MAP: Record<string, IssuePriority> = {
104
- highest: 'urgent',
105
- high: 'high',
106
- medium: 'medium',
107
- low: 'low',
108
- lowest: 'low',
109
- }
110
-
111
- // =============================================================================
112
- // MCP Instruction Generator
113
- // =============================================================================
114
-
115
- /**
116
- * Represents an MCP instruction for Claude to execute.
117
- * Instead of making direct API calls, we generate instructions
118
- * that Claude will execute using the Atlassian MCP tools.
119
- */
120
- export interface MCPInstruction {
121
- tool: string
122
- params: Record<string, unknown>
123
- description: string
124
- }
125
-
126
- /**
127
- * Generate MCP instruction for searching JIRA issues
128
- */
129
- export function createSearchInstruction(jql: string, maxResults = 50): MCPInstruction {
130
- return {
131
- tool: 'mcp__atlassian__jira_search_issues',
132
- params: {
133
- jql,
134
- maxResults,
135
- },
136
- description: `Search JIRA issues: ${jql}`,
137
- }
138
- }
139
-
140
- /**
141
- * Generate MCP instruction for fetching a single issue
142
- */
143
- export function createGetIssueInstruction(issueKey: string): MCPInstruction {
144
- return {
145
- tool: 'mcp__atlassian__jira_get_issue',
146
- params: {
147
- issueKey,
148
- },
149
- description: `Get JIRA issue: ${issueKey}`,
150
- }
151
- }
152
-
153
- /**
154
- * Generate MCP instruction for transitioning an issue
155
- */
156
- export function createTransitionInstruction(
157
- issueKey: string,
158
- transitionName: string
159
- ): MCPInstruction {
160
- return {
161
- tool: 'mcp__atlassian__jira_transition_issue',
162
- params: {
163
- issueKey,
164
- transitionName,
165
- },
166
- description: `Transition ${issueKey} to: ${transitionName}`,
167
- }
168
- }
169
-
170
- /**
171
- * Generate MCP instruction for updating an issue
172
- */
173
- export function createUpdateInstruction(
174
- issueKey: string,
175
- fields: Record<string, unknown>
176
- ): MCPInstruction {
177
- return {
178
- tool: 'mcp__atlassian__jira_update_issue',
179
- params: {
180
- issueKey,
181
- fields,
182
- },
183
- description: `Update JIRA issue: ${issueKey}`,
184
- }
185
- }
186
-
187
- /**
188
- * Generate MCP instruction for creating an issue
189
- */
190
- export function createCreateIssueInstruction(
191
- projectKey: string,
192
- issueType: string,
193
- summary: string,
194
- description?: string
195
- ): MCPInstruction {
196
- return {
197
- tool: 'mcp__atlassian__jira_create_issue',
198
- params: {
199
- projectKey,
200
- issueType,
201
- summary,
202
- description,
203
- },
204
- description: `Create JIRA issue in ${projectKey}: ${summary}`,
205
- }
206
- }
207
-
208
- // =============================================================================
209
- // MCP Adapter Implementation
210
- // =============================================================================
211
-
212
- /**
213
- * JIRA MCP Adapter
214
- *
215
- * This adapter doesn't make direct API calls. Instead, it provides:
216
- * 1. MCPInstruction objects that Claude executes using MCP tools
217
- * 2. Mapping functions to convert MCP responses to normalized Issue types
218
- *
219
- * Usage in templates:
220
- * - Check if MCP mode is active
221
- * - Call adapter methods to get instructions
222
- * - Claude executes the MCP tools
223
- * - Parse results with mapMCPIssue()
224
- */
225
- export class JiraMCPAdapter implements Partial<IssueTrackerProvider> {
226
- readonly name = 'jira' as const
227
- readonly displayName = 'JIRA (MCP)'
228
-
229
- private config: JiraConfig | null = null
230
- private baseUrl: string = ''
231
-
232
- /**
233
- * Check if adapter is ready (config loaded)
234
- */
235
- isConfigured(): boolean {
236
- return this.config?.enabled === true
237
- }
238
-
239
- /**
240
- * Initialize with config (no API verification needed - MCP handles auth)
241
- */
242
- async initialize(config: JiraConfig): Promise<void> {
243
- this.config = config
244
- this.baseUrl = config.baseUrl || ''
245
- console.log('[jira-mcp] Initialized MCP adapter')
246
- }
247
-
248
- /**
249
- * Get instruction to fetch assigned issues
250
- */
251
- getAssignedIssuesInstruction(options?: FetchOptions): MCPInstruction {
252
- const maxResults = options?.limit || 50
253
- const jql = options?.includeCompleted
254
- ? 'assignee = currentUser() ORDER BY updated DESC'
255
- : 'assignee = currentUser() AND statusCategory != Done ORDER BY updated DESC'
256
-
257
- return createSearchInstruction(jql, maxResults)
258
- }
259
-
260
- /**
261
- * Get instruction to fetch team issues
262
- */
263
- getTeamIssuesInstruction(projectKey: string, options?: FetchOptions): MCPInstruction {
264
- const maxResults = options?.limit || 50
265
- const jql = options?.includeCompleted
266
- ? `project = ${projectKey} ORDER BY updated DESC`
267
- : `project = ${projectKey} AND statusCategory != Done ORDER BY updated DESC`
268
-
269
- return createSearchInstruction(jql, maxResults)
270
- }
271
-
272
- /**
273
- * Get instruction to fetch a single issue
274
- */
275
- getIssueInstruction(issueKey: string): MCPInstruction {
276
- return createGetIssueInstruction(issueKey)
277
- }
278
-
279
- /**
280
- * Get instruction to mark issue in progress
281
- */
282
- getMarkInProgressInstruction(issueKey: string): MCPInstruction {
283
- return createTransitionInstruction(issueKey, 'In Progress')
284
- }
285
-
286
- /**
287
- * Get instruction to mark issue done
288
- */
289
- getMarkDoneInstruction(issueKey: string): MCPInstruction {
290
- return createTransitionInstruction(issueKey, 'Done')
291
- }
292
-
293
- /**
294
- * Get instruction to update issue description
295
- */
296
- getUpdateDescriptionInstruction(issueKey: string, description: string): MCPInstruction {
297
- return createUpdateInstruction(issueKey, { description })
298
- }
299
-
300
- /**
301
- * Get instruction to create a new issue
302
- */
303
- getCreateIssueInstruction(input: CreateIssueInput): MCPInstruction {
304
- const projectKey = input.teamId || this.config?.projectKey
305
- if (!projectKey) {
306
- throw new Error('Project key required for creating issues')
307
- }
308
-
309
- return createCreateIssueInstruction(
310
- projectKey,
311
- this.mapTypeToJira(input.type),
312
- input.title,
313
- input.description
314
- )
315
- }
316
-
317
- /**
318
- * Map MCP response to normalized Issue
319
- */
320
- mapMCPIssue(mcpIssue: MCPJiraIssue): Issue {
321
- const statusName = mcpIssue.fields.status.name.toLowerCase()
322
- const statusCategory = mcpIssue.fields.status.statusCategory?.key || ''
323
-
324
- const status: IssueStatus =
325
- JIRA_STATUS_NAME_MAP[statusName] || JIRA_STATUS_CATEGORY_MAP[statusCategory] || 'backlog'
326
-
327
- const priorityName = mcpIssue.fields.priority?.name?.toLowerCase() || 'medium'
328
- const priority: IssuePriority = JIRA_PRIORITY_MAP[priorityName] || 'medium'
329
-
330
- return {
331
- id: mcpIssue.id,
332
- externalId: mcpIssue.key,
333
- provider: 'jira',
334
- title: mcpIssue.fields.summary,
335
- description: mcpIssue.fields.description,
336
- status,
337
- priority,
338
- type: this.inferType(mcpIssue.fields.issuetype.name, mcpIssue.fields.labels || []),
339
- assignee: mcpIssue.fields.assignee
340
- ? {
341
- id: mcpIssue.fields.assignee.accountId,
342
- name: mcpIssue.fields.assignee.displayName,
343
- email: mcpIssue.fields.assignee.emailAddress,
344
- }
345
- : undefined,
346
- labels: mcpIssue.fields.labels || [],
347
- team: {
348
- id: mcpIssue.fields.project.id,
349
- name: mcpIssue.fields.project.name,
350
- key: mcpIssue.fields.project.key,
351
- },
352
- project: {
353
- id: mcpIssue.fields.project.id,
354
- name: mcpIssue.fields.project.name,
355
- },
356
- url: this.baseUrl
357
- ? `${this.baseUrl}/browse/${mcpIssue.key}`
358
- : `https://jira.atlassian.com/browse/${mcpIssue.key}`,
359
- createdAt: mcpIssue.fields.created,
360
- updatedAt: mcpIssue.fields.updated,
361
- raw: mcpIssue,
362
- }
363
- }
364
-
365
- /**
366
- * Map array of MCP issues
367
- */
368
- mapMCPIssues(mcpIssues: MCPJiraIssue[]): Issue[] {
369
- return mcpIssues.map((issue) => this.mapMCPIssue(issue))
370
- }
371
-
372
- // ===========================================================================
373
- // Private Helpers
374
- // ===========================================================================
375
-
376
- private inferType(issueTypeName: string, labels: string[]): IssueType {
377
- const typeLower = issueTypeName.toLowerCase()
378
- const labelsLower = labels.map((l) => l.toLowerCase())
379
-
380
- if (typeLower === 'bug' || labelsLower.includes('bug')) return 'bug'
381
- if (typeLower === 'story' || typeLower === 'feature' || labelsLower.includes('feature'))
382
- return 'feature'
383
- if (typeLower === 'improvement' || labelsLower.includes('improvement')) return 'improvement'
384
- if (typeLower === 'epic') return 'epic'
385
- if (typeLower === 'sub-task' || typeLower === 'subtask') return 'task'
386
-
387
- return 'task'
388
- }
389
-
390
- private mapTypeToJira(type?: IssueType): string {
391
- switch (type) {
392
- case 'bug':
393
- return 'Bug'
394
- case 'feature':
395
- return 'Story'
396
- case 'improvement':
397
- return 'Improvement'
398
- case 'epic':
399
- return 'Epic'
400
- default:
401
- return 'Task'
402
- }
403
- }
404
- }
405
-
406
- // Singleton instance
407
- export const jiraMCPAdapter = new JiraMCPAdapter()
408
-
409
- // =============================================================================
410
- // Utility: Check if MCP is available
411
- // =============================================================================
412
-
413
- /**
414
- * Check if Atlassian MCP tools are available in the current session.
415
- * This is determined by checking the MCP configuration.
416
- */
417
- export function isMCPAvailable(): boolean {
418
- // In template context, Claude can detect available MCP tools
419
- // This function serves as documentation for the check
420
- // Actual detection happens in the template execution
421
- return true
422
- }
423
-
424
- /**
425
- * Get MCP setup instructions for users
426
- */
427
- export function getMCPSetupInstructions(): string {
428
- return `
429
- ## JIRA MCP Setup
430
-
431
- Add to ~/.claude/mcp.json:
432
-
433
- \`\`\`json
434
- {
435
- "mcpServers": {
436
- "Atlassian": {
437
- "command": "npx",
438
- "args": ["-y", "mcp-remote@latest", "https://mcp.atlassian.com/v1/sse"]
439
- }
440
- }
441
- }
442
- \`\`\`
443
-
444
- Then restart Claude Code and authenticate via browser when prompted.
445
- `.trim()
446
- }