jira-pilot 1.0.2 → 1.1.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.
package/README.md CHANGED
@@ -138,6 +138,22 @@ Once connected, you can ask Claude things like:
138
138
 
139
139
  ---
140
140
 
141
+ ## šŸ› ļø Testing & Verification
142
+
143
+ ### Testing the MCP Server
144
+ You can test the MCP server functionality using the official [MCP Inspector](https://github.com/modelcontextprotocol/inspector):
145
+
146
+ ```bash
147
+ npx @modelcontextprotocol/inspector node ./bin/jira.js mcp
148
+ ```
149
+
150
+ This will launch a web interface where you can:
151
+ 1. View available tools (`jira_list_issues`, `jira_get_issue`, `jira_create_issue`).
152
+ 2. Execute tools and view output.
153
+ 3. Inspect request/response logs.
154
+
155
+ ---
156
+
141
157
  ## šŸ› ļø Project Structure
142
158
  - `bin/`: Entry point.
143
159
  - `src/commands/`: CLI command definitions (Human UI).
package/bin/jira.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import chalk from 'chalk';
4
4
  import { readFileSync } from 'fs';
@@ -14,7 +14,14 @@ const program = new Command();
14
14
  program
15
15
  .name('jira')
16
16
  .description('AI-powered Jira CLI for humans and agents')
17
- .version(pkg.version);
17
+ .version(pkg.version)
18
+ .addHelpText('after', `
19
+ Examples:
20
+ $ jira issue list
21
+ $ jira issue view PROJ-123
22
+ $ jira sprint list --board 123
23
+ $ jira ai summarize PROJ-123
24
+ `);
18
25
 
19
26
  import { registerConfigCommand } from '../src/commands/config.js';
20
27
  import { registerIssueCommand } from '../src/commands/issue.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jira-pilot",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "AI-powered Jira CLI for humans and agents",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -16,7 +16,15 @@
16
16
  "cli",
17
17
  "ai",
18
18
  "agent",
19
- "mcp"
19
+ "mcp",
20
+ "jira-client",
21
+ "jiracli",
22
+ "atlassian",
23
+ "jira-api",
24
+ "issue-tracker",
25
+ "task-management",
26
+ "command-line",
27
+ "typescript"
20
28
  ],
21
29
  "author": "Arul",
