claude-autopm 2.4.0 → 2.6.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.
@@ -0,0 +1,528 @@
1
+ /**
2
+ * CLI PM (Project Management) Commands
3
+ *
4
+ * Provides workflow and project management commands for ClaudeAutoPM.
5
+ * Implements subcommands for workflow analysis and task prioritization.
6
+ *
7
+ * Commands:
8
+ * - next: Get next priority task based on dependencies and priorities
9
+ * - what-next: AI-powered suggestions for next steps
10
+ * - standup: Generate daily standup report
11
+ * - status: Project status overview and health
12
+ * - in-progress: Show all active tasks
13
+ * - blocked: Show all blocked tasks
14
+ *
15
+ * @module cli/commands/pm
16
+ * @requires ../../services/WorkflowService
17
+ * @requires ../../services/IssueService
18
+ * @requires ../../services/EpicService
19
+ * @requires fs-extra
20
+ * @requires ora
21
+ * @requires chalk
22
+ * @requires path
23
+ */
24
+
25
+ const WorkflowService = require('../../services/WorkflowService');
26
+ const IssueService = require('../../services/IssueService');
27
+ const EpicService = require('../../services/EpicService');
28
+ const fs = require('fs-extra');
29
+ const ora = require('ora');
30
+ const chalk = require('chalk');
31
+ const path = require('path');
32
+
33
+ /**
34
+ * Get service instances
35
+ * @returns {Object} Service instances
36
+ */
37
+ async function getServices() {
38
+ const issueService = new IssueService();
39
+ const epicService = new EpicService();
40
+ const workflowService = new WorkflowService({
41
+ issueService,
42
+ epicService
43
+ });
44
+ return { workflowService, issueService, epicService };
45
+ }
46
+
47
+ /**
48
+ * PM Next - Get next priority task
49
+ * @param {Object} argv - Command arguments
50
+ */
51
+ async function pmNext(argv) {
52
+ const spinner = ora('Finding next priority task...').start();
53
+
54
+ try {
55
+ const { workflowService } = await getServices();
56
+ const nextTask = await workflowService.getNextTask();
57
+
58
+ if (!nextTask) {
59
+ spinner.info(chalk.yellow('No available tasks found'));
60
+
61
+ console.log(chalk.yellow('\n⚠️ No tasks available to start\n'));
62
+ console.log(chalk.bold('Possible reasons:'));
63
+ console.log(' • All tasks are completed or in-progress');
64
+ console.log(' • Remaining tasks are blocked by dependencies');
65
+ console.log(' • No tasks have been created yet\n');
66
+
67
+ console.log(chalk.bold('💡 Suggestions:'));
68
+ console.log(` ${chalk.cyan('1.')} Check blocked tasks: ${chalk.yellow('autopm pm blocked')}`);
69
+ console.log(` ${chalk.cyan('2.')} Check active work: ${chalk.yellow('autopm pm in-progress')}`);
70
+ console.log(` ${chalk.cyan('3.')} View all tasks: ${chalk.yellow('autopm issue list')}\n`);
71
+ return;
72
+ }
73
+
74
+ spinner.succeed(chalk.green('Found next task'));
75
+
76
+ // Display task with reasoning
77
+ console.log(chalk.cyan('\n📋 Next Task:\n'));
78
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
79
+
80
+ console.log(chalk.bold(`#${nextTask.id}: ${nextTask.title}`));
81
+ if (nextTask.epic) {
82
+ console.log(chalk.gray(`Epic: ${nextTask.epic}`));
83
+ }
84
+ console.log(chalk.gray(`Priority: ${nextTask.priority || 'P2'}`));
85
+ if (nextTask.effort) {
86
+ console.log(chalk.gray(`Estimated effort: ${nextTask.effort}`));
87
+ }
88
+
89
+ console.log(chalk.yellow(`\n💡 Why this task?\n${nextTask.reasoning}`));
90
+
91
+ console.log('\n' + chalk.gray('─'.repeat(60)) + '\n');
92
+
93
+ // TDD Reminder
94
+ console.log(chalk.red('⚠️ TDD REMINDER - Before starting work:\n'));
95
+ console.log(chalk.dim(' 🚨 ALWAYS follow Test-Driven Development:'));
96
+ console.log(chalk.dim(' 1. RED: Write failing test first'));
97
+ console.log(chalk.dim(' 2. GREEN: Write minimal code to pass'));
98
+ console.log(chalk.dim(' 3. REFACTOR: Clean up while keeping tests green\n'));
99
+
100
+ console.log(chalk.green('✅ Ready to start?'));
101
+ console.log(` ${chalk.yellow(`autopm issue start ${nextTask.id}`)}\n`);
102
+ } catch (error) {
103
+ spinner.fail(chalk.red('Failed to find next task'));
104
+ console.error(chalk.red(`\nError: ${error.message}\n`));
105
+ }
106
+ }
107
+
108
+ /**
109
+ * PM What-Next - AI-powered next step suggestions
110
+ * @param {Object} argv - Command arguments
111
+ */
112
+ async function pmWhatNext(argv) {
113
+ const spinner = ora('Analyzing project state...').start();
114
+
115
+ try {
116
+ const { workflowService } = await getServices();
117
+ const result = await workflowService.getWhatNext();
118
+
119
+ spinner.succeed(chalk.green('Analysis complete'));
120
+
121
+ console.log(chalk.cyan('\n🤔 What Should You Work On Next?\n'));
122
+ console.log(chalk.gray('='.repeat(60)) + '\n');
123
+
124
+ // Display project state
125
+ console.log(chalk.bold('📊 Current Project State:\n'));
126
+ console.log(` PRDs: ${result.projectState.prdCount}`);
127
+ console.log(` Epics: ${result.projectState.epicCount}`);
128
+ console.log(` Total Issues: ${result.projectState.issueCount}`);
129
+ console.log(` Open: ${result.projectState.openIssues}`);
130
+ console.log(` In Progress: ${result.projectState.inProgressIssues}`);
131
+ console.log(` Blocked: ${result.projectState.blockedIssues}\n`);
132
+
133
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
134
+
135
+ // Display suggestions
136
+ console.log(chalk.bold('💡 Suggested Next Steps:\n'));
137
+
138
+ result.suggestions.forEach((suggestion, index) => {
139
+ const marker = suggestion.recommended ? '⭐' : '○';
140
+ const priorityColor = suggestion.priority === 'high' ? chalk.red : chalk.yellow;
141
+
142
+ console.log(`${index + 1}. ${marker} ${chalk.bold(suggestion.title)}`);
143
+ console.log(` ${suggestion.description}`);
144
+ console.log(` ${priorityColor(`Priority: ${suggestion.priority.toUpperCase()}`)}`);
145
+
146
+ if (Array.isArray(suggestion.commands)) {
147
+ suggestion.commands.forEach(cmd => {
148
+ console.log(` ${chalk.yellow(cmd)}`);
149
+ });
150
+ }
151
+
152
+ console.log(` ${chalk.dim(`💭 ${suggestion.why}`)}\n`);
153
+ });
154
+
155
+ if (result.suggestions.length === 0) {
156
+ console.log(chalk.yellow(' No specific suggestions at this time\n'));
157
+ }
158
+
159
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
160
+ } catch (error) {
161
+ spinner.fail(chalk.red('Failed to analyze project'));
162
+ console.error(chalk.red(`\nError: ${error.message}\n`));
163
+ }
164
+ }
165
+
166
+ /**
167
+ * PM Standup - Generate daily standup report
168
+ * @param {Object} argv - Command arguments
169
+ */
170
+ async function pmStandup(argv) {
171
+ const spinner = ora('Generating standup report...').start();
172
+
173
+ try {
174
+ const { workflowService } = await getServices();
175
+ const report = await workflowService.generateStandup();
176
+
177
+ spinner.succeed(chalk.green('Standup report generated'));
178
+
179
+ console.log(chalk.cyan(`\n📅 Daily Standup - ${report.date}\n`));
180
+ console.log(chalk.gray('='.repeat(60)) + '\n');
181
+
182
+ // Yesterday
183
+ console.log(chalk.bold('✅ Yesterday (Completed):\n'));
184
+ if (report.yesterday.length > 0) {
185
+ report.yesterday.forEach(task => {
186
+ console.log(` #${task.id} - ${task.title}`);
187
+ if (task.epic) {
188
+ console.log(` ${chalk.gray(`Epic: ${task.epic}`)}`);
189
+ }
190
+ console.log('');
191
+ });
192
+ } else {
193
+ console.log(chalk.gray(' No tasks completed yesterday\n'));
194
+ }
195
+
196
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
197
+
198
+ // Today
199
+ console.log(chalk.bold('🚀 Today (In Progress):\n'));
200
+ if (report.today.length > 0) {
201
+ report.today.forEach(task => {
202
+ console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
203
+ if (task.stale) {
204
+ console.log(` ${chalk.red('⚠️ STALE (>3 days in progress)')}`);
205
+ }
206
+ console.log('');
207
+ });
208
+ } else {
209
+ console.log(chalk.gray(' No tasks currently in progress\n'));
210
+ }
211
+
212
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
213
+
214
+ // Blockers
215
+ console.log(chalk.bold('🚫 Blockers:\n'));
216
+ if (report.blockers.length > 0) {
217
+ report.blockers.forEach(task => {
218
+ console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
219
+ console.log(` ${chalk.red(`Blocked by: ${task.reason}`)}`);
220
+ if (task.daysBlocked) {
221
+ console.log(` ${chalk.gray(`Blocked for: ${task.daysBlocked} days`)}`);
222
+ }
223
+ console.log('');
224
+ });
225
+ } else {
226
+ console.log(chalk.green(' No blockers! 🎉\n'));
227
+ }
228
+
229
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
230
+
231
+ // Metrics
232
+ console.log(chalk.bold('📊 Metrics:\n'));
233
+ console.log(` Velocity: ${report.velocity} tasks/day (7-day avg)`);
234
+ console.log(` Sprint Progress: ${report.sprintProgress.completed}/${report.sprintProgress.total} (${report.sprintProgress.percentage}%)\n`);
235
+
236
+ console.log(chalk.gray('='.repeat(60)) + '\n');
237
+ } catch (error) {
238
+ spinner.fail(chalk.red('Failed to generate standup'));
239
+ console.error(chalk.red(`\nError: ${error.message}\n`));
240
+ }
241
+ }
242
+
243
+ /**
244
+ * PM Status - Project status overview
245
+ * @param {Object} argv - Command arguments
246
+ */
247
+ async function pmStatus(argv) {
248
+ const spinner = ora('Analyzing project status...').start();
249
+
250
+ try {
251
+ const { workflowService } = await getServices();
252
+ const status = await workflowService.getProjectStatus();
253
+
254
+ spinner.succeed(chalk.green('Status analysis complete'));
255
+
256
+ console.log(chalk.cyan('\n📊 Project Status Overview\n'));
257
+ console.log(chalk.gray('='.repeat(60)) + '\n');
258
+
259
+ // Epics
260
+ console.log(chalk.bold('📚 Epics:\n'));
261
+ console.log(` Backlog: ${status.epics.backlog}`);
262
+ console.log(` Planning: ${status.epics.planning}`);
263
+ console.log(` In Progress: ${status.epics.inProgress}`);
264
+ console.log(` Completed: ${status.epics.completed}`);
265
+ console.log(` ${chalk.bold('Total:')} ${status.epics.total}\n`);
266
+
267
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
268
+
269
+ // Issues
270
+ console.log(chalk.bold('📋 Issues:\n'));
271
+ console.log(` Open: ${status.issues.open}`);
272
+ console.log(` In Progress: ${status.issues.inProgress}`);
273
+ console.log(` Blocked: ${chalk.red(status.issues.blocked)}`);
274
+ console.log(` Closed: ${chalk.green(status.issues.closed)}`);
275
+ console.log(` ${chalk.bold('Total:')} ${status.issues.total}\n`);
276
+
277
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
278
+
279
+ // Progress
280
+ console.log(chalk.bold('📈 Progress:\n'));
281
+ console.log(` Overall: ${status.progress.overall}% complete`);
282
+ console.log(` Velocity: ${status.progress.velocity} tasks/day\n`);
283
+
284
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
285
+
286
+ // Health
287
+ const healthColor = status.health === 'ON_TRACK' ? chalk.green : chalk.red;
288
+ console.log(chalk.bold('🎯 Health: ') + healthColor(status.health) + '\n');
289
+
290
+ if (status.recommendations.length > 0) {
291
+ console.log(chalk.bold('💡 Recommendations:\n'));
292
+ status.recommendations.forEach((rec, index) => {
293
+ console.log(` ${index + 1}. ${rec}`);
294
+ });
295
+ console.log('');
296
+ }
297
+
298
+ console.log(chalk.gray('='.repeat(60)) + '\n');
299
+ } catch (error) {
300
+ spinner.fail(chalk.red('Failed to analyze status'));
301
+ console.error(chalk.red(`\nError: ${error.message}\n`));
302
+ }
303
+ }
304
+
305
+ /**
306
+ * PM In-Progress - Show all active tasks
307
+ * @param {Object} argv - Command arguments
308
+ */
309
+ async function pmInProgress(argv) {
310
+ const spinner = ora('Finding active tasks...').start();
311
+
312
+ try {
313
+ const { workflowService } = await getServices();
314
+ const tasks = await workflowService.getInProgressTasks();
315
+
316
+ spinner.succeed(chalk.green('Active tasks found'));
317
+
318
+ console.log(chalk.cyan('\n🚀 In Progress Tasks\n'));
319
+ console.log(chalk.gray('='.repeat(60)) + '\n');
320
+
321
+ if (tasks.length === 0) {
322
+ console.log(chalk.yellow('No tasks currently in progress\n'));
323
+ console.log(chalk.bold('💡 Ready to start work?'));
324
+ console.log(` ${chalk.yellow('autopm pm next')}\n`);
325
+ return;
326
+ }
327
+
328
+ // Group by epic if available
329
+ const byEpic = {};
330
+ tasks.forEach(task => {
331
+ const epic = task.epic || 'No Epic';
332
+ if (!byEpic[epic]) {
333
+ byEpic[epic] = [];
334
+ }
335
+ byEpic[epic].push(task);
336
+ });
337
+
338
+ Object.keys(byEpic).forEach(epicName => {
339
+ console.log(chalk.bold(`Epic: ${epicName}\n`));
340
+
341
+ byEpic[epicName].forEach(task => {
342
+ console.log(` #${task.id} - ${task.title || 'Unnamed task'}`);
343
+ if (task.started) {
344
+ console.log(` ${chalk.gray(`Started: ${new Date(task.started).toLocaleDateString()}`)}`);
345
+ }
346
+ if (task.assignee) {
347
+ console.log(` ${chalk.gray(`Assignee: ${task.assignee}`)}`);
348
+ }
349
+ if (task.stale) {
350
+ console.log(` ${chalk.red('⚠️ STALE (>3 days without update)')}`);
351
+ console.log(` ${chalk.yellow('💡 Consider checking in or splitting task')}`);
352
+ }
353
+ console.log('');
354
+ });
355
+ });
356
+
357
+ // Summary
358
+ const staleCount = tasks.filter(t => t.stale).length;
359
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
360
+ console.log(chalk.bold('📊 Summary:\n'));
361
+ console.log(` Total active: ${tasks.length} tasks`);
362
+ if (staleCount > 0) {
363
+ console.log(` ${chalk.red(`⚠️ Stale: ${staleCount} tasks (>3 days)`)}`);
364
+ }
365
+ console.log('');
366
+
367
+ console.log(chalk.gray('='.repeat(60)) + '\n');
368
+ } catch (error) {
369
+ spinner.fail(chalk.red('Failed to find active tasks'));
370
+ console.error(chalk.red(`\nError: ${error.message}\n`));
371
+ }
372
+ }
373
+
374
+ /**
375
+ * PM Blocked - Show all blocked tasks
376
+ * @param {Object} argv - Command arguments
377
+ */
378
+ async function pmBlocked(argv) {
379
+ const spinner = ora('Finding blocked tasks...').start();
380
+
381
+ try {
382
+ const { workflowService } = await getServices();
383
+ const tasks = await workflowService.getBlockedTasks();
384
+
385
+ spinner.succeed(chalk.green('Blocked tasks analyzed'));
386
+
387
+ console.log(chalk.cyan('\n🚫 Blocked Tasks\n'));
388
+ console.log(chalk.gray('='.repeat(60)) + '\n');
389
+
390
+ if (tasks.length === 0) {
391
+ console.log(chalk.green('✅ No blocked tasks! All tasks are unblocked.\n'));
392
+ console.log(chalk.bold('💡 Ready to work?'));
393
+ console.log(` ${chalk.yellow('autopm pm next')}\n`);
394
+ return;
395
+ }
396
+
397
+ tasks.forEach((task, index) => {
398
+ console.log(`${index + 1}. ${chalk.bold(`#${task.id} - ${task.title || 'Unnamed task'}`)}`);
399
+ console.log(` ${chalk.red(`Blocked by: ${task.reason}`)}`);
400
+ if (task.daysBlocked !== undefined) {
401
+ const daysLabel = task.daysBlocked === 1 ? 'day' : 'days';
402
+ const daysColor = task.daysBlocked > 3 ? chalk.red : chalk.yellow;
403
+ console.log(` ${daysColor(`Blocked since: ${task.daysBlocked} ${daysLabel} ago`)}`);
404
+ }
405
+ if (task.suggestedAction) {
406
+ console.log(` ${chalk.yellow(`💡 Action: ${task.suggestedAction}`)}`);
407
+ }
408
+ console.log('');
409
+ });
410
+
411
+ // Summary
412
+ const criticalCount = tasks.filter(t => t.daysBlocked > 3).length;
413
+ console.log(chalk.gray('─'.repeat(60)) + '\n');
414
+ console.log(chalk.bold('📊 Summary:\n'));
415
+ console.log(` Total blocked: ${tasks.length} tasks`);
416
+ if (criticalCount > 0) {
417
+ console.log(` ${chalk.red(`🔴 Critical: ${criticalCount} tasks (>3 days)`)}`);
418
+ }
419
+ console.log('');
420
+
421
+ console.log(chalk.bold('💡 Recommendations:\n'));
422
+ console.log(' 1. Unblock critical tasks first (>3 days)');
423
+ console.log(' 2. Review and resolve dependencies');
424
+ console.log(' 3. Update stakeholders on delays\n');
425
+
426
+ console.log(chalk.gray('='.repeat(60)) + '\n');
427
+ } catch (error) {
428
+ spinner.fail(chalk.red('Failed to find blocked tasks'));
429
+ console.error(chalk.red(`\nError: ${error.message}\n`));
430
+ }
431
+ }
432
+
433
+ /**
434
+ * Command builder - registers all subcommands
435
+ * @param {Object} yargs - Yargs instance
436
+ * @returns {Object} Configured yargs instance
437
+ */
438
+ function builder(yargs) {
439
+ return yargs
440
+ .command(
441
+ 'next',
442
+ 'Get next priority task',
443
+ (yargs) => {
444
+ return yargs
445
+ .example('autopm pm next', 'Show next priority task to work on');
446
+ },
447
+ pmNext
448
+ )
449
+ .command(
450
+ 'what-next',
451
+ 'AI-powered next step suggestions',
452
+ (yargs) => {
453
+ return yargs
454
+ .example('autopm pm what-next', 'Get intelligent suggestions for next steps');
455
+ },
456
+ pmWhatNext
457
+ )
458
+ .command(
459
+ 'standup',
460
+ 'Generate daily standup report',
461
+ (yargs) => {
462
+ return yargs
463
+ .example('autopm pm standup', 'Generate daily standup summary');
464
+ },
465
+ pmStandup
466
+ )
467
+ .command(
468
+ 'status',
469
+ 'Project status overview',
470
+ (yargs) => {
471
+ return yargs
472
+ .example('autopm pm status', 'Show overall project health and metrics');
473
+ },
474
+ pmStatus
475
+ )
476
+ .command(
477
+ 'in-progress',
478
+ 'Show all active tasks',
479
+ (yargs) => {
480
+ return yargs
481
+ .example('autopm pm in-progress', 'List all tasks currently being worked on');
482
+ },
483
+ pmInProgress
484
+ )
485
+ .command(
486
+ 'blocked',
487
+ 'Show all blocked tasks',
488
+ (yargs) => {
489
+ return yargs
490
+ .example('autopm pm blocked', 'List all blocked tasks with reasons');
491
+ },
492
+ pmBlocked
493
+ )
494
+ .demandCommand(1, 'You must specify a pm command')
495
+ .strictCommands()
496
+ .help();
497
+ }
498
+
499
+ /**
500
+ * Command export
501
+ */
502
+ module.exports = {
503
+ command: 'pm',
504
+ describe: 'Project management and workflow commands',
505
+ builder,
506
+ handler: (argv) => {
507
+ if (!argv._.includes('pm') || argv._.length === 1) {
508
+ console.log(chalk.yellow('\nPlease specify a pm command\n'));
509
+ console.log('Usage: autopm pm <command>\n');
510
+ console.log('Available commands:');
511
+ console.log(' next Get next priority task');
512
+ console.log(' what-next AI-powered next step suggestions');
513
+ console.log(' standup Generate daily standup report');
514
+ console.log(' status Project status overview');
515
+ console.log(' in-progress Show all active tasks');
516
+ console.log(' blocked Show all blocked tasks');
517
+ console.log('\nUse: autopm pm <command> --help for more info\n');
518
+ }
519
+ },
520
+ handlers: {
521
+ next: pmNext,
522
+ whatNext: pmWhatNext,
523
+ standup: pmStandup,
524
+ status: pmStatus,
525
+ inProgress: pmInProgress,
526
+ blocked: pmBlocked
527
+ }
528
+ };