jira-pilot 2.0.1 → 2.0.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.
@@ -1,209 +1,232 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import ora from 'ora';
4
- import enquirer from 'enquirer';
5
- import { api } from '../services/api-service.js';
6
- import { aiService } from '../services/ai-service.js';
7
- import { parseADF } from '../utils/adf-parser.js';
8
- import { validateIssueKey } from '../utils/validators.js';
9
-
10
- export function registerAiCommand(program) {
11
- const aiCmd = new Command('ai')
12
- .description('AI Helper commands')
13
- .addHelpText('after', `
14
- Common Actions:
15
- $ jira ai summarize <KEY> # Summarize an issue
16
- $ jira ai draft # Draft an issue description from bullet points
17
- $ jira ai suggest <KEY> # Suggest next actions for an issue
18
- `);
19
-
20
- // ── SUMMARIZE ─────────────────────────────────────────────────────
21
- aiCmd
22
- .command('summarize')
23
- .description('Summarize an issue using AI')
24
- .argument('<issueKey>', 'Jira Issue Key')
25
- .action(async (issueKey) => {
26
- const check = validateIssueKey(issueKey);
27
- if (!check.valid) { console.error(chalk.red(check.message)); return; }
28
- const spinner = ora(`Fetching issue ${issueKey}...`).start();
29
- try {
30
- const issue = await api.get(`/issue/${issueKey}?fields=summary,description,comment`);
31
- spinner.text = 'Generating summary...';
32
-
33
- const summary = issue.fields.summary;
34
- const description = issue.fields.description
35
- ? parseADF(issue.fields.description)
36
- : 'No description';
37
- const comments = (issue.fields.comment?.comments || [])
38
- .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
39
- .join('\n');
40
-
41
- const prompt = `
42
- You are a helpful Jira assistant. Please summarize the following Jira issue.
43
-
44
- Title: ${summary}
45
- Description: ${description}
46
-
47
- Comments:
48
- ${comments || 'No comments'}
49
-
50
- Provide a concise summary of the current status, key discussion points, and next steps if clear.
51
- `;
52
-
53
- const aiResponse = await aiService.generate(prompt);
54
- spinner.stop();
55
-
56
- console.log(chalk.green(`\n🤖 AI Summary for ${issueKey}:\n`));
57
- console.log(aiResponse);
58
-
59
- } catch (e) {
60
- spinner.stop();
61
- if (e.response && e.response.config && e.response.config.url.includes('/issue/')) {
62
- console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
63
- } else {
64
- console.error(chalk.red('\nFailed to generate summary:'));
65
- if (e.response) {
66
- console.error(chalk.red(`API Error ${e.response.status}: `), e.response.data);
67
- } else {
68
- console.error(chalk.red(e.message));
69
- }
70
- }
71
- }
72
- });
73
-
74
- // ── DRAFT ─────────────────────────────────────────────────────────
75
- aiCmd
76
- .command('draft')
77
- .description('Draft a structured issue description from bullet points')
78
- .option('-i, --input <text>', 'Bullet points or rough notes (alternative to interactive prompt)')
79
- .option('-t, --type <type>', 'Issue type context (bug, story, task)', 'task')
80
- .addHelpText('after', `
81
- Examples:
82
- $ jira ai draft # Interactive
83
- $ jira ai draft -i "login fails, returns 500, only on mobile"
84
- $ jira ai draft -i "add dark mode toggle" -t story
85
- `)
86
- .action(async (options) => {
87
- try {
88
- let bulletPoints = options.input;
89
-
90
- if (!bulletPoints) {
91
- const { inputNotes } = await enquirer.prompt({
92
- type: 'input',
93
- name: 'inputNotes',
94
- message: 'Enter your bullet points or rough notes:',
95
- validate: (val) => val.trim().length > 0 || 'Input cannot be empty'
96
- });
97
- bulletPoints = inputNotes;
98
- }
99
-
100
- const issueType = options.type || 'task';
101
-
102
- const spinner = ora('Drafting description...').start();
103
-
104
- const prompt = `
105
- You are a Jira expert. Given the following rough notes/bullet points, generate a well-structured Jira issue description.
106
-
107
- Issue Type: ${issueType}
108
- Notes: ${bulletPoints}
109
-
110
- Format the output as follows:
111
- ## Summary
112
- A clear one-line summary for the issue title.
113
-
114
- ## Description
115
- A well-structured description with:
116
- - Context / Background
117
- - Expected Behavior (if applicable)
118
- - Steps to Reproduce (if it's a bug)
119
- - Acceptance Criteria (if it's a story)
120
-
121
- Keep it professional and concise. Output in plain text (not markdown headers, use plain labels).
122
- `;
123
-
124
- const aiResponse = await aiService.generate(prompt);
125
- spinner.stop();
126
-
127
- console.log(chalk.green('\n✍️ AI-Generated Draft:\n'));
128
- console.log(aiResponse);
129
- console.log(chalk.grey('\nTip: Copy this into "jira issue create" or use it as a starting point.'));
130
-
131
- } catch (e) {
132
- if (e === '' || e.message === '') {
133
- console.log(chalk.yellow('\nCancelled.'));
134
- return;
135
- }
136
- console.error(chalk.red('\nFailed to generate draft:'));
137
- console.error(chalk.red(e.message));
138
- }
139
- });
140
-
141
- // ── SUGGEST ───────────────────────────────────────────────────────
142
- aiCmd
143
- .command('suggest')
144
- .description('Suggest next actions for an issue based on its context')
145
- .argument('<issueKey>', 'Jira Issue Key')
146
- .action(async (issueKey) => {
147
- const check = validateIssueKey(issueKey);
148
- if (!check.valid) { console.error(chalk.red(check.message)); return; }
149
- const spinner = ora(`Analyzing issue ${issueKey}...`).start();
150
- try {
151
- const issue = await api.get(`/issue/${issueKey}?fields=summary,description,status,assignee,priority,comment,issuetype`);
152
-
153
- const summary = issue.fields.summary;
154
- const description = issue.fields.description
155
- ? parseADF(issue.fields.description)
156
- : 'No description';
157
- const status = issue.fields.status?.name || 'Unknown';
158
- const issueType = issue.fields.issuetype?.name || 'Unknown';
159
- const priority = issue.fields.priority?.name || 'None';
160
- const assignee = issue.fields.assignee?.displayName || 'Unassigned';
161
- const comments = (issue.fields.comment?.comments || [])
162
- .slice(-5) // Last 5 comments for context
163
- .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
164
- .join('\n');
165
-
166
- spinner.text = 'Generating suggestions...';
167
-
168
- const prompt = `
169
- You are a senior software engineer and Jira workflow expert. Analyze the following Jira issue and suggest practical next actions.
170
-
171
- Issue Key: ${issueKey}
172
- Type: ${issueType}
173
- Status: ${status}
174
- Priority: ${priority}
175
- Assignee: ${assignee}
176
- Title: ${summary}
177
- Description: ${description}
178
-
179
- Recent Comments:
180
- ${comments || 'No comments'}
181
-
182
- Based on the current status and context, suggest:
183
- 1. **Immediate Next Action** — What should be done right now?
184
- 2. **Potential Blockers** — Are there any risks or dependencies to watch?
185
- 3. **Suggested Status Transition** Should this issue be moved to a different status?
186
- 4. **Recommendations** — Any other advice for this issue?
187
-
188
- Keep suggestions actionable and concise.
189
- `;
190
-
191
- const aiResponse = await aiService.generate(prompt);
192
- spinner.stop();
193
-
194
- console.log(chalk.green(`\n💡 AI Suggestions for ${issueKey}:\n`));
195
- console.log(aiResponse);
196
-
197
- } catch (e) {
198
- spinner.stop();
199
- if (e.response && e.response.status === 404) {
200
- console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
201
- } else {
202
- console.error(chalk.red('\nFailed to generate suggestions:'));
203
- console.error(chalk.red(e.message));
204
- }
205
- }
206
- });
207
-
208
- program.addCommand(aiCmd);
209
- }
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import enquirer from 'enquirer';
5
+ import { api } from '../services/api-service.js';
6
+ import { aiService } from '../services/ai-service.js';
7
+ import { parseADF } from '../utils/adf-parser.js';
8
+ import { validateIssueKey } from '../utils/validators.js';
9
+ import { reviewAction } from './ai-actions/review.js';
10
+ import { planAction } from './ai-actions/plan.js';
11
+ import { standupAction } from './ai-actions/standup.js';
12
+
13
+ export function registerAiCommand(program) {
14
+ const aiCmd = new Command('ai')
15
+ .description('AI Helper commands')
16
+ .addHelpText('after', `
17
+ Common Actions:
18
+ $ jira ai summarize <KEY> # Summarize an issue
19
+ $ jira ai draft # Draft an issue description from bullet points
20
+ $ jira ai suggest <KEY> # Suggest next actions for an issue
21
+ `);
22
+
23
+ // ── SUMMARIZE ─────────────────────────────────────────────────────
24
+ aiCmd
25
+ .command('summarize')
26
+ .description('Summarize an issue using AI')
27
+ .argument('<issueKey>', 'Jira Issue Key')
28
+ .action(async (issueKey) => {
29
+ const check = validateIssueKey(issueKey);
30
+ if (!check.valid) { console.error(chalk.red(check.message)); return; }
31
+ const spinner = ora(`Fetching issue ${issueKey}...`).start();
32
+ try {
33
+ const issue = await api.get(`/issue/${issueKey}?fields=summary,description,comment`);
34
+ spinner.text = 'Generating summary...';
35
+
36
+ const summary = issue.fields.summary;
37
+ const description = issue.fields.description
38
+ ? parseADF(issue.fields.description)
39
+ : 'No description';
40
+ const comments = (issue.fields.comment?.comments || [])
41
+ .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
42
+ .join('\n');
43
+
44
+ const prompt = `
45
+ You are a helpful Jira assistant. Please summarize the following Jira issue.
46
+
47
+ Title: ${summary}
48
+ Description: ${description}
49
+
50
+ Comments:
51
+ ${comments || 'No comments'}
52
+
53
+ Provide a concise summary of the current status, key discussion points, and next steps if clear.
54
+ `;
55
+
56
+ const aiResponse = await aiService.generate(prompt);
57
+ spinner.stop();
58
+
59
+ console.log(chalk.green(`\n🤖 AI Summary for ${issueKey}:\n`));
60
+ console.log(aiResponse);
61
+
62
+ } catch (e) {
63
+ spinner.stop();
64
+ if (e.response && e.response.config && e.response.config.url.includes('/issue/')) {
65
+ console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
66
+ } else {
67
+ console.error(chalk.red('\nFailed to generate summary:'));
68
+ if (e.response) {
69
+ console.error(chalk.red(`API Error ${e.response.status}: `), e.response.data);
70
+ } else {
71
+ console.error(chalk.red(e.message));
72
+ }
73
+ }
74
+ }
75
+ });
76
+
77
+ // ── DRAFT ─────────────────────────────────────────────────────────
78
+ aiCmd
79
+ .command('draft')
80
+ .description('Draft a structured issue description from bullet points')
81
+ .option('-i, --input <text>', 'Bullet points or rough notes (alternative to interactive prompt)')
82
+ .option('-t, --type <type>', 'Issue type context (bug, story, task)', 'task')
83
+ .addHelpText('after', `
84
+ Examples:
85
+ $ jira ai draft # Interactive
86
+ $ jira ai draft -i "login fails, returns 500, only on mobile"
87
+ $ jira ai draft -i "add dark mode toggle" -t story
88
+ `)
89
+ .action(async (options) => {
90
+ try {
91
+ let bulletPoints = options.input;
92
+
93
+ if (!bulletPoints) {
94
+ const { inputNotes } = await enquirer.prompt({
95
+ type: 'input',
96
+ name: 'inputNotes',
97
+ message: 'Enter your bullet points or rough notes:',
98
+ validate: (val) => val.trim().length > 0 || 'Input cannot be empty'
99
+ });
100
+ bulletPoints = inputNotes;
101
+ }
102
+
103
+ const issueType = options.type || 'task';
104
+
105
+ const spinner = ora('Drafting description...').start();
106
+
107
+ const prompt = `
108
+ You are a Jira expert. Given the following rough notes/bullet points, generate a well-structured Jira issue description.
109
+
110
+ Issue Type: ${issueType}
111
+ Notes: ${bulletPoints}
112
+
113
+ Format the output as follows:
114
+ ## Summary
115
+ A clear one-line summary for the issue title.
116
+
117
+ ## Description
118
+ A well-structured description with:
119
+ - Context / Background
120
+ - Expected Behavior (if applicable)
121
+ - Steps to Reproduce (if it's a bug)
122
+ - Acceptance Criteria (if it's a story)
123
+
124
+ Keep it professional and concise. Output in plain text (not markdown headers, use plain labels).
125
+ `;
126
+
127
+ const aiResponse = await aiService.generate(prompt);
128
+ spinner.stop();
129
+
130
+ console.log(chalk.green('\n✍️ AI-Generated Draft:\n'));
131
+ console.log(aiResponse);
132
+ console.log(chalk.grey('\nTip: Copy this into "jira issue create" or use it as a starting point.'));
133
+
134
+ } catch (e) {
135
+ if (e === '' || e.message === '') {
136
+ console.log(chalk.yellow('\nCancelled.'));
137
+ return;
138
+ }
139
+ console.error(chalk.red('\nFailed to generate draft:'));
140
+ console.error(chalk.red(e.message));
141
+ }
142
+ });
143
+
144
+ // ── SUGGEST ───────────────────────────────────────────────────────
145
+ aiCmd
146
+ .command('suggest')
147
+ .description('Suggest next actions for an issue based on its context')
148
+ .argument('<issueKey>', 'Jira Issue Key')
149
+ .action(async (issueKey) => {
150
+ const check = validateIssueKey(issueKey);
151
+ if (!check.valid) { console.error(chalk.red(check.message)); return; }
152
+ const spinner = ora(`Analyzing issue ${issueKey}...`).start();
153
+ try {
154
+ const issue = await api.get(`/issue/${issueKey}?fields=summary,description,status,assignee,priority,comment,issuetype`);
155
+
156
+ const summary = issue.fields.summary;
157
+ const description = issue.fields.description
158
+ ? parseADF(issue.fields.description)
159
+ : 'No description';
160
+ const status = issue.fields.status?.name || 'Unknown';
161
+ const issueType = issue.fields.issuetype?.name || 'Unknown';
162
+ const priority = issue.fields.priority?.name || 'None';
163
+ const assignee = issue.fields.assignee?.displayName || 'Unassigned';
164
+ const comments = (issue.fields.comment?.comments || [])
165
+ .slice(-5) // Last 5 comments for context
166
+ .map(c => `${c.author.displayName}: ${typeof c.body === 'object' ? parseADF(c.body) : c.body}`)
167
+ .join('\n');
168
+
169
+ spinner.text = 'Generating suggestions...';
170
+
171
+ const prompt = `
172
+ You are a senior software engineer and Jira workflow expert. Analyze the following Jira issue and suggest practical next actions.
173
+
174
+ Issue Key: ${issueKey}
175
+ Type: ${issueType}
176
+ Status: ${status}
177
+ Priority: ${priority}
178
+ Assignee: ${assignee}
179
+ Title: ${summary}
180
+ Description: ${description}
181
+
182
+ Recent Comments:
183
+ ${comments || 'No comments'}
184
+
185
+ Based on the current status and context, suggest:
186
+ 1. **Immediate Next Action** — What should be done right now?
187
+ 2. **Potential Blockers** — Are there any risks or dependencies to watch?
188
+ 3. **Suggested Status Transition** — Should this issue be moved to a different status?
189
+ 4. **Recommendations** — Any other advice for this issue?
190
+
191
+ Keep suggestions actionable and concise.
192
+ `;
193
+
194
+ const aiResponse = await aiService.generate(prompt);
195
+ spinner.stop();
196
+
197
+ console.log(chalk.green(`\n💡 AI Suggestions for ${issueKey}:\n`));
198
+ console.log(aiResponse);
199
+
200
+ } catch (e) {
201
+ spinner.stop();
202
+ if (e.response && e.response.status === 404) {
203
+ console.error(chalk.red(`\nError: Issue "${issueKey}" not found.`));
204
+ } else {
205
+ console.error(chalk.red('\nFailed to generate suggestions:'));
206
+ console.error(chalk.red(e.message));
207
+ }
208
+ }
209
+ });
210
+
211
+ // ── REVIEW ────────────────────────────────────────────────────────
212
+ aiCmd
213
+ .command('review')
214
+ .description('Analyze linked code/PRs for an issue')
215
+ .argument('<issueKey>', 'Jira Issue Key')
216
+ .action(reviewAction);
217
+
218
+ // ── PLAN ──────────────────────────────────────────────────────────
219
+ aiCmd
220
+ .command('plan')
221
+ .description('Break down an Epic into child stories/tasks')
222
+ .argument('<epicKey>', 'Epic Issue Key')
223
+ .action(planAction);
224
+
225
+ // ── STANDUP ───────────────────────────────────────────────────────
226
+ aiCmd
227
+ .command('standup')
228
+ .description('Generate a daily standup report from activity')
229
+ .action(standupAction);
230
+
231
+ program.addCommand(aiCmd);
232
+ }
@@ -1,66 +1,75 @@
1
- import { Command } from 'commander';
2
- import chalk from 'chalk';
3
- import { table } from 'table';
4
- import { api } from '../services/api-service.js';
5
- import ora from 'ora';
6
- import { handleCommandError } from '../utils/error-handler.js';
7
-
8
- export function registerBoardCommand(program) {
9
- const boardCmd = new Command('board')
10
- .description('Manage Jira boards')
11
- .addHelpText('after', `
12
- Common Actions:
13
- $ jira board list # List all boards
14
- $ jira board list -p PROJ # List boards for a project
15
- `);
16
-
17
- boardCmd
18
- .command('list')
19
- .description('List Jira boards')
20
- .option('-p, --project <key>', 'Filter by project key')
21
- .option('-t, --type <type>', 'Filter by board type (scrum, kanban, simple)')
22
- .option('-l, --limit <n>', 'Max results', '50')
23
- .action(async (options) => {
24
- const spinner = ora('Fetching boards...').start();
25
- try {
26
- const params = new URLSearchParams();
27
- params.set('maxResults', options.limit);
28
-
29
- if (options.project) {
30
- params.set('projectKeyOrId', options.project);
31
- }
32
- if (options.type) {
33
- params.set('type', options.type);
34
- }
35
-
36
- const data = await api.agileGet(`/board?${params.toString()}`);
37
- spinner.stop();
38
-
39
- if (!data.values || data.values.length === 0) {
40
- console.log(chalk.yellow('No boards found.'));
41
- return;
42
- }
43
-
44
- const tableData = [
45
- [chalk.bold('ID'), chalk.bold('Name'), chalk.bold('Type'), chalk.bold('Project')]
46
- ];
47
-
48
- data.values.forEach(b => {
49
- tableData.push([
50
- b.id,
51
- b.name,
52
- b.type,
53
- b.location?.projectKey || '-'
54
- ]);
55
- });
56
-
57
- console.log(table(tableData));
58
- console.log(chalk.grey(`Showing ${data.values.length} board(s)`));
59
-
60
- } catch (e) {
61
- handleCommandError(spinner, e, 'Failed to list boards');
62
- }
63
- });
64
-
65
- program.addCommand(boardCmd);
66
- }
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { table } from 'table';
4
+ import { api } from '../services/api-service.js';
5
+ import ora from 'ora';
6
+ import { handleCommandError } from '../utils/error-handler.js';
7
+
8
+ export function registerBoardCommand(program) {
9
+ const boardCmd = new Command('board')
10
+ .description('Manage Jira boards')
11
+ .addHelpText('after', `
12
+ Common Actions:
13
+ $ jira board list # List all boards
14
+ $ jira board list -p PROJ # List boards for a project
15
+ `);
16
+
17
+ boardCmd
18
+ .command('list')
19
+ .description('List Jira boards')
20
+ .option('-p, --project <key>', 'Filter by project key')
21
+ .option('-t, --type <type>', 'Filter by board type (scrum, kanban, simple)')
22
+ .option('-l, --limit <n>', 'Max results', '50')
23
+ .option('-o, --output <format>', 'Output format (json)')
24
+ .action(async (options) => {
25
+ const spinner = ora('Fetching boards...').start();
26
+ try {
27
+ const params = new URLSearchParams();
28
+ params.set('maxResults', options.limit);
29
+
30
+ if (options.project) {
31
+ params.set('projectKeyOrId', options.project);
32
+ }
33
+ if (options.type) {
34
+ params.set('type', options.type);
35
+ }
36
+
37
+ const data = await api.agileGet(`/board?${params.toString()}`);
38
+ spinner.stop();
39
+
40
+ if (!data.values || data.values.length === 0) {
41
+ console.log(chalk.yellow('No boards found.'));
42
+ return;
43
+ }
44
+
45
+ if (options.output === 'json') {
46
+ console.log(JSON.stringify(data.values.map(b => ({
47
+ id: b.id, name: b.name,
48
+ type: b.type, project: b.location?.projectKey || null
49
+ })), null, 2));
50
+ return;
51
+ }
52
+
53
+ const tableData = [
54
+ [chalk.bold('ID'), chalk.bold('Name'), chalk.bold('Type'), chalk.bold('Project')]
55
+ ];
56
+
57
+ data.values.forEach(b => {
58
+ tableData.push([
59
+ b.id,
60
+ b.name,
61
+ b.type,
62
+ b.location?.projectKey || '-'
63
+ ]);
64
+ });
65
+
66
+ console.log(table(tableData));
67
+ console.log(chalk.grey(`Showing ${data.values.length} board(s)`));
68
+
69
+ } catch (e) {
70
+ handleCommandError(spinner, e, 'Failed to list boards');
71
+ }
72
+ });
73
+
74
+ program.addCommand(boardCmd);
75
+ }