22
30
  "repository": {
@@ -1,13 +1,22 @@
1
1
  import { Command } from 'commander';
2
2
  import chalk from 'chalk';
3
3
  import ora from 'ora';
4
+ import enquirer from 'enquirer';
4
5
  import { api } from '../services/api-service.js';
5
6
  import { aiService } from '../services/ai-service.js';
7
+ import { parseADF } from '../utils/adf-parser.js';
6
8
 
7
9
  export function registerAiCommand(program) {
8
10
  const aiCmd = new Command('ai')
9
- .description('AI Helper commands');
11
+ .description('AI Helper commands')
12
+ .addHelpText('after', `
13
+ Common Actions:
14
+ $ jira ai summarize <KEY> # Summarize an issue
15
+ $ jira ai draft # Draft an issue description from bullet points
16
+ $ jira ai suggest <KEY> # Suggest next actions for an issue
17
+ `);
10
18
 
19
+ // ── SUMMARIZE ─────────────────────────────────────────────────────
11
20
  aiCmd
12
21
  .command('summarize')
13
22
  .description('Summarize an issue using AI')
@@ -15,24 +24,27 @@ export function registerAiCommand(program) {
15
24
  .action(async (issueKey) => {
16
25
  const spinner = ora(`Fetching issue ${issueKey}...`).start();
17
26
  try {
18
- // Fetch issue details and comments
19
27
  const issue = await api.get(`/issue/${issueKey}?fields=summary,description,comment`);
20
28
  spinner.text = 'Generating summary...';
21
29
 
22
30
  const summary = issue.fields.summary;
23
- const description = issue.fields.description || 'No description';
24
- const comments = issue.fields.comment.comments.map(c => `${c.author.displayName}: ${c.body}`).join('\n');
31
+ const description = issue.fields.description
32
+ ? parseADF(issue.fields.description)
33
+ : 'No description';
34
+ const comments = (issue.fields.comment?.comments || [])
35
+ .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
36
+ .join('\n');
25
37
 
26
38
  const prompt = `
27
- You are a helpful Jira assistant. Please summarize the following Jira issue.
28
-
29
- Title: ${summary}
30
- Description: ${description}
31
-
32
- Comments:
33
- ${comments}
34
-
35
- Provide a concise summary of the current status, key discussion points, and next steps if clear.
39
+ You are a helpful Jira assistant. Please summarize the following Jira issue.
40
+
41
+ Title: ${summary}
42
+ Description: ${description}
43
+
44
+ Comments:
45
+ ${comments || 'No comments'}
46
+
47
+ Provide a concise summary of the current status, key discussion points, and next steps if clear.
36
48
  `;
37
49
 
38
50
  const aiResponse = await aiService.generate(prompt);
@@ -42,7 +54,7 @@ export function registerAiCommand(program) {
42
54
  console.log(aiResponse);
43
55
 
44
56
  } catch (e) {
45
- spinner.stop(); // Ensure spinner stops
57
+ spinner.stop();
46
58
  if (e.response && e.response.config && e.response.config.url.includes('/issue/')) {
47
59
  console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
48
60
  } else {
@@ -56,5 +68,137 @@ export function registerAiCommand(program) {
56
68
  }
57
69
  });
58
70
 
71
+ // ── DRAFT ─────────────────────────────────────────────────────────
72
+ aiCmd
73
+ .command('draft')
74
+ .description('Draft a structured issue description from bullet points')
75
+ .option('-i, --input <text>', 'Bullet points or rough notes (alternative to interactive prompt)')
76
+ .option('-t, --type <type>', 'Issue type context (bug, story, task)', 'task')
77
+ .addHelpText('after', `
78
+ Examples:
79
+ $ jira ai draft # Interactive
80
+ $ jira ai draft -i "login fails, returns 500, only on mobile"
81
+ $ jira ai draft -i "add dark mode toggle" -t story
82
+ `)
83
+ .action(async (options) => {
84
+ try {
85
+ let bulletPoints = options.input;
86
+
87
+ if (!bulletPoints) {
88
+ const { inputNotes } = await enquirer.prompt({
89
+ type: 'input',
90
+ name: 'inputNotes',
91
+ message: 'Enter your bullet points or rough notes:',
92
+ validate: (val) => val.trim().length > 0 || 'Input cannot be empty'
93
+ });
94
+ bulletPoints = inputNotes;
95
+ }
96
+
97
+ const issueType = options.type || 'task';
98
+
99
+ const spinner = ora('Drafting description...').start();
100
+
101
+ const prompt = `
102
+ You are a Jira expert. Given the following rough notes/bullet points, generate a well-structured Jira issue description.
103
+
104
+ Issue Type: ${issueType}
105
+ Notes: ${bulletPoints}
106
+
107
+ Format the output as follows:
108
+ ## Summary
109
+ A clear one-line summary for the issue title.
110
+
111
+ ## Description
112
+ A well-structured description with:
113
+ - Context / Background
114
+ - Expected Behavior (if applicable)
115
+ - Steps to Reproduce (if it's a bug)
116
+ - Acceptance Criteria (if it's a story)
117
+
118
+ Keep it professional and concise. Output in plain text (not markdown headers, use plain labels).
119
+ `;
120
+
121
+ const aiResponse = await aiService.generate(prompt);
122
+ spinner.stop();
123
+
124
+ console.log(chalk.green('\nāœļø AI-Generated Draft:\n'));
125
+ console.log(aiResponse);
126
+ console.log(chalk.grey('\nTip: Copy this into "jira issue create" or use it as a starting point.'));
127
+
128
+ } catch (e) {
129
+ if (e === '' || e.message === '') {
130
+ console.log(chalk.yellow('\nCancelled.'));
131
+ return;
132
+ }
133
+ console.error(chalk.red('\nFailed to generate draft:'));
134
+ console.error(chalk.red(e.message));
135
+ }
136
+ });
137
+
138
+ // ── SUGGEST ───────────────────────────────────────────────────────
139
+ aiCmd
140
+ .command('suggest')
141
+ .description('Suggest next actions for an issue based on its context')
142
+ .argument('<issueKey>', 'Jira Issue Key')
143
+ .action(async (issueKey) => {
144
+ const spinner = ora(`Analyzing issue ${issueKey}...`).start();
145
+ try {
146
+ const issue = await api.get(`/issue/${issueKey}?fields=summary,description,status,assignee,priority,comment,issuetype`);
147
+
148
+ const summary = issue.fields.summary;
149
+ const description = issue.fields.description
150
+ ? parseADF(issue.fields.description)
151
+ : 'No description';
152
+ const status = issue.fields.status?.name || 'Unknown';
153
+ const issueType = issue.fields.issuetype?.name || 'Unknown';
154
+ const priority = issue.fields.priority?.name || 'None';
155
+ const assignee = issue.fields.assignee?.displayName || 'Unassigned';
156
+ const comments = (issue.fields.comment?.comments || [])
157
+ .slice(-5) // Last 5 comments for context
158
+ .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
159
+ .join('\n');
160
+
161
+ spinner.text = 'Generating suggestions...';
162
+
163
+ const prompt = `
164
+ You are a senior software engineer and Jira workflow expert. Analyze the following Jira issue and suggest practical next actions.
165
+
166
+ Issue Key: ${issueKey}
167
+ Type: ${issueType}
168
+ Status: ${status}
169
+ Priority: ${priority}
170
+ Assignee: ${assignee}
171
+ Title: ${summary}
172
+ Description: ${description}
173
+
174
+ Recent Comments:
175
+ ${comments || 'No comments'}
176
+
177
+ Based on the current status and context, suggest:
178
+ 1. **Immediate Next Action** — What should be done right now?
179
+ 2. **Potential Blockers** — Are there any risks or dependencies to watch?
180
+ 3. **Suggested Status Transition** — Should this issue be moved to a different status?
181
+ 4. **Recommendations** — Any other advice for this issue?
182
+
183
+ Keep suggestions actionable and concise.
184
+ `;
185
+
186
+ const aiResponse = await aiService.generate(prompt);
187
+ spinner.stop();
188
+
189
+ console.log(chalk.green(`\nšŸ’” AI Suggestions for ${issueKey}:\n`));
190
+ console.log(aiResponse);
191
+
192
+ } catch (e) {
193
+ spinner.stop();
194
+ if (e.response && e.response.status === 404) {
195
+ console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
196
+ } else {
197
+ console.error(chalk.red('\nFailed to generate suggestions:'));
198
+ console.error(chalk.red(e.message));
199
+ }
200
+ }
201
+ });
202
+
59
203
  program.addCommand(aiCmd);
60
204
  }
@@ -47,7 +47,7 @@ export function registerConfigCommand(program) {
47
47
  type: 'select',
48
48
  name: 'aiProvider',
49
49
  message: 'Select AI Provider:',
50
- choices: ['openai'], // Add 'gemini', 'anthropic' when ready
50
+ choices: ['openai', 'gemini', 'anthropic'],
51
51
  initial: current.aiProvider || 'openai',
52
52
  skip: (state) => !state.answers.aiEnabled
53
53
  